[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: 789c898339 -s ours am: 98ab2d2c27 -s ours
am skip reason: subject contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/18775058
Change-Id: I1b614d9719c1cf58f69798caf21e4018bb02346d
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 7eb935e..4eeaf51 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -53,6 +53,9 @@
"name": "connectivity_native_test"
},
{
+ "name": "libclat_test"
+ },
+ {
"name": "netd_updatable_unit_test"
},
{
@@ -80,16 +83,10 @@
"keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
},
{
- "name": "libclat_test"
- },
- {
"name": "traffic_controller_unit_test",
"keywords": ["netd-device-kernel-4.9", "netd-device-kernel-4.14"]
},
{
- "name": "libnetworkstats_test"
- },
- {
"name": "FrameworksNetDeflakeTest"
}
],
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 2c7b868..28edc8a 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -33,6 +33,7 @@
":framework-connectivity-shared-srcs",
":tethering-module-utils-srcs",
":services-tethering-shared-srcs",
+ ":statslog-tethering-java-gen",
],
static_libs: [
"androidx.annotation_annotation",
@@ -133,7 +134,7 @@
"-Wthread-safety",
],
- ldflags: ["-Wl,--exclude-libs=ALL,-error-limit=0"],
+ ldflags: ["-Wl,--exclude-libs=ALL,--error-limit=0"],
}
// Common defaults for compiling the actual APK.
@@ -223,3 +224,11 @@
apex_available: ["com.android.tethering"],
min_sdk_version: "30",
}
+
+genrule {
+ name: "statslog-tethering-java-gen",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --java $(out) --module network_tethering" +
+ " --javaPackage com.android.networkstack.tethering.metrics --javaClass TetheringStatsLog",
+ out: ["com/android/networkstack/tethering/metrics/TetheringStatsLog.java"],
+}
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index c718f4c..437ed71 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -69,6 +69,7 @@
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
import com.android.networkstack.tethering.TetheringConfiguration;
+import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
@@ -282,13 +283,15 @@
private LinkAddress mIpv4Address;
+ private final TetheringMetrics mTetheringMetrics;
+
// TODO: Add a dependency object to pass the data members or variables from the tethering
// object. It helps to reduce the arguments of the constructor.
public IpServer(
String ifaceName, Looper looper, int interfaceType, SharedLog log,
INetd netd, @NonNull BpfCoordinator coordinator, Callback callback,
TetheringConfiguration config, PrivateAddressCoordinator addressCoordinator,
- Dependencies deps) {
+ TetheringMetrics tetheringMetrics, Dependencies deps) {
super(ifaceName, looper);
mLog = log.forSubComponent(ifaceName);
mNetd = netd;
@@ -303,6 +306,7 @@
mP2pLeasesSubnetPrefixLength = config.getP2pLeasesSubnetPrefixLength();
mPrivateAddressCoordinator = addressCoordinator;
mDeps = deps;
+ mTetheringMetrics = tetheringMetrics;
resetLinkProperties();
mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
mServingMode = STATE_AVAILABLE;
@@ -1201,6 +1205,9 @@
stopConntrackMonitoring();
resetLinkProperties();
+
+ mTetheringMetrics.updateErrorCode(mInterfaceType, mLastError);
+ mTetheringMetrics.sendReport(mInterfaceType);
}
@Override
diff --git a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index cc2422f..41a10ae 100644
--- a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -172,6 +172,9 @@
return new LinkAddress(LEGACY_WIFI_P2P_IFACE_ADDRESS);
}
+ // This ensures that tethering isn't started on 2 different interfaces with the same type.
+ // Once tethering could support multiple interface with the same type,
+ // TetheringSoftApCallback would need to handle it among others.
final LinkAddress cachedAddress = mCachedAddresses.get(ipServer.interfaceType());
if (useLastAddress && cachedAddress != null
&& !isConflictWithUpstream(asIpPrefix(cachedAddress))) {
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 35a394d..af017f3 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -140,6 +140,7 @@
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
import com.android.networkstack.tethering.util.TetheringUtils;
@@ -254,6 +255,7 @@
private final UserManager mUserManager;
private final BpfCoordinator mBpfCoordinator;
private final PrivateAddressCoordinator mPrivateAddressCoordinator;
+ private final TetheringMetrics mTetheringMetrics;
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
private volatile TetheringConfiguration mConfig;
@@ -292,6 +294,7 @@
mNetd = mDeps.getINetd(mContext);
mLooper = mDeps.getTetheringLooper();
mNotificationUpdater = mDeps.getNotificationUpdater(mContext, mLooper);
+ mTetheringMetrics = mDeps.getTetheringMetrics();
// This is intended to ensrure that if something calls startTethering(bluetooth) just after
// bluetooth is enabled. Before onServiceConnected is called, store the calls into this
@@ -445,8 +448,22 @@
mStateReceiver, noUpstreamFilter, PERMISSION_MAINLINE_NETWORK_STACK, mHandler);
final WifiManager wifiManager = getWifiManager();
+ TetheringSoftApCallback softApCallback = new TetheringSoftApCallback();
if (wifiManager != null) {
- wifiManager.registerSoftApCallback(mExecutor, new TetheringSoftApCallback());
+ wifiManager.registerSoftApCallback(mExecutor, softApCallback);
+ }
+ if (SdkLevel.isAtLeastT() && wifiManager != null) {
+ try {
+ // Although WifiManager#registerLocalOnlyHotspotSoftApCallback document that it need
+ // NEARBY_WIFI_DEVICES permission, but actually a caller who have NETWORK_STACK
+ // or MAINLINE_NETWORK_STACK permission would also able to use this API.
+ wifiManager.registerLocalOnlyHotspotSoftApCallback(mExecutor, softApCallback);
+ } catch (UnsupportedOperationException e) {
+ // Since wifi module development in internal branch,
+ // #registerLocalOnlyHotspotSoftApCallback currently doesn't supported in AOSP
+ // before AOSP switch to Android T + 1.
+ Log.wtf(TAG, "registerLocalOnlyHotspotSoftApCallback API is not supported");
+ }
}
startTrackDefaultNetwork();
@@ -542,6 +559,13 @@
}
// Called by wifi when the number of soft AP clients changed.
+ // Currently multiple softAp would not behave well in PrivateAddressCoordinator
+ // (where it gets the address from cache), it ensure tethering only support one ipServer for
+ // TETHERING_WIFI. Once tethering support multiple softAp enabled simultaneously,
+ // onConnectedClientsChanged should also be updated to support tracking different softAp's
+ // clients individually.
+ // TODO: Add wtf log and have check to reject request duplicated type with different
+ // interface.
@Override
public void onConnectedClientsChanged(final List<WifiClient> clients) {
updateConnectedClients(clients);
@@ -616,7 +640,8 @@
processInterfaceStateChange(iface, false /* enabled */);
}
- void startTethering(final TetheringRequestParcel request, final IIntResultListener listener) {
+ void startTethering(final TetheringRequestParcel request, final String callerPkg,
+ final IIntResultListener listener) {
mHandler.post(() -> {
final TetheringRequestParcel unfinishedRequest = mActiveTetheringRequests.get(
request.tetheringType);
@@ -636,6 +661,7 @@
request.showProvisioningUi);
}
enableTetheringInternal(request.tetheringType, true /* enabled */, listener);
+ mTetheringMetrics.createBuilder(request.tetheringType, callerPkg);
});
}
@@ -695,7 +721,11 @@
// If changing tethering fail, remove corresponding request
// no matter who trigger the start/stop.
- if (result != TETHER_ERROR_NO_ERROR) mActiveTetheringRequests.remove(type);
+ if (result != TETHER_ERROR_NO_ERROR) {
+ mActiveTetheringRequests.remove(type);
+ mTetheringMetrics.updateErrorCode(type, result);
+ mTetheringMetrics.sendReport(type);
+ }
}
private int setWifiTethering(final boolean enable) {
@@ -2779,7 +2809,7 @@
final TetherState tetherState = new TetherState(
new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator,
makeControlCallback(), mConfig, mPrivateAddressCoordinator,
- mDeps.getIpServerDependencies()), isNcm);
+ mTetheringMetrics, mDeps.getIpServerDependencies()), isNcm);
mTetherStates.put(iface, tetherState);
tetherState.ipServer.start();
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 9224213..8e0354d 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -34,6 +34,7 @@
import com.android.internal.util.StateMachine;
import com.android.networkstack.apishim.BluetoothPanShimImpl;
import com.android.networkstack.apishim.common.BluetoothPanShim;
+import com.android.networkstack.tethering.metrics.TetheringMetrics;
import java.util.ArrayList;
@@ -163,4 +164,11 @@
public BluetoothPanShim getBluetoothPanShim(BluetoothPan pan) {
return BluetoothPanShimImpl.newInstance(pan);
}
+
+ /**
+ * Get a reference to the TetheringMetrics to be used by tethering.
+ */
+ public TetheringMetrics getTetheringMetrics() {
+ return new TetheringMetrics();
+ }
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index 9fb61fe..f147e10 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -137,7 +137,7 @@
return;
}
- mTethering.startTethering(request, listener);
+ mTethering.startTethering(request, callerPkg, listener);
}
@Override
diff --git a/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
new file mode 100644
index 0000000..e25f2ae
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/metrics/TetheringMetrics.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.tethering.metrics;
+
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringManager.TETHERING_NCM;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
+import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
+import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
+import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
+import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
+import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+
+import android.stats.connectivity.DownstreamType;
+import android.stats.connectivity.ErrorCode;
+import android.stats.connectivity.UpstreamType;
+import android.stats.connectivity.UserType;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * Collection of utilities for tethering metrics.
+ *
+ * To see if the logs are properly sent to statsd, execute following commands
+ *
+ * $ adb shell cmd stats print-logs
+ * $ adb logcat | grep statsd OR $ adb logcat -b stats
+ *
+ * @hide
+ */
+public class TetheringMetrics {
+ private static final String TAG = TetheringMetrics.class.getSimpleName();
+ private static final boolean DBG = false;
+ private static final String SETTINGS_PKG_NAME = "com.android.settings";
+ private static final String SYSTEMUI_PKG_NAME = "com.android.systemui";
+ private static final String GMS_PKG_NAME = "com.google.android.gms";
+ private final SparseArray<NetworkTetheringReported.Builder> mBuilderMap = new SparseArray<>();
+
+ /** Update Tethering stats about caller's package name and downstream type. */
+ public void createBuilder(final int downstreamType, final String callerPkg) {
+ mBuilderMap.clear();
+ NetworkTetheringReported.Builder statsBuilder =
+ NetworkTetheringReported.newBuilder();
+ statsBuilder.setDownstreamType(downstreamTypeToEnum(downstreamType))
+ .setUserType(userTypeToEnum(callerPkg))
+ .setUpstreamType(UpstreamType.UT_UNKNOWN)
+ .setErrorCode(ErrorCode.EC_NO_ERROR)
+ .build();
+ mBuilderMap.put(downstreamType, statsBuilder);
+ }
+
+ /** Update error code of given downstreamType. */
+ public void updateErrorCode(final int downstreamType, final int errCode) {
+ NetworkTetheringReported.Builder statsBuilder = mBuilderMap.get(downstreamType);
+ if (statsBuilder == null) {
+ Log.e(TAG, "Given downstreamType does not exist, this is a bug!");
+ return;
+ }
+ statsBuilder.setErrorCode(errorCodeToEnum(errCode));
+ }
+
+ /** Remove Tethering stats.
+ * If Tethering stats is ready to write then write it before removing.
+ */
+ public void sendReport(final int downstreamType) {
+ final NetworkTetheringReported.Builder statsBuilder =
+ mBuilderMap.get(downstreamType);
+ if (statsBuilder == null) {
+ Log.e(TAG, "Given downstreamType does not exist, this is a bug!");
+ return;
+ }
+ write(statsBuilder.build());
+ mBuilderMap.remove(downstreamType);
+ }
+
+ /** Collect Tethering stats and write metrics data to statsd pipeline. */
+ @VisibleForTesting
+ public void write(@NonNull final NetworkTetheringReported reported) {
+ TetheringStatsLog.write(TetheringStatsLog.NETWORK_TETHERING_REPORTED,
+ reported.getErrorCode().getNumber(),
+ reported.getDownstreamType().getNumber(),
+ reported.getUpstreamType().getNumber(),
+ reported.getUserType().getNumber());
+ if (DBG) {
+ Log.d(TAG, "Write errorCode: " + reported.getErrorCode().getNumber()
+ + ", downstreamType: " + reported.getDownstreamType().getNumber()
+ + ", upstreamType: " + reported.getUpstreamType().getNumber()
+ + ", userType: " + reported.getUserType().getNumber());
+ }
+ }
+
+ /** Map {@link TetheringType} to {@link DownstreamType} */
+ private DownstreamType downstreamTypeToEnum(final int ifaceType) {
+ switch(ifaceType) {
+ case TETHERING_WIFI:
+ return DownstreamType.DS_TETHERING_WIFI;
+ case TETHERING_WIFI_P2P:
+ return DownstreamType.DS_TETHERING_WIFI_P2P;
+ case TETHERING_USB:
+ return DownstreamType.DS_TETHERING_USB;
+ case TETHERING_BLUETOOTH:
+ return DownstreamType.DS_TETHERING_BLUETOOTH;
+ case TETHERING_NCM:
+ return DownstreamType.DS_TETHERING_NCM;
+ case TETHERING_ETHERNET:
+ return DownstreamType.DS_TETHERING_ETHERNET;
+ default:
+ return DownstreamType.DS_UNSPECIFIED;
+ }
+ }
+
+ /** Map {@link StartTetheringError} to {@link ErrorCode} */
+ private ErrorCode errorCodeToEnum(final int lastError) {
+ switch(lastError) {
+ case TETHER_ERROR_NO_ERROR:
+ return ErrorCode.EC_NO_ERROR;
+ case TETHER_ERROR_UNKNOWN_IFACE:
+ return ErrorCode.EC_UNKNOWN_IFACE;
+ case TETHER_ERROR_SERVICE_UNAVAIL:
+ return ErrorCode.EC_SERVICE_UNAVAIL;
+ case TETHER_ERROR_UNSUPPORTED:
+ return ErrorCode.EC_UNSUPPORTED;
+ case TETHER_ERROR_UNAVAIL_IFACE:
+ return ErrorCode.EC_UNAVAIL_IFACE;
+ case TETHER_ERROR_INTERNAL_ERROR:
+ return ErrorCode.EC_INTERNAL_ERROR;
+ case TETHER_ERROR_TETHER_IFACE_ERROR:
+ return ErrorCode.EC_TETHER_IFACE_ERROR;
+ case TETHER_ERROR_UNTETHER_IFACE_ERROR:
+ return ErrorCode.EC_UNTETHER_IFACE_ERROR;
+ case TETHER_ERROR_ENABLE_FORWARDING_ERROR:
+ return ErrorCode.EC_ENABLE_FORWARDING_ERROR;
+ case TETHER_ERROR_DISABLE_FORWARDING_ERROR:
+ return ErrorCode.EC_DISABLE_FORWARDING_ERROR;
+ case TETHER_ERROR_IFACE_CFG_ERROR:
+ return ErrorCode.EC_IFACE_CFG_ERROR;
+ case TETHER_ERROR_PROVISIONING_FAILED:
+ return ErrorCode.EC_PROVISIONING_FAILED;
+ case TETHER_ERROR_DHCPSERVER_ERROR:
+ return ErrorCode.EC_DHCPSERVER_ERROR;
+ case TETHER_ERROR_ENTITLEMENT_UNKNOWN:
+ return ErrorCode.EC_ENTITLEMENT_UNKNOWN;
+ case TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION:
+ return ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION;
+ case TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION:
+ return ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION;
+ default:
+ return ErrorCode.EC_UNKNOWN_TYPE;
+ }
+ }
+
+ /** Map callerPkg to {@link UserType} */
+ private UserType userTypeToEnum(final String callerPkg) {
+ if (callerPkg.equals(SETTINGS_PKG_NAME)) {
+ return UserType.USER_SETTINGS;
+ } else if (callerPkg.equals(SYSTEMUI_PKG_NAME)) {
+ return UserType.USER_SYSTEMUI;
+ } else if (callerPkg.equals(GMS_PKG_NAME)) {
+ return UserType.USER_GMS;
+ } else {
+ return UserType.USER_UNKNOWN;
+ }
+ }
+}
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 5869f2b..819936d 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -26,15 +26,18 @@
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_ETHERNET;
-import static android.net.TetheringTester.RemoteResponder;
-import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.net.TetheringTester.isExpectedIcmpv6Packet;
+import static android.net.TetheringTester.isExpectedUdpPacket;
import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_IPV6;
import static android.system.OsConstants.IPPROTO_UDP;
import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
import static com.android.net.module.util.HexDump.dumpHexString;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
import static com.android.testutils.DeviceInfoUtils.KVersion;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
@@ -73,17 +76,14 @@
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.PacketBuilder;
import com.android.net.module.util.Struct;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
import com.android.net.module.util.bpf.TetherStatsKey;
import com.android.net.module.util.bpf.TetherStatsValue;
-import com.android.net.module.util.structs.EthernetHeader;
-import com.android.net.module.util.structs.Icmpv6Header;
-import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.Ipv6Header;
-import com.android.net.module.util.structs.UdpHeader;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
@@ -101,6 +101,7 @@
import java.io.FileDescriptor;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
@@ -140,11 +141,15 @@
// Per TX UDP packet size: ethhdr (14) + iphdr (20) + udphdr (8) + payload (2) = 44 bytes.
private static final int TX_UDP_PACKET_SIZE = 44;
private static final int TX_UDP_PACKET_COUNT = 123;
+ private static final long WAIT_RA_TIMEOUT_MS = 2000;
private static final LinkAddress TEST_IP4_ADDR = new LinkAddress("10.0.0.1/8");
private static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64");
private static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8");
private static final InetAddress TEST_IP6_DNS = parseNumericAddress("2001:db8:1::888");
+ private static final IpPrefix TEST_NAT64PREFIX = new IpPrefix("64:ff9b::/96");
+ private static final Inet6Address REMOTE_NAT64_ADDR =
+ (Inet6Address) parseNumericAddress("64:ff9b::808:808");
private static final ByteBuffer TEST_REACHABILITY_PAYLOAD =
ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
@@ -154,6 +159,10 @@
private static final String BASE64_DELIMITER = ",";
private static final String LINE_DELIMITER = "\\n";
+ // version=6, traffic class=0x0, flowlabel=0x0;
+ private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000;
+ private static final short HOP_LIMIT = 0x40;
+
private final Context mContext = InstrumentationRegistry.getContext();
private final EthernetManager mEm = mContext.getSystemService(EthernetManager.class);
private final TetheringManager mTm = mContext.getSystemService(TetheringManager.class);
@@ -316,27 +325,13 @@
}
- private static boolean isRouterAdvertisement(byte[] pkt) {
- if (pkt == null) return false;
-
- ByteBuffer buf = ByteBuffer.wrap(pkt);
-
- final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
- if (ethHdr.etherType != ETHER_TYPE_IPV6) return false;
-
- final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
- if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return false;
-
- final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
- return icmpv6Hdr.type == (short) ICMPV6_ROUTER_ADVERTISEMENT;
- }
-
- private static void expectRouterAdvertisement(TapPacketReader reader, String iface,
+ private static void waitForRouterAdvertisement(TapPacketReader reader, String iface,
long timeoutMs) {
final long deadline = SystemClock.uptimeMillis() + timeoutMs;
do {
byte[] pkt = reader.popPacket(timeoutMs);
- if (isRouterAdvertisement(pkt)) return;
+ if (isExpectedIcmpv6Packet(pkt, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT)) return;
+
timeoutMs = deadline - SystemClock.uptimeMillis();
} while (timeoutMs > 0);
fail("Did not receive router advertisement on " + iface + " after "
@@ -388,7 +383,7 @@
// before the reader is started.
mDownstreamReader = makePacketReader(mDownstreamIface);
- expectRouterAdvertisement(mDownstreamReader, iface, 2000 /* timeoutMs */);
+ waitForRouterAdvertisement(mDownstreamReader, iface, WAIT_RA_TIMEOUT_MS);
expectLocalOnlyAddresses(iface);
}
@@ -781,36 +776,42 @@
}
}
- private TestNetworkTracker createTestUpstream(final List<LinkAddress> addresses)
- throws Exception {
+ private TestNetworkTracker createTestUpstream(final List<LinkAddress> addresses,
+ final List<InetAddress> dnses) throws Exception {
mTm.setPreferTestNetworks(true);
- return initTestNetwork(mContext, addresses, TIMEOUT_MS);
+ final LinkProperties lp = new LinkProperties();
+ lp.setLinkAddresses(addresses);
+ lp.setDnsServers(dnses);
+ lp.setNat64Prefix(TEST_NAT64PREFIX);
+
+ return initTestNetwork(mContext, lp, TIMEOUT_MS);
}
@Test
- public void testTestNetworkUpstream() throws Exception {
- assumeFalse(mEm.isAvailable());
+ public void testIcmpv6Echo() throws Exception {
+ runPing6Test(initTetheringTester(toList(TEST_IP4_ADDR, TEST_IP6_ADDR),
+ toList(TEST_IP4_DNS, TEST_IP6_DNS)));
+ }
- // MyTetheringEventCallback currently only support await first available upstream. Tethering
- // may select internet network as upstream if test network is not available and not be
- // preferred yet. Create test upstream network before enable tethering.
- mUpstreamTracker = createTestUpstream(toList(TEST_IP4_ADDR, TEST_IP6_ADDR));
+ private void runPing6Test(TetheringTester tester) throws Exception {
+ TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString("1:2:3:4:5:6"),
+ true /* hasIpv6 */);
+ Inet6Address remoteIp6Addr = (Inet6Address) parseNumericAddress("2400:222:222::222");
+ ByteBuffer request = Ipv6Utils.buildEchoRequestPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv6Addr, remoteIp6Addr);
+ tester.verifyUpload(request, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- mDownstreamIface = createTestInterface();
- mEm.setIncludeTestInterfaces(true);
+ return isExpectedIcmpv6Packet(p, false /* hasEth */, ICMPV6_ECHO_REQUEST_TYPE);
+ });
- final String iface = mTetheredInterfaceRequester.getInterface();
- assertEquals("TetheredInterfaceCallback for unexpected interface",
- mDownstreamIface.getInterfaceName(), iface);
+ ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(remoteIp6Addr, tethered.ipv6Addr);
+ tester.verifyDownload(reply, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
- mTetheringEventCallback = enableEthernetTethering(mDownstreamIface.getInterfaceName(),
- mUpstreamTracker.getNetwork());
- assertEquals("onUpstreamChanged for unexpected network", mUpstreamTracker.getNetwork(),
- mTetheringEventCallback.awaitUpstreamChanged());
-
- mDownstreamReader = makePacketReader(mDownstreamIface);
- // TODO: do basic forwarding test here.
+ return isExpectedIcmpv6Packet(p, true /* hasEth */, ICMPV6_ECHO_REPLY_TYPE);
+ });
}
// Test network topology:
@@ -828,12 +829,11 @@
// Used by public port and private port. Assume port 9876 has not been used yet before the
// testing that public port and private port are the same in the testing. Note that NAT port
// forwarding could be different between private port and public port.
+ // TODO: move to the start of test class.
private static final short LOCAL_PORT = 9876;
private static final short REMOTE_PORT = 433;
private static final byte TYPE_OF_SERVICE = 0;
private static final short ID = 27149;
- private static final short ID2 = 27150;
- private static final short ID3 = 27151;
private static final short FLAGS_AND_FRAGMENT_OFFSET = (short) 0x4000; // flags=DF, offset=0
private static final byte TIME_TO_LIVE = (byte) 0x40;
private static final ByteBuffer PAYLOAD =
@@ -843,43 +843,48 @@
private static final ByteBuffer PAYLOAD3 =
ByteBuffer.wrap(new byte[] { (byte) 0x9a, (byte) 0xbc });
- private boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEther,
- @NonNull final ByteBuffer payload) {
- final ByteBuffer buf = ByteBuffer.wrap(rawPacket);
-
- if (hasEther) {
- final EthernetHeader etherHeader = Struct.parse(EthernetHeader.class, buf);
- if (etherHeader == null) return false;
- }
-
- final Ipv4Header ipv4Header = Struct.parse(Ipv4Header.class, buf);
- if (ipv4Header == null) return false;
-
- final UdpHeader udpHeader = Struct.parse(UdpHeader.class, buf);
- if (udpHeader == null) return false;
-
- if (buf.remaining() != payload.limit()) return false;
-
- return Arrays.equals(Arrays.copyOfRange(buf.array(), buf.position(), buf.limit()),
- payload.array());
- }
-
@NonNull
- private ByteBuffer buildUdpv4Packet(@Nullable final MacAddress srcMac,
- @Nullable final MacAddress dstMac, short id,
- @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp,
+ private ByteBuffer buildUdpPacket(
+ @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
+ @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp,
short srcPort, short dstPort, @Nullable final ByteBuffer payload)
throws Exception {
+ int ipProto;
+ short ethType;
+ if (srcIp instanceof Inet4Address && dstIp instanceof Inet4Address) {
+ ipProto = IPPROTO_IP;
+ ethType = (short) ETHER_TYPE_IPV4;
+ } else if (srcIp instanceof Inet6Address && dstIp instanceof Inet6Address) {
+ ipProto = IPPROTO_IPV6;
+ ethType = (short) ETHER_TYPE_IPV6;
+ } else {
+ fail("Unsupported conditions: srcIp " + srcIp + ", dstIp " + dstIp);
+ // Make compiler happy to the uninitialized ipProto and ethType.
+ return null; // unreachable, the annotation @NonNull of function return value is true.
+ }
+
final boolean hasEther = (srcMac != null && dstMac != null);
final int payloadLen = (payload == null) ? 0 : payload.limit();
- final ByteBuffer buffer = PacketBuilder.allocate(hasEther, IPPROTO_IP, IPPROTO_UDP,
+ final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_UDP,
payloadLen);
final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+ // [1] Ethernet header
if (hasEther) packetBuilder.writeL2Header(srcMac, dstMac, (short) ETHER_TYPE_IPV4);
- packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
- TIME_TO_LIVE, (byte) IPPROTO_UDP, srcIp, dstIp);
+
+ // [2] IP header
+ if (ipProto == IPPROTO_IP) {
+ packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+ TIME_TO_LIVE, (byte) IPPROTO_UDP, (Inet4Address) srcIp, (Inet4Address) dstIp);
+ } else {
+ packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_UDP,
+ HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp);
+ }
+
+ // [3] UDP header
packetBuilder.writeUdpHeader(srcPort, dstPort);
+
+ // [4] Payload
if (payload != null) {
buffer.put(payload);
// in case data might be reused by caller, restore the position and
@@ -891,38 +896,47 @@
}
@NonNull
- private ByteBuffer buildUdpv4Packet(short id, @NonNull final Inet4Address srcIp,
- @NonNull final Inet4Address dstIp, short srcPort, short dstPort,
+ private ByteBuffer buildUdpPacket(@NonNull final InetAddress srcIp,
+ @NonNull final InetAddress dstIp, short srcPort, short dstPort,
@Nullable final ByteBuffer payload) throws Exception {
- return buildUdpv4Packet(null /* srcMac */, null /* dstMac */, id, srcIp, dstIp, srcPort,
+ return buildUdpPacket(null /* srcMac */, null /* dstMac */, srcIp, dstIp, srcPort,
dstPort, payload);
}
- // TODO: remove this verification once upstream connected notification race is fixed.
- // See #runUdp4Test.
- private boolean isIpv4TetherConnectivityVerified(TetheringTester tester,
- RemoteResponder remote, TetheredDevice tethered) throws Exception {
- final ByteBuffer probePacket = buildUdpv4Packet(tethered.macAddr,
- tethered.routerMacAddr, ID, tethered.ipv4Addr /* srcIp */,
- REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /*dstPort */,
+ // TODO: remove ipv4 verification (is4To6 = false) once upstream connected notification race is
+ // fixed. See #runUdp4Test.
+ //
+ // This function sends a probe packet to downstream interface and exam the result from upstream
+ // interface to make sure ipv4 tethering is ready. Return the entire packet which received from
+ // upstream interface.
+ @NonNull
+ private byte[] probeV4TetheringConnectivity(TetheringTester tester, TetheredDevice tethered,
+ boolean is4To6) throws Exception {
+ final ByteBuffer probePacket = buildUdpPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
TEST_REACHABILITY_PAYLOAD);
// Send a UDP packet from client and check the packet can be found on upstream interface.
for (int i = 0; i < TETHER_REACHABILITY_ATTEMPTS; i++) {
- tester.sendPacket(probePacket);
- byte[] expectedPacket = remote.getNextMatchedPacket(p -> {
+ byte[] expectedPacket = tester.testUpload(probePacket, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, false /* hasEther */, TEST_REACHABILITY_PAYLOAD);
+ // If is4To6 is true, the ipv4 probe packet would be translated to ipv6 by Clat and
+ // would see this translated ipv6 packet in upstream interface.
+ return isExpectedUdpPacket(p, false /* hasEther */, !is4To6 /* isIpv4 */,
+ TEST_REACHABILITY_PAYLOAD);
});
- if (expectedPacket != null) return true;
+ if (expectedPacket != null) return expectedPacket;
}
- return false;
+
+ fail("Can't verify " + (is4To6 ? "ipv4 to ipv6" : "ipv4") + " tethering connectivity after "
+ + TETHER_REACHABILITY_ATTEMPTS + " attempts");
+ return null;
}
- private void runUdp4Test(TetheringTester tester, RemoteResponder remote, boolean usingBpf)
- throws Exception {
+ private void runUdp4Test(TetheringTester tester, boolean usingBpf) throws Exception {
final TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString(
- "1:2:3:4:5:6"));
+ "1:2:3:4:5:6"), false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
// Because async upstream connected notification can't guarantee the tethering routing is
@@ -930,26 +944,26 @@
// For short term plan, consider using IPv6 RA to get MAC address because the prefix comes
// from upstream. That can guarantee that the routing is ready. Long term plan is that
// refactors upstream connected notification from async to sync.
- assertTrue(isIpv4TetherConnectivityVerified(tester, remote, tethered));
+ probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
// Send a UDP packet in original direction.
- final ByteBuffer originalPacket = buildUdpv4Packet(tethered.macAddr,
- tethered.routerMacAddr, ID, tethered.ipv4Addr /* srcIp */,
- REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /*dstPort */,
+ final ByteBuffer originalPacket = buildUdpPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
PAYLOAD /* payload */);
- tester.verifyUpload(remote, originalPacket, p -> {
+ tester.verifyUpload(originalPacket, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD);
+ return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, PAYLOAD);
});
// Send a UDP packet in reply direction.
final Inet4Address publicIp4Addr = (Inet4Address) TEST_IP4_ADDR.getAddress();
- final ByteBuffer replyPacket = buildUdpv4Packet(ID2, REMOTE_IP4_ADDR /* srcIp */,
- publicIp4Addr /* dstIp */, REMOTE_PORT /* srcPort */, LOCAL_PORT /*dstPort */,
+ final ByteBuffer replyPacket = buildUdpPacket(REMOTE_IP4_ADDR /* srcIp */,
+ publicIp4Addr /* dstIp */, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */,
PAYLOAD2 /* payload */);
- remote.verifyDownload(tester, replyPacket, p -> {
+ tester.verifyDownload(replyPacket, p -> {
Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, true/* hasEther */, PAYLOAD2);
+ return isExpectedUdpPacket(p, true /* hasEther */, true /* isIpv4 */, PAYLOAD2);
});
if (usingBpf) {
@@ -962,13 +976,13 @@
// See kernel upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5 and
// nf_conntrack_udp_packet in net/netfilter/nf_conntrack_proto_udp.c
Thread.sleep(UDP_STREAM_TS_MS);
- final ByteBuffer originalPacket2 = buildUdpv4Packet(tethered.macAddr,
- tethered.routerMacAddr, ID, tethered.ipv4Addr /* srcIp */,
+ final ByteBuffer originalPacket2 = buildUdpPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */,
- REMOTE_PORT /*dstPort */, PAYLOAD3 /* payload */);
- tester.verifyUpload(remote, originalPacket2, p -> {
+ REMOTE_PORT /* dstPort */, PAYLOAD3 /* payload */);
+ tester.verifyUpload(originalPacket2, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD3);
+ return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, PAYLOAD3);
});
// [1] Verify IPv4 upstream rule map.
@@ -1002,17 +1016,17 @@
// Send packets on original direction.
for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
- tester.verifyUpload(remote, originalPacket, p -> {
+ tester.verifyUpload(originalPacket, p -> {
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD);
+ return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, PAYLOAD);
});
}
// Send packets on reply direction.
for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
- remote.verifyDownload(tester, replyPacket, p -> {
+ tester.verifyDownload(replyPacket, p -> {
Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, true/* hasEther */, PAYLOAD2);
+ return isExpectedUdpPacket(p, true /* hasEther */, true /* isIpv4 */, PAYLOAD2);
});
}
@@ -1037,20 +1051,21 @@
}
}
- void initializeTethering() throws Exception {
+ private TetheringTester initTetheringTester(List<LinkAddress> upstreamAddresses,
+ List<InetAddress> upstreamDnses) throws Exception {
assumeFalse(mEm.isAvailable());
// MyTetheringEventCallback currently only support await first available upstream. Tethering
// may select internet network as upstream if test network is not available and not be
// preferred yet. Create test upstream network before enable tethering.
- mUpstreamTracker = createTestUpstream(toList(TEST_IP4_ADDR));
+ mUpstreamTracker = createTestUpstream(upstreamAddresses, upstreamDnses);
mDownstreamIface = createTestInterface();
mEm.setIncludeTestInterfaces(true);
- final String iface = mTetheredInterfaceRequester.getInterface();
+ // Make sure EtherentTracker use "mDownstreamIface" as server mode interface.
assertEquals("TetheredInterfaceCallback for unexpected interface",
- mDownstreamIface.getInterfaceName(), iface);
+ mDownstreamIface.getInterfaceName(), mTetheredInterfaceRequester.getInterface());
mTetheringEventCallback = enableEthernetTethering(mDownstreamIface.getInterfaceName(),
mUpstreamTracker.getNetwork());
@@ -1059,13 +1074,22 @@
mDownstreamReader = makePacketReader(mDownstreamIface);
mUpstreamReader = makePacketReader(mUpstreamTracker.getTestIface());
+
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ // Currently tethering don't have API to tell when ipv6 tethering is available. Thus, make
+ // sure tethering already have ipv6 connectivity before testing.
+ if (cm.getLinkProperties(mUpstreamTracker.getNetwork()).hasGlobalIpv6Address()) {
+ waitForRouterAdvertisement(mDownstreamReader, mDownstreamIface.getInterfaceName(),
+ WAIT_RA_TIMEOUT_MS);
+ }
+
+ return new TetheringTester(mDownstreamReader, mUpstreamReader);
}
@Test
@IgnoreAfter(Build.VERSION_CODES.R)
public void testTetherUdpV4UpToR() throws Exception {
- initializeTethering();
- runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
+ runUdp4Test(initTetheringTester(toList(TEST_IP4_ADDR), toList(TEST_IP4_DNS)),
false /* usingBpf */);
}
@@ -1100,15 +1124,13 @@
@Test
@IgnoreUpTo(Build.VERSION_CODES.R)
public void testTetherUdpV4AfterR() throws Exception {
- initializeTethering();
final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
boolean usingBpf = isUdpOffloadSupportedByKernel(kernelVersion);
if (!usingBpf) {
Log.i(TAG, "testTetherUdpV4AfterR will skip BPF offload test for kernel "
+ kernelVersion);
}
- runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
- usingBpf);
+ runUdp4Test(initTetheringTester(toList(TEST_IP4_ADDR), toList(TEST_IP4_DNS)), usingBpf);
}
@Nullable
@@ -1167,6 +1189,70 @@
return null;
}
+ @NonNull
+ private Inet6Address getClatIpv6Address(TetheringTester tester, TetheredDevice tethered)
+ throws Exception {
+ // Send an IPv4 UDP packet from client and check that a CLAT translated IPv6 UDP packet can
+ // be found on upstream interface. Get CLAT IPv6 address from the CLAT translated IPv6 UDP
+ // packet.
+ byte[] expectedPacket = probeV4TetheringConnectivity(tester, tethered, true /* is4To6 */);
+
+ // Above has guaranteed that the found packet is an IPv6 packet without ether header.
+ return Struct.parse(Ipv6Header.class, ByteBuffer.wrap(expectedPacket)).srcIp;
+ }
+
+ // Test network topology:
+ //
+ // public network (rawip) private network
+ // | UE (CLAT support) |
+ // +---------------+ V +------------+------------+ V +------------+
+ // | NAT64 Gateway +---------+ Upstream | Downstream +---------+ Client |
+ // +---------------+ +------------+------------+ +------------+
+ // remote ip public ip private ip
+ // [64:ff9b::808:808]:443 [clat ipv6]:9876 [TetheredDevice ipv4]:9876
+ //
+ // Note that CLAT IPv6 address is generated by ClatCoordinator. Get the CLAT IPv6 address by
+ // sending out an IPv4 packet and extracting the source address from CLAT translated IPv6
+ // packet.
+ //
+ private void runClatUdpTest(TetheringTester tester) throws Exception {
+ final TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString(
+ "1:2:3:4:5:6"), true /* hasIpv6 */);
+
+ // Get CLAT IPv6 address.
+ final Inet6Address clatAddr6 = getClatIpv6Address(tester, tethered);
+
+ // Send an IPv4 UDP packet in original direction.
+ // IPv4 packet -- CLAT translation --> IPv6 packet
+ final ByteBuffer originalPacket = buildUdpPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
+ PAYLOAD /* payload */);
+ tester.verifyUpload(originalPacket, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, false /* hasEther */, false /* isIpv4 */, PAYLOAD);
+ });
+
+ // Send an IPv6 UDP packet in reply direction.
+ // IPv6 packet -- CLAT translation --> IPv4 packet
+ final ByteBuffer replyPacket = buildUdpPacket(REMOTE_NAT64_ADDR /* srcIp */,
+ clatAddr6 /* dstIp */, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */,
+ PAYLOAD2 /* payload */);
+ tester.verifyDownload(replyPacket, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, true /* hasEther */, true /* isIpv4 */, PAYLOAD2);
+ });
+
+ // TODO: test CLAT bpf maps.
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testTetherClatUdp() throws Exception {
+ // CLAT only starts on IPv6 only network.
+ runClatUdpTest(initTetheringTester(toList(TEST_IP6_ADDR), toList(TEST_IP6_DNS)));
+ }
+
private <T> List<T> toList(T... array) {
return Arrays.asList(array);
}
diff --git a/Tethering/tests/integration/src/android/net/TetheringTester.java b/Tethering/tests/integration/src/android/net/TetheringTester.java
index d24661a..4d90d39 100644
--- a/Tethering/tests/integration/src/android/net/TetheringTester.java
+++ b/Tethering/tests/integration/src/android/net/TetheringTester.java
@@ -16,10 +16,24 @@
package android.net;
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_UDP;
+
import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
+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.IPV6_ADDR_ALL_NODES_MULTICAST;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -30,13 +44,30 @@
import android.util.ArrayMap;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.net.module.util.Ipv6Utils;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv4Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.LlaOption;
+import com.android.net.module.util.structs.NsHeader;
+import com.android.net.module.util.structs.PrefixInformationOption;
+import com.android.net.module.util.structs.RaHeader;
+import com.android.net.module.util.structs.UdpHeader;
import com.android.networkstack.arp.ArpPacket;
import com.android.testutils.TapPacketReader;
import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeoutException;
import java.util.function.Predicate;
@@ -50,31 +81,40 @@
private static final String TAG = TetheringTester.class.getSimpleName();
private static final int PACKET_READ_TIMEOUT_MS = 100;
private static final int DHCP_DISCOVER_ATTEMPTS = 10;
+ private static final int READ_RA_ATTEMPTS = 10;
private static final byte[] DHCP_REQUESTED_PARAMS = new byte[] {
DhcpPacket.DHCP_SUBNET_MASK,
DhcpPacket.DHCP_ROUTER,
DhcpPacket.DHCP_DNS_SERVER,
DhcpPacket.DHCP_LEASE_TIME,
};
+ private static final InetAddress LINK_LOCAL = parseNumericAddress("fe80::1");
public static final String DHCP_HOSTNAME = "testhostname";
private final ArrayMap<MacAddress, TetheredDevice> mTetheredDevices;
private final TapPacketReader mDownstreamReader;
+ private final TapPacketReader mUpstreamReader;
public TetheringTester(TapPacketReader downstream) {
+ this(downstream, null);
+ }
+
+ public TetheringTester(TapPacketReader downstream, TapPacketReader upstream) {
if (downstream == null) fail("Downstream reader could not be NULL");
mDownstreamReader = downstream;
+ mUpstreamReader = upstream;
mTetheredDevices = new ArrayMap<>();
}
- public TetheredDevice createTetheredDevice(MacAddress macAddr) throws Exception {
+ public TetheredDevice createTetheredDevice(MacAddress macAddr, boolean hasIpv6)
+ throws Exception {
if (mTetheredDevices.get(macAddr) != null) {
fail("Tethered device already created");
}
- TetheredDevice tethered = new TetheredDevice(macAddr);
+ TetheredDevice tethered = new TetheredDevice(macAddr, hasIpv6);
mTetheredDevices.put(macAddr, tethered);
return tethered;
@@ -84,14 +124,15 @@
public final MacAddress macAddr;
public final MacAddress routerMacAddr;
public final Inet4Address ipv4Addr;
+ public final Inet6Address ipv6Addr;
- private TetheredDevice(MacAddress mac) throws Exception {
+ private TetheredDevice(MacAddress mac, boolean hasIpv6) throws Exception {
macAddr = mac;
-
DhcpResults dhcpResults = runDhcp(macAddr.toByteArray());
ipv4Addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
routerMacAddr = getRouterMacAddressFromArp(ipv4Addr, macAddr,
dhcpResults.serverAddress);
+ ipv6Addr = hasIpv6 ? runSlaac(macAddr, routerMacAddr) : null;
}
}
@@ -141,7 +182,7 @@
}
private DhcpPacket getNextDhcpPacket() throws Exception {
- final byte[] packet = getNextMatchedPacket((p) -> {
+ final byte[] packet = getDownloadPacket((p) -> {
// Test whether this is DHCP packet.
try {
DhcpPacket.decodeFullPacket(p, p.length, DhcpPacket.ENCAP_L2);
@@ -184,7 +225,7 @@
tethered.ipv4Addr.getAddress() /* sender IP */,
(short) ARP_REPLY);
try {
- sendPacket(arpReply);
+ sendUploadPacket(arpReply);
} catch (Exception e) {
fail("Failed to reply ARP for " + tethered.ipv4Addr);
}
@@ -198,9 +239,9 @@
tetherMac.toByteArray() /* srcMac */, routerIp.getAddress() /* target IP */,
new byte[ETHER_ADDR_LEN] /* target HW address */,
tetherIp.getAddress() /* sender IP */, (short) ARP_REQUEST);
- sendPacket(arpProbe);
+ sendUploadPacket(arpProbe);
- final byte[] packet = getNextMatchedPacket((p) -> {
+ final byte[] packet = getDownloadPacket((p) -> {
final ArpPacket arpPacket = parseArpPacket(p);
if (arpPacket == null || arpPacket.opCode != ARP_REPLY) return false;
return arpPacket.targetIp.equals(tetherIp);
@@ -216,45 +257,205 @@
return null;
}
- public void sendPacket(ByteBuffer packet) throws Exception {
+ private List<PrefixInformationOption> getRaPrefixOptions(byte[] packet) {
+ ByteBuffer buf = ByteBuffer.wrap(packet);
+ if (!isExpectedIcmpv6Packet(buf, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT)) {
+ fail("Parsing RA packet fail");
+ }
+
+ Struct.parse(RaHeader.class, buf);
+ final ArrayList<PrefixInformationOption> pioList = new ArrayList<>();
+ while (buf.position() < packet.length) {
+ final int currentPos = buf.position();
+ final int type = Byte.toUnsignedInt(buf.get());
+ final int length = Byte.toUnsignedInt(buf.get());
+ if (type == ICMPV6_ND_OPTION_PIO) {
+ final ByteBuffer pioBuf = ByteBuffer.wrap(buf.array(), currentPos,
+ Struct.getSize(PrefixInformationOption.class));
+ final PrefixInformationOption pio =
+ Struct.parse(PrefixInformationOption.class, pioBuf);
+ pioList.add(pio);
+
+ // Move ByteBuffer position to the next option.
+ buf.position(currentPos + Struct.getSize(PrefixInformationOption.class));
+ } else {
+ buf.position(currentPos + (length * 8));
+ }
+ }
+ return pioList;
+ }
+
+ private Inet6Address runSlaac(MacAddress srcMac, MacAddress dstMac) throws Exception {
+ sendRsPacket(srcMac, dstMac);
+
+ final byte[] raPacket = verifyPacketNotNull("Receive RA fail", getDownloadPacket(p -> {
+ return isExpectedIcmpv6Packet(p, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT);
+ }));
+
+ final List<PrefixInformationOption> options = getRaPrefixOptions(raPacket);
+
+ for (PrefixInformationOption pio : options) {
+ if (pio.validLifetime > 0) {
+ final byte[] addressBytes = pio.prefix;
+ // Random the last two bytes as suffix.
+ // TODO: Currently do not implmement DAD in the test. Rely the gateway ipv6 address
+ // genetrated by tethering module always has random the last byte.
+ addressBytes[addressBytes.length - 1] = (byte) (new Random()).nextInt();
+ addressBytes[addressBytes.length - 2] = (byte) (new Random()).nextInt();
+
+ return (Inet6Address) InetAddress.getByAddress(addressBytes);
+ }
+ }
+
+ fail("No available ipv6 prefix");
+ return null;
+ }
+
+ private void sendRsPacket(MacAddress srcMac, MacAddress dstMac) throws Exception {
+ Log.d(TAG, "Sending RS");
+ ByteBuffer slla = LlaOption.build((byte) ICMPV6_ND_OPTION_SLLA, srcMac);
+ ByteBuffer rs = Ipv6Utils.buildRsPacket(srcMac, dstMac, (Inet6Address) LINK_LOCAL,
+ IPV6_ADDR_ALL_NODES_MULTICAST, slla);
+
+ sendUploadPacket(rs);
+ }
+
+ private void maybeReplyNa(byte[] packet) {
+ ByteBuffer buf = ByteBuffer.wrap(packet);
+ final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
+ if (ethHdr.etherType != ETHER_TYPE_IPV6) return;
+
+ final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
+ if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return;
+
+ final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
+ if (icmpv6Hdr.type != (short) ICMPV6_NEIGHBOR_SOLICITATION) return;
+
+ final NsHeader nsHdr = Struct.parse(NsHeader.class, buf);
+ for (int i = 0; i < mTetheredDevices.size(); i++) {
+ TetheredDevice tethered = mTetheredDevices.valueAt(i);
+ if (!nsHdr.target.equals(tethered.ipv6Addr)) continue;
+
+ final ByteBuffer tlla = LlaOption.build((byte) ICMPV6_ND_OPTION_TLLA, tethered.macAddr);
+ int flags = NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED
+ | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
+ ByteBuffer ns = Ipv6Utils.buildNaPacket(tethered.macAddr, tethered.routerMacAddr,
+ nsHdr.target, ipv6Hdr.srcIp, flags, nsHdr.target, tlla);
+ try {
+ sendUploadPacket(ns);
+ } catch (Exception e) {
+ fail("Failed to reply NA for " + tethered.ipv6Addr);
+ }
+
+ return;
+ }
+ }
+
+ public static boolean isExpectedIcmpv6Packet(byte[] packet, boolean hasEth, int type) {
+ final ByteBuffer buf = ByteBuffer.wrap(packet);
+ return isExpectedIcmpv6Packet(buf, hasEth, type);
+ }
+
+ private static boolean isExpectedIcmpv6Packet(ByteBuffer buf, boolean hasEth, int type) {
+ try {
+ if (hasEth && !hasExpectedEtherHeader(buf, false /* isIpv4 */)) return false;
+
+ if (!hasExpectedIpHeader(buf, false /* isIpv4 */, IPPROTO_ICMPV6)) return false;
+
+ return Struct.parse(Icmpv6Header.class, buf).type == (short) type;
+ } catch (Exception e) {
+ // Parsing packet fail means it is not icmpv6 packet.
+ }
+
+ return false;
+ }
+
+ private static boolean hasExpectedEtherHeader(@NonNull final ByteBuffer buf, boolean isIpv4)
+ throws Exception {
+ final int expected = isIpv4 ? ETHER_TYPE_IPV4 : ETHER_TYPE_IPV6;
+
+ return Struct.parse(EthernetHeader.class, buf).etherType == expected;
+ }
+
+ private static boolean hasExpectedIpHeader(@NonNull final ByteBuffer buf, boolean isIpv4,
+ int ipProto) throws Exception {
+ if (isIpv4) {
+ return Struct.parse(Ipv4Header.class, buf).protocol == (byte) ipProto;
+ } else {
+ return Struct.parse(Ipv6Header.class, buf).nextHeader == (byte) ipProto;
+ }
+ }
+
+ public static boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEth,
+ boolean isIpv4, @NonNull final ByteBuffer payload) {
+ final ByteBuffer buf = ByteBuffer.wrap(rawPacket);
+ try {
+ if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false;
+
+ if (!hasExpectedIpHeader(buf, isIpv4, IPPROTO_UDP)) return false;
+
+ if (Struct.parse(UdpHeader.class, buf) == null) return false;
+ } catch (Exception e) {
+ // Parsing packet fail means it is not udp packet.
+ return false;
+ }
+
+ if (buf.remaining() != payload.limit()) return false;
+
+ return Arrays.equals(Arrays.copyOfRange(buf.array(), buf.position(), buf.limit()),
+ payload.array());
+ }
+
+ private void sendUploadPacket(ByteBuffer packet) throws Exception {
mDownstreamReader.sendResponse(packet);
}
- public byte[] getNextMatchedPacket(Predicate<byte[]> filter) {
+ private void sendDownloadPacket(ByteBuffer packet) throws Exception {
+ assertNotNull("Can't deal with upstream interface in local only mode", mUpstreamReader);
+
+ mUpstreamReader.sendResponse(packet);
+ }
+
+ private byte[] getDownloadPacket(Predicate<byte[]> filter) {
byte[] packet;
while ((packet = mDownstreamReader.poll(PACKET_READ_TIMEOUT_MS)) != null) {
if (filter.test(packet)) return packet;
maybeReplyArp(packet);
+ maybeReplyNa(packet);
}
return null;
}
- public void verifyUpload(final RemoteResponder dst, final ByteBuffer packet,
- final Predicate<byte[]> filter) throws Exception {
- sendPacket(packet);
- assertNotNull("Upload fail", dst.getNextMatchedPacket(filter));
+ private byte[] getUploadPacket(Predicate<byte[]> filter) {
+ assertNotNull("Can't deal with upstream interface in local only mode", mUpstreamReader);
+
+ return mUpstreamReader.poll(PACKET_READ_TIMEOUT_MS, filter);
}
- public static class RemoteResponder {
- final TapPacketReader mUpstreamReader;
- public RemoteResponder(TapPacketReader reader) {
- mUpstreamReader = reader;
- }
+ private @NonNull byte[] verifyPacketNotNull(String message, @Nullable byte[] packet) {
+ assertNotNull(message, packet);
- public void sendPacket(ByteBuffer packet) throws Exception {
- mUpstreamReader.sendResponse(packet);
- }
+ return packet;
+ }
- public byte[] getNextMatchedPacket(Predicate<byte[]> filter) throws Exception {
- return mUpstreamReader.poll(PACKET_READ_TIMEOUT_MS, filter);
- }
+ public byte[] testUpload(final ByteBuffer packet, final Predicate<byte[]> filter)
+ throws Exception {
+ sendUploadPacket(packet);
- public void verifyDownload(final TetheringTester dst, final ByteBuffer packet,
- final Predicate<byte[]> filter) throws Exception {
- sendPacket(packet);
- assertNotNull("Download fail", dst.getNextMatchedPacket(filter));
- }
+ return getUploadPacket(filter);
+ }
+
+ public byte[] verifyUpload(final ByteBuffer packet, final Predicate<byte[]> filter)
+ throws Exception {
+ return verifyPacketNotNull("Upload fail", testUpload(packet, filter));
+ }
+
+ public byte[] verifyDownload(final ByteBuffer packet, final Predicate<byte[]> filter)
+ throws Exception {
+ sendDownloadPacket(packet);
+
+ return verifyPacketNotNull("Download fail", getDownloadPacket(filter));
}
}
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index aac531a..bf7e887 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -116,6 +116,7 @@
import com.android.networkstack.tethering.TetherLimitValue;
import com.android.networkstack.tethering.TetherUpstream6Key;
import com.android.networkstack.tethering.TetheringConfiguration;
+import com.android.networkstack.tethering.metrics.TetheringMetrics;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
import com.android.testutils.DevSdkIgnoreRule;
@@ -186,6 +187,7 @@
@Mock private NetworkStatsManager mStatsManager;
@Mock private TetheringConfiguration mTetherConfig;
@Mock private ConntrackMonitor mConntrackMonitor;
+ @Mock private TetheringMetrics mTetheringMetrics;
@Mock private BpfMap<Tether4Key, Tether4Value> mBpfDownstream4Map;
@Mock private BpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map;
@Mock private BpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map;
@@ -235,7 +237,7 @@
when(mTetherConfig.getP2pLeasesSubnetPrefixLength()).thenReturn(P2P_SUBNET_PREFIX_LENGTH);
mIpServer = new IpServer(
IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator,
- mCallback, mTetherConfig, mAddressCoordinator, mDependencies);
+ mCallback, mTetherConfig, mAddressCoordinator, mTetheringMetrics, mDependencies);
mIpServer.start();
mNeighborEventConsumer = neighborCaptor.getValue();
@@ -367,7 +369,7 @@
.thenReturn(mIpNeighborMonitor);
mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
mNetd, mBpfCoordinator, mCallback, mTetherConfig, mAddressCoordinator,
- mDependencies);
+ mTetheringMetrics, mDependencies);
mIpServer.start();
mLooper.dispatchAll();
verify(mCallback).updateInterfaceState(
@@ -451,6 +453,9 @@
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
+ verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_BLUETOOTH),
+ eq(TETHER_ERROR_NO_ERROR));
+ verify(mTetheringMetrics).sendReport(eq(TETHERING_BLUETOOTH));
verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
}
@@ -658,6 +663,9 @@
usbTeardownOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), mLinkPropertiesCaptor.capture());
assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue());
+ verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_USB),
+ eq(TETHER_ERROR_TETHER_IFACE_ERROR));
+ verify(mTetheringMetrics).sendReport(eq(TETHERING_USB));
}
@Test
@@ -676,6 +684,9 @@
usbTeardownOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), mLinkPropertiesCaptor.capture());
assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue());
+ verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_USB),
+ eq(TETHER_ERROR_ENABLE_FORWARDING_ERROR));
+ verify(mTetheringMetrics).sendReport(eq(TETHERING_USB));
}
@Test
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
index e9716b3..8ef0c76 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
@@ -26,6 +26,7 @@
import static android.net.RouteInfo.RTN_UNICAST;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_IFACE;
import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_UID;
@@ -668,7 +669,7 @@
if (isAtLeastT()) {
mTetherStatsProviderCb.expectNotifyLimitReached();
- } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.S) {
+ } else if (isAtLeastS()) {
mTetherStatsProviderCb.expectNotifyWarningOrLimitReached();
} else {
mTetherStatsProviderCb.expectNotifyLimitReached();
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index f664d5d..9db8f16 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -275,7 +275,7 @@
mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
result);
verify(mTethering).isTetheringSupported();
- verify(mTethering).startTethering(eq(request), eq(result));
+ verify(mTethering).startTethering(eq(request), eq(TEST_CALLER_PKG), eq(result));
}
@Test
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 6ef0e24..773cae3 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -45,6 +45,7 @@
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
@@ -193,11 +194,15 @@
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
+import com.android.networkstack.tethering.metrics.TetheringMetrics;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.MiscAsserts;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -220,6 +225,8 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class TetheringTest {
+ @Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
private static final int IFINDEX_OFFSET = 100;
private static final String TEST_MOBILE_IFNAME = "test_rmnet_data0";
@@ -240,6 +247,7 @@
private static final String TEST_WIFI_REGEX = "test_wlan\\d";
private static final String TEST_P2P_REGEX = "test_p2p-p2p\\d-.*";
private static final String TEST_BT_REGEX = "test_pan\\d";
+ private static final String TEST_CALLER_PKG = "com.test.tethering";
private static final int CELLULAR_NETID = 100;
private static final int WIFI_NETID = 101;
@@ -274,6 +282,7 @@
@Mock private BluetoothPan mBluetoothPan;
@Mock private BluetoothPanShim mBluetoothPanShim;
@Mock private TetheredInterfaceRequestShim mTetheredInterfaceRequestShim;
+ @Mock private TetheringMetrics mTetheringMetrics;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -297,6 +306,7 @@
private OffloadController mOffloadCtrl;
private PrivateAddressCoordinator mPrivateAddressCoordinator;
private SoftApCallback mSoftApCallback;
+ private SoftApCallback mLocalOnlyHotspotCallback;
private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim;
@@ -498,6 +508,11 @@
}
@Override
+ public TetheringMetrics getTetheringMetrics() {
+ return mTetheringMetrics;
+ }
+
+ @Override
public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx,
TetheringConfiguration cfg) {
mPrivateAddressCoordinator = super.getPrivateAddressCoordinator(ctx, cfg);
@@ -662,6 +677,14 @@
verify(mWifiManager).registerSoftApCallback(any(), softApCallbackCaptor.capture());
mSoftApCallback = softApCallbackCaptor.getValue();
+ if (isAtLeastT()) {
+ final ArgumentCaptor<SoftApCallback> localOnlyCallbackCaptor =
+ ArgumentCaptor.forClass(SoftApCallback.class);
+ verify(mWifiManager).registerLocalOnlyHotspotSoftApCallback(any(),
+ localOnlyCallbackCaptor.capture());
+ mLocalOnlyHotspotCallback = localOnlyCallbackCaptor.getValue();
+ }
+
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)).thenReturn(true);
}
@@ -856,7 +879,8 @@
private void prepareNcmTethering() {
// Emulate startTethering(TETHERING_NCM) called
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NCM);
}
@@ -864,7 +888,7 @@
private void prepareUsbTethering() {
// Emulate pressing the USB tethering button in Settings UI.
final TetheringRequestParcel request = createTetheringRequestParcel(TETHERING_USB);
- mTethering.startTethering(request, null);
+ mTethering.startTethering(request, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
assertEquals(1, mTethering.getActiveTetheringRequests().size());
@@ -1434,7 +1458,8 @@
when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
@@ -1461,7 +1486,8 @@
when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
@@ -1537,11 +1563,13 @@
doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
// Emulate pressing the WiFi tethering button.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
verifyNoMoreInteractions(mNetd);
+ verify(mTetheringMetrics).createBuilder(eq(TETHERING_WIFI), anyString());
// Emulate externally-visible WifiManager effects, causing the
// per-interface state machine to start up, and telling us that
@@ -1580,6 +1608,10 @@
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
+ verify(mTetheringMetrics, times(2)).updateErrorCode(eq(TETHERING_WIFI),
+ eq(TETHER_ERROR_INTERNAL_ERROR));
+ verify(mTetheringMetrics, times(2)).sendReport(eq(TETHERING_WIFI));
+
verifyNoMoreInteractions(mWifiManager);
verifyNoMoreInteractions(mNetd);
}
@@ -1882,7 +1914,8 @@
tetherState = callback.pollTetherStatesChanged();
assertArrayEquals(tetherState.availableList, new TetheringInterface[] {wifiIface});
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_WIFI), TEST_CALLER_PKG,
+ null);
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
tetherState = callback.pollTetherStatesChanged();
assertArrayEquals(tetherState.tetheredList, new TetheringInterface[] {wifiIface});
@@ -1985,10 +2018,12 @@
public void testNoDuplicatedEthernetRequest() throws Exception {
final TetheredInterfaceRequest mockRequest = mock(TetheredInterfaceRequest.class);
when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verify(mEm, times(1)).requestTetheredInterface(any(), any());
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), TEST_CALLER_PKG,
+ null);
mLooper.dispatchAll();
verifyNoMoreInteractions(mEm);
mTethering.stopTethering(TETHERING_ETHERNET);
@@ -2192,14 +2227,16 @@
final ResultListener thirdResult = new ResultListener(TETHER_ERROR_NO_ERROR);
// Enable USB tethering and check that Tethering starts USB.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), firstResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), TEST_CALLER_PKG,
+ firstResult);
mLooper.dispatchAll();
firstResult.assertHasResult();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
verifyNoMoreInteractions(mUsbManager);
// Enable USB tethering again with the same request and expect no change to USB.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), secondResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), TEST_CALLER_PKG,
+ secondResult);
mLooper.dispatchAll();
secondResult.assertHasResult();
verify(mUsbManager, never()).setCurrentFunctions(UsbManager.FUNCTION_NONE);
@@ -2208,7 +2245,8 @@
// Enable USB tethering with a different request and expect that USB is stopped and
// started.
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL), thirdResult);
+ serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL),
+ TEST_CALLER_PKG, thirdResult);
mLooper.dispatchAll();
thirdResult.assertHasResult();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
@@ -2237,7 +2275,8 @@
final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor =
ArgumentCaptor.forClass(DhcpServingParamsParcel.class);
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL), null);
+ serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL),
+ TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NCM);
mTethering.interfaceStatusChanged(TEST_NCM_IFNAME, true);
@@ -2305,7 +2344,7 @@
final TetheringRequestParcel wifiNotExemptRequest =
createTetheringRequestParcel(TETHERING_WIFI, null, null, false,
CONNECTIVITY_SCOPE_GLOBAL);
- mTethering.startTethering(wifiNotExemptRequest, null);
+ mTethering.startTethering(wifiNotExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false);
verify(mEntitleMgr, never()).setExemptedDownstreamType(TETHERING_WIFI);
@@ -2319,7 +2358,7 @@
final TetheringRequestParcel wifiExemptRequest =
createTetheringRequestParcel(TETHERING_WIFI, null, null, true,
CONNECTIVITY_SCOPE_GLOBAL);
- mTethering.startTethering(wifiExemptRequest, null);
+ mTethering.startTethering(wifiExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false);
verify(mEntitleMgr).setExemptedDownstreamType(TETHERING_WIFI);
@@ -2332,14 +2371,14 @@
// If one app enables tethering without provisioning check first, then another app enables
// tethering of the same type but does not disable the provisioning check.
setupForRequiredProvisioning();
- mTethering.startTethering(wifiExemptRequest, null);
+ mTethering.startTethering(wifiExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false);
verify(mEntitleMgr).setExemptedDownstreamType(TETHERING_WIFI);
assertTrue(mEntitleMgr.isCellularUpstreamPermitted());
reset(mEntitleMgr);
setupForRequiredProvisioning();
- mTethering.startTethering(wifiNotExemptRequest, null);
+ mTethering.startTethering(wifiNotExemptRequest, TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false);
verify(mEntitleMgr, never()).setExemptedDownstreamType(TETHERING_WIFI);
@@ -2429,7 +2468,8 @@
when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest);
final ArgumentCaptor<TetheredInterfaceCallback> callbackCaptor =
ArgumentCaptor.forClass(TetheredInterfaceCallback.class);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET),
+ TEST_CALLER_PKG, null);
mLooper.dispatchAll();
verify(mEm).requestTetheredInterface(any(), callbackCaptor.capture());
TetheredInterfaceCallback ethCallback = callbackCaptor.getValue();
@@ -2511,12 +2551,11 @@
eventCallbacks = dhcpEventCbsCaptor.getValue();
// Update lease for local only tethering.
final MacAddress testMac1 = MacAddress.fromString("11:11:11:11:11:11");
- final ArrayList<DhcpLeaseParcelable> p2pLeases = new ArrayList<>();
- p2pLeases.add(createDhcpLeaseParcelable("clientId1", testMac1, "192.168.50.24", 24,
- Long.MAX_VALUE, "test1"));
- notifyDhcpLeasesChanged(p2pLeases, eventCallbacks);
- final List<TetheredClient> clients = toTetheredClients(p2pLeases, TETHERING_WIFI_P2P);
- callback.expectTetheredClientChanged(clients);
+ final DhcpLeaseParcelable p2pLease = createDhcpLeaseParcelable("clientId1", testMac1,
+ "192.168.50.24", 24, Long.MAX_VALUE, "test1");
+ final List<TetheredClient> p2pClients = notifyDhcpLeasesChanged(TETHERING_WIFI_P2P,
+ eventCallbacks, p2pLease);
+ callback.expectTetheredClientChanged(p2pClients);
reset(mDhcpServer);
// Run wifi tethering.
@@ -2526,25 +2565,20 @@
any(), dhcpEventCbsCaptor.capture());
eventCallbacks = dhcpEventCbsCaptor.getValue();
// Update mac address from softAp callback before getting dhcp lease.
- final ArrayList<WifiClient> wifiClients = new ArrayList<>();
final MacAddress testMac2 = MacAddress.fromString("22:22:22:22:22:22");
- final WifiClient testClient = mock(WifiClient.class);
- when(testClient.getMacAddress()).thenReturn(testMac2);
- wifiClients.add(testClient);
- mSoftApCallback.onConnectedClientsChanged(wifiClients);
- final TetheredClient noAddrClient = new TetheredClient(testMac2,
- Collections.emptyList() /* addresses */, TETHERING_WIFI);
- clients.add(noAddrClient);
- callback.expectTetheredClientChanged(clients);
+ final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac2,
+ false /* isLocalOnly */);
+ final List<TetheredClient> p2pAndNoAddrClients = new ArrayList<>(p2pClients);
+ p2pAndNoAddrClients.add(noAddrClient);
+ callback.expectTetheredClientChanged(p2pAndNoAddrClients);
// Update dhcp lease for wifi tethering.
- clients.remove(noAddrClient);
- final ArrayList<DhcpLeaseParcelable> wifiLeases = new ArrayList<>();
- wifiLeases.add(createDhcpLeaseParcelable("clientId2", testMac2, "192.168.43.24", 24,
- Long.MAX_VALUE, "test2"));
- notifyDhcpLeasesChanged(wifiLeases, eventCallbacks);
- clients.addAll(toTetheredClients(wifiLeases, TETHERING_WIFI));
- callback.expectTetheredClientChanged(clients);
+ final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId2", testMac2,
+ "192.168.43.24", 24, Long.MAX_VALUE, "test2");
+ final List<TetheredClient> p2pAndWifiClients = new ArrayList<>(p2pClients);
+ p2pAndWifiClients.addAll(notifyDhcpLeasesChanged(TETHERING_WIFI,
+ eventCallbacks, wifiLease));
+ callback.expectTetheredClientChanged(p2pAndWifiClients);
// Test onStarted callback that register second callback when tethering is running.
TestTetheringEventCallback callback2 = new TestTetheringEventCallback();
@@ -2552,18 +2586,74 @@
mTethering.registerTetheringEventCallback(callback2);
mLooper.dispatchAll();
});
- callback2.expectTetheredClientChanged(clients);
+ callback2.expectTetheredClientChanged(p2pAndWifiClients);
}
- private void notifyDhcpLeasesChanged(List<DhcpLeaseParcelable> leaseParcelables,
- IDhcpEventCallbacks callback) throws Exception {
- callback.onLeasesChanged(leaseParcelables);
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testUpdateConnectedClientsForLocalOnlyHotspot() throws Exception {
+ TestTetheringEventCallback callback = new TestTetheringEventCallback();
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mTethering.registerTetheringEventCallback(callback);
+ mLooper.dispatchAll();
+ });
+ callback.expectTetheredClientChanged(Collections.emptyList());
+
+ // Run local only hotspot.
+ mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
+ sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY);
+
+ final ArgumentCaptor<IDhcpEventCallbacks> dhcpEventCbsCaptor =
+ ArgumentCaptor.forClass(IDhcpEventCallbacks.class);
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
+ any(), dhcpEventCbsCaptor.capture());
+ final IDhcpEventCallbacks eventCallbacks = dhcpEventCbsCaptor.getValue();
+ // Update mac address from softAp callback before getting dhcp lease.
+ final MacAddress testMac = MacAddress.fromString("22:22:22:22:22:22");
+ final TetheredClient noAddrClient = notifyConnectedWifiClientsChanged(testMac,
+ true /* isLocalOnly */);
+ final List<TetheredClient> noAddrLocalOnlyClients = new ArrayList<>();
+ noAddrLocalOnlyClients.add(noAddrClient);
+ callback.expectTetheredClientChanged(noAddrLocalOnlyClients);
+
+ // Update dhcp lease for local only hotspot.
+ final DhcpLeaseParcelable wifiLease = createDhcpLeaseParcelable("clientId", testMac,
+ "192.168.43.24", 24, Long.MAX_VALUE, "test");
+ final List<TetheredClient> localOnlyClients = notifyDhcpLeasesChanged(TETHERING_WIFI,
+ eventCallbacks, wifiLease);
+ callback.expectTetheredClientChanged(localOnlyClients);
+
+ // Client disconnect from local only hotspot.
+ mLocalOnlyHotspotCallback.onConnectedClientsChanged(Collections.emptyList());
+ callback.expectTetheredClientChanged(Collections.emptyList());
+ }
+
+ private TetheredClient notifyConnectedWifiClientsChanged(final MacAddress mac,
+ boolean isLocalOnly) throws Exception {
+ final ArrayList<WifiClient> wifiClients = new ArrayList<>();
+ final WifiClient testClient = mock(WifiClient.class);
+ when(testClient.getMacAddress()).thenReturn(mac);
+ wifiClients.add(testClient);
+ if (isLocalOnly) {
+ mLocalOnlyHotspotCallback.onConnectedClientsChanged(wifiClients);
+ } else {
+ mSoftApCallback.onConnectedClientsChanged(wifiClients);
+ }
+ return new TetheredClient(mac, Collections.emptyList() /* addresses */, TETHERING_WIFI);
+ }
+
+ private List<TetheredClient> notifyDhcpLeasesChanged(int type, IDhcpEventCallbacks callback,
+ DhcpLeaseParcelable... leases) throws Exception {
+ final List<DhcpLeaseParcelable> dhcpLeases = Arrays.asList(leases);
+ callback.onLeasesChanged(dhcpLeases);
mLooper.dispatchAll();
+
+ return toTetheredClients(dhcpLeases, type);
}
private List<TetheredClient> toTetheredClients(List<DhcpLeaseParcelable> leaseParcelables,
int type) throws Exception {
- final ArrayList<TetheredClient> leases = new ArrayList<>();
+ final ArrayList<TetheredClient> clients = new ArrayList<>();
for (DhcpLeaseParcelable lease : leaseParcelables) {
final LinkAddress address = new LinkAddress(
intToInet4AddressHTH(lease.netAddr), lease.prefixLength,
@@ -2573,13 +2663,13 @@
final MacAddress macAddress = MacAddress.fromBytes(lease.hwAddr);
final AddressInfo addressInfo = new TetheredClient.AddressInfo(address, lease.hostname);
- leases.add(new TetheredClient(
+ clients.add(new TetheredClient(
macAddress,
Collections.singletonList(addressInfo),
type));
}
- return leases;
+ return clients;
}
private DhcpLeaseParcelable createDhcpLeaseParcelable(final String clientId,
@@ -2604,7 +2694,8 @@
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ TEST_CALLER_PKG, result);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
result.assertHasResult();
@@ -2639,7 +2730,8 @@
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ TEST_CALLER_PKG, result);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
result.assertHasResult();
@@ -2660,7 +2752,8 @@
// already bound.
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
final ResultListener secondResult = new ResultListener(TETHER_ERROR_NO_ERROR);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), secondResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ TEST_CALLER_PKG, secondResult);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, false /* bindToPanService */);
secondResult.assertHasResult();
@@ -2681,7 +2774,8 @@
public void testBluetoothServiceDisconnects() throws Exception {
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH),
+ TEST_CALLER_PKG, result);
mLooper.dispatchAll();
ServiceListener panListener = verifySetBluetoothTethering(true /* enable */,
true /* bindToPanService */);
@@ -2832,18 +2926,26 @@
runNcmTethering();
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
+ verify(mTetheringMetrics).createBuilder(eq(TETHERING_NCM), anyString());
// Change the USB tethering function to NCM. Because the USB tethering function was set to
// RNDIS (the default), tethering is stopped.
forceUsbTetheringUse(TETHER_USB_NCM_FUNCTION);
verifyUsbTetheringStopDueToSettingChange(TEST_NCM_IFNAME);
+ verify(mTetheringMetrics).updateErrorCode(anyInt(), eq(TETHER_ERROR_NO_ERROR));
+ verify(mTetheringMetrics).sendReport(eq(TETHERING_NCM));
// If TETHERING_USB is forced to use ncm function, TETHERING_NCM would no longer be
// available.
final ResultListener ncmResult = new ResultListener(TETHER_ERROR_SERVICE_UNAVAIL);
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), ncmResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_NCM), TEST_CALLER_PKG,
+ ncmResult);
mLooper.dispatchAll();
ncmResult.assertHasResult();
+ verify(mTetheringMetrics, times(2)).createBuilder(eq(TETHERING_NCM), anyString());
+ verify(mTetheringMetrics).updateErrorCode(eq(TETHERING_NCM),
+ eq(TETHER_ERROR_SERVICE_UNAVAIL));
+ verify(mTetheringMetrics, times(2)).sendReport(eq(TETHERING_NCM));
// Run TETHERING_USB with ncm configuration.
runDualStackUsbTethering(TEST_NCM_IFNAME);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
new file mode 100644
index 0000000..c34cf5f
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/metrics/TetheringMetricsTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.tethering.metrics;
+
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringManager.TETHERING_NCM;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_DISABLE_FORWARDING_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_ENABLE_FORWARDING_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
+import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_INTERNAL_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
+import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
+import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
+import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_TYPE;
+import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
+import static android.net.TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.stats.connectivity.DownstreamType;
+import android.stats.connectivity.ErrorCode;
+import android.stats.connectivity.UpstreamType;
+import android.stats.connectivity.UserType;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class TetheringMetricsTest {
+ private static final String TEST_CALLER_PKG = "com.test.caller.pkg";
+ private static final String SETTINGS_PKG = "com.android.settings";
+ private static final String SYSTEMUI_PKG = "com.android.systemui";
+ private static final String GMS_PKG = "com.google.android.gms";
+ private TetheringMetrics mTetheringMetrics;
+
+ private final NetworkTetheringReported.Builder mStatsBuilder =
+ NetworkTetheringReported.newBuilder();
+
+ private class MockTetheringMetrics extends TetheringMetrics {
+ @Override
+ public void write(final NetworkTetheringReported reported) { }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mTetheringMetrics = spy(new MockTetheringMetrics());
+ }
+
+ private void runDownstreamTypesTest(final Pair<Integer, DownstreamType>... testPairs)
+ throws Exception {
+ for (Pair<Integer, DownstreamType> testPair : testPairs) {
+ final int type = testPair.first;
+ final DownstreamType expectedResult = testPair.second;
+
+ mTetheringMetrics.createBuilder(type, TEST_CALLER_PKG);
+ mTetheringMetrics.updateErrorCode(type, TETHER_ERROR_NO_ERROR);
+ mTetheringMetrics.sendReport(type);
+ NetworkTetheringReported expectedReport =
+ mStatsBuilder.setDownstreamType(expectedResult)
+ .setUserType(UserType.USER_UNKNOWN)
+ .setUpstreamType(UpstreamType.UT_UNKNOWN)
+ .setErrorCode(ErrorCode.EC_NO_ERROR)
+ .build();
+ verify(mTetheringMetrics).write(expectedReport);
+ reset(mTetheringMetrics);
+ }
+ }
+
+ @Test
+ public void testDownstreamTypes() throws Exception {
+ runDownstreamTypesTest(new Pair<>(TETHERING_WIFI, DownstreamType.DS_TETHERING_WIFI),
+ new Pair<>(TETHERING_WIFI_P2P, DownstreamType.DS_TETHERING_WIFI_P2P),
+ new Pair<>(TETHERING_BLUETOOTH, DownstreamType.DS_TETHERING_BLUETOOTH),
+ new Pair<>(TETHERING_USB, DownstreamType.DS_TETHERING_USB),
+ new Pair<>(TETHERING_NCM, DownstreamType.DS_TETHERING_NCM),
+ new Pair<>(TETHERING_ETHERNET, DownstreamType.DS_TETHERING_ETHERNET));
+ }
+
+ private void runErrorCodesTest(final Pair<Integer, ErrorCode>... testPairs)
+ throws Exception {
+ for (Pair<Integer, ErrorCode> testPair : testPairs) {
+ final int errorCode = testPair.first;
+ final ErrorCode expectedResult = testPair.second;
+
+ mTetheringMetrics.createBuilder(TETHERING_WIFI, TEST_CALLER_PKG);
+ mTetheringMetrics.updateErrorCode(TETHERING_WIFI, errorCode);
+ mTetheringMetrics.sendReport(TETHERING_WIFI);
+ NetworkTetheringReported expectedReport =
+ mStatsBuilder.setDownstreamType(DownstreamType.DS_TETHERING_WIFI)
+ .setUserType(UserType.USER_UNKNOWN)
+ .setUpstreamType(UpstreamType.UT_UNKNOWN)
+ .setErrorCode(expectedResult)
+ .build();
+ verify(mTetheringMetrics).write(expectedReport);
+ reset(mTetheringMetrics);
+ }
+ }
+
+ @Test
+ public void testErrorCodes() throws Exception {
+ runErrorCodesTest(new Pair<>(TETHER_ERROR_NO_ERROR, ErrorCode.EC_NO_ERROR),
+ new Pair<>(TETHER_ERROR_UNKNOWN_IFACE, ErrorCode.EC_UNKNOWN_IFACE),
+ new Pair<>(TETHER_ERROR_SERVICE_UNAVAIL, ErrorCode.EC_SERVICE_UNAVAIL),
+ new Pair<>(TETHER_ERROR_UNSUPPORTED, ErrorCode.EC_UNSUPPORTED),
+ new Pair<>(TETHER_ERROR_UNAVAIL_IFACE, ErrorCode.EC_UNAVAIL_IFACE),
+ new Pair<>(TETHER_ERROR_INTERNAL_ERROR, ErrorCode.EC_INTERNAL_ERROR),
+ new Pair<>(TETHER_ERROR_TETHER_IFACE_ERROR, ErrorCode.EC_TETHER_IFACE_ERROR),
+ new Pair<>(TETHER_ERROR_UNTETHER_IFACE_ERROR, ErrorCode.EC_UNTETHER_IFACE_ERROR),
+ new Pair<>(TETHER_ERROR_ENABLE_FORWARDING_ERROR,
+ ErrorCode.EC_ENABLE_FORWARDING_ERROR),
+ new Pair<>(TETHER_ERROR_DISABLE_FORWARDING_ERROR,
+ ErrorCode.EC_DISABLE_FORWARDING_ERROR),
+ new Pair<>(TETHER_ERROR_IFACE_CFG_ERROR, ErrorCode.EC_IFACE_CFG_ERROR),
+ new Pair<>(TETHER_ERROR_PROVISIONING_FAILED, ErrorCode.EC_PROVISIONING_FAILED),
+ new Pair<>(TETHER_ERROR_DHCPSERVER_ERROR, ErrorCode.EC_DHCPSERVER_ERROR),
+ new Pair<>(TETHER_ERROR_ENTITLEMENT_UNKNOWN, ErrorCode.EC_ENTITLEMENT_UNKNOWN),
+ new Pair<>(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION,
+ ErrorCode.EC_NO_CHANGE_TETHERING_PERMISSION),
+ new Pair<>(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION,
+ ErrorCode.EC_NO_ACCESS_TETHERING_PERMISSION),
+ new Pair<>(TETHER_ERROR_UNKNOWN_TYPE, ErrorCode.EC_UNKNOWN_TYPE));
+ }
+
+ private void runUserTypesTest(final Pair<String, UserType>... testPairs)
+ throws Exception {
+ for (Pair<String, UserType> testPair : testPairs) {
+ final String callerPkg = testPair.first;
+ final UserType expectedResult = testPair.second;
+
+ mTetheringMetrics.createBuilder(TETHERING_WIFI, callerPkg);
+ mTetheringMetrics.updateErrorCode(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
+ mTetheringMetrics.sendReport(TETHERING_WIFI);
+ NetworkTetheringReported expectedReport =
+ mStatsBuilder.setDownstreamType(DownstreamType.DS_TETHERING_WIFI)
+ .setUserType(expectedResult)
+ .setUpstreamType(UpstreamType.UT_UNKNOWN)
+ .setErrorCode(ErrorCode.EC_NO_ERROR)
+ .build();
+ verify(mTetheringMetrics).write(expectedReport);
+ reset(mTetheringMetrics);
+ }
+ }
+
+ @Test
+ public void testUserTypes() throws Exception {
+ runUserTypesTest(new Pair<>(TEST_CALLER_PKG, UserType.USER_UNKNOWN),
+ new Pair<>(SETTINGS_PKG, UserType.USER_SETTINGS),
+ new Pair<>(SYSTEMUI_PKG, UserType.USER_SYSTEMUI),
+ new Pair<>(GMS_PKG, UserType.USER_GMS));
+ }
+}
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 9e516bf..78fca29 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -63,6 +63,7 @@
bpf {
name: "block.o",
srcs: ["block.c"],
+ btf: true,
cflags: [
"-Wall",
"-Werror",
@@ -73,6 +74,7 @@
bpf {
name: "dscp_policy.o",
srcs: ["dscp_policy.c"],
+ btf: true,
cflags: [
"-Wall",
"-Werror",
@@ -101,6 +103,7 @@
bpf {
name: "clatd.o",
srcs: ["clatd.c"],
+ btf: true,
cflags: [
"-Wall",
"-Werror",
@@ -112,6 +115,7 @@
// WARNING: Android T's non-updatable netd depends on 'netd' string for xt_bpf programs it loads
name: "netd.o",
srcs: ["netd.c"],
+ btf: true,
cflags: [
"-Wall",
"-Werror",
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 24b3fed..44f76de 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -217,6 +217,11 @@
return *config;
}
+// DROP_IF_SET is set of rules that BPF_DROP if rule is globally enabled, and per-uid bit is set
+#define DROP_IF_SET (STANDBY_MATCH | OEM_DENY_1_MATCH | OEM_DENY_2_MATCH | OEM_DENY_3_MATCH)
+// DROP_IF_UNSET is set of rules that should DROP if globally enabled, and per-uid bit is NOT set
+#define DROP_IF_UNSET (DOZABLE_MATCH | POWERSAVE_MATCH | RESTRICTED_MATCH | LOW_POWER_STANDBY_MATCH)
+
static inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid, int direction) {
if (skip_owner_match(skb)) return BPF_PASS;
@@ -228,32 +233,13 @@
uint32_t uidRules = uidEntry ? uidEntry->rule : 0;
uint32_t allowed_iif = uidEntry ? uidEntry->iif : 0;
- if (enabledRules) {
- if ((enabledRules & DOZABLE_MATCH) && !(uidRules & DOZABLE_MATCH)) {
- return BPF_DROP;
- }
- if ((enabledRules & STANDBY_MATCH) && (uidRules & STANDBY_MATCH)) {
- return BPF_DROP;
- }
- if ((enabledRules & POWERSAVE_MATCH) && !(uidRules & POWERSAVE_MATCH)) {
- return BPF_DROP;
- }
- if ((enabledRules & RESTRICTED_MATCH) && !(uidRules & RESTRICTED_MATCH)) {
- return BPF_DROP;
- }
- if ((enabledRules & LOW_POWER_STANDBY_MATCH) && !(uidRules & LOW_POWER_STANDBY_MATCH)) {
- return BPF_DROP;
- }
- if ((enabledRules & OEM_DENY_1_MATCH) && (uidRules & OEM_DENY_1_MATCH)) {
- return BPF_DROP;
- }
- if ((enabledRules & OEM_DENY_2_MATCH) && (uidRules & OEM_DENY_2_MATCH)) {
- return BPF_DROP;
- }
- if ((enabledRules & OEM_DENY_3_MATCH) && (uidRules & OEM_DENY_3_MATCH)) {
- return BPF_DROP;
- }
- }
+ // Warning: funky bit-wise arithmetic: in parallel, for all DROP_IF_SET/UNSET rules
+ // check whether the rules are globally enabled, and if so whether the rules are
+ // set/unset for the specific uid. BPF_DROP if that is the case for ANY of the rules.
+ // We achieve this by masking out only the bits/rules we're interested in checking,
+ // and negating (via bit-wise xor) the bits/rules that should drop if unset.
+ if (enabledRules & (DROP_IF_SET | DROP_IF_UNSET) & (uidRules ^ DROP_IF_UNSET)) return BPF_DROP;
+
if (direction == BPF_INGRESS && skb->ifindex != 1) {
if (uidRules & IIF_MATCH) {
if (allowed_iif && skb->ifindex != allowed_iif) {
@@ -425,8 +411,7 @@
return BPF_NOMATCH;
}
-DEFINE_BPF_PROG_EXT("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create,
- KVER(4, 14, 0), KVER_INF, false, "fs_bpf_netd_readonly", "")
+DEFINE_NETD_BPF_PROG("cgroupsock/inet/create", AID_ROOT, AID_ROOT, inet_socket_create)
(struct bpf_sock* sk) {
uint64_t gid_uid = bpf_get_current_uid_gid();
/*
diff --git a/framework-t/src/android/net/EthernetManager.java b/framework-t/src/android/net/EthernetManager.java
index 886d194..b8070f0 100644
--- a/framework-t/src/android/net/EthernetManager.java
+++ b/framework-t/src/android/net/EthernetManager.java
@@ -22,13 +22,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.Build;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
@@ -573,7 +571,6 @@
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_STACK,
android.Manifest.permission.MANAGE_ETHERNET_NETWORKS})
- @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
public void enableInterface(
@NonNull String iface,
@Nullable @CallbackExecutor Executor executor,
@@ -582,7 +579,7 @@
final NetworkInterfaceOutcomeReceiver proxy = makeNetworkInterfaceOutcomeReceiver(
executor, callback);
try {
- mService.connectNetwork(iface, proxy);
+ mService.enableInterface(iface, proxy);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -610,7 +607,6 @@
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
android.Manifest.permission.NETWORK_STACK,
android.Manifest.permission.MANAGE_ETHERNET_NETWORKS})
- @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
public void disableInterface(
@NonNull String iface,
@Nullable @CallbackExecutor Executor executor,
@@ -619,7 +615,7 @@
final NetworkInterfaceOutcomeReceiver proxy = makeNetworkInterfaceOutcomeReceiver(
executor, callback);
try {
- mService.disconnectNetwork(iface, proxy);
+ mService.disableInterface(iface, proxy);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/framework-t/src/android/net/IEthernetManager.aidl b/framework-t/src/android/net/IEthernetManager.aidl
index 42e4c1a..c1efc29 100644
--- a/framework-t/src/android/net/IEthernetManager.aidl
+++ b/framework-t/src/android/net/IEthernetManager.aidl
@@ -43,8 +43,8 @@
void releaseTetheredInterface(in ITetheredInterfaceCallback callback);
void updateConfiguration(String iface, in EthernetNetworkUpdateRequest request,
in INetworkInterfaceOutcomeReceiver listener);
- void connectNetwork(String iface, in INetworkInterfaceOutcomeReceiver listener);
- void disconnectNetwork(String iface, in INetworkInterfaceOutcomeReceiver listener);
+ void enableInterface(String iface, in INetworkInterfaceOutcomeReceiver listener);
+ void disableInterface(String iface, in INetworkInterfaceOutcomeReceiver listener);
void setEthernetEnabled(boolean enabled);
List<String> getInterfaceList();
}
diff --git a/framework-t/src/android/net/IpSecManager.java b/framework-t/src/android/net/IpSecManager.java
index 9cb0947..9cceac2 100644
--- a/framework-t/src/android/net/IpSecManager.java
+++ b/framework-t/src/android/net/IpSecManager.java
@@ -817,10 +817,10 @@
* </ol>
*
* @param underlyingNetwork the new {@link Network} that will carry traffic for this tunnel.
- * This network MUST never be the network exposing this IpSecTunnelInterface, otherwise
- * this method will throw an {@link IllegalArgumentException}. If the
- * IpSecTunnelInterface is later added to this network, all outbound traffic will be
- * blackholed.
+ * This network MUST be a functional {@link Network} with valid {@link LinkProperties},
+ * and MUST never be the network exposing this IpSecTunnelInterface, otherwise this
+ * method will throw an {@link IllegalArgumentException}. If the IpSecTunnelInterface is
+ * later added to this network, all outbound traffic will be blackholed.
*/
// TODO: b/169171001 Update the documentation when transform migration is supported.
// The purpose of making updating network and applying transforms separate is to leave open
@@ -962,7 +962,6 @@
* IP header and IPsec Header on all inbound traffic).
* <p>Applications should probably not use this API directly.
*
- *
* @param tunnel The {@link IpSecManager#IpSecTunnelInterface} that will use the supplied
* transform.
* @param direction the direction, {@link DIRECTION_OUT} or {@link #DIRECTION_IN} in which
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index f19bf4a..fad63e5 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -175,6 +175,7 @@
*
* @see #ACTION_NSD_STATE_CHANGED
*/
+ // TODO: Deprecate this since NSD service is never disabled.
public static final int NSD_STATE_DISABLED = 1;
/**
@@ -230,17 +231,12 @@
public static final int DAEMON_STARTUP = 19;
/** @hide */
- public static final int ENABLE = 20;
- /** @hide */
- public static final int DISABLE = 21;
+ public static final int MDNS_SERVICE_EVENT = 20;
/** @hide */
- public static final int MDNS_SERVICE_EVENT = 22;
-
+ public static final int REGISTER_CLIENT = 21;
/** @hide */
- public static final int REGISTER_CLIENT = 23;
- /** @hide */
- public static final int UNREGISTER_CLIENT = 24;
+ public static final int UNREGISTER_CLIENT = 22;
/** Dns based service discovery protocol */
public static final int PROTOCOL_DNS_SD = 0x0001;
@@ -266,8 +262,6 @@
EVENT_NAMES.put(RESOLVE_SERVICE_SUCCEEDED, "RESOLVE_SERVICE_SUCCEEDED");
EVENT_NAMES.put(DAEMON_CLEANUP, "DAEMON_CLEANUP");
EVENT_NAMES.put(DAEMON_STARTUP, "DAEMON_STARTUP");
- EVENT_NAMES.put(ENABLE, "ENABLE");
- EVENT_NAMES.put(DISABLE, "DISABLE");
EVENT_NAMES.put(MDNS_SERVICE_EVENT, "MDNS_SERVICE_EVENT");
}
diff --git a/framework/Android.bp b/framework/Android.bp
index d7de439..24d8cca 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -64,7 +64,6 @@
":framework-connectivity-sources",
":net-utils-framework-common-srcs",
":framework-connectivity-api-shared-srcs",
- ":framework-connectivity-javastream-protos",
],
aidl: {
generate_get_transaction_name: true,
@@ -90,6 +89,7 @@
"modules-utils-backgroundthread",
"modules-utils-build",
"modules-utils-preconditions",
+ "framework-connectivity-javastream-protos",
],
libs: [
"app-compat-annotations",
@@ -197,28 +197,16 @@
visibility: ["//frameworks/base"],
}
-gensrcs {
+java_library {
name: "framework-connectivity-javastream-protos",
- depfile: true,
-
- tools: [
- "aprotoc",
- "protoc-gen-javastream",
- "soong_zip",
+ proto: {
+ type: "stream",
+ },
+ srcs: [":framework-connectivity-protos"],
+ installable: false,
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ apex_available: [
+ "com.android.tethering",
],
-
- cmd: "mkdir -p $(genDir)/$(in) " +
- "&& $(location aprotoc) " +
- " --plugin=$(location protoc-gen-javastream) " +
- " --dependency_out=$(depfile) " +
- " --javastream_out=$(genDir)/$(in) " +
- " -Iexternal/protobuf/src " +
- " -I . " +
- " $(in) " +
- "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)",
-
- srcs: [
- ":framework-connectivity-protos",
- ],
- output_extension: "srcjar",
}
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index ddac19d..a2a1ac0 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -51,6 +51,9 @@
field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
field public static final int FIREWALL_CHAIN_DOZABLE = 1; // 0x1
field public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5; // 0x5
+ field public static final int FIREWALL_CHAIN_OEM_DENY_1 = 7; // 0x7
+ field public static final int FIREWALL_CHAIN_OEM_DENY_2 = 8; // 0x8
+ field public static final int FIREWALL_CHAIN_OEM_DENY_3 = 9; // 0x9
field public static final int FIREWALL_CHAIN_POWERSAVE = 3; // 0x3
field public static final int FIREWALL_CHAIN_RESTRICTED = 4; // 0x4
field public static final int FIREWALL_CHAIN_STANDBY = 2; // 0x2
@@ -197,6 +200,8 @@
method public int describeContents();
method @NonNull public android.os.ParcelFileDescriptor getFileDescriptor();
method @NonNull public String getInterfaceName();
+ method @Nullable public android.net.MacAddress getMacAddress();
+ method public int getMtu();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.TestNetworkInterface> CREATOR;
}
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index db1d7e9..f1298ce 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -249,10 +249,10 @@
method public void onValidationStatus(int, @Nullable android.net.Uri);
method @NonNull public android.net.Network register();
method public void sendAddDscpPolicy(@NonNull android.net.DscpPolicy);
- method public final void sendLinkProperties(@NonNull android.net.LinkProperties);
- method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
- method public final void sendNetworkScore(@NonNull android.net.NetworkScore);
- method public final void sendNetworkScore(@IntRange(from=0, to=99) int);
+ method public void sendLinkProperties(@NonNull android.net.LinkProperties);
+ method public void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
+ method public void sendNetworkScore(@NonNull android.net.NetworkScore);
+ method public void sendNetworkScore(@IntRange(from=0, to=99) int);
method public final void sendQosCallbackError(int, int);
method public final void sendQosSessionAvailable(int, int, @NonNull android.net.QosSessionAttributes);
method public final void sendQosSessionLost(int, int, int);
@@ -262,7 +262,7 @@
method @Deprecated public void setLegacySubtype(int, @NonNull String);
method public void setLingerDuration(@NonNull java.time.Duration);
method public void setTeardownDelayMillis(@IntRange(from=0, to=0x1388) int);
- method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
+ method public void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
method public void unregister();
method public void unregisterAfterReplacement(@IntRange(from=0, to=0x1388) int);
field public static final int DSCP_POLICY_STATUS_DELETED = 4; // 0x4
diff --git a/framework/jarjar-excludes.txt b/framework/jarjar-excludes.txt
new file mode 100644
index 0000000..1311765
--- /dev/null
+++ b/framework/jarjar-excludes.txt
@@ -0,0 +1,25 @@
+# INetworkStatsProvider / INetworkStatsProviderCallback are referenced from net-tests-utils, which
+# may be used by tests that do not apply connectivity jarjar rules.
+# TODO: move files to a known internal package (like android.net.connectivity.visiblefortesting)
+# so that they do not need jarjar
+android\.net\.netstats\.provider\.INetworkStatsProvider(\$.+)?
+android\.net\.netstats\.provider\.INetworkStatsProviderCallback(\$.+)?
+
+# INetworkAgent / INetworkAgentRegistry are used in NetworkAgentTest
+# TODO: move files to android.net.connectivity.visiblefortesting
+android\.net\.INetworkAgent(\$.+)?
+android\.net\.INetworkAgentRegistry(\$.+)?
+
+# IConnectivityDiagnosticsCallback used in ConnectivityDiagnosticsManagerTest
+# TODO: move files to android.net.connectivity.visiblefortesting
+android\.net\.IConnectivityDiagnosticsCallback(\$.+)?
+
+
+# KeepaliveUtils is used by ConnectivityManager CTS
+# TODO: move into service-connectivity so framework-connectivity stops using
+# ServiceConnectivityResources (callers need high permissions to find/query the resource apk anyway)
+# and have a ConnectivityManager test API instead
+android\.net\.util\.KeepaliveUtils(\$.+)?
+
+# TODO (b/217115866): add jarjar rules for Nearby
+android\.nearby\..+
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index f741c2b..39cd7f3 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -983,20 +983,11 @@
public static final int FIREWALL_CHAIN_LOW_POWER_STANDBY = 5;
/**
- * Firewall chain used for lockdown VPN.
- * Denylist of apps that cannot receive incoming packets except on loopback because they are
- * subject to an always-on VPN which is not currently connected.
- *
- * @see #BLOCKED_REASON_LOCKDOWN_VPN
- * @hide
- */
- public static final int FIREWALL_CHAIN_LOCKDOWN_VPN = 6;
-
- /**
* Firewall chain used for OEM-specific application restrictions.
* Denylist of apps that will not have network access due to OEM-specific restrictions.
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public static final int FIREWALL_CHAIN_OEM_DENY_1 = 7;
/**
@@ -1004,6 +995,7 @@
* Denylist of apps that will not have network access due to OEM-specific restrictions.
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public static final int FIREWALL_CHAIN_OEM_DENY_2 = 8;
/**
@@ -1011,6 +1003,7 @@
* Denylist of apps that will not have network access due to OEM-specific restrictions.
* @hide
*/
+ @SystemApi(client = MODULE_LIBRARIES)
public static final int FIREWALL_CHAIN_OEM_DENY_3 = 9;
/** @hide */
@@ -1021,7 +1014,6 @@
FIREWALL_CHAIN_POWERSAVE,
FIREWALL_CHAIN_RESTRICTED,
FIREWALL_CHAIN_LOW_POWER_STANDBY,
- FIREWALL_CHAIN_LOCKDOWN_VPN,
FIREWALL_CHAIN_OEM_DENY_1,
FIREWALL_CHAIN_OEM_DENY_2,
FIREWALL_CHAIN_OEM_DENY_3
@@ -2624,9 +2616,24 @@
* {@hide}
*/
public ConnectivityManager(Context context, IConnectivityManager service) {
+ this(context, service, true /* newStatic */);
+ }
+
+ private ConnectivityManager(Context context, IConnectivityManager service, boolean newStatic) {
mContext = Objects.requireNonNull(context, "missing context");
mService = Objects.requireNonNull(service, "missing IConnectivityManager");
- sInstance = this;
+ // sInstance is accessed without a lock, so it may actually be reassigned several times with
+ // different ConnectivityManager, but that's still OK considering its usage.
+ if (sInstance == null && newStatic) {
+ final Context appContext = mContext.getApplicationContext();
+ // Don't create static ConnectivityManager instance again to prevent infinite loop.
+ // If the application context is null, we're either in the system process or
+ // it's the application context very early in app initialization. In both these
+ // cases, the passed-in Context will not be freed, so it's safe to pass it to the
+ // service. http://b/27532714 .
+ sInstance = new ConnectivityManager(appContext != null ? appContext : context, service,
+ false /* newStatic */);
+ }
}
/** {@hide} */
diff --git a/framework/src/android/net/ITestNetworkManager.aidl b/framework/src/android/net/ITestNetworkManager.aidl
index 27d13c1..d18b931 100644
--- a/framework/src/android/net/ITestNetworkManager.aidl
+++ b/framework/src/android/net/ITestNetworkManager.aidl
@@ -29,8 +29,10 @@
*/
interface ITestNetworkManager
{
- TestNetworkInterface createInterface(boolean isTun, boolean bringUp, in LinkAddress[] addrs,
- in @nullable String iface);
+ TestNetworkInterface createInterface(boolean isTun, boolean hasCarrier, boolean bringUp,
+ in LinkAddress[] addrs, in @nullable String iface);
+
+ void setCarrierEnabled(in TestNetworkInterface iface, boolean enabled);
void setupTestNetwork(in String iface, in LinkProperties lp, in boolean isMetered,
in int[] administratorUids, in IBinder binder);
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index 2c50c73..5659a35 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -913,7 +913,7 @@
* Must be called by the agent when the network's {@link LinkProperties} change.
* @param linkProperties the new LinkProperties.
*/
- public final void sendLinkProperties(@NonNull LinkProperties linkProperties) {
+ public void sendLinkProperties(@NonNull LinkProperties linkProperties) {
Objects.requireNonNull(linkProperties);
final LinkProperties lp = new LinkProperties(linkProperties);
queueOrSendMessage(reg -> reg.sendLinkProperties(lp));
@@ -938,7 +938,7 @@
* @param underlyingNetworks the new list of underlying networks.
* @see {@link VpnService.Builder#setUnderlyingNetworks(Network[])}
*/
- public final void setUnderlyingNetworks(
+ public void setUnderlyingNetworks(
@SuppressLint("NullableCollection") @Nullable List<Network> underlyingNetworks) {
final ArrayList<Network> underlyingArray = (underlyingNetworks != null)
? new ArrayList<>(underlyingNetworks) : null;
@@ -1088,7 +1088,7 @@
* Must be called by the agent when the network's {@link NetworkCapabilities} change.
* @param networkCapabilities the new NetworkCapabilities.
*/
- public final void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
+ public void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
Objects.requireNonNull(networkCapabilities);
mBandwidthUpdatePending.set(false);
mLastBwRefreshTime = System.currentTimeMillis();
@@ -1102,7 +1102,7 @@
*
* @param score the new score.
*/
- public final void sendNetworkScore(@NonNull NetworkScore score) {
+ public void sendNetworkScore(@NonNull NetworkScore score) {
Objects.requireNonNull(score);
queueOrSendMessage(reg -> reg.sendScore(score));
}
@@ -1113,7 +1113,7 @@
* @param score the new score, between 0 and 99.
* deprecated use sendNetworkScore(NetworkScore) TODO : remove in S.
*/
- public final void sendNetworkScore(@IntRange(from = 0, to = 99) int score) {
+ public void sendNetworkScore(@IntRange(from = 0, to = 99) int score) {
sendNetworkScore(new NetworkScore.Builder().setLegacyInt(score).build());
}
diff --git a/framework/src/android/net/QosCallbackException.java b/framework/src/android/net/QosCallbackException.java
index ed6eb15..b80cff4 100644
--- a/framework/src/android/net/QosCallbackException.java
+++ b/framework/src/android/net/QosCallbackException.java
@@ -46,8 +46,10 @@
EX_TYPE_FILTER_NONE,
EX_TYPE_FILTER_NETWORK_RELEASED,
EX_TYPE_FILTER_SOCKET_NOT_BOUND,
+ EX_TYPE_FILTER_SOCKET_NOT_CONNECTED,
EX_TYPE_FILTER_NOT_SUPPORTED,
EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED,
+ EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ExceptionType {}
@@ -65,10 +67,16 @@
public static final int EX_TYPE_FILTER_SOCKET_NOT_BOUND = 2;
/** {@hide} */
- public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 3;
+ public static final int EX_TYPE_FILTER_SOCKET_NOT_CONNECTED = 3;
/** {@hide} */
- public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 4;
+ public static final int EX_TYPE_FILTER_NOT_SUPPORTED = 4;
+
+ /** {@hide} */
+ public static final int EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED = 5;
+
+ /** {@hide} */
+ public static final int EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED = 6;
/**
* Creates exception based off of a type and message. Not all types of exceptions accept a
@@ -83,12 +91,17 @@
return new QosCallbackException(new NetworkReleasedException());
case EX_TYPE_FILTER_SOCKET_NOT_BOUND:
return new QosCallbackException(new SocketNotBoundException());
+ case EX_TYPE_FILTER_SOCKET_NOT_CONNECTED:
+ return new QosCallbackException(new SocketNotConnectedException());
case EX_TYPE_FILTER_NOT_SUPPORTED:
return new QosCallbackException(new UnsupportedOperationException(
"This device does not support the specified filter"));
case EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED:
return new QosCallbackException(
new SocketLocalAddressChangedException());
+ case EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED:
+ return new QosCallbackException(
+ new SocketRemoteAddressChangedException());
default:
Log.wtf(TAG, "create: No case setup for exception type: '" + type + "'");
return new QosCallbackException(
diff --git a/framework/src/android/net/QosFilter.java b/framework/src/android/net/QosFilter.java
index 5c1c3cc..b432644 100644
--- a/framework/src/android/net/QosFilter.java
+++ b/framework/src/android/net/QosFilter.java
@@ -90,5 +90,15 @@
*/
public abstract boolean matchesRemoteAddress(@NonNull InetAddress address,
int startPort, int endPort);
+
+ /**
+ * Determines whether or not the parameter will be matched with this filter.
+ *
+ * @param protocol the protocol such as TCP or UDP included in IP packet filter set of a QoS
+ * flow assigned on {@link Network}.
+ * @return whether the parameters match the socket type of the filter
+ * @hide
+ */
+ public abstract boolean matchesProtocol(int protocol);
}
diff --git a/framework/src/android/net/QosFilterParcelable.java b/framework/src/android/net/QosFilterParcelable.java
index da3b2cf..6e71fa3 100644
--- a/framework/src/android/net/QosFilterParcelable.java
+++ b/framework/src/android/net/QosFilterParcelable.java
@@ -104,7 +104,7 @@
if (mQosFilter instanceof QosSocketFilter) {
dest.writeInt(QOS_SOCKET_FILTER);
final QosSocketFilter qosSocketFilter = (QosSocketFilter) mQosFilter;
- qosSocketFilter.getQosSocketInfo().writeToParcel(dest, 0);
+ qosSocketFilter.getQosSocketInfo().writeToParcelWithoutFd(dest, 0);
return;
}
dest.writeInt(NO_FILTER_PRESENT);
diff --git a/framework/src/android/net/QosSocketFilter.java b/framework/src/android/net/QosSocketFilter.java
index 69da7f4..5ceeb67 100644
--- a/framework/src/android/net/QosSocketFilter.java
+++ b/framework/src/android/net/QosSocketFilter.java
@@ -18,6 +18,13 @@
import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE;
import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_STREAM;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -74,19 +81,34 @@
* 2. In the scenario that the socket is now bound to a different local address, which can
* happen in the case of UDP, then
* {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED} is returned.
+ * 3. In the scenario that the UDP socket changed remote address, then
+ * {@link QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED} is returned.
+ *
* @return validation error code
*/
@Override
public int validate() {
- final InetSocketAddress sa = getAddressFromFileDescriptor();
- if (sa == null) {
- return QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
+ final InetSocketAddress sa = getLocalAddressFromFileDescriptor();
+
+ if (sa == null || (sa.getAddress().isAnyLocalAddress() && sa.getPort() == 0)) {
+ return EX_TYPE_FILTER_SOCKET_NOT_BOUND;
}
if (!sa.equals(mQosSocketInfo.getLocalSocketAddress())) {
return EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
}
+ if (mQosSocketInfo.getRemoteSocketAddress() != null) {
+ final InetSocketAddress da = getRemoteAddressFromFileDescriptor();
+ if (da == null) {
+ return EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
+ }
+
+ if (!da.equals(mQosSocketInfo.getRemoteSocketAddress())) {
+ return EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
+ }
+ }
+
return EX_TYPE_FILTER_NONE;
}
@@ -98,17 +120,14 @@
* @return the local address
*/
@Nullable
- private InetSocketAddress getAddressFromFileDescriptor() {
+ private InetSocketAddress getLocalAddressFromFileDescriptor() {
final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor();
- if (parcelFileDescriptor == null) return null;
-
final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor();
- if (fd == null) return null;
final SocketAddress address;
try {
address = Os.getsockname(fd);
- } catch (final ErrnoException e) {
+ } catch (ErrnoException e) {
Log.e(TAG, "getAddressFromFileDescriptor: getLocalAddress exception", e);
return null;
}
@@ -119,6 +138,31 @@
}
/**
+ * The remote address of the socket's connected.
+ *
+ * <p>Note: If the socket is no longer connected, null is returned.
+ *
+ * @return the remote address
+ */
+ @Nullable
+ private InetSocketAddress getRemoteAddressFromFileDescriptor() {
+ final ParcelFileDescriptor parcelFileDescriptor = mQosSocketInfo.getParcelFileDescriptor();
+ final FileDescriptor fd = parcelFileDescriptor.getFileDescriptor();
+
+ final SocketAddress address;
+ try {
+ address = Os.getpeername(fd);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "getAddressFromFileDescriptor: getRemoteAddress exception", e);
+ return null;
+ }
+ if (address instanceof InetSocketAddress) {
+ return (InetSocketAddress) address;
+ }
+ return null;
+ }
+
+ /**
* The network used with this filter.
*
* @return the registered {@link Network}
@@ -156,6 +200,18 @@
}
/**
+ * @inheritDoc
+ */
+ @Override
+ public boolean matchesProtocol(final int protocol) {
+ if ((mQosSocketInfo.getSocketType() == SOCK_STREAM && protocol == IPPROTO_TCP)
+ || (mQosSocketInfo.getSocketType() == SOCK_DGRAM && protocol == IPPROTO_UDP)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Called from {@link QosSocketFilter#matchesLocalAddress(InetAddress, int, int)}
* and {@link QosSocketFilter#matchesRemoteAddress(InetAddress, int, int)} with the
* filterSocketAddress coming from {@link QosSocketInfo#getLocalSocketAddress()}.
@@ -174,6 +230,7 @@
final int startPort, final int endPort) {
return startPort <= filterSocketAddress.getPort()
&& endPort >= filterSocketAddress.getPort()
- && filterSocketAddress.getAddress().equals(address);
+ && (address.isAnyLocalAddress()
+ || filterSocketAddress.getAddress().equals(address));
}
}
diff --git a/framework/src/android/net/QosSocketInfo.java b/framework/src/android/net/QosSocketInfo.java
index 39c2f33..49ac22b 100644
--- a/framework/src/android/net/QosSocketInfo.java
+++ b/framework/src/android/net/QosSocketInfo.java
@@ -165,25 +165,28 @@
/* Parcelable methods */
private QosSocketInfo(final Parcel in) {
mNetwork = Objects.requireNonNull(Network.CREATOR.createFromParcel(in));
- mParcelFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in);
+ final boolean withFd = in.readBoolean();
+ if (withFd) {
+ mParcelFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(in);
+ } else {
+ mParcelFileDescriptor = null;
+ }
- final int localAddressLength = in.readInt();
- mLocalSocketAddress = readSocketAddress(in, localAddressLength);
-
- final int remoteAddressLength = in.readInt();
- mRemoteSocketAddress = remoteAddressLength == 0 ? null
- : readSocketAddress(in, remoteAddressLength);
+ mLocalSocketAddress = readSocketAddress(in);
+ mRemoteSocketAddress = readSocketAddress(in);
mSocketType = in.readInt();
}
- private @NonNull InetSocketAddress readSocketAddress(final Parcel in, final int addressLength) {
- final byte[] address = new byte[addressLength];
- in.readByteArray(address);
+ private InetSocketAddress readSocketAddress(final Parcel in) {
+ final byte[] addrBytes = in.createByteArray();
+ if (addrBytes == null) {
+ return null;
+ }
final int port = in.readInt();
try {
- return new InetSocketAddress(InetAddress.getByAddress(address), port);
+ return new InetSocketAddress(InetAddress.getByAddress(addrBytes), port);
} catch (final UnknownHostException e) {
/* This can never happen. UnknownHostException will never be thrown
since the address provided is numeric and non-null. */
@@ -198,20 +201,35 @@
@Override
public void writeToParcel(@NonNull final Parcel dest, final int flags) {
- mNetwork.writeToParcel(dest, 0);
- mParcelFileDescriptor.writeToParcel(dest, 0);
+ writeToParcelInternal(dest, flags, /*includeFd=*/ true);
+ }
- final byte[] localAddress = mLocalSocketAddress.getAddress().getAddress();
- dest.writeInt(localAddress.length);
- dest.writeByteArray(localAddress);
+ /**
+ * Used when sending QosSocketInfo to telephony, which does not need access to the socket FD.
+ * @hide
+ */
+ public void writeToParcelWithoutFd(@NonNull final Parcel dest, final int flags) {
+ writeToParcelInternal(dest, flags, /*includeFd=*/ false);
+ }
+
+ private void writeToParcelInternal(
+ @NonNull final Parcel dest, final int flags, boolean includeFd) {
+ mNetwork.writeToParcel(dest, 0);
+
+ if (includeFd) {
+ dest.writeBoolean(true);
+ mParcelFileDescriptor.writeToParcel(dest, 0);
+ } else {
+ dest.writeBoolean(false);
+ }
+
+ dest.writeByteArray(mLocalSocketAddress.getAddress().getAddress());
dest.writeInt(mLocalSocketAddress.getPort());
if (mRemoteSocketAddress == null) {
- dest.writeInt(0);
+ dest.writeByteArray(null);
} else {
- final byte[] remoteAddress = mRemoteSocketAddress.getAddress().getAddress();
- dest.writeInt(remoteAddress.length);
- dest.writeByteArray(remoteAddress);
+ dest.writeByteArray(mRemoteSocketAddress.getAddress().getAddress());
dest.writeInt(mRemoteSocketAddress.getPort());
}
dest.writeInt(mSocketType);
diff --git a/framework/src/android/net/SocketNotConnectedException.java b/framework/src/android/net/SocketNotConnectedException.java
new file mode 100644
index 0000000..fa2a615
--- /dev/null
+++ b/framework/src/android/net/SocketNotConnectedException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Thrown when a previously bound socket becomes unbound.
+ *
+ * @hide
+ */
+public class SocketNotConnectedException extends Exception {
+ /** @hide */
+ public SocketNotConnectedException() {
+ super("The socket is not connected");
+ }
+}
diff --git a/framework/src/android/net/SocketRemoteAddressChangedException.java b/framework/src/android/net/SocketRemoteAddressChangedException.java
new file mode 100644
index 0000000..ecaeebc
--- /dev/null
+++ b/framework/src/android/net/SocketRemoteAddressChangedException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Thrown when the local address of the socket has changed.
+ *
+ * @hide
+ */
+public class SocketRemoteAddressChangedException extends Exception {
+ /** @hide */
+ public SocketRemoteAddressChangedException() {
+ super("The remote address of the socket changed");
+ }
+}
diff --git a/framework/src/android/net/TestNetworkInterface.java b/framework/src/android/net/TestNetworkInterface.java
index 4449ff8..26200e1 100644
--- a/framework/src/android/net/TestNetworkInterface.java
+++ b/framework/src/android/net/TestNetworkInterface.java
@@ -16,22 +16,32 @@
package android.net;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
+import android.util.Log;
+
+import java.net.NetworkInterface;
+import java.net.SocketException;
/**
- * This class is used to return the interface name and fd of the test interface
+ * This class is used to return the interface name, fd, MAC, and MTU of the test interface
*
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final class TestNetworkInterface implements Parcelable {
+ private static final String TAG = "TestNetworkInterface";
+
@NonNull
private final ParcelFileDescriptor mFileDescriptor;
@NonNull
private final String mInterfaceName;
+ @Nullable
+ private final MacAddress mMacAddress;
+ private final int mMtu;
@Override
public int describeContents() {
@@ -40,18 +50,41 @@
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
- out.writeParcelable(mFileDescriptor, PARCELABLE_WRITE_RETURN_VALUE);
+ out.writeParcelable(mFileDescriptor, flags);
out.writeString(mInterfaceName);
+ out.writeParcelable(mMacAddress, flags);
+ out.writeInt(mMtu);
}
public TestNetworkInterface(@NonNull ParcelFileDescriptor pfd, @NonNull String intf) {
mFileDescriptor = pfd;
mInterfaceName = intf;
+
+ MacAddress macAddress = null;
+ int mtu = 1500;
+ try {
+ // This constructor is called by TestNetworkManager which runs inside the system server,
+ // which has permission to read the MacAddress.
+ NetworkInterface nif = NetworkInterface.getByName(mInterfaceName);
+
+ // getHardwareAddress() returns null for tun interfaces.
+ byte[] hardwareAddress = nif.getHardwareAddress();
+ if (hardwareAddress != null) {
+ macAddress = MacAddress.fromBytes(nif.getHardwareAddress());
+ }
+ mtu = nif.getMTU();
+ } catch (SocketException e) {
+ Log.e(TAG, "Failed to fetch MacAddress or MTU size from NetworkInterface", e);
+ }
+ mMacAddress = macAddress;
+ mMtu = mtu;
}
private TestNetworkInterface(@NonNull Parcel in) {
mFileDescriptor = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
mInterfaceName = in.readString();
+ mMacAddress = in.readParcelable(MacAddress.class.getClassLoader());
+ mMtu = in.readInt();
}
@NonNull
@@ -64,6 +97,15 @@
return mInterfaceName;
}
+ @Nullable
+ public MacAddress getMacAddress() {
+ return mMacAddress;
+ }
+
+ public int getMtu() {
+ return mMtu;
+ }
+
@NonNull
public static final Parcelable.Creator<TestNetworkInterface> CREATOR =
new Parcelable.Creator<TestNetworkInterface>() {
diff --git a/framework/src/android/net/TestNetworkManager.java b/framework/src/android/net/TestNetworkManager.java
index 4e78823..7b18765 100644
--- a/framework/src/android/net/TestNetworkManager.java
+++ b/framework/src/android/net/TestNetworkManager.java
@@ -58,6 +58,7 @@
private static final boolean TAP = false;
private static final boolean TUN = true;
private static final boolean BRING_UP = true;
+ private static final boolean CARRIER_UP = true;
private static final LinkAddress[] NO_ADDRS = new LinkAddress[0];
/** @hide */
@@ -166,7 +167,7 @@
public TestNetworkInterface createTunInterface(@NonNull Collection<LinkAddress> linkAddrs) {
try {
final LinkAddress[] arr = new LinkAddress[linkAddrs.size()];
- return mService.createInterface(TUN, BRING_UP, linkAddrs.toArray(arr),
+ return mService.createInterface(TUN, CARRIER_UP, BRING_UP, linkAddrs.toArray(arr),
null /* iface */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -185,7 +186,7 @@
@NonNull
public TestNetworkInterface createTapInterface() {
try {
- return mService.createInterface(TAP, BRING_UP, NO_ADDRS, null /* iface */);
+ return mService.createInterface(TAP, CARRIER_UP, BRING_UP, NO_ADDRS, null /* iface */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -204,7 +205,7 @@
@NonNull
public TestNetworkInterface createTapInterface(boolean bringUp) {
try {
- return mService.createInterface(TAP, bringUp, NO_ADDRS, null /* iface */);
+ return mService.createInterface(TAP, CARRIER_UP, bringUp, NO_ADDRS, null /* iface */);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -226,7 +227,43 @@
@NonNull
public TestNetworkInterface createTapInterface(boolean bringUp, @NonNull String iface) {
try {
- return mService.createInterface(TAP, bringUp, NO_ADDRS, iface);
+ return mService.createInterface(TAP, CARRIER_UP, bringUp, NO_ADDRS, iface);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Create a tap interface with or without carrier for testing purposes.
+ *
+ * @param carrierUp whether the created interface has a carrier or not.
+ * @param bringUp whether to bring up the interface before returning it.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS)
+ @NonNull
+ public TestNetworkInterface createTapInterface(boolean carrierUp, boolean bringUp) {
+ try {
+ return mService.createInterface(TAP, carrierUp, bringUp, NO_ADDRS, null /* iface */);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Enable / disable carrier on TestNetworkInterface
+ *
+ * Note: TUNSETCARRIER is not supported until kernel version 5.0.
+ * TODO: add RequiresApi annotation.
+ *
+ * @param iface the interface to configure.
+ * @param enabled true to turn carrier on, false to turn carrier off.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_TEST_NETWORKS)
+ public void setCarrierEnabled(@NonNull TestNetworkInterface iface, boolean enabled) {
+ try {
+ mService.setCarrierEnabled(iface, enabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/nearby/framework/java/android/nearby/DataElement.java b/nearby/framework/java/android/nearby/DataElement.java
index 6fa5fb5..1b1377d 100644
--- a/nearby/framework/java/android/nearby/DataElement.java
+++ b/nearby/framework/java/android/nearby/DataElement.java
@@ -16,6 +16,7 @@
package android.nearby;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
@@ -35,6 +36,13 @@
private final int mKey;
private final byte[] mValue;
+ /** @hide */
+ @IntDef({DataType.BLE_SERVICE_DATA, DataType.ACCOUNT_KEY})
+ @interface DataType {
+ int BLE_SERVICE_DATA = 0;
+ int ACCOUNT_KEY = 1;
+ }
+
/**
* Constructs a {@link DataElement}.
*/
diff --git a/nearby/service/java/com/android/server/nearby/common/ble/BleFilter.java b/nearby/service/java/com/android/server/nearby/common/ble/BleFilter.java
index 23d5170..dc4e11e 100644
--- a/nearby/service/java/com/android/server/nearby/common/ble/BleFilter.java
+++ b/nearby/service/java/com/android/server/nearby/common/ble/BleFilter.java
@@ -500,16 +500,16 @@
return false;
}
BleFilter other = (BleFilter) obj;
- return mDeviceName.equals(other.mDeviceName)
- && mDeviceAddress.equals(other.mDeviceAddress)
+ return equal(mDeviceName, other.mDeviceName)
+ && equal(mDeviceAddress, other.mDeviceAddress)
&& mManufacturerId == other.mManufacturerId
&& Arrays.equals(mManufacturerData, other.mManufacturerData)
&& Arrays.equals(mManufacturerDataMask, other.mManufacturerDataMask)
- && mServiceDataUuid.equals(other.mServiceDataUuid)
+ && equal(mServiceDataUuid, other.mServiceDataUuid)
&& Arrays.equals(mServiceData, other.mServiceData)
&& Arrays.equals(mServiceDataMask, other.mServiceDataMask)
- && mServiceUuid.equals(other.mServiceUuid)
- && mServiceUuidMask.equals(other.mServiceUuidMask);
+ && equal(mServiceUuid, other.mServiceUuid)
+ && equal(mServiceUuidMask, other.mServiceUuidMask);
}
/** Builder class for {@link BleFilter}. */
@@ -743,4 +743,11 @@
}
return osFilterBuilder.build();
}
+
+ /**
+ * equal() method for two possibly-null objects
+ */
+ private static boolean equal(@Nullable Object obj1, @Nullable Object obj2) {
+ return obj1 == obj2 || (obj1 != null && obj1.equals(obj2));
+ }
}
diff --git a/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java b/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java
index f27899f..b4f46f8 100644
--- a/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java
+++ b/nearby/service/java/com/android/server/nearby/common/ble/testing/FastPairTestData.java
@@ -46,6 +46,12 @@
/** Model ID in {@link #getFastPairRecord()}. */
public static final byte[] FAST_PAIR_MODEL_ID = Hex.stringToBytes("AABBCC");
+ /** An arbitrary BLE device address. */
+ public static final String DEVICE_ADDRESS = "00:00:00:00:00:01";
+
+ /** Arbitrary RSSI (Received Signal Strength Indicator). */
+ public static final int RSSI = -72;
+
/** @see #getFastPairRecord() */
public static byte[] newFastPairRecord(byte header, byte[] modelId) {
return newFastPairRecord(
@@ -61,6 +67,45 @@
Hex.bytesToStringUppercase(serviceData)));
}
+ // This is an example extended inquiry response for a phone with PANU
+ // and Hands-free Audio Gateway
+ public static byte[] eir_1 = {
+ 0x06, // Length of this Data
+ 0x09, // <<Complete Local Name>>
+ 'P',
+ 'h',
+ 'o',
+ 'n',
+ 'e',
+ 0x05, // Length of this Data
+ 0x03, // <<Complete list of 16-bit Service UUIDs>>
+ 0x15,
+ 0x11, // PANU service class UUID
+ 0x1F,
+ 0x11, // Hands-free Audio Gateway service class UUID
+ 0x01, // Length of this data
+ 0x05, // <<Complete list of 32-bit Service UUIDs>>
+ 0x11, // Length of this data
+ 0x07, // <<Complete list of 128-bit Service UUIDs>>
+ 0x01,
+ 0x02,
+ 0x03,
+ 0x04,
+ 0x05,
+ 0x06,
+ 0x07,
+ 0x08, // Made up UUID
+ 0x11,
+ 0x12,
+ 0x13,
+ 0x14,
+ 0x15,
+ 0x16,
+ 0x17,
+ 0x18, //
+ 0x00 // End of Data (Not transmitted over the air
+ };
+
// This is an example of advertising data with AD types
public static byte[] adv_1 = {
0x02, // Length of this Data
@@ -138,4 +183,46 @@
0x00
};
+ // An Eddystone UID frame. go/eddystone for more info
+ public static byte[] eddystone_header_and_uuid = {
+ // BLE Flags
+ (byte) 0x02,
+ (byte) 0x01,
+ (byte) 0x06,
+ // Service UUID
+ (byte) 0x03,
+ (byte) 0x03,
+ (byte) 0xaa,
+ (byte) 0xfe,
+ // Service data header
+ (byte) 0x17,
+ (byte) 0x16,
+ (byte) 0xaa,
+ (byte) 0xfe,
+ // Eddystone frame type
+ (byte) 0x00,
+ // Ranging data
+ (byte) 0xb3,
+ // Eddystone ID namespace
+ (byte) 0x0a,
+ (byte) 0x09,
+ (byte) 0x08,
+ (byte) 0x07,
+ (byte) 0x06,
+ (byte) 0x05,
+ (byte) 0x04,
+ (byte) 0x03,
+ (byte) 0x02,
+ (byte) 0x01,
+ // Eddystone ID instance
+ (byte) 0x16,
+ (byte) 0x15,
+ (byte) 0x14,
+ (byte) 0x13,
+ (byte) 0x12,
+ (byte) 0x11,
+ // RFU
+ (byte) 0x00,
+ (byte) 0x00
+ };
}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Event.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Event.java
index 0b50dfd..7a0548b 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Event.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Event.java
@@ -133,14 +133,11 @@
Event that = (Event) o;
return this.mEventCode == that.getEventCode()
&& this.mTimestamp == that.getTimestamp()
- && (this.mProfile == null
- ? that.getProfile() == null : this.mProfile.equals(that.getProfile()))
&& (this.mBluetoothDevice == null
- ? that.getBluetoothDevice() == null :
- this.mBluetoothDevice.equals(that.getBluetoothDevice()))
- && (this.mException == null
- ? that.getException() == null :
- this.mException.equals(that.getException()));
+ ? that.getBluetoothDevice() == null :
+ this.mBluetoothDevice.equals(that.getBluetoothDevice()))
+ && (this.mProfile == null
+ ? that.getProfile() == null : this.mProfile.equals(that.getProfile()));
}
return false;
}
@@ -150,7 +147,6 @@
return Objects.hash(mEventCode, mTimestamp, mProfile, mBluetoothDevice, mException);
}
-
/**
* Builder
*/
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Preferences.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Preferences.java
index bb7b71b..eb5bad5 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Preferences.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/fastpair/Preferences.java
@@ -1042,104 +1042,6 @@
*/
public static Builder builder() {
return new Preferences.Builder()
- .setGattOperationTimeoutSeconds(3)
- .setGattConnectionTimeoutSeconds(15)
- .setBluetoothToggleTimeoutSeconds(10)
- .setBluetoothToggleSleepSeconds(2)
- .setClassicDiscoveryTimeoutSeconds(10)
- .setNumDiscoverAttempts(3)
- .setDiscoveryRetrySleepSeconds(1)
- .setIgnoreDiscoveryError(false)
- .setSdpTimeoutSeconds(10)
- .setNumSdpAttempts(3)
- .setNumCreateBondAttempts(3)
- .setNumConnectAttempts(1)
- .setNumWriteAccountKeyAttempts(3)
- .setToggleBluetoothOnFailure(false)
- .setBluetoothStateUsesPolling(true)
- .setBluetoothStatePollingMillis(1000)
- .setNumAttempts(2)
- .setEnableBrEdrHandover(false)
- .setBrHandoverDataCharacteristicId(get16BitUuid(
- Constants.TransportDiscoveryService.BrHandoverDataCharacteristic.ID))
- .setBluetoothSigDataCharacteristicId(get16BitUuid(
- Constants.TransportDiscoveryService.BluetoothSigDataCharacteristic.ID))
- .setFirmwareVersionCharacteristicId(get16BitUuid(FirmwareVersionCharacteristic.ID))
- .setBrTransportBlockDataDescriptorId(
- get16BitUuid(
- Constants.TransportDiscoveryService.BluetoothSigDataCharacteristic
- .BrTransportBlockDataDescriptor.ID))
- .setWaitForUuidsAfterBonding(true)
- .setReceiveUuidsAndBondedEventBeforeClose(true)
- .setRemoveBondTimeoutSeconds(5)
- .setRemoveBondSleepMillis(1000)
- .setCreateBondTimeoutSeconds(15)
- .setHidCreateBondTimeoutSeconds(40)
- .setProxyTimeoutSeconds(2)
- .setRejectPhonebookAccess(false)
- .setRejectMessageAccess(false)
- .setRejectSimAccess(false)
- .setAcceptPasskey(true)
- .setSupportedProfileUuids(Constants.getSupportedProfiles())
- .setWriteAccountKeySleepMillis(2000)
- .setProviderInitiatesBondingIfSupported(false)
- .setAttemptDirectConnectionWhenPreviouslyBonded(false)
- .setAutomaticallyReconnectGattWhenNeeded(false)
- .setSkipDisconnectingGattBeforeWritingAccountKey(false)
- .setSkipConnectingProfiles(false)
- .setIgnoreUuidTimeoutAfterBonded(false)
- .setSpecifyCreateBondTransportType(false)
- .setCreateBondTransportType(0 /*BluetoothDevice.TRANSPORT_AUTO*/)
- .setIncreaseIntentFilterPriority(true)
- .setEvaluatePerformance(false)
- .setKeepSameAccountKeyWrite(true)
- .setEnableNamingCharacteristic(false)
- .setEnableFirmwareVersionCharacteristic(false)
- .setIsRetroactivePairing(false)
- .setNumSdpAttemptsAfterBonded(1)
- .setSupportHidDevice(false)
- .setEnablePairingWhileDirectlyConnecting(true)
- .setAcceptConsentForFastPairOne(true)
- .setGattConnectRetryTimeoutMillis(0)
- .setEnable128BitCustomGattCharacteristicsId(true)
- .setEnableSendExceptionStepToValidator(true)
- .setEnableAdditionalDataTypeWhenActionOverBle(true)
- .setCheckBondStateWhenSkipConnectingProfiles(true)
- .setHandlePasskeyConfirmationByUi(false)
- .setMoreEventLogForQuality(true)
- .setRetryGattConnectionAndSecretHandshake(true)
- .setGattConnectShortTimeoutMs(7000)
- .setGattConnectLongTimeoutMs(15000)
- .setGattConnectShortTimeoutRetryMaxSpentTimeMs(10000)
- .setAddressRotateRetryMaxSpentTimeMs(15000)
- .setPairingRetryDelayMs(100)
- .setSecretHandshakeShortTimeoutMs(3000)
- .setSecretHandshakeLongTimeoutMs(10000)
- .setSecretHandshakeShortTimeoutRetryMaxSpentTimeMs(5000)
- .setSecretHandshakeLongTimeoutRetryMaxSpentTimeMs(7000)
- .setSecretHandshakeRetryAttempts(3)
- .setSecretHandshakeRetryGattConnectionMaxSpentTimeMs(15000)
- .setSignalLostRetryMaxSpentTimeMs(15000)
- .setGattConnectionAndSecretHandshakeNoRetryGattError(ImmutableSet.of())
- .setRetrySecretHandshakeTimeout(false)
- .setLogUserManualRetry(true)
- .setPairFailureCounts(0)
- .setEnablePairFlowShowUiWithoutProfileConnection(true)
- .setPairFailureCounts(0)
- .setLogPairWithCachedModelId(true)
- .setDirectConnectProfileIfModelIdInCache(false)
- .setCachedDeviceAddress("")
- .setPossibleCachedDeviceAddress("")
- .setSameModelIdPairedDeviceCount(0)
- .setIsDeviceFinishCheckAddressFromCache(true);
- }
-
- /**
- * Constructs a builder from GmsLog.
- */
- // TODO(b/206668142): remove this builder once api is ready.
- public static Builder builderFromGmsLog() {
- return new Preferences.Builder()
.setGattOperationTimeoutSeconds(10)
.setGattConnectionTimeoutSeconds(15)
.setBluetoothToggleTimeoutSeconds(10)
@@ -1158,10 +1060,15 @@
.setBluetoothStatePollingMillis(1000)
.setNumAttempts(2)
.setEnableBrEdrHandover(false)
- .setBrHandoverDataCharacteristicId((short) 11265)
- .setBluetoothSigDataCharacteristicId((short) 11266)
- .setFirmwareVersionCharacteristicId((short) 10790)
- .setBrTransportBlockDataDescriptorId((short) 11267)
+ .setBrHandoverDataCharacteristicId(get16BitUuid(
+ Constants.TransportDiscoveryService.BrHandoverDataCharacteristic.ID))
+ .setBluetoothSigDataCharacteristicId(get16BitUuid(
+ Constants.TransportDiscoveryService.BluetoothSigDataCharacteristic.ID))
+ .setFirmwareVersionCharacteristicId(get16BitUuid(FirmwareVersionCharacteristic.ID))
+ .setBrTransportBlockDataDescriptorId(
+ get16BitUuid(
+ Constants.TransportDiscoveryService.BluetoothSigDataCharacteristic
+ .BrTransportBlockDataDescriptor.ID))
.setWaitForUuidsAfterBonding(true)
.setReceiveUuidsAndBondedEventBeforeClose(true)
.setRemoveBondTimeoutSeconds(5)
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java
index 5b45f61..b2002c5 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java
@@ -187,12 +187,6 @@
return mWrappedBluetoothDevice.createInsecureRfcommSocketToServiceRecord(uuid);
}
- /** See {@link android.bluetooth.BluetoothDevice#setPin(byte[])}. */
- @TargetApi(19)
- public boolean setPairingConfirmation(byte[] pin) {
- return mWrappedBluetoothDevice.setPin(pin);
- }
-
/** See {@link android.bluetooth.BluetoothDevice#setPairingConfirmation(boolean)}. */
public boolean setPairingConfirmation(boolean confirm) {
return mWrappedBluetoothDevice.setPairingConfirmation(confirm);
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServer.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServer.java
index 3f6f361..d4873fd 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServer.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServer.java
@@ -51,6 +51,11 @@
return new BluetoothGattServer(instance);
}
+ /** Unwraps a Bluetooth Gatt server. */
+ public android.bluetooth.BluetoothGattServer unwrap() {
+ return mWrappedInstance;
+ }
+
/**
* See {@link android.bluetooth.BluetoothGattServer#connect(
* android.bluetooth.BluetoothDevice, boolean)}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeAdvertiser.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeAdvertiser.java
index 6fe4432..b2c61ab 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -71,4 +71,10 @@
}
return new BluetoothLeAdvertiser(bluetoothLeAdvertiser);
}
+
+ /** Unwraps a Bluetooth LE advertiser. */
+ @Nullable
+ public android.bluetooth.le.BluetoothLeAdvertiser unwrap() {
+ return mWrappedInstance;
+ }
}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScanner.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScanner.java
index 8a13abe..9b3447e 100644
--- a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScanner.java
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScanner.java
@@ -77,6 +77,12 @@
mWrappedBluetoothLeScanner.stopScan(callbackIntent);
}
+ /** Unwraps a Bluetooth LE scanner. */
+ @Nullable
+ public android.bluetooth.le.BluetoothLeScanner unwrap() {
+ return mWrappedBluetoothLeScanner;
+ }
+
/** Wraps a Bluetooth LE scanner. */
@Nullable
public static BluetoothLeScanner wrap(
diff --git a/nearby/service/java/com/android/server/nearby/common/locator/Locator.java b/nearby/service/java/com/android/server/nearby/common/locator/Locator.java
index f8b43a6..2003335 100644
--- a/nearby/service/java/com/android/server/nearby/common/locator/Locator.java
+++ b/nearby/service/java/com/android/server/nearby/common/locator/Locator.java
@@ -110,7 +110,8 @@
throw new IllegalStateException(errorMessage);
}
- private String getUnboundErrorMessage(Class<?> type) {
+ @VisibleForTesting
+ String getUnboundErrorMessage(Class<?> type) {
StringBuilder sb = new StringBuilder();
sb.append("Unbound type: ").append(type.getName()).append("\n").append(
"Searched locators:\n");
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
index 2ecce47..d459329 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
@@ -31,10 +31,7 @@
import com.android.server.nearby.common.ble.decode.FastPairDecoder;
import com.android.server.nearby.common.ble.util.RangingUtils;
import com.android.server.nearby.common.bloomfilter.BloomFilter;
-import com.android.server.nearby.common.bloomfilter.FastPairBloomFilterHasher;
import com.android.server.nearby.common.locator.Locator;
-import com.android.server.nearby.fastpair.cache.DiscoveryItem;
-import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
import com.android.server.nearby.provider.FastPairDataProvider;
import com.android.server.nearby.util.DataUtils;
@@ -120,7 +117,7 @@
Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
}
} else {
- // Start to process bloom filter
+ // Start to process bloom filter. Yet to finish.
try {
List<Account> accountList = mPairDataProvider.loadFastPairEligibleAccounts();
byte[] bloomFilterByteArray = FastPairDecoder
@@ -130,73 +127,9 @@
if (bloomFilterByteArray == null || bloomFilterByteArray.length == 0) {
return;
}
- for (Account account : accountList) {
- List<Data.FastPairDeviceWithAccountKey> listDevices =
- mPairDataProvider.loadFastPairDeviceWithAccountKey(account);
- Data.FastPairDeviceWithAccountKey recognizedDevice =
- findRecognizedDevice(listDevices,
- new BloomFilter(bloomFilterByteArray,
- new FastPairBloomFilterHasher()), bloomFilterSalt);
-
- if (recognizedDevice != null) {
- Log.d(TAG, "find matched device show notification to remind"
- + " user to pair");
- // Check the distance of the device if the distance is larger than the
- // threshold
- // do not show half sheet.
- if (!isNearby(fastPairDevice.getRssi(),
- recognizedDevice.getDiscoveryItem().getTxPower() == 0
- ? fastPairDevice.getTxPower()
- : recognizedDevice.getDiscoveryItem().getTxPower())) {
- return;
- }
- // Check if the device is already paired
- List<Cache.StoredFastPairItem> storedFastPairItemList =
- Locator.get(mContext, FastPairCacheManager.class)
- .getAllSavedStoredFastPairItem();
- Cache.StoredFastPairItem recognizedStoredFastPairItem =
- findRecognizedDeviceFromCachedItem(storedFastPairItemList,
- new BloomFilter(bloomFilterByteArray,
- new FastPairBloomFilterHasher()), bloomFilterSalt);
- if (recognizedStoredFastPairItem != null) {
- // The bloomfilter is recognized in the cache so the device is paired
- // before
- Log.d(TAG, "bloom filter is recognized in the cache");
- continue;
- } else {
- Log.d(TAG, "bloom filter is recognized not paired before should"
- + "show subsequent pairing notification");
- if (mIsFirst) {
- mIsFirst = false;
- // Get full info from api the initial request will only return
- // part of the info due to size limit.
- List<Data.FastPairDeviceWithAccountKey> resList =
- mPairDataProvider.loadFastPairDeviceWithAccountKey(account,
- List.of(recognizedDevice.getAccountKey()
- .toByteArray()));
- if (resList != null && resList.size() > 0) {
- //Saved device from footprint does not have ble address so
- // fill ble address with current scan result.
- Cache.StoredDiscoveryItem storedDiscoveryItem =
- resList.get(0).getDiscoveryItem().toBuilder()
- .setMacAddress(
- fastPairDevice.getBluetoothAddress())
- .build();
- Locator.get(mContext, FastPairController.class).pair(
- new DiscoveryItem(mContext, storedDiscoveryItem),
- resList.get(0).getAccountKey().toByteArray(),
- /** companionApp=*/null);
- }
- }
- }
-
- return;
- }
- }
} catch (IllegalStateException e) {
Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
}
-
}
}
@@ -249,5 +182,4 @@
boolean isNearby(int rssi, int txPower) {
return RangingUtils.distanceFromRssiAndTxPower(rssi, txPower) < NEARBY_DISTANCE_THRESHOLD;
}
-
}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
index f368080..e3de4e2 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
@@ -242,7 +242,7 @@
String modelId = item.getTriggerId();
Preferences.Builder prefsBuilder =
- Preferences.builderFromGmsLog()
+ Preferences.builder()
.setEnableBrEdrHandover(false)
.setIgnoreDiscoveryError(true);
pairingProgressHandlerBase.onSetupPreferencesBuilder(prefsBuilder);
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java
index 6065f99..5ce4488 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItem.java
@@ -28,6 +28,7 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.nearby.common.ble.util.RangingUtils;
import com.android.server.nearby.common.fastpair.IconUtils;
import com.android.server.nearby.common.locator.Locator;
@@ -106,15 +107,6 @@
}
/**
- * Sets the store discovery item mac address.
- */
- public void setMacAddress(String address) {
- mStoredDiscoveryItem = mStoredDiscoveryItem.toBuilder().setMacAddress(address).build();
-
- mFastPairCacheManager.saveDiscoveryItem(this);
- }
-
- /**
* Checks if the item is expired. Expired items are those over getItemExpirationMillis() eg. 2
* minutes
*/
@@ -295,7 +287,8 @@
* Returns the app name of discovery item.
*/
@Nullable
- private String getAppName() {
+ @VisibleForTesting
+ protected String getAppName() {
return mStoredDiscoveryItem.getAppName();
}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java
index b840091..c6134f5 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java
@@ -64,16 +64,6 @@
}
/**
- * Checks if the entry can be auto deleted from the cache
- */
- public boolean isDeletable(Cache.ServerResponseDbItem entry) {
- if (!entry.getExpirable()) {
- return false;
- }
- return true;
- }
-
- /**
* Save discovery item into database. Discovery item is item that discovered through Ble before
* pairing success.
*/
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java
index ccd7e5e..5fb05d5 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBase.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
import com.android.server.nearby.common.bluetooth.fastpair.Preferences;
import com.android.server.nearby.fastpair.cache.DiscoveryItem;
@@ -184,7 +185,8 @@
+ maskBluetoothAddress(address));
}
- private static void optInFootprintsForInitialPairing(
+ @VisibleForTesting
+ static void optInFootprintsForInitialPairing(
FootprintsDeviceManager footprints,
DiscoveryItem item,
byte[] accountKey,
diff --git a/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java b/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java
index 5077ffe..5ed7454 100644
--- a/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java
+++ b/nearby/service/java/com/android/server/nearby/provider/ChreCommunication.java
@@ -27,6 +27,7 @@
import android.hardware.location.NanoAppState;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.nearby.injector.ContextHubManagerAdapter;
import com.android.server.nearby.injector.Injector;
@@ -172,7 +173,8 @@
mCallback.onNanoAppRestart(nanoAppId);
}
- private static String contextHubTransactionResultToString(int result) {
+ @VisibleForTesting
+ static String contextHubTransactionResultToString(int result) {
switch (result) {
case ContextHubTransaction.RESULT_SUCCESS:
return "RESULT_SUCCESS";
diff --git a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
index bdeab51..07ef7ee 100644
--- a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
+++ b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
@@ -33,6 +33,7 @@
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.nearby.injector.Injector;
import com.android.server.nearby.metrics.NearbyMetrics;
import com.android.server.nearby.presence.PresenceDiscoveryResult;
@@ -50,7 +51,6 @@
/** Manages all aspects of discovery providers. */
public class DiscoveryProviderManager implements AbstractDiscoveryProvider.Listener {
-
protected final Object mLock = new Object();
private final Context mContext;
private final BleDiscoveryProvider mBleDiscoveryProvider;
@@ -227,7 +227,8 @@
}
}
- private void startChreProvider() {
+ @VisibleForTesting
+ void startChreProvider() {
Log.d(TAG, "DiscoveryProviderManager starts CHRE scanning.");
synchronized (mLock) {
mChreDiscoveryProvider.getController().setListener(this);
@@ -261,7 +262,8 @@
mChreDiscoveryProvider.getController().stop();
}
- private void invalidateProviderScanMode() {
+ @VisibleForTesting
+ void invalidateProviderScanMode() {
if (mBleDiscoveryProvider.getController().isStarted()) {
mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode);
} else {
@@ -272,7 +274,8 @@
}
}
- private static boolean presenceFilterMatches(
+ @VisibleForTesting
+ static boolean presenceFilterMatches(
NearbyDeviceParcelable device, List<ScanFilter> scanFilters) {
if (scanFilters.isEmpty()) {
return true;
diff --git a/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java b/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
index 0f99a2f..d925f07 100644
--- a/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
@@ -30,7 +30,7 @@
import androidx.annotation.WorkerThread;
-import com.android.server.nearby.common.bloomfilter.BloomFilter;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.nearby.fastpair.footprint.FastPairUploadInfo;
import java.util.ArrayList;
@@ -80,6 +80,11 @@
}
}
+ @VisibleForTesting
+ void setProxyDataProvider(ProxyFastPairDataProvider proxyFastPairDataProvider) {
+ this.mProxyFastPairDataProvider = proxyFastPairDataProvider;
+ }
+
/**
* Loads FastPairAntispoofKeyDeviceMetadata.
*
@@ -136,14 +141,6 @@
}
/**
- * Get recognized device from bloom filter.
- */
- public Data.FastPairDeviceWithAccountKey getRecognizedDevice(BloomFilter bloomFilter,
- byte[] salt) {
- return Data.FastPairDeviceWithAccountKey.newBuilder().build();
- }
-
- /**
* Loads FastPair device accountKeys for a given account, but not other detailed fields.
*
* @throws IllegalStateException If ProxyFastPairDataProvider is not available.
diff --git a/nearby/service/java/com/android/server/nearby/util/DataUtils.java b/nearby/service/java/com/android/server/nearby/util/DataUtils.java
index 8bb83e9..c3bae08 100644
--- a/nearby/service/java/com/android/server/nearby/util/DataUtils.java
+++ b/nearby/service/java/com/android/server/nearby/util/DataUtils.java
@@ -57,11 +57,9 @@
*/
public static String toString(ScanFastPairStoreItem item) {
return "ScanFastPairStoreItem=[address:" + item.getAddress()
- + ", actionUr:" + item.getActionUrl()
+ + ", actionUrl:" + item.getActionUrl()
+ ", deviceName:" + item.getDeviceName()
- + ", iconPng:" + item.getIconPng()
+ ", iconFifeUrl:" + item.getIconFifeUrl()
- + ", antiSpoofingKeyPair:" + item.getAntiSpoofingPublicKey()
+ ", fastPairStrings:" + toString(item.getFastPairStrings())
+ "]";
}
diff --git a/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java b/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java
deleted file mode 100644
index 6021ff6..0000000
--- a/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.nearby.util;
-
-import android.annotation.Nullable;
-import android.bluetooth.le.ScanRecord;
-import android.os.ParcelUuid;
-import android.util.SparseArray;
-
-import com.android.server.nearby.common.ble.BleFilter;
-import com.android.server.nearby.common.ble.BleRecord;
-
-import java.util.Arrays;
-
-/**
- * Parses Fast Pair information out of {@link BleRecord}s.
- *
- * <p>There are 2 different packet formats that are supported, which is used can be determined by
- * packet length:
- *
- * <p>For 3-byte packets, the full packet is the model ID.
- *
- * <p>For all other packets, the first byte is the header, followed by the model ID, followed by
- * zero or more extra fields. Each field has its own header byte followed by the field value. The
- * packet header is formatted as 0bVVVLLLLR (V = version, L = model ID length, R = reserved) and
- * each extra field header is 0bLLLLTTTT (L = field length, T = field type).
- */
-public class FastPairDecoder {
-
- private static final int FIELD_TYPE_BLOOM_FILTER = 0;
- private static final int FIELD_TYPE_BLOOM_FILTER_SALT = 1;
- private static final int FIELD_TYPE_BLOOM_FILTER_NO_NOTIFICATION = 2;
- private static final int FIELD_TYPE_BATTERY = 3;
- private static final int FIELD_TYPE_BATTERY_NO_NOTIFICATION = 4;
- public static final int FIELD_TYPE_CONNECTION_STATE = 5;
- private static final int FIELD_TYPE_RANDOM_RESOLVABLE_DATA = 6;
-
-
- /** FE2C is the 16-bit Service UUID. The rest is the base UUID. See BluetoothUuid (hidden). */
- private static final ParcelUuid FAST_PAIR_SERVICE_PARCEL_UUID =
- ParcelUuid.fromString("0000FE2C-0000-1000-8000-00805F9B34FB");
-
- /** The filter you use to scan for Fast Pair BLE advertisements. */
- public static final BleFilter FILTER =
- new BleFilter.Builder().setServiceData(FAST_PAIR_SERVICE_PARCEL_UUID,
- new byte[0]).build();
-
- // NOTE: Ensure that all bitmasks are always ints, not bytes so that bitshifting works correctly
- // without needing worry about signing errors.
- private static final int HEADER_VERSION_BITMASK = 0b11100000;
- private static final int HEADER_LENGTH_BITMASK = 0b00011110;
- private static final int HEADER_VERSION_OFFSET = 5;
- private static final int HEADER_LENGTH_OFFSET = 1;
-
- private static final int EXTRA_FIELD_LENGTH_BITMASK = 0b11110000;
- private static final int EXTRA_FIELD_TYPE_BITMASK = 0b00001111;
- private static final int EXTRA_FIELD_LENGTH_OFFSET = 4;
- private static final int EXTRA_FIELD_TYPE_OFFSET = 0;
-
- private static final int MIN_ID_LENGTH = 3;
- private static final int MAX_ID_LENGTH = 14;
- private static final int HEADER_INDEX = 0;
- private static final int HEADER_LENGTH = 1;
- private static final int FIELD_HEADER_LENGTH = 1;
-
- // Not using java.util.IllegalFormatException because it is unchecked.
- private static class IllegalFormatException extends Exception {
- private IllegalFormatException(String message) {
- super(message);
- }
- }
-
- /**
- * Gets model id data from broadcast
- */
- @Nullable
- public static byte[] getModelId(@Nullable byte[] serviceData) {
- if (serviceData == null) {
- return null;
- }
-
- if (serviceData.length >= MIN_ID_LENGTH) {
- if (serviceData.length == MIN_ID_LENGTH) {
- // If the length == 3, all bytes are the ID. See flag docs for more about
- // endianness.
- return serviceData;
- } else {
- // Otherwise, the first byte is a header which contains the length of the big-endian
- // model ID that follows. The model ID will be trimmed if it contains leading zeros.
- int idIndex = 1;
- int end = idIndex + getIdLength(serviceData);
- while (serviceData[idIndex] == 0 && end - idIndex > MIN_ID_LENGTH) {
- idIndex++;
- }
- return Arrays.copyOfRange(serviceData, idIndex, end);
- }
- }
- return null;
- }
-
- /** Gets the FastPair service data array if available, otherwise returns null. */
- @Nullable
- public static byte[] getServiceDataArray(BleRecord bleRecord) {
- return bleRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID);
- }
-
- /** Gets the FastPair service data array if available, otherwise returns null. */
- @Nullable
- public static byte[] getServiceDataArray(ScanRecord scanRecord) {
- return scanRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID);
- }
-
- /** Gets the bloom filter from the extra fields if available, otherwise returns null. */
- @Nullable
- public static byte[] getBloomFilter(@Nullable byte[] serviceData) {
- return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER);
- }
-
- /** Gets the bloom filter salt from the extra fields if available, otherwise returns null. */
- @Nullable
- public static byte[] getBloomFilterSalt(byte[] serviceData) {
- return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER_SALT);
- }
-
- /**
- * Gets the suppress notification with bloom filter from the extra fields if available,
- * otherwise returns null.
- */
- @Nullable
- public static byte[] getBloomFilterNoNotification(@Nullable byte[] serviceData) {
- return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER_NO_NOTIFICATION);
- }
-
- /**
- * Get random resolvableData
- */
- @Nullable
- public static byte[] getRandomResolvableData(byte[] serviceData) {
- return getExtraField(serviceData, FIELD_TYPE_RANDOM_RESOLVABLE_DATA);
- }
-
- @Nullable
- private static byte[] getExtraField(@Nullable byte[] serviceData, int fieldId) {
- if (serviceData == null || serviceData.length < HEADER_INDEX + HEADER_LENGTH) {
- return null;
- }
- try {
- return getExtraFields(serviceData).get(fieldId);
- } catch (IllegalFormatException e) {
- return null;
- }
- }
-
- /** Gets extra field data at the end of the packet, defined by the extra field header. */
- private static SparseArray<byte[]> getExtraFields(byte[] serviceData)
- throws IllegalFormatException {
- SparseArray<byte[]> extraFields = new SparseArray<>();
- if (getVersion(serviceData) != 0) {
- return extraFields;
- }
- int headerIndex = getFirstExtraFieldHeaderIndex(serviceData);
- while (headerIndex < serviceData.length) {
- int length = getExtraFieldLength(serviceData, headerIndex);
- int index = headerIndex + FIELD_HEADER_LENGTH;
- int type = getExtraFieldType(serviceData, headerIndex);
- int end = index + length;
- if (extraFields.get(type) == null) {
- if (end <= serviceData.length) {
- extraFields.put(type, Arrays.copyOfRange(serviceData, index, end));
- } else {
- throw new IllegalFormatException(
- "Invalid length, " + end + " is longer than service data size "
- + serviceData.length);
- }
- }
- headerIndex = end;
- }
- return extraFields;
- }
-
- /** Checks whether or not a valid ID is included in the service data packet. */
- public static boolean hasBeaconIdBytes(BleRecord bleRecord) {
- byte[] serviceData = bleRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID);
- return checkModelId(serviceData);
- }
-
- /** Check whether byte array is FastPair model id or not. */
- public static boolean checkModelId(@Nullable byte[] scanResult) {
- return scanResult != null
- // The 3-byte format has no header byte (all bytes are the ID).
- && (scanResult.length == MIN_ID_LENGTH
- // Header byte exists. We support only format version 0. (A different version
- // indicates
- // a breaking change in the format.)
- || (scanResult.length > MIN_ID_LENGTH
- && getVersion(scanResult) == 0
- && isIdLengthValid(scanResult)));
- }
-
- /** Checks whether or not bloom filter is included in the service data packet. */
- public static boolean hasBloomFilter(BleRecord bleRecord) {
- return (getBloomFilter(getServiceDataArray(bleRecord)) != null
- || getBloomFilterNoNotification(getServiceDataArray(bleRecord)) != null);
- }
-
- /** Checks whether or not bloom filter is included in the service data packet. */
- public static boolean hasBloomFilter(ScanRecord scanRecord) {
- return (getBloomFilter(getServiceDataArray(scanRecord)) != null
- || getBloomFilterNoNotification(getServiceDataArray(scanRecord)) != null);
- }
-
- private static int getVersion(byte[] serviceData) {
- return serviceData.length == MIN_ID_LENGTH
- ? 0
- : (serviceData[HEADER_INDEX] & HEADER_VERSION_BITMASK) >> HEADER_VERSION_OFFSET;
- }
-
- private static int getIdLength(byte[] serviceData) {
- return serviceData.length == MIN_ID_LENGTH
- ? MIN_ID_LENGTH
- : (serviceData[HEADER_INDEX] & HEADER_LENGTH_BITMASK) >> HEADER_LENGTH_OFFSET;
- }
-
- private static int getFirstExtraFieldHeaderIndex(byte[] serviceData) {
- return HEADER_INDEX + HEADER_LENGTH + getIdLength(serviceData);
- }
-
- private static int getExtraFieldLength(byte[] serviceData, int extraFieldIndex) {
- return (serviceData[extraFieldIndex] & EXTRA_FIELD_LENGTH_BITMASK)
- >> EXTRA_FIELD_LENGTH_OFFSET;
- }
-
- private static int getExtraFieldType(byte[] serviceData, int extraFieldIndex) {
- return (serviceData[extraFieldIndex] & EXTRA_FIELD_TYPE_BITMASK) >> EXTRA_FIELD_TYPE_OFFSET;
- }
-
- private static boolean isIdLengthValid(byte[] serviceData) {
- int idLength = getIdLength(serviceData);
- return MIN_ID_LENGTH <= idLength
- && idLength <= MAX_ID_LENGTH
- && idLength + HEADER_LENGTH <= serviceData.length;
- }
-}
-
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/CredentialElementTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/CredentialElementTest.java
index aacb6d8..a2da967 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/CredentialElementTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/CredentialElementTest.java
@@ -42,7 +42,6 @@
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testBuilder() {
CredentialElement element = new CredentialElement(KEY, VALUE);
-
assertThat(element.getKey()).isEqualTo(KEY);
assertThat(Arrays.equals(element.getValue(), VALUE)).isTrue();
}
@@ -58,9 +57,31 @@
CredentialElement elementFromParcel = element.CREATOR.createFromParcel(
parcel);
parcel.recycle();
-
assertThat(elementFromParcel.getKey()).isEqualTo(KEY);
assertThat(Arrays.equals(elementFromParcel.getValue(), VALUE)).isTrue();
}
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void describeContents() {
+ CredentialElement element = new CredentialElement(KEY, VALUE);
+ assertThat(element.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testEqual() {
+ CredentialElement element1 = new CredentialElement(KEY, VALUE);
+ CredentialElement element2 = new CredentialElement(KEY, VALUE);
+ assertThat(element1.equals(element2)).isTrue();
+ assertThat(element1.hashCode()).isEqualTo(element2.hashCode());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testCreatorNewArray() {
+ CredentialElement [] elements =
+ CredentialElement.CREATOR.newArray(2);
+ assertThat(elements.length).isEqualTo(2);
+ }
}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceParcelableTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceParcelableTest.java
index dd9cbb0..0487aa4 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceParcelableTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceParcelableTest.java
@@ -42,8 +42,11 @@
private static final String BLUETOOTH_ADDRESS = "00:11:22:33:FF:EE";
private static final byte[] SCAN_DATA = new byte[] {1, 2, 3, 4};
+ private static final byte[] SALT = new byte[] {1, 2, 3, 4};
private static final String FAST_PAIR_MODEL_ID = "1234";
private static final int RSSI = -60;
+ private static final int TX_POWER = -10;
+ private static final int ACTION = 1;
private NearbyDeviceParcelable.Builder mBuilder;
@@ -66,11 +69,11 @@
public void testToString() {
PublicCredential publicCredential =
new PublicCredential.Builder(
- new byte[] {1},
- new byte[] {2},
- new byte[] {3},
- new byte[] {4},
- new byte[] {5})
+ new byte[] {1},
+ new byte[] {2},
+ new byte[] {3},
+ new byte[] {4},
+ new byte[] {5})
.build();
NearbyDeviceParcelable nearbyDeviceParcelable =
mBuilder.setFastPairModelId(null)
@@ -88,20 +91,36 @@
@Test
@SdkSuppress(minSdkVersion = 33, codeName = "T")
- public void test_defaultNullFields() {
+ public void testNullFields() {
+ PublicCredential publicCredential =
+ new PublicCredential.Builder(
+ new byte[] {1},
+ new byte[] {2},
+ new byte[] {3},
+ new byte[] {4},
+ new byte[] {5})
+ .build();
NearbyDeviceParcelable nearbyDeviceParcelable =
new NearbyDeviceParcelable.Builder()
.setMedium(NearbyDevice.Medium.BLE)
+ .setPublicCredential(publicCredential)
+ .setAction(ACTION)
.setRssi(RSSI)
+ .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+ .setTxPower(TX_POWER)
+ .setSalt(SALT)
.build();
assertThat(nearbyDeviceParcelable.getName()).isNull();
assertThat(nearbyDeviceParcelable.getFastPairModelId()).isNull();
assertThat(nearbyDeviceParcelable.getBluetoothAddress()).isNull();
assertThat(nearbyDeviceParcelable.getData()).isNull();
-
assertThat(nearbyDeviceParcelable.getMedium()).isEqualTo(NearbyDevice.Medium.BLE);
assertThat(nearbyDeviceParcelable.getRssi()).isEqualTo(RSSI);
+ assertThat(nearbyDeviceParcelable.getAction()).isEqualTo(ACTION);
+ assertThat(nearbyDeviceParcelable.getPublicCredential()).isEqualTo(publicCredential);
+ assertThat(nearbyDeviceParcelable.getSalt()).isEqualTo(SALT);
+ assertThat(nearbyDeviceParcelable.getTxPower()).isEqualTo(TX_POWER);
}
@Test
@@ -141,7 +160,6 @@
@SdkSuppress(minSdkVersion = 33, codeName = "T")
public void testWriteParcel_nullBluetoothAddress() {
NearbyDeviceParcelable nearbyDeviceParcelable = mBuilder.setBluetoothAddress(null).build();
-
Parcel parcel = Parcel.obtain();
nearbyDeviceParcelable.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
@@ -151,4 +169,36 @@
assertThat(actualNearbyDevice.getBluetoothAddress()).isNull();
}
+
+ @Test
+ @SdkSuppress(minSdkVersion = 33, codeName = "T")
+ public void describeContents() {
+ NearbyDeviceParcelable nearbyDeviceParcelable = mBuilder.setBluetoothAddress(null).build();
+ assertThat(nearbyDeviceParcelable.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 33, codeName = "T")
+ public void testEqual() {
+ PublicCredential publicCredential =
+ new PublicCredential.Builder(
+ new byte[] {1},
+ new byte[] {2},
+ new byte[] {3},
+ new byte[] {4},
+ new byte[] {5})
+ .build();
+ NearbyDeviceParcelable nearbyDeviceParcelable1 =
+ mBuilder.setPublicCredential(publicCredential).build();
+ NearbyDeviceParcelable nearbyDeviceParcelable2 =
+ mBuilder.setPublicCredential(publicCredential).build();
+ assertThat(nearbyDeviceParcelable1.equals(nearbyDeviceParcelable2)).isTrue();
+ }
+
+ @Test
+ public void testCreatorNewArray() {
+ NearbyDeviceParcelable[] nearbyDeviceParcelables =
+ NearbyDeviceParcelable.CREATOR.newArray(2);
+ assertThat(nearbyDeviceParcelables.length).isEqualTo(2);
+ }
}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceTest.java
index f37800a..998714c 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyDeviceTest.java
@@ -16,6 +16,8 @@
package android.nearby.cts;
+import static android.nearby.NearbyDevice.Medium.BLE;
+
import android.annotation.TargetApi;
import android.nearby.FastPairDevice;
import android.nearby.NearbyDevice;
@@ -34,13 +36,18 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@TargetApi(Build.VERSION_CODES.TIRAMISU)
public class NearbyDeviceTest {
+ private static final String NAME = "NearbyDevice";
+ private static final String MODEL_ID = "112233";
+ private static final int TX_POWER = -10;
+ private static final int RSSI = -60;
+ private static final String BLUETOOTH_ADDRESS = "00:11:22:33:FF:EE";
+ private static final byte[] SCAN_DATA = new byte[] {1, 2, 3, 4};
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void test_isValidMedium() {
assertThat(NearbyDevice.isValidMedium(1)).isTrue();
assertThat(NearbyDevice.isValidMedium(2)).isTrue();
-
assertThat(NearbyDevice.isValidMedium(0)).isFalse();
assertThat(NearbyDevice.isValidMedium(3)).isFalse();
}
@@ -49,11 +56,54 @@
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void test_getMedium_fromChild() {
FastPairDevice fastPairDevice = new FastPairDevice.Builder()
- .addMedium(NearbyDevice.Medium.BLE)
- .setRssi(-60)
+ .addMedium(BLE)
+ .setRssi(RSSI)
.build();
assertThat(fastPairDevice.getMediums()).contains(1);
- assertThat(fastPairDevice.getRssi()).isEqualTo(-60);
+ assertThat(fastPairDevice.getRssi()).isEqualTo(RSSI);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testEqual() {
+ FastPairDevice fastPairDevice1 = new FastPairDevice.Builder()
+ .setModelId(MODEL_ID)
+ .setTxPower(TX_POWER)
+ .setBluetoothAddress(BLUETOOTH_ADDRESS)
+ .setData(SCAN_DATA)
+ .setRssi(RSSI)
+ .addMedium(BLE)
+ .setName(NAME)
+ .build();
+ FastPairDevice fastPairDevice2 = new FastPairDevice.Builder()
+ .setModelId(MODEL_ID)
+ .setTxPower(TX_POWER)
+ .setBluetoothAddress(BLUETOOTH_ADDRESS)
+ .setData(SCAN_DATA)
+ .setRssi(RSSI)
+ .addMedium(BLE)
+ .setName(NAME)
+ .build();
+
+ assertThat(fastPairDevice1.equals(fastPairDevice1)).isTrue();
+ assertThat(fastPairDevice1.equals(fastPairDevice2)).isFalse();
+ assertThat(fastPairDevice1.hashCode()).isEqualTo(fastPairDevice2.hashCode());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testToString() {
+ FastPairDevice fastPairDevice1 = new FastPairDevice.Builder()
+ .addMedium(BLE)
+ .setRssi(RSSI)
+ .setModelId(MODEL_ID)
+ .setTxPower(TX_POWER)
+ .setBluetoothAddress(BLUETOOTH_ADDRESS)
+ .build();
+
+ assertThat(fastPairDevice1.toString())
+ .isEqualTo("FastPairDevice [medium={BLE} rssi=-60 "
+ + "txPower=-10 modelId=112233 bluetoothAddress=00:11:22:33:FF:EE]");
}
}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
index 7696a61..462d05a 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
@@ -158,6 +158,15 @@
mNearbyManager.stopBroadcast(callback);
}
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void setFastPairScanEnabled() {
+ mNearbyManager.setFastPairScanEnabled(mContext, true);
+ assertThat(mNearbyManager.getFastPairScanEnabled(mContext)).isTrue();
+ mNearbyManager.setFastPairScanEnabled(mContext, false);
+ assertThat(mNearbyManager.getFastPairScanEnabled(mContext)).isFalse();
+ }
+
private void enableBluetooth() {
BluetoothManager manager = mContext.getSystemService(BluetoothManager.class);
BluetoothAdapter bluetoothAdapter = manager.getAdapter();
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java
index 1daa410..be0c4b7 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceBroadcastRequestTest.java
@@ -114,4 +114,18 @@
assertThat(parcelRequest.getType()).isEqualTo(BROADCAST_TYPE_NEARBY_PRESENCE);
}
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void describeContents() {
+ PresenceBroadcastRequest broadcastRequest = mBuilder.build();
+ assertThat(broadcastRequest.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void testCreatorNewArray() {
+ PresenceBroadcastRequest[] presenceBroadcastRequests =
+ PresenceBroadcastRequest.CREATOR.newArray(2);
+ assertThat(presenceBroadcastRequests.length).isEqualTo(2);
+ }
}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java
index 5fefc68..ab0b7b4 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceDeviceTest.java
@@ -104,4 +104,24 @@
assertThat(parcelDevice.getMediums()).containsExactly(MEDIUM);
assertThat(parcelDevice.getName()).isEqualTo(DEVICE_NAME);
}
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void describeContents() {
+ PresenceDevice device =
+ new PresenceDevice.Builder(DEVICE_ID, SALT, SECRET_ID, ENCRYPTED_IDENTITY)
+ .addExtendedProperty(new DataElement(KEY, VALUE))
+ .setRssi(RSSI)
+ .addMedium(MEDIUM)
+ .setName(DEVICE_NAME)
+ .build();
+ assertThat(device.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void testCreatorNewArray() {
+ PresenceDevice[] devices =
+ PresenceDevice.CREATOR.newArray(2);
+ assertThat(devices.length).isEqualTo(2);
+ }
}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java
index b7fe40a..806e7c0 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PresenceScanFilterTest.java
@@ -91,4 +91,19 @@
assertThat(parcelFilter.getMaxPathLoss()).isEqualTo(RSSI);
assertThat(parcelFilter.getPresenceActions()).containsExactly(ACTION);
}
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void describeContents() {
+ PresenceScanFilter filter = mBuilder.build();
+ assertThat(filter.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testCreatorNewArray() {
+ PresenceScanFilter[] filters =
+ PresenceScanFilter.CREATOR.newArray(2);
+ assertThat(filters.length).isEqualTo(2);
+ }
}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java
index f05f65f..fa8c954 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PrivateCredentialTest.java
@@ -99,4 +99,19 @@
assertThat(credentialElement.getKey()).isEqualTo(KEY);
assertThat(Arrays.equals(credentialElement.getValue(), VALUE)).isTrue();
}
+
+ @Test
+ @SdkSuppress(minSdkVersion = 33, codeName = "T")
+ public void describeContents() {
+ PrivateCredential credential = mBuilder.build();
+ assertThat(credential.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 33, codeName = "T")
+ public void testCreatorNewArray() {
+ PrivateCredential[] credentials =
+ PrivateCredential.CREATOR.newArray(2);
+ assertThat(credentials.length).isEqualTo(2);
+ }
}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java
index 11bbacc..05a4598 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/PublicCredentialTest.java
@@ -161,4 +161,19 @@
.build();
assertThat(credentialOne.equals((Object) credentialTwo)).isFalse();
}
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void describeContents() {
+ PublicCredential credential = mBuilder.build();
+ assertThat(credential.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testCreatorNewArray() {
+ PublicCredential[] credentials =
+ PublicCredential.CREATOR.newArray(2);
+ assertThat(credentials.length).isEqualTo(2);
+ }
}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/ScanRequestTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/ScanRequestTest.java
index 21f3d28..de4b1c3 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/ScanRequestTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/ScanRequestTest.java
@@ -171,6 +171,23 @@
assertThat(request.getScanFilters().get(0).getMaxPathLoss()).isEqualTo(RSSI);
}
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void describeContents() {
+ ScanRequest request = new ScanRequest.Builder()
+ .setScanType(SCAN_TYPE_FAST_PAIR)
+ .build();
+ assertThat(request.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testCreatorNewArray() {
+ ScanRequest[] requests =
+ ScanRequest.CREATOR.newArray(2);
+ assertThat(requests.length).isEqualTo(2);
+ }
+
private static PresenceScanFilter getPresenceScanFilter() {
final byte[] secretId = new byte[]{1, 2, 3, 4};
final byte[] authenticityKey = new byte[]{0, 1, 1, 1};
diff --git a/nearby/tests/unit/AndroidManifest.xml b/nearby/tests/unit/AndroidManifest.xml
index 9f58baf..7dcb263 100644
--- a/nearby/tests/unit/AndroidManifest.xml
+++ b/nearby/tests/unit/AndroidManifest.xml
@@ -23,6 +23,7 @@
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
+ <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
diff --git a/nearby/tests/unit/src/android/nearby/FastPairAntispoofKeyDeviceMetadataTest.java b/nearby/tests/unit/src/android/nearby/FastPairAntispoofKeyDeviceMetadataTest.java
new file mode 100644
index 0000000..d095529
--- /dev/null
+++ b/nearby/tests/unit/src/android/nearby/FastPairAntispoofKeyDeviceMetadataTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nearby;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class FastPairAntispoofKeyDeviceMetadataTest {
+
+ private static final int BLE_TX_POWER = 5;
+ private static final String CONNECT_SUCCESS_COMPANION_APP_INSTALLED =
+ "CONNECT_SUCCESS_COMPANION_APP_INSTALLED";
+ private static final String CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED =
+ "CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED";
+ private static final float DELTA = 0.001f;
+ private static final int DEVICE_TYPE = 7;
+ private static final String DOWNLOAD_COMPANION_APP_DESCRIPTION =
+ "DOWNLOAD_COMPANION_APP_DESCRIPTION";
+ private static final String FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION =
+ "FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION";
+ private static final byte[] IMAGE = new byte[] {7, 9};
+ private static final String IMAGE_URL = "IMAGE_URL";
+ private static final String INITIAL_NOTIFICATION_DESCRIPTION =
+ "INITIAL_NOTIFICATION_DESCRIPTION";
+ private static final String INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT =
+ "INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT";
+ private static final String INITIAL_PAIRING_DESCRIPTION = "INITIAL_PAIRING_DESCRIPTION";
+ private static final String INTENT_URI = "INTENT_URI";
+ private static final String OPEN_COMPANION_APP_DESCRIPTION = "OPEN_COMPANION_APP_DESCRIPTION";
+ private static final String RETRO_ACTIVE_PAIRING_DESCRIPTION =
+ "RETRO_ACTIVE_PAIRING_DESCRIPTION";
+ private static final String SUBSEQUENT_PAIRING_DESCRIPTION = "SUBSEQUENT_PAIRING_DESCRIPTION";
+ private static final float TRIGGER_DISTANCE = 111;
+ private static final String TRUE_WIRELESS_IMAGE_URL_CASE = "TRUE_WIRELESS_IMAGE_URL_CASE";
+ private static final String TRUE_WIRELESS_IMAGE_URL_LEFT_BUD =
+ "TRUE_WIRELESS_IMAGE_URL_LEFT_BUD";
+ private static final String TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD =
+ "TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD";
+ private static final String UNABLE_TO_CONNECT_DESCRIPTION = "UNABLE_TO_CONNECT_DESCRIPTION";
+ private static final String UNABLE_TO_CONNECT_TITLE = "UNABLE_TO_CONNECT_TITLE";
+ private static final String UPDATE_COMPANION_APP_DESCRIPTION =
+ "UPDATE_COMPANION_APP_DESCRIPTION";
+ private static final String WAIT_LAUNCH_COMPANION_APP_DESCRIPTION =
+ "WAIT_LAUNCH_COMPANION_APP_DESCRIPTION";
+ private static final byte[] ANTI_SPOOFING_KEY = new byte[] {4, 5, 6};
+ private static final String NAME = "NAME";
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testSetGetFastPairAntispoofKeyDeviceMetadataNotNull() {
+ FastPairDeviceMetadata fastPairDeviceMetadata = genFastPairDeviceMetadata();
+ FastPairAntispoofKeyDeviceMetadata fastPairAntispoofKeyDeviceMetadata =
+ genFastPairAntispoofKeyDeviceMetadata(ANTI_SPOOFING_KEY, fastPairDeviceMetadata);
+
+ assertThat(fastPairAntispoofKeyDeviceMetadata.getAntispoofPublicKey()).isEqualTo(
+ ANTI_SPOOFING_KEY);
+ ensureFastPairDeviceMetadataAsExpected(
+ fastPairAntispoofKeyDeviceMetadata.getFastPairDeviceMetadata());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testSetGetFastPairAntispoofKeyDeviceMetadataNull() {
+ FastPairAntispoofKeyDeviceMetadata fastPairAntispoofKeyDeviceMetadata =
+ genFastPairAntispoofKeyDeviceMetadata(null, null);
+ assertThat(fastPairAntispoofKeyDeviceMetadata.getAntispoofPublicKey()).isEqualTo(
+ null);
+ assertThat(fastPairAntispoofKeyDeviceMetadata.getFastPairDeviceMetadata()).isEqualTo(
+ null);
+ }
+
+ /* Verifies DeviceMetadata. */
+ private static void ensureFastPairDeviceMetadataAsExpected(FastPairDeviceMetadata metadata) {
+ assertThat(metadata.getBleTxPower()).isEqualTo(BLE_TX_POWER);
+ assertThat(metadata.getConnectSuccessCompanionAppInstalled())
+ .isEqualTo(CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+ assertThat(metadata.getConnectSuccessCompanionAppNotInstalled())
+ .isEqualTo(CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+ assertThat(metadata.getDeviceType()).isEqualTo(DEVICE_TYPE);
+ assertThat(metadata.getDownloadCompanionAppDescription())
+ .isEqualTo(DOWNLOAD_COMPANION_APP_DESCRIPTION);
+ assertThat(metadata.getFailConnectGoToSettingsDescription())
+ .isEqualTo(FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+ assertThat(metadata.getImage()).isEqualTo(IMAGE);
+ assertThat(metadata.getImageUrl()).isEqualTo(IMAGE_URL);
+ assertThat(metadata.getInitialNotificationDescription())
+ .isEqualTo(INITIAL_NOTIFICATION_DESCRIPTION);
+ assertThat(metadata.getInitialNotificationDescriptionNoAccount())
+ .isEqualTo(INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+ assertThat(metadata.getInitialPairingDescription()).isEqualTo(INITIAL_PAIRING_DESCRIPTION);
+ assertThat(metadata.getIntentUri()).isEqualTo(INTENT_URI);
+ assertThat(metadata.getName()).isEqualTo(NAME);
+ assertThat(metadata.getOpenCompanionAppDescription())
+ .isEqualTo(OPEN_COMPANION_APP_DESCRIPTION);
+ assertThat(metadata.getRetroactivePairingDescription())
+ .isEqualTo(RETRO_ACTIVE_PAIRING_DESCRIPTION);
+ assertThat(metadata.getSubsequentPairingDescription())
+ .isEqualTo(SUBSEQUENT_PAIRING_DESCRIPTION);
+ assertThat(metadata.getTriggerDistance()).isWithin(DELTA).of(TRIGGER_DISTANCE);
+ assertThat(metadata.getTrueWirelessImageUrlCase()).isEqualTo(TRUE_WIRELESS_IMAGE_URL_CASE);
+ assertThat(metadata.getTrueWirelessImageUrlLeftBud())
+ .isEqualTo(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+ assertThat(metadata.getTrueWirelessImageUrlRightBud())
+ .isEqualTo(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+ assertThat(metadata.getUnableToConnectDescription())
+ .isEqualTo(UNABLE_TO_CONNECT_DESCRIPTION);
+ assertThat(metadata.getUnableToConnectTitle()).isEqualTo(UNABLE_TO_CONNECT_TITLE);
+ assertThat(metadata.getUpdateCompanionAppDescription())
+ .isEqualTo(UPDATE_COMPANION_APP_DESCRIPTION);
+ assertThat(metadata.getWaitLaunchCompanionAppDescription())
+ .isEqualTo(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+ }
+
+ /* Generates FastPairAntispoofKeyDeviceMetadata. */
+ private static FastPairAntispoofKeyDeviceMetadata genFastPairAntispoofKeyDeviceMetadata(
+ byte[] antispoofPublicKey, FastPairDeviceMetadata deviceMetadata) {
+ FastPairAntispoofKeyDeviceMetadata.Builder builder =
+ new FastPairAntispoofKeyDeviceMetadata.Builder();
+ builder.setAntispoofPublicKey(antispoofPublicKey);
+ builder.setFastPairDeviceMetadata(deviceMetadata);
+
+ return builder.build();
+ }
+
+ /* Generates FastPairDeviceMetadata. */
+ private static FastPairDeviceMetadata genFastPairDeviceMetadata() {
+ FastPairDeviceMetadata.Builder builder = new FastPairDeviceMetadata.Builder();
+ builder.setBleTxPower(BLE_TX_POWER);
+ builder.setConnectSuccessCompanionAppInstalled(CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+ builder.setConnectSuccessCompanionAppNotInstalled(
+ CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+ builder.setDeviceType(DEVICE_TYPE);
+ builder.setDownloadCompanionAppDescription(DOWNLOAD_COMPANION_APP_DESCRIPTION);
+ builder.setFailConnectGoToSettingsDescription(FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+ builder.setImage(IMAGE);
+ builder.setImageUrl(IMAGE_URL);
+ builder.setInitialNotificationDescription(INITIAL_NOTIFICATION_DESCRIPTION);
+ builder.setInitialNotificationDescriptionNoAccount(
+ INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+ builder.setInitialPairingDescription(INITIAL_PAIRING_DESCRIPTION);
+ builder.setIntentUri(INTENT_URI);
+ builder.setName(NAME);
+ builder.setOpenCompanionAppDescription(OPEN_COMPANION_APP_DESCRIPTION);
+ builder.setRetroactivePairingDescription(RETRO_ACTIVE_PAIRING_DESCRIPTION);
+ builder.setSubsequentPairingDescription(SUBSEQUENT_PAIRING_DESCRIPTION);
+ builder.setTriggerDistance(TRIGGER_DISTANCE);
+ builder.setTrueWirelessImageUrlCase(TRUE_WIRELESS_IMAGE_URL_CASE);
+ builder.setTrueWirelessImageUrlLeftBud(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+ builder.setTrueWirelessImageUrlRightBud(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+ builder.setUnableToConnectDescription(UNABLE_TO_CONNECT_DESCRIPTION);
+ builder.setUnableToConnectTitle(UNABLE_TO_CONNECT_TITLE);
+ builder.setUpdateCompanionAppDescription(UPDATE_COMPANION_APP_DESCRIPTION);
+ builder.setWaitLaunchCompanionAppDescription(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+
+ return builder.build();
+ }
+}
diff --git a/nearby/tests/unit/src/android/nearby/FastPairDataProviderServiceTest.java b/nearby/tests/unit/src/android/nearby/FastPairDataProviderServiceTest.java
new file mode 100644
index 0000000..b3f2442
--- /dev/null
+++ b/nearby/tests/unit/src/android/nearby/FastPairDataProviderServiceTest.java
@@ -0,0 +1,966 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nearby;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.accounts.Account;
+import android.content.Intent;
+import android.nearby.aidl.ByteArrayParcel;
+import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel;
+import android.nearby.aidl.FastPairAccountKeyDeviceMetadataParcel;
+import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel;
+import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel;
+import android.nearby.aidl.FastPairDeviceMetadataParcel;
+import android.nearby.aidl.FastPairDiscoveryItemParcel;
+import android.nearby.aidl.FastPairEligibleAccountParcel;
+import android.nearby.aidl.FastPairEligibleAccountsRequestParcel;
+import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel;
+import android.nearby.aidl.FastPairManageAccountRequestParcel;
+import android.nearby.aidl.IFastPairAccountDevicesMetadataCallback;
+import android.nearby.aidl.IFastPairAntispoofKeyDeviceMetadataCallback;
+import android.nearby.aidl.IFastPairDataProvider;
+import android.nearby.aidl.IFastPairEligibleAccountsCallback;
+import android.nearby.aidl.IFastPairManageAccountCallback;
+import android.nearby.aidl.IFastPairManageAccountDeviceCallback;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+@RunWith(AndroidJUnit4.class)
+public class FastPairDataProviderServiceTest {
+
+ private static final String TAG = "FastPairDataProviderServiceTest";
+
+ private static final int BLE_TX_POWER = 5;
+ private static final String CONNECT_SUCCESS_COMPANION_APP_INSTALLED =
+ "CONNECT_SUCCESS_COMPANION_APP_INSTALLED";
+ private static final String CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED =
+ "CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED";
+ private static final float DELTA = 0.001f;
+ private static final int DEVICE_TYPE = 7;
+ private static final String DOWNLOAD_COMPANION_APP_DESCRIPTION =
+ "DOWNLOAD_COMPANION_APP_DESCRIPTION";
+ private static final Account ELIGIBLE_ACCOUNT_1 = new Account("abc@google.com", "type1");
+ private static final boolean ELIGIBLE_ACCOUNT_1_OPT_IN = true;
+ private static final Account ELIGIBLE_ACCOUNT_2 = new Account("def@gmail.com", "type2");
+ private static final boolean ELIGIBLE_ACCOUNT_2_OPT_IN = false;
+ private static final Account MANAGE_ACCOUNT = new Account("ghi@gmail.com", "type3");
+ private static final Account ACCOUNTDEVICES_METADATA_ACCOUNT =
+ new Account("jk@gmail.com", "type4");
+ private static final int NUM_ACCOUNT_DEVICES = 2;
+
+ private static final int ERROR_CODE_BAD_REQUEST =
+ FastPairDataProviderService.ERROR_CODE_BAD_REQUEST;
+ private static final int MANAGE_ACCOUNT_REQUEST_TYPE =
+ FastPairDataProviderService.MANAGE_REQUEST_ADD;
+ private static final String ERROR_STRING = "ERROR_STRING";
+ private static final String FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION =
+ "FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION";
+ private static final byte[] IMAGE = new byte[] {7, 9};
+ private static final String IMAGE_URL = "IMAGE_URL";
+ private static final String INITIAL_NOTIFICATION_DESCRIPTION =
+ "INITIAL_NOTIFICATION_DESCRIPTION";
+ private static final String INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT =
+ "INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT";
+ private static final String INITIAL_PAIRING_DESCRIPTION = "INITIAL_PAIRING_DESCRIPTION";
+ private static final String INTENT_URI = "INTENT_URI";
+ private static final String OPEN_COMPANION_APP_DESCRIPTION = "OPEN_COMPANION_APP_DESCRIPTION";
+ private static final String RETRO_ACTIVE_PAIRING_DESCRIPTION =
+ "RETRO_ACTIVE_PAIRING_DESCRIPTION";
+ private static final String SUBSEQUENT_PAIRING_DESCRIPTION = "SUBSEQUENT_PAIRING_DESCRIPTION";
+ private static final float TRIGGER_DISTANCE = 111;
+ private static final String TRUE_WIRELESS_IMAGE_URL_CASE = "TRUE_WIRELESS_IMAGE_URL_CASE";
+ private static final String TRUE_WIRELESS_IMAGE_URL_LEFT_BUD =
+ "TRUE_WIRELESS_IMAGE_URL_LEFT_BUD";
+ private static final String TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD =
+ "TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD";
+ private static final String UNABLE_TO_CONNECT_DESCRIPTION = "UNABLE_TO_CONNECT_DESCRIPTION";
+ private static final String UNABLE_TO_CONNECT_TITLE = "UNABLE_TO_CONNECT_TITLE";
+ private static final String UPDATE_COMPANION_APP_DESCRIPTION =
+ "UPDATE_COMPANION_APP_DESCRIPTION";
+ private static final String WAIT_LAUNCH_COMPANION_APP_DESCRIPTION =
+ "WAIT_LAUNCH_COMPANION_APP_DESCRIPTION";
+ private static final byte[] ACCOUNT_KEY = new byte[] {3};
+ private static final byte[] ACCOUNT_KEY_2 = new byte[] {9, 3};
+ private static final byte[] SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS = new byte[] {2, 8};
+ private static final byte[] REQUEST_MODEL_ID = new byte[] {1, 2, 3};
+ private static final byte[] ANTI_SPOOFING_KEY = new byte[] {4, 5, 6};
+ private static final String ACTION_URL = "ACTION_URL";
+ private static final int ACTION_URL_TYPE = 5;
+ private static final String APP_NAME = "APP_NAME";
+ private static final byte[] AUTHENTICATION_PUBLIC_KEY_SEC_P256R1 = new byte[] {5, 7};
+ private static final String DESCRIPTION = "DESCRIPTION";
+ private static final String DEVICE_NAME = "DEVICE_NAME";
+ private static final String DISPLAY_URL = "DISPLAY_URL";
+ private static final long FIRST_OBSERVATION_TIMESTAMP_MILLIS = 8393L;
+ private static final String ICON_FIFE_URL = "ICON_FIFE_URL";
+ private static final byte[] ICON_PNG = new byte[]{2, 5};
+ private static final String ID = "ID";
+ private static final long LAST_OBSERVATION_TIMESTAMP_MILLIS = 934234L;
+ private static final String MAC_ADDRESS = "MAC_ADDRESS";
+ private static final String NAME = "NAME";
+ private static final String PACKAGE_NAME = "PACKAGE_NAME";
+ private static final long PENDING_APP_INSTALL_TIMESTAMP_MILLIS = 832393L;
+ private static final int RSSI = 9;
+ private static final int STATE = 63;
+ private static final String TITLE = "TITLE";
+ private static final String TRIGGER_ID = "TRIGGER_ID";
+ private static final int TX_POWER = 62;
+
+ private static final int ELIGIBLE_ACCOUNTS_NUM = 2;
+ private static final ImmutableList<FastPairEligibleAccount> ELIGIBLE_ACCOUNTS =
+ ImmutableList.of(
+ genHappyPathFastPairEligibleAccount(ELIGIBLE_ACCOUNT_1,
+ ELIGIBLE_ACCOUNT_1_OPT_IN),
+ genHappyPathFastPairEligibleAccount(ELIGIBLE_ACCOUNT_2,
+ ELIGIBLE_ACCOUNT_2_OPT_IN));
+ private static final int ACCOUNTKEY_DEVICE_NUM = 2;
+ private static final ImmutableList<FastPairAccountKeyDeviceMetadata>
+ FAST_PAIR_ACCOUNT_DEVICES_METADATA =
+ ImmutableList.of(
+ genHappyPathFastPairAccountkeyDeviceMetadata(),
+ genHappyPathFastPairAccountkeyDeviceMetadata());
+
+ private static final FastPairAntispoofKeyDeviceMetadataRequestParcel
+ FAST_PAIR_ANTI_SPOOF_KEY_DEVICE_METADATA_REQUEST_PARCEL =
+ genFastPairAntispoofKeyDeviceMetadataRequestParcel();
+ private static final FastPairAccountDevicesMetadataRequestParcel
+ FAST_PAIR_ACCOUNT_DEVICES_METADATA_REQUEST_PARCEL =
+ genFastPairAccountDevicesMetadataRequestParcel();
+ private static final FastPairEligibleAccountsRequestParcel
+ FAST_PAIR_ELIGIBLE_ACCOUNTS_REQUEST_PARCEL =
+ genFastPairEligibleAccountsRequestParcel();
+ private static final FastPairManageAccountRequestParcel
+ FAST_PAIR_MANAGE_ACCOUNT_REQUEST_PARCEL =
+ genFastPairManageAccountRequestParcel();
+ private static final FastPairManageAccountDeviceRequestParcel
+ FAST_PAIR_MANAGE_ACCOUNT_DEVICE_REQUEST_PARCEL =
+ genFastPairManageAccountDeviceRequestParcel();
+ private static final FastPairAntispoofKeyDeviceMetadata
+ HAPPY_PATH_FAST_PAIR_ANTI_SPOOF_KEY_DEVICE_METADATA =
+ genHappyPathFastPairAntispoofKeyDeviceMetadata();
+
+ @Captor private ArgumentCaptor<FastPairEligibleAccountParcel[]>
+ mFastPairEligibleAccountParcelsArgumentCaptor;
+ @Captor private ArgumentCaptor<FastPairAccountKeyDeviceMetadataParcel[]>
+ mFastPairAccountKeyDeviceMetadataParcelsArgumentCaptor;
+
+ @Mock private FastPairDataProviderService mMockFastPairDataProviderService;
+ @Mock private IFastPairAntispoofKeyDeviceMetadataCallback.Stub
+ mAntispoofKeyDeviceMetadataCallback;
+ @Mock private IFastPairAccountDevicesMetadataCallback.Stub mAccountDevicesMetadataCallback;
+ @Mock private IFastPairEligibleAccountsCallback.Stub mEligibleAccountsCallback;
+ @Mock private IFastPairManageAccountCallback.Stub mManageAccountCallback;
+ @Mock private IFastPairManageAccountDeviceCallback.Stub mManageAccountDeviceCallback;
+
+ private MyHappyPathProvider mHappyPathFastPairDataProvider;
+ private MyErrorPathProvider mErrorPathFastPairDataProvider;
+
+ @Before
+ public void setUp() throws Exception {
+ initMocks(this);
+
+ mHappyPathFastPairDataProvider =
+ new MyHappyPathProvider(TAG, mMockFastPairDataProviderService);
+ mErrorPathFastPairDataProvider =
+ new MyErrorPathProvider(TAG, mMockFastPairDataProviderService);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testHappyPathLoadFastPairAntispoofKeyDeviceMetadata() throws Exception {
+ // AOSP sends calls to OEM via Parcelable.
+ mHappyPathFastPairDataProvider.asProvider().loadFastPairAntispoofKeyDeviceMetadata(
+ FAST_PAIR_ANTI_SPOOF_KEY_DEVICE_METADATA_REQUEST_PARCEL,
+ mAntispoofKeyDeviceMetadataCallback);
+
+ // OEM receives request and verifies that it is as expected.
+ final ArgumentCaptor<FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataRequest>
+ fastPairAntispoofKeyDeviceMetadataRequestCaptor =
+ ArgumentCaptor.forClass(
+ FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataRequest.class
+ );
+ verify(mMockFastPairDataProviderService).onLoadFastPairAntispoofKeyDeviceMetadata(
+ fastPairAntispoofKeyDeviceMetadataRequestCaptor.capture(),
+ any(FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataCallback.class));
+ ensureHappyPathAsExpected(fastPairAntispoofKeyDeviceMetadataRequestCaptor.getValue());
+
+ // AOSP receives responses and verifies that it is as expected.
+ final ArgumentCaptor<FastPairAntispoofKeyDeviceMetadataParcel>
+ fastPairAntispoofKeyDeviceMetadataParcelCaptor =
+ ArgumentCaptor.forClass(FastPairAntispoofKeyDeviceMetadataParcel.class);
+ verify(mAntispoofKeyDeviceMetadataCallback).onFastPairAntispoofKeyDeviceMetadataReceived(
+ fastPairAntispoofKeyDeviceMetadataParcelCaptor.capture());
+ ensureHappyPathAsExpected(fastPairAntispoofKeyDeviceMetadataParcelCaptor.getValue());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testHappyPathLoadFastPairAccountDevicesMetadata() throws Exception {
+ // AOSP sends calls to OEM via Parcelable.
+ mHappyPathFastPairDataProvider.asProvider().loadFastPairAccountDevicesMetadata(
+ FAST_PAIR_ACCOUNT_DEVICES_METADATA_REQUEST_PARCEL,
+ mAccountDevicesMetadataCallback);
+
+ // OEM receives request and verifies that it is as expected.
+ final ArgumentCaptor<FastPairDataProviderService.FastPairAccountDevicesMetadataRequest>
+ fastPairAccountDevicesMetadataRequestCaptor =
+ ArgumentCaptor.forClass(
+ FastPairDataProviderService.FastPairAccountDevicesMetadataRequest.class);
+ verify(mMockFastPairDataProviderService).onLoadFastPairAccountDevicesMetadata(
+ fastPairAccountDevicesMetadataRequestCaptor.capture(),
+ any(FastPairDataProviderService.FastPairAccountDevicesMetadataCallback.class));
+ ensureHappyPathAsExpected(fastPairAccountDevicesMetadataRequestCaptor.getValue());
+
+ // AOSP receives responses and verifies that it is as expected.
+ verify(mAccountDevicesMetadataCallback).onFastPairAccountDevicesMetadataReceived(
+ mFastPairAccountKeyDeviceMetadataParcelsArgumentCaptor.capture());
+ ensureHappyPathAsExpected(
+ mFastPairAccountKeyDeviceMetadataParcelsArgumentCaptor.getValue());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testHappyPathLoadFastPairEligibleAccounts() throws Exception {
+ // AOSP sends calls to OEM via Parcelable.
+ mHappyPathFastPairDataProvider.asProvider().loadFastPairEligibleAccounts(
+ FAST_PAIR_ELIGIBLE_ACCOUNTS_REQUEST_PARCEL,
+ mEligibleAccountsCallback);
+
+ // OEM receives request and verifies that it is as expected.
+ final ArgumentCaptor<FastPairDataProviderService.FastPairEligibleAccountsRequest>
+ fastPairEligibleAccountsRequestCaptor =
+ ArgumentCaptor.forClass(
+ FastPairDataProviderService.FastPairEligibleAccountsRequest.class);
+ verify(mMockFastPairDataProviderService).onLoadFastPairEligibleAccounts(
+ fastPairEligibleAccountsRequestCaptor.capture(),
+ any(FastPairDataProviderService.FastPairEligibleAccountsCallback.class));
+ ensureHappyPathAsExpected(fastPairEligibleAccountsRequestCaptor.getValue());
+
+ // AOSP receives responses and verifies that it is as expected.
+ verify(mEligibleAccountsCallback).onFastPairEligibleAccountsReceived(
+ mFastPairEligibleAccountParcelsArgumentCaptor.capture());
+ ensureHappyPathAsExpected(mFastPairEligibleAccountParcelsArgumentCaptor.getValue());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testHappyPathManageFastPairAccount() throws Exception {
+ // AOSP sends calls to OEM via Parcelable.
+ mHappyPathFastPairDataProvider.asProvider().manageFastPairAccount(
+ FAST_PAIR_MANAGE_ACCOUNT_REQUEST_PARCEL,
+ mManageAccountCallback);
+
+ // OEM receives request and verifies that it is as expected.
+ final ArgumentCaptor<FastPairDataProviderService.FastPairManageAccountRequest>
+ fastPairManageAccountRequestCaptor =
+ ArgumentCaptor.forClass(
+ FastPairDataProviderService.FastPairManageAccountRequest.class);
+ verify(mMockFastPairDataProviderService).onManageFastPairAccount(
+ fastPairManageAccountRequestCaptor.capture(),
+ any(FastPairDataProviderService.FastPairManageActionCallback.class));
+ ensureHappyPathAsExpected(fastPairManageAccountRequestCaptor.getValue());
+
+ // AOSP receives SUCCESS response.
+ verify(mManageAccountCallback).onSuccess();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testHappyPathManageFastPairAccountDevice() throws Exception {
+ // AOSP sends calls to OEM via Parcelable.
+ mHappyPathFastPairDataProvider.asProvider().manageFastPairAccountDevice(
+ FAST_PAIR_MANAGE_ACCOUNT_DEVICE_REQUEST_PARCEL,
+ mManageAccountDeviceCallback);
+
+ // OEM receives request and verifies that it is as expected.
+ final ArgumentCaptor<FastPairDataProviderService.FastPairManageAccountDeviceRequest>
+ fastPairManageAccountDeviceRequestCaptor =
+ ArgumentCaptor.forClass(
+ FastPairDataProviderService.FastPairManageAccountDeviceRequest.class);
+ verify(mMockFastPairDataProviderService).onManageFastPairAccountDevice(
+ fastPairManageAccountDeviceRequestCaptor.capture(),
+ any(FastPairDataProviderService.FastPairManageActionCallback.class));
+ ensureHappyPathAsExpected(fastPairManageAccountDeviceRequestCaptor.getValue());
+
+ // AOSP receives SUCCESS response.
+ verify(mManageAccountDeviceCallback).onSuccess();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testErrorPathLoadFastPairAntispoofKeyDeviceMetadata() throws Exception {
+ mErrorPathFastPairDataProvider.asProvider().loadFastPairAntispoofKeyDeviceMetadata(
+ FAST_PAIR_ANTI_SPOOF_KEY_DEVICE_METADATA_REQUEST_PARCEL,
+ mAntispoofKeyDeviceMetadataCallback);
+ verify(mMockFastPairDataProviderService).onLoadFastPairAntispoofKeyDeviceMetadata(
+ any(FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataRequest.class),
+ any(FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataCallback.class));
+ verify(mAntispoofKeyDeviceMetadataCallback).onError(
+ eq(ERROR_CODE_BAD_REQUEST), eq(ERROR_STRING));
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testErrorPathLoadFastPairAccountDevicesMetadata() throws Exception {
+ mErrorPathFastPairDataProvider.asProvider().loadFastPairAccountDevicesMetadata(
+ FAST_PAIR_ACCOUNT_DEVICES_METADATA_REQUEST_PARCEL,
+ mAccountDevicesMetadataCallback);
+ verify(mMockFastPairDataProviderService).onLoadFastPairAccountDevicesMetadata(
+ any(FastPairDataProviderService.FastPairAccountDevicesMetadataRequest.class),
+ any(FastPairDataProviderService.FastPairAccountDevicesMetadataCallback.class));
+ verify(mAccountDevicesMetadataCallback).onError(
+ eq(ERROR_CODE_BAD_REQUEST), eq(ERROR_STRING));
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testErrorPathLoadFastPairEligibleAccounts() throws Exception {
+ mErrorPathFastPairDataProvider.asProvider().loadFastPairEligibleAccounts(
+ FAST_PAIR_ELIGIBLE_ACCOUNTS_REQUEST_PARCEL,
+ mEligibleAccountsCallback);
+ verify(mMockFastPairDataProviderService).onLoadFastPairEligibleAccounts(
+ any(FastPairDataProviderService.FastPairEligibleAccountsRequest.class),
+ any(FastPairDataProviderService.FastPairEligibleAccountsCallback.class));
+ verify(mEligibleAccountsCallback).onError(
+ eq(ERROR_CODE_BAD_REQUEST), eq(ERROR_STRING));
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testErrorPathManageFastPairAccount() throws Exception {
+ mErrorPathFastPairDataProvider.asProvider().manageFastPairAccount(
+ FAST_PAIR_MANAGE_ACCOUNT_REQUEST_PARCEL,
+ mManageAccountCallback);
+ verify(mMockFastPairDataProviderService).onManageFastPairAccount(
+ any(FastPairDataProviderService.FastPairManageAccountRequest.class),
+ any(FastPairDataProviderService.FastPairManageActionCallback.class));
+ verify(mManageAccountCallback).onError(eq(ERROR_CODE_BAD_REQUEST), eq(ERROR_STRING));
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testErrorPathManageFastPairAccountDevice() throws Exception {
+ mErrorPathFastPairDataProvider.asProvider().manageFastPairAccountDevice(
+ FAST_PAIR_MANAGE_ACCOUNT_DEVICE_REQUEST_PARCEL,
+ mManageAccountDeviceCallback);
+ verify(mMockFastPairDataProviderService).onManageFastPairAccountDevice(
+ any(FastPairDataProviderService.FastPairManageAccountDeviceRequest.class),
+ any(FastPairDataProviderService.FastPairManageActionCallback.class));
+ verify(mManageAccountDeviceCallback).onError(eq(ERROR_CODE_BAD_REQUEST), eq(ERROR_STRING));
+ }
+
+ public static class MyHappyPathProvider extends FastPairDataProviderService {
+
+ private final FastPairDataProviderService mMockFastPairDataProviderService;
+
+ public MyHappyPathProvider(@NonNull String tag, FastPairDataProviderService mock) {
+ super(tag);
+ mMockFastPairDataProviderService = mock;
+ }
+
+ public IFastPairDataProvider asProvider() {
+ Intent intent = new Intent();
+ return IFastPairDataProvider.Stub.asInterface(onBind(intent));
+ }
+
+ @Override
+ public void onLoadFastPairAntispoofKeyDeviceMetadata(
+ @NonNull FastPairAntispoofKeyDeviceMetadataRequest request,
+ @NonNull FastPairAntispoofKeyDeviceMetadataCallback callback) {
+ mMockFastPairDataProviderService.onLoadFastPairAntispoofKeyDeviceMetadata(
+ request, callback);
+ callback.onFastPairAntispoofKeyDeviceMetadataReceived(
+ HAPPY_PATH_FAST_PAIR_ANTI_SPOOF_KEY_DEVICE_METADATA);
+ }
+
+ @Override
+ public void onLoadFastPairAccountDevicesMetadata(
+ @NonNull FastPairAccountDevicesMetadataRequest request,
+ @NonNull FastPairAccountDevicesMetadataCallback callback) {
+ mMockFastPairDataProviderService.onLoadFastPairAccountDevicesMetadata(
+ request, callback);
+ callback.onFastPairAccountDevicesMetadataReceived(FAST_PAIR_ACCOUNT_DEVICES_METADATA);
+ }
+
+ @Override
+ public void onLoadFastPairEligibleAccounts(
+ @NonNull FastPairEligibleAccountsRequest request,
+ @NonNull FastPairEligibleAccountsCallback callback) {
+ mMockFastPairDataProviderService.onLoadFastPairEligibleAccounts(
+ request, callback);
+ callback.onFastPairEligibleAccountsReceived(ELIGIBLE_ACCOUNTS);
+ }
+
+ @Override
+ public void onManageFastPairAccount(
+ @NonNull FastPairManageAccountRequest request,
+ @NonNull FastPairManageActionCallback callback) {
+ mMockFastPairDataProviderService.onManageFastPairAccount(request, callback);
+ callback.onSuccess();
+ }
+
+ @Override
+ public void onManageFastPairAccountDevice(
+ @NonNull FastPairManageAccountDeviceRequest request,
+ @NonNull FastPairManageActionCallback callback) {
+ mMockFastPairDataProviderService.onManageFastPairAccountDevice(request, callback);
+ callback.onSuccess();
+ }
+ }
+
+ public static class MyErrorPathProvider extends FastPairDataProviderService {
+
+ private final FastPairDataProviderService mMockFastPairDataProviderService;
+
+ public MyErrorPathProvider(@NonNull String tag, FastPairDataProviderService mock) {
+ super(tag);
+ mMockFastPairDataProviderService = mock;
+ }
+
+ public IFastPairDataProvider asProvider() {
+ Intent intent = new Intent();
+ return IFastPairDataProvider.Stub.asInterface(onBind(intent));
+ }
+
+ @Override
+ public void onLoadFastPairAntispoofKeyDeviceMetadata(
+ @NonNull FastPairAntispoofKeyDeviceMetadataRequest request,
+ @NonNull FastPairAntispoofKeyDeviceMetadataCallback callback) {
+ mMockFastPairDataProviderService.onLoadFastPairAntispoofKeyDeviceMetadata(
+ request, callback);
+ callback.onError(ERROR_CODE_BAD_REQUEST, ERROR_STRING);
+ }
+
+ @Override
+ public void onLoadFastPairAccountDevicesMetadata(
+ @NonNull FastPairAccountDevicesMetadataRequest request,
+ @NonNull FastPairAccountDevicesMetadataCallback callback) {
+ mMockFastPairDataProviderService.onLoadFastPairAccountDevicesMetadata(
+ request, callback);
+ callback.onError(ERROR_CODE_BAD_REQUEST, ERROR_STRING);
+ }
+
+ @Override
+ public void onLoadFastPairEligibleAccounts(
+ @NonNull FastPairEligibleAccountsRequest request,
+ @NonNull FastPairEligibleAccountsCallback callback) {
+ mMockFastPairDataProviderService.onLoadFastPairEligibleAccounts(request, callback);
+ callback.onError(ERROR_CODE_BAD_REQUEST, ERROR_STRING);
+ }
+
+ @Override
+ public void onManageFastPairAccount(
+ @NonNull FastPairManageAccountRequest request,
+ @NonNull FastPairManageActionCallback callback) {
+ mMockFastPairDataProviderService.onManageFastPairAccount(request, callback);
+ callback.onError(ERROR_CODE_BAD_REQUEST, ERROR_STRING);
+ }
+
+ @Override
+ public void onManageFastPairAccountDevice(
+ @NonNull FastPairManageAccountDeviceRequest request,
+ @NonNull FastPairManageActionCallback callback) {
+ mMockFastPairDataProviderService.onManageFastPairAccountDevice(request, callback);
+ callback.onError(ERROR_CODE_BAD_REQUEST, ERROR_STRING);
+ }
+ }
+
+ /* Generates AntispoofKeyDeviceMetadataRequestParcel. */
+ private static FastPairAntispoofKeyDeviceMetadataRequestParcel
+ genFastPairAntispoofKeyDeviceMetadataRequestParcel() {
+ FastPairAntispoofKeyDeviceMetadataRequestParcel requestParcel =
+ new FastPairAntispoofKeyDeviceMetadataRequestParcel();
+ requestParcel.modelId = REQUEST_MODEL_ID;
+
+ return requestParcel;
+ }
+
+ /* Generates AccountDevicesMetadataRequestParcel. */
+ private static FastPairAccountDevicesMetadataRequestParcel
+ genFastPairAccountDevicesMetadataRequestParcel() {
+ FastPairAccountDevicesMetadataRequestParcel requestParcel =
+ new FastPairAccountDevicesMetadataRequestParcel();
+
+ requestParcel.account = ACCOUNTDEVICES_METADATA_ACCOUNT;
+ requestParcel.deviceAccountKeys = new ByteArrayParcel[NUM_ACCOUNT_DEVICES];
+ requestParcel.deviceAccountKeys[0] = new ByteArrayParcel();
+ requestParcel.deviceAccountKeys[1] = new ByteArrayParcel();
+ requestParcel.deviceAccountKeys[0].byteArray = ACCOUNT_KEY;
+ requestParcel.deviceAccountKeys[1].byteArray = ACCOUNT_KEY_2;
+
+ return requestParcel;
+ }
+
+ /* Generates FastPairEligibleAccountsRequestParcel. */
+ private static FastPairEligibleAccountsRequestParcel
+ genFastPairEligibleAccountsRequestParcel() {
+ FastPairEligibleAccountsRequestParcel requestParcel =
+ new FastPairEligibleAccountsRequestParcel();
+ // No fields since FastPairEligibleAccountsRequestParcel is just a place holder now.
+ return requestParcel;
+ }
+
+ /* Generates FastPairManageAccountRequestParcel. */
+ private static FastPairManageAccountRequestParcel
+ genFastPairManageAccountRequestParcel() {
+ FastPairManageAccountRequestParcel requestParcel =
+ new FastPairManageAccountRequestParcel();
+ requestParcel.account = MANAGE_ACCOUNT;
+ requestParcel.requestType = MANAGE_ACCOUNT_REQUEST_TYPE;
+
+ return requestParcel;
+ }
+
+ /* Generates FastPairManageAccountDeviceRequestParcel. */
+ private static FastPairManageAccountDeviceRequestParcel
+ genFastPairManageAccountDeviceRequestParcel() {
+ FastPairManageAccountDeviceRequestParcel requestParcel =
+ new FastPairManageAccountDeviceRequestParcel();
+ requestParcel.account = MANAGE_ACCOUNT;
+ requestParcel.requestType = MANAGE_ACCOUNT_REQUEST_TYPE;
+ requestParcel.accountKeyDeviceMetadata =
+ genHappyPathFastPairAccountkeyDeviceMetadataParcel();
+
+ return requestParcel;
+ }
+
+ /* Generates Happy Path AntispoofKeyDeviceMetadata. */
+ private static FastPairAntispoofKeyDeviceMetadata
+ genHappyPathFastPairAntispoofKeyDeviceMetadata() {
+ FastPairAntispoofKeyDeviceMetadata.Builder builder =
+ new FastPairAntispoofKeyDeviceMetadata.Builder();
+ builder.setAntispoofPublicKey(ANTI_SPOOFING_KEY);
+ builder.setFastPairDeviceMetadata(genHappyPathFastPairDeviceMetadata());
+
+ return builder.build();
+ }
+
+ /* Generates Happy Path FastPairAccountKeyDeviceMetadata. */
+ private static FastPairAccountKeyDeviceMetadata
+ genHappyPathFastPairAccountkeyDeviceMetadata() {
+ FastPairAccountKeyDeviceMetadata.Builder builder =
+ new FastPairAccountKeyDeviceMetadata.Builder();
+ builder.setDeviceAccountKey(ACCOUNT_KEY);
+ builder.setFastPairDeviceMetadata(genHappyPathFastPairDeviceMetadata());
+ builder.setSha256DeviceAccountKeyPublicAddress(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS);
+ builder.setFastPairDiscoveryItem(genHappyPathFastPairDiscoveryItem());
+
+ return builder.build();
+ }
+
+ /* Generates Happy Path FastPairAccountKeyDeviceMetadataParcel. */
+ private static FastPairAccountKeyDeviceMetadataParcel
+ genHappyPathFastPairAccountkeyDeviceMetadataParcel() {
+ FastPairAccountKeyDeviceMetadataParcel parcel =
+ new FastPairAccountKeyDeviceMetadataParcel();
+ parcel.deviceAccountKey = ACCOUNT_KEY;
+ parcel.metadata = genHappyPathFastPairDeviceMetadataParcel();
+ parcel.sha256DeviceAccountKeyPublicAddress = SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS;
+ parcel.discoveryItem = genHappyPathFastPairDiscoveryItemParcel();
+
+ return parcel;
+ }
+
+ /* Generates Happy Path DiscoveryItem. */
+ private static FastPairDiscoveryItem genHappyPathFastPairDiscoveryItem() {
+ FastPairDiscoveryItem.Builder builder = new FastPairDiscoveryItem.Builder();
+
+ builder.setActionUrl(ACTION_URL);
+ builder.setActionUrlType(ACTION_URL_TYPE);
+ builder.setAppName(APP_NAME);
+ builder.setAuthenticationPublicKeySecp256r1(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1);
+ builder.setDescription(DESCRIPTION);
+ builder.setDeviceName(DEVICE_NAME);
+ builder.setDisplayUrl(DISPLAY_URL);
+ builder.setFirstObservationTimestampMillis(FIRST_OBSERVATION_TIMESTAMP_MILLIS);
+ builder.setIconFfeUrl(ICON_FIFE_URL);
+ builder.setIconPng(ICON_PNG);
+ builder.setId(ID);
+ builder.setLastObservationTimestampMillis(LAST_OBSERVATION_TIMESTAMP_MILLIS);
+ builder.setMacAddress(MAC_ADDRESS);
+ builder.setPackageName(PACKAGE_NAME);
+ builder.setPendingAppInstallTimestampMillis(PENDING_APP_INSTALL_TIMESTAMP_MILLIS);
+ builder.setRssi(RSSI);
+ builder.setState(STATE);
+ builder.setTitle(TITLE);
+ builder.setTriggerId(TRIGGER_ID);
+ builder.setTxPower(TX_POWER);
+
+ return builder.build();
+ }
+
+ /* Generates Happy Path DiscoveryItemParcel. */
+ private static FastPairDiscoveryItemParcel genHappyPathFastPairDiscoveryItemParcel() {
+ FastPairDiscoveryItemParcel parcel = new FastPairDiscoveryItemParcel();
+
+ parcel.actionUrl = ACTION_URL;
+ parcel.actionUrlType = ACTION_URL_TYPE;
+ parcel.appName = APP_NAME;
+ parcel.authenticationPublicKeySecp256r1 = AUTHENTICATION_PUBLIC_KEY_SEC_P256R1;
+ parcel.description = DESCRIPTION;
+ parcel.deviceName = DEVICE_NAME;
+ parcel.displayUrl = DISPLAY_URL;
+ parcel.firstObservationTimestampMillis = FIRST_OBSERVATION_TIMESTAMP_MILLIS;
+ parcel.iconFifeUrl = ICON_FIFE_URL;
+ parcel.iconPng = ICON_PNG;
+ parcel.id = ID;
+ parcel.lastObservationTimestampMillis = LAST_OBSERVATION_TIMESTAMP_MILLIS;
+ parcel.macAddress = MAC_ADDRESS;
+ parcel.packageName = PACKAGE_NAME;
+ parcel.pendingAppInstallTimestampMillis = PENDING_APP_INSTALL_TIMESTAMP_MILLIS;
+ parcel.rssi = RSSI;
+ parcel.state = STATE;
+ parcel.title = TITLE;
+ parcel.triggerId = TRIGGER_ID;
+ parcel.txPower = TX_POWER;
+
+ return parcel;
+ }
+
+ /* Generates Happy Path DeviceMetadata. */
+ private static FastPairDeviceMetadata genHappyPathFastPairDeviceMetadata() {
+ FastPairDeviceMetadata.Builder builder = new FastPairDeviceMetadata.Builder();
+ builder.setBleTxPower(BLE_TX_POWER);
+ builder.setConnectSuccessCompanionAppInstalled(CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+ builder.setConnectSuccessCompanionAppNotInstalled(
+ CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+ builder.setDeviceType(DEVICE_TYPE);
+ builder.setDownloadCompanionAppDescription(DOWNLOAD_COMPANION_APP_DESCRIPTION);
+ builder.setFailConnectGoToSettingsDescription(FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+ builder.setImage(IMAGE);
+ builder.setImageUrl(IMAGE_URL);
+ builder.setInitialNotificationDescription(INITIAL_NOTIFICATION_DESCRIPTION);
+ builder.setInitialNotificationDescriptionNoAccount(
+ INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+ builder.setInitialPairingDescription(INITIAL_PAIRING_DESCRIPTION);
+ builder.setIntentUri(INTENT_URI);
+ builder.setName(NAME);
+ builder.setOpenCompanionAppDescription(OPEN_COMPANION_APP_DESCRIPTION);
+ builder.setRetroactivePairingDescription(RETRO_ACTIVE_PAIRING_DESCRIPTION);
+ builder.setSubsequentPairingDescription(SUBSEQUENT_PAIRING_DESCRIPTION);
+ builder.setTriggerDistance(TRIGGER_DISTANCE);
+ builder.setTrueWirelessImageUrlCase(TRUE_WIRELESS_IMAGE_URL_CASE);
+ builder.setTrueWirelessImageUrlLeftBud(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+ builder.setTrueWirelessImageUrlRightBud(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+ builder.setUnableToConnectDescription(UNABLE_TO_CONNECT_DESCRIPTION);
+ builder.setUnableToConnectTitle(UNABLE_TO_CONNECT_TITLE);
+ builder.setUpdateCompanionAppDescription(UPDATE_COMPANION_APP_DESCRIPTION);
+ builder.setWaitLaunchCompanionAppDescription(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+
+ return builder.build();
+ }
+
+ /* Generates Happy Path DeviceMetadataParcel. */
+ private static FastPairDeviceMetadataParcel genHappyPathFastPairDeviceMetadataParcel() {
+ FastPairDeviceMetadataParcel parcel = new FastPairDeviceMetadataParcel();
+
+ parcel.bleTxPower = BLE_TX_POWER;
+ parcel.connectSuccessCompanionAppInstalled = CONNECT_SUCCESS_COMPANION_APP_INSTALLED;
+ parcel.connectSuccessCompanionAppNotInstalled =
+ CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED;
+ parcel.deviceType = DEVICE_TYPE;
+ parcel.downloadCompanionAppDescription = DOWNLOAD_COMPANION_APP_DESCRIPTION;
+ parcel.failConnectGoToSettingsDescription = FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION;
+ parcel.image = IMAGE;
+ parcel.imageUrl = IMAGE_URL;
+ parcel.initialNotificationDescription = INITIAL_NOTIFICATION_DESCRIPTION;
+ parcel.initialNotificationDescriptionNoAccount =
+ INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT;
+ parcel.initialPairingDescription = INITIAL_PAIRING_DESCRIPTION;
+ parcel.intentUri = INTENT_URI;
+ parcel.name = NAME;
+ parcel.openCompanionAppDescription = OPEN_COMPANION_APP_DESCRIPTION;
+ parcel.retroactivePairingDescription = RETRO_ACTIVE_PAIRING_DESCRIPTION;
+ parcel.subsequentPairingDescription = SUBSEQUENT_PAIRING_DESCRIPTION;
+ parcel.triggerDistance = TRIGGER_DISTANCE;
+ parcel.trueWirelessImageUrlCase = TRUE_WIRELESS_IMAGE_URL_CASE;
+ parcel.trueWirelessImageUrlLeftBud = TRUE_WIRELESS_IMAGE_URL_LEFT_BUD;
+ parcel.trueWirelessImageUrlRightBud = TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD;
+ parcel.unableToConnectDescription = UNABLE_TO_CONNECT_DESCRIPTION;
+ parcel.unableToConnectTitle = UNABLE_TO_CONNECT_TITLE;
+ parcel.updateCompanionAppDescription = UPDATE_COMPANION_APP_DESCRIPTION;
+ parcel.waitLaunchCompanionAppDescription = WAIT_LAUNCH_COMPANION_APP_DESCRIPTION;
+
+ return parcel;
+ }
+
+ /* Generates Happy Path FastPairEligibleAccount. */
+ private static FastPairEligibleAccount genHappyPathFastPairEligibleAccount(
+ Account account, boolean optIn) {
+ FastPairEligibleAccount.Builder builder = new FastPairEligibleAccount.Builder();
+ builder.setAccount(account);
+ builder.setOptIn(optIn);
+
+ return builder.build();
+ }
+
+ /* Verifies Happy Path AntispoofKeyDeviceMetadataRequest. */
+ private static void ensureHappyPathAsExpected(
+ FastPairDataProviderService.FastPairAntispoofKeyDeviceMetadataRequest request) {
+ assertThat(request.getModelId()).isEqualTo(REQUEST_MODEL_ID);
+ }
+
+ /* Verifies Happy Path AccountDevicesMetadataRequest. */
+ private static void ensureHappyPathAsExpected(
+ FastPairDataProviderService.FastPairAccountDevicesMetadataRequest request) {
+ assertThat(request.getAccount()).isEqualTo(ACCOUNTDEVICES_METADATA_ACCOUNT);
+ assertThat(request.getDeviceAccountKeys().size()).isEqualTo(ACCOUNTKEY_DEVICE_NUM);
+ assertThat(request.getDeviceAccountKeys()).contains(ACCOUNT_KEY);
+ assertThat(request.getDeviceAccountKeys()).contains(ACCOUNT_KEY_2);
+ }
+
+ /* Verifies Happy Path FastPairEligibleAccountsRequest. */
+ @SuppressWarnings("UnusedVariable")
+ private static void ensureHappyPathAsExpected(
+ FastPairDataProviderService.FastPairEligibleAccountsRequest request) {
+ // No fields since FastPairEligibleAccountsRequest is just a place holder now.
+ }
+
+ /* Verifies Happy Path FastPairManageAccountRequest. */
+ private static void ensureHappyPathAsExpected(
+ FastPairDataProviderService.FastPairManageAccountRequest request) {
+ assertThat(request.getAccount()).isEqualTo(MANAGE_ACCOUNT);
+ assertThat(request.getRequestType()).isEqualTo(MANAGE_ACCOUNT_REQUEST_TYPE);
+ }
+
+ /* Verifies Happy Path FastPairManageAccountDeviceRequest. */
+ private static void ensureHappyPathAsExpected(
+ FastPairDataProviderService.FastPairManageAccountDeviceRequest request) {
+ assertThat(request.getAccount()).isEqualTo(MANAGE_ACCOUNT);
+ assertThat(request.getRequestType()).isEqualTo(MANAGE_ACCOUNT_REQUEST_TYPE);
+ ensureHappyPathAsExpected(request.getAccountKeyDeviceMetadata());
+ }
+
+ /* Verifies Happy Path AntispoofKeyDeviceMetadataParcel. */
+ private static void ensureHappyPathAsExpected(
+ FastPairAntispoofKeyDeviceMetadataParcel metadataParcel) {
+ assertThat(metadataParcel).isNotNull();
+ assertThat(metadataParcel.antispoofPublicKey).isEqualTo(ANTI_SPOOFING_KEY);
+ ensureHappyPathAsExpected(metadataParcel.deviceMetadata);
+ }
+
+ /* Verifies Happy Path FastPairAccountKeyDeviceMetadataParcel[]. */
+ private static void ensureHappyPathAsExpected(
+ FastPairAccountKeyDeviceMetadataParcel[] metadataParcels) {
+ assertThat(metadataParcels).isNotNull();
+ assertThat(metadataParcels).hasLength(ACCOUNTKEY_DEVICE_NUM);
+ for (FastPairAccountKeyDeviceMetadataParcel parcel: metadataParcels) {
+ ensureHappyPathAsExpected(parcel);
+ }
+ }
+
+ /* Verifies Happy Path FastPairAccountKeyDeviceMetadataParcel. */
+ private static void ensureHappyPathAsExpected(
+ FastPairAccountKeyDeviceMetadataParcel metadataParcel) {
+ assertThat(metadataParcel).isNotNull();
+ assertThat(metadataParcel.deviceAccountKey).isEqualTo(ACCOUNT_KEY);
+ assertThat(metadataParcel.sha256DeviceAccountKeyPublicAddress)
+ .isEqualTo(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS);
+ ensureHappyPathAsExpected(metadataParcel.metadata);
+ ensureHappyPathAsExpected(metadataParcel.discoveryItem);
+ }
+
+ /* Verifies Happy Path FastPairAccountKeyDeviceMetadata. */
+ private static void ensureHappyPathAsExpected(
+ FastPairAccountKeyDeviceMetadata metadata) {
+ assertThat(metadata.getDeviceAccountKey()).isEqualTo(ACCOUNT_KEY);
+ assertThat(metadata.getSha256DeviceAccountKeyPublicAddress())
+ .isEqualTo(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS);
+ ensureHappyPathAsExpected(metadata.getFastPairDeviceMetadata());
+ ensureHappyPathAsExpected(metadata.getFastPairDiscoveryItem());
+ }
+
+ /* Verifies Happy Path DeviceMetadataParcel. */
+ private static void ensureHappyPathAsExpected(FastPairDeviceMetadataParcel metadataParcel) {
+ assertThat(metadataParcel).isNotNull();
+ assertThat(metadataParcel.bleTxPower).isEqualTo(BLE_TX_POWER);
+
+ assertThat(metadataParcel.connectSuccessCompanionAppInstalled).isEqualTo(
+ CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+ assertThat(metadataParcel.connectSuccessCompanionAppNotInstalled).isEqualTo(
+ CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+
+ assertThat(metadataParcel.deviceType).isEqualTo(DEVICE_TYPE);
+ assertThat(metadataParcel.downloadCompanionAppDescription).isEqualTo(
+ DOWNLOAD_COMPANION_APP_DESCRIPTION);
+
+ assertThat(metadataParcel.failConnectGoToSettingsDescription).isEqualTo(
+ FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+
+ assertThat(metadataParcel.image).isEqualTo(IMAGE);
+ assertThat(metadataParcel.imageUrl).isEqualTo(IMAGE_URL);
+ assertThat(metadataParcel.initialNotificationDescription).isEqualTo(
+ INITIAL_NOTIFICATION_DESCRIPTION);
+ assertThat(metadataParcel.initialNotificationDescriptionNoAccount).isEqualTo(
+ INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+ assertThat(metadataParcel.initialPairingDescription).isEqualTo(INITIAL_PAIRING_DESCRIPTION);
+ assertThat(metadataParcel.intentUri).isEqualTo(INTENT_URI);
+
+ assertThat(metadataParcel.name).isEqualTo(NAME);
+
+ assertThat(metadataParcel.openCompanionAppDescription).isEqualTo(
+ OPEN_COMPANION_APP_DESCRIPTION);
+
+ assertThat(metadataParcel.retroactivePairingDescription).isEqualTo(
+ RETRO_ACTIVE_PAIRING_DESCRIPTION);
+
+ assertThat(metadataParcel.subsequentPairingDescription).isEqualTo(
+ SUBSEQUENT_PAIRING_DESCRIPTION);
+
+ assertThat(metadataParcel.triggerDistance).isWithin(DELTA).of(TRIGGER_DISTANCE);
+ assertThat(metadataParcel.trueWirelessImageUrlCase).isEqualTo(TRUE_WIRELESS_IMAGE_URL_CASE);
+ assertThat(metadataParcel.trueWirelessImageUrlLeftBud).isEqualTo(
+ TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+ assertThat(metadataParcel.trueWirelessImageUrlRightBud).isEqualTo(
+ TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+
+ assertThat(metadataParcel.unableToConnectDescription).isEqualTo(
+ UNABLE_TO_CONNECT_DESCRIPTION);
+ assertThat(metadataParcel.unableToConnectTitle).isEqualTo(UNABLE_TO_CONNECT_TITLE);
+ assertThat(metadataParcel.updateCompanionAppDescription).isEqualTo(
+ UPDATE_COMPANION_APP_DESCRIPTION);
+
+ assertThat(metadataParcel.waitLaunchCompanionAppDescription).isEqualTo(
+ WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+ }
+
+ /* Verifies Happy Path DeviceMetadata. */
+ private static void ensureHappyPathAsExpected(FastPairDeviceMetadata metadata) {
+ assertThat(metadata.getBleTxPower()).isEqualTo(BLE_TX_POWER);
+ assertThat(metadata.getConnectSuccessCompanionAppInstalled())
+ .isEqualTo(CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+ assertThat(metadata.getConnectSuccessCompanionAppNotInstalled())
+ .isEqualTo(CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+ assertThat(metadata.getDeviceType()).isEqualTo(DEVICE_TYPE);
+ assertThat(metadata.getDownloadCompanionAppDescription())
+ .isEqualTo(DOWNLOAD_COMPANION_APP_DESCRIPTION);
+ assertThat(metadata.getFailConnectGoToSettingsDescription())
+ .isEqualTo(FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+ assertThat(metadata.getImage()).isEqualTo(IMAGE);
+ assertThat(metadata.getImageUrl()).isEqualTo(IMAGE_URL);
+ assertThat(metadata.getInitialNotificationDescription())
+ .isEqualTo(INITIAL_NOTIFICATION_DESCRIPTION);
+ assertThat(metadata.getInitialNotificationDescriptionNoAccount())
+ .isEqualTo(INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+ assertThat(metadata.getInitialPairingDescription()).isEqualTo(INITIAL_PAIRING_DESCRIPTION);
+ assertThat(metadata.getIntentUri()).isEqualTo(INTENT_URI);
+ assertThat(metadata.getName()).isEqualTo(NAME);
+ assertThat(metadata.getOpenCompanionAppDescription())
+ .isEqualTo(OPEN_COMPANION_APP_DESCRIPTION);
+ assertThat(metadata.getRetroactivePairingDescription())
+ .isEqualTo(RETRO_ACTIVE_PAIRING_DESCRIPTION);
+ assertThat(metadata.getSubsequentPairingDescription())
+ .isEqualTo(SUBSEQUENT_PAIRING_DESCRIPTION);
+ assertThat(metadata.getTriggerDistance()).isWithin(DELTA).of(TRIGGER_DISTANCE);
+ assertThat(metadata.getTrueWirelessImageUrlCase()).isEqualTo(TRUE_WIRELESS_IMAGE_URL_CASE);
+ assertThat(metadata.getTrueWirelessImageUrlLeftBud())
+ .isEqualTo(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+ assertThat(metadata.getTrueWirelessImageUrlRightBud())
+ .isEqualTo(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+ assertThat(metadata.getUnableToConnectDescription())
+ .isEqualTo(UNABLE_TO_CONNECT_DESCRIPTION);
+ assertThat(metadata.getUnableToConnectTitle()).isEqualTo(UNABLE_TO_CONNECT_TITLE);
+ assertThat(metadata.getUpdateCompanionAppDescription())
+ .isEqualTo(UPDATE_COMPANION_APP_DESCRIPTION);
+ assertThat(metadata.getWaitLaunchCompanionAppDescription())
+ .isEqualTo(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+ }
+
+ /* Verifies Happy Path FastPairDiscoveryItemParcel. */
+ private static void ensureHappyPathAsExpected(FastPairDiscoveryItemParcel itemParcel) {
+ assertThat(itemParcel.actionUrl).isEqualTo(ACTION_URL);
+ assertThat(itemParcel.actionUrlType).isEqualTo(ACTION_URL_TYPE);
+ assertThat(itemParcel.appName).isEqualTo(APP_NAME);
+ assertThat(itemParcel.authenticationPublicKeySecp256r1)
+ .isEqualTo(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1);
+ assertThat(itemParcel.description).isEqualTo(DESCRIPTION);
+ assertThat(itemParcel.deviceName).isEqualTo(DEVICE_NAME);
+ assertThat(itemParcel.displayUrl).isEqualTo(DISPLAY_URL);
+ assertThat(itemParcel.firstObservationTimestampMillis)
+ .isEqualTo(FIRST_OBSERVATION_TIMESTAMP_MILLIS);
+ assertThat(itemParcel.iconFifeUrl).isEqualTo(ICON_FIFE_URL);
+ assertThat(itemParcel.iconPng).isEqualTo(ICON_PNG);
+ assertThat(itemParcel.id).isEqualTo(ID);
+ assertThat(itemParcel.lastObservationTimestampMillis)
+ .isEqualTo(LAST_OBSERVATION_TIMESTAMP_MILLIS);
+ assertThat(itemParcel.macAddress).isEqualTo(MAC_ADDRESS);
+ assertThat(itemParcel.packageName).isEqualTo(PACKAGE_NAME);
+ assertThat(itemParcel.pendingAppInstallTimestampMillis)
+ .isEqualTo(PENDING_APP_INSTALL_TIMESTAMP_MILLIS);
+ assertThat(itemParcel.rssi).isEqualTo(RSSI);
+ assertThat(itemParcel.state).isEqualTo(STATE);
+ assertThat(itemParcel.title).isEqualTo(TITLE);
+ assertThat(itemParcel.triggerId).isEqualTo(TRIGGER_ID);
+ assertThat(itemParcel.txPower).isEqualTo(TX_POWER);
+ }
+
+ /* Verifies Happy Path FastPairDiscoveryItem. */
+ private static void ensureHappyPathAsExpected(FastPairDiscoveryItem item) {
+ assertThat(item.getActionUrl()).isEqualTo(ACTION_URL);
+ assertThat(item.getActionUrlType()).isEqualTo(ACTION_URL_TYPE);
+ assertThat(item.getAppName()).isEqualTo(APP_NAME);
+ assertThat(item.getAuthenticationPublicKeySecp256r1())
+ .isEqualTo(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1);
+ assertThat(item.getDescription()).isEqualTo(DESCRIPTION);
+ assertThat(item.getDeviceName()).isEqualTo(DEVICE_NAME);
+ assertThat(item.getDisplayUrl()).isEqualTo(DISPLAY_URL);
+ assertThat(item.getFirstObservationTimestampMillis())
+ .isEqualTo(FIRST_OBSERVATION_TIMESTAMP_MILLIS);
+ assertThat(item.getIconFfeUrl()).isEqualTo(ICON_FIFE_URL);
+ assertThat(item.getIconPng()).isEqualTo(ICON_PNG);
+ assertThat(item.getId()).isEqualTo(ID);
+ assertThat(item.getLastObservationTimestampMillis())
+ .isEqualTo(LAST_OBSERVATION_TIMESTAMP_MILLIS);
+ assertThat(item.getMacAddress()).isEqualTo(MAC_ADDRESS);
+ assertThat(item.getPackageName()).isEqualTo(PACKAGE_NAME);
+ assertThat(item.getPendingAppInstallTimestampMillis())
+ .isEqualTo(PENDING_APP_INSTALL_TIMESTAMP_MILLIS);
+ assertThat(item.getRssi()).isEqualTo(RSSI);
+ assertThat(item.getState()).isEqualTo(STATE);
+ assertThat(item.getTitle()).isEqualTo(TITLE);
+ assertThat(item.getTriggerId()).isEqualTo(TRIGGER_ID);
+ assertThat(item.getTxPower()).isEqualTo(TX_POWER);
+ }
+
+ /* Verifies Happy Path EligibleAccountParcel[]. */
+ private static void ensureHappyPathAsExpected(FastPairEligibleAccountParcel[] accountsParcel) {
+ assertThat(accountsParcel).hasLength(ELIGIBLE_ACCOUNTS_NUM);
+
+ assertThat(accountsParcel[0].account).isEqualTo(ELIGIBLE_ACCOUNT_1);
+ assertThat(accountsParcel[0].optIn).isEqualTo(ELIGIBLE_ACCOUNT_1_OPT_IN);
+
+ assertThat(accountsParcel[1].account).isEqualTo(ELIGIBLE_ACCOUNT_2);
+ assertThat(accountsParcel[1].optIn).isEqualTo(ELIGIBLE_ACCOUNT_2_OPT_IN);
+ }
+}
diff --git a/nearby/tests/unit/src/android/nearby/FastPairDeviceTest.java b/nearby/tests/unit/src/android/nearby/FastPairDeviceTest.java
new file mode 100644
index 0000000..1d7e8ac
--- /dev/null
+++ b/nearby/tests/unit/src/android/nearby/FastPairDeviceTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nearby;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FastPairDeviceTest {
+ private static final String NAME = "name";
+ private static final byte[] DATA = new byte[] {0x01, 0x02};
+ private static final String MODEL_ID = "112233";
+ private static final int RSSI = -80;
+ private static final int TX_POWER = -10;
+ private static final String MAC_ADDRESS = "00:11:22:33:44:55";
+ private static List<Integer> sMediums = new ArrayList<Integer>(List.of(1));
+ private static FastPairDevice sDevice;
+
+
+ @Before
+ public void setup() {
+ sDevice = new FastPairDevice(NAME, sMediums, RSSI, TX_POWER, MODEL_ID, MAC_ADDRESS, DATA);
+ }
+
+ @Test
+ public void testParcelable() {
+ Parcel dest = Parcel.obtain();
+ sDevice.writeToParcel(dest, 0);
+ dest.setDataPosition(0);
+ FastPairDevice compareDevice = FastPairDevice.CREATOR.createFromParcel(dest);
+ assertThat(compareDevice.getName()).isEqualTo(NAME);
+ assertThat(compareDevice.getMediums()).isEqualTo(sMediums);
+ assertThat(compareDevice.getRssi()).isEqualTo(RSSI);
+ assertThat(compareDevice.getTxPower()).isEqualTo(TX_POWER);
+ assertThat(compareDevice.getModelId()).isEqualTo(MODEL_ID);
+ assertThat(compareDevice.getBluetoothAddress()).isEqualTo(MAC_ADDRESS);
+ assertThat(compareDevice.getData()).isEqualTo(DATA);
+ assertThat(compareDevice.equals(sDevice)).isFalse();
+ assertThat(compareDevice.hashCode()).isEqualTo(sDevice.hashCode());
+ }
+
+ @Test
+ public void describeContents() {
+ assertThat(sDevice.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void testToString() {
+ assertThat(sDevice.toString()).isEqualTo(
+ "FastPairDevice [name=name, medium={BLE} "
+ + "rssi=-80 txPower=-10 "
+ + "modelId=112233 bluetoothAddress=00:11:22:33:44:55]");
+ }
+
+ @Test
+ public void testCreatorNewArray() {
+ FastPairDevice[] fastPairDevices = FastPairDevice.CREATOR.newArray(2);
+ assertThat(fastPairDevices.length).isEqualTo(2);
+ }
+
+ @Test
+ public void testBuilder() {
+ FastPairDevice.Builder builder = new FastPairDevice.Builder();
+ FastPairDevice compareDevice = builder.setName(NAME)
+ .addMedium(1)
+ .setBluetoothAddress(MAC_ADDRESS)
+ .setRssi(RSSI)
+ .setTxPower(TX_POWER)
+ .setData(DATA)
+ .setModelId(MODEL_ID)
+ .build();
+ assertThat(compareDevice.getName()).isEqualTo(NAME);
+ assertThat(compareDevice.getMediums()).isEqualTo(sMediums);
+ assertThat(compareDevice.getRssi()).isEqualTo(RSSI);
+ assertThat(compareDevice.getTxPower()).isEqualTo(TX_POWER);
+ assertThat(compareDevice.getModelId()).isEqualTo(MODEL_ID);
+ assertThat(compareDevice.getBluetoothAddress()).isEqualTo(MAC_ADDRESS);
+ assertThat(compareDevice.getData()).isEqualTo(DATA);
+ }
+}
diff --git a/nearby/tests/unit/src/android/nearby/FastPairEligibleAccountTest.java b/nearby/tests/unit/src/android/nearby/FastPairEligibleAccountTest.java
new file mode 100644
index 0000000..da5a518
--- /dev/null
+++ b/nearby/tests/unit/src/android/nearby/FastPairEligibleAccountTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nearby;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.accounts.Account;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class FastPairEligibleAccountTest {
+
+ private static final Account ACCOUNT = new Account("abc@google.com", "type1");
+ private static final Account ACCOUNT_NULL = null;
+
+ private static final boolean OPT_IN_TRUE = true;
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testSetGetFastPairEligibleAccountNotNull() {
+ FastPairEligibleAccount eligibleAccount =
+ genFastPairEligibleAccount(ACCOUNT, OPT_IN_TRUE);
+
+ assertThat(eligibleAccount.getAccount()).isEqualTo(ACCOUNT);
+ assertThat(eligibleAccount.isOptIn()).isEqualTo(OPT_IN_TRUE);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testSetGetFastPairEligibleAccountNull() {
+ FastPairEligibleAccount eligibleAccount =
+ genFastPairEligibleAccount(ACCOUNT_NULL, OPT_IN_TRUE);
+
+ assertThat(eligibleAccount.getAccount()).isEqualTo(ACCOUNT_NULL);
+ assertThat(eligibleAccount.isOptIn()).isEqualTo(OPT_IN_TRUE);
+ }
+
+ /* Generates FastPairEligibleAccount. */
+ private static FastPairEligibleAccount genFastPairEligibleAccount(
+ Account account, boolean optIn) {
+ FastPairEligibleAccount.Builder builder = new FastPairEligibleAccount.Builder();
+ builder.setAccount(account);
+ builder.setOptIn(optIn);
+
+ return builder.build();
+ }
+}
diff --git a/nearby/tests/unit/src/android/nearby/PairStatusMetadataTest.java b/nearby/tests/unit/src/android/nearby/PairStatusMetadataTest.java
new file mode 100644
index 0000000..7bc6519
--- /dev/null
+++ b/nearby/tests/unit/src/android/nearby/PairStatusMetadataTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nearby;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+
+import org.junit.Test;
+
+public class PairStatusMetadataTest {
+ private static final int UNKNOWN = 1000;
+ private static final int SUCCESS = 1001;
+ private static final int FAIL = 1002;
+ private static final int DISMISS = 1003;
+
+ @Test
+ public void statusToString() {
+ assertThat(PairStatusMetadata.statusToString(UNKNOWN)).isEqualTo("UNKNOWN");
+ assertThat(PairStatusMetadata.statusToString(SUCCESS)).isEqualTo("SUCCESS");
+ assertThat(PairStatusMetadata.statusToString(FAIL)).isEqualTo("FAIL");
+ assertThat(PairStatusMetadata.statusToString(DISMISS)).isEqualTo("DISMISS");
+ }
+
+ @Test
+ public void getStatus() {
+ PairStatusMetadata pairStatusMetadata = new PairStatusMetadata(SUCCESS);
+ assertThat(pairStatusMetadata.getStatus()).isEqualTo(1001);
+ pairStatusMetadata = new PairStatusMetadata(FAIL);
+ assertThat(pairStatusMetadata.getStatus()).isEqualTo(1002);
+ pairStatusMetadata = new PairStatusMetadata(DISMISS);
+ assertThat(pairStatusMetadata.getStatus()).isEqualTo(1003);
+ pairStatusMetadata = new PairStatusMetadata(UNKNOWN);
+ assertThat(pairStatusMetadata.getStatus()).isEqualTo(1000);
+ }
+
+ @Test
+ public void testToString() {
+ PairStatusMetadata pairStatusMetadata = new PairStatusMetadata(SUCCESS);
+ assertThat(pairStatusMetadata.toString())
+ .isEqualTo("PairStatusMetadata[ status=SUCCESS]");
+ pairStatusMetadata = new PairStatusMetadata(FAIL);
+ assertThat(pairStatusMetadata.toString())
+ .isEqualTo("PairStatusMetadata[ status=FAIL]");
+ pairStatusMetadata = new PairStatusMetadata(DISMISS);
+ assertThat(pairStatusMetadata.toString())
+ .isEqualTo("PairStatusMetadata[ status=DISMISS]");
+ pairStatusMetadata = new PairStatusMetadata(UNKNOWN);
+ assertThat(pairStatusMetadata.toString())
+ .isEqualTo("PairStatusMetadata[ status=UNKNOWN]");
+ }
+
+ @Test
+ public void testEquals() {
+ PairStatusMetadata pairStatusMetadata = new PairStatusMetadata(SUCCESS);
+ PairStatusMetadata pairStatusMetadata1 = new PairStatusMetadata(SUCCESS);
+ PairStatusMetadata pairStatusMetadata2 = new PairStatusMetadata(UNKNOWN);
+ assertThat(pairStatusMetadata.equals(pairStatusMetadata1)).isTrue();
+ assertThat(pairStatusMetadata.equals(pairStatusMetadata2)).isFalse();
+ assertThat(pairStatusMetadata.hashCode()).isEqualTo(pairStatusMetadata1.hashCode());
+ }
+
+ @Test
+ public void testParcelable() {
+ PairStatusMetadata pairStatusMetadata = new PairStatusMetadata(SUCCESS);
+ Parcel dest = Parcel.obtain();
+ pairStatusMetadata.writeToParcel(dest, 0);
+ dest.setDataPosition(0);
+ PairStatusMetadata comparStatusMetadata =
+ PairStatusMetadata.CREATOR.createFromParcel(dest);
+ assertThat(pairStatusMetadata.equals(comparStatusMetadata)).isTrue();
+ }
+
+ @Test
+ public void testCreatorNewArray() {
+ PairStatusMetadata[] pairStatusMetadatas = PairStatusMetadata.CREATOR.newArray(2);
+ assertThat(pairStatusMetadatas.length).isEqualTo(2);
+ }
+
+ @Test
+ public void describeContents() {
+ PairStatusMetadata pairStatusMetadata = new PairStatusMetadata(SUCCESS);
+ assertThat(pairStatusMetadata.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void getStability() {
+ PairStatusMetadata pairStatusMetadata = new PairStatusMetadata(SUCCESS);
+ assertThat(pairStatusMetadata.getStability()).isEqualTo(0);
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleFilterTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleFilterTest.java
index 1d3653b..c4a9729 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleFilterTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleFilterTest.java
@@ -20,7 +20,11 @@
import static org.junit.Assert.fail;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothAssignedNumbers;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.le.ScanFilter;
+import android.os.Parcel;
import android.os.ParcelUuid;
import android.util.SparseArray;
@@ -44,6 +48,83 @@
public static final ParcelUuid EDDYSTONE_SERVICE_DATA_PARCELUUID =
ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB");
+ private final BleFilter mEddystoneFilter = createEddystoneFilter();
+ private final BleFilter mEddystoneUidFilter = createEddystoneUidFilter();
+ private final BleFilter mEddystoneUrlFilter = createEddystoneUrlFilter();
+ private final BleFilter mEddystoneEidFilter = createEddystoneEidFilter();
+ private final BleFilter mIBeaconWithoutUuidFilter = createIBeaconWithoutUuidFilter();
+ private final BleFilter mIBeaconWithUuidFilter = createIBeaconWithUuidFilter();
+ private final BleFilter mChromecastFilter =
+ new BleFilter.Builder().setServiceUuid(
+ new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")))
+ .build();
+ private final BleFilter mEddystoneWithDeviceNameFilter =
+ new BleFilter.Builder()
+ .setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID)
+ .setDeviceName("BERT")
+ .build();
+ private final BleFilter mEddystoneWithDeviceAddressFilter =
+ new BleFilter.Builder()
+ .setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID)
+ .setDeviceAddress("00:11:22:33:AA:BB")
+ .build();
+ private final BleFilter mServiceUuidWithMaskFilter1 =
+ new BleFilter.Builder()
+ .setServiceUuid(
+ new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")),
+ new ParcelUuid(UUID.fromString("0000000-0000-000-FFFF-FFFFFFFFFFFF")))
+ .build();
+ private final BleFilter mServiceUuidWithMaskFilter2 =
+ new BleFilter.Builder()
+ .setServiceUuid(
+ new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")),
+ new ParcelUuid(UUID.fromString("FFFFFFF-FFFF-FFF-FFFF-FFFFFFFFFFFF")))
+ .build();
+
+ private final BleFilter mSmartSetupFilter =
+ new BleFilter.Builder()
+ .setManufacturerData(
+ BluetoothAssignedNumbers.GOOGLE,
+ new byte[] {0x00, 0x10},
+ new byte[] {0x00, (byte) 0xFF})
+ .build();
+ private final BleFilter mWearFilter =
+ new BleFilter.Builder()
+ .setManufacturerData(
+ BluetoothAssignedNumbers.GOOGLE,
+ new byte[] {0x00, 0x00, 0x00},
+ new byte[] {0x00, 0x00, (byte) 0xFF})
+ .build();
+ private final BleFilter mFakeSmartSetupSubsetFilter =
+ new BleFilter.Builder()
+ .setManufacturerData(
+ BluetoothAssignedNumbers.GOOGLE,
+ new byte[] {0x00, 0x10, 0x50},
+ new byte[] {0x00, (byte) 0xFF, (byte) 0xFF})
+ .build();
+ private final BleFilter mFakeSmartSetupNotSubsetFilter =
+ new BleFilter.Builder()
+ .setManufacturerData(
+ BluetoothAssignedNumbers.GOOGLE,
+ new byte[] {0x00, 0x10, 0x50},
+ new byte[] {0x00, (byte) 0x00, (byte) 0xFF})
+ .build();
+
+ private final BleFilter mFakeFilter1 =
+ new BleFilter.Builder()
+ .setServiceData(
+ ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"),
+ new byte[] {0x51, 0x64},
+ new byte[] {0x00, (byte) 0xFF})
+ .build();
+ private final BleFilter mFakeFilter2 =
+ new BleFilter.Builder()
+ .setServiceData(
+ ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"),
+ new byte[] {0x51, 0x64, 0x34},
+ new byte[] {0x00, (byte) 0xFF, (byte) 0xFF})
+ .build();
+
private ParcelUuid mServiceDataUuid;
private BleSighting mBleSighting;
private BleFilter.Builder mFilterBuilder;
@@ -229,6 +310,16 @@
}
@Test
+ public void serviceDataUuidNotInBleRecord() {
+ byte[] bleRecord = FastPairTestData.eir_1;
+ byte[] serviceData = {(byte) 0xe0, (byte) 0x00};
+
+ // Verify Service Data with 2-byte UUID, no data, and NOT in scan record
+ BleFilter filter = mFilterBuilder.setServiceData(mServiceDataUuid, serviceData).build();
+ assertThat(matches(filter, null, 0, bleRecord)).isFalse();
+ }
+
+ @Test
public void serviceDataMask() {
byte[] bleRecord = FastPairTestData.sd1;
BleFilter filter;
@@ -263,6 +354,18 @@
mFilterBuilder.setServiceData(mServiceDataUuid, serviceData, mask).build();
}
+ @Test
+ public void serviceDataMaskNotInBleRecord() {
+ byte[] bleRecord = FastPairTestData.eir_1;
+ BleFilter filter;
+
+ // Verify matching partial manufacturer with data and mask
+ byte[] serviceData1 = {(byte) 0xe0, (byte) 0x00, (byte) 0x15};
+ byte[] mask1 = {(byte) 0xff, (byte) 0xff, (byte) 0xff};
+ filter = mFilterBuilder.setServiceData(mServiceDataUuid, serviceData1, mask1).build();
+ assertThat(matches(filter, null, 0, bleRecord)).isFalse();
+ }
+
@Test
public void deviceNameTest() {
@@ -280,12 +383,241 @@
assertThat(matches(filter, null, 0, bleRecord)).isFalse();
}
+ @Test
+ public void deviceNameNotInBleRecord() {
+ // Verify the name filter does not match
+ byte[] bleRecord = FastPairTestData.eir_1;
+ BleFilter filter = mFilterBuilder.setDeviceName("Pedometer").build();
+ assertThat(matches(filter, null, 0, bleRecord)).isFalse();
+ }
+
+ @Test
+ public void serviceUuid() {
+ byte[] bleRecord = FastPairTestData.eddystone_header_and_uuid;
+ ParcelUuid uuid = ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB");
+
+ BleFilter filter = mFilterBuilder.setServiceUuid(uuid).build();
+ assertMatches(filter, null, 0, bleRecord);
+ }
+
+ @Test
+ public void serviceUuidNoMatch() {
+ // Verify the name filter does not match
+ byte[] bleRecord = FastPairTestData.eddystone_header_and_uuid;
+ ParcelUuid uuid = ParcelUuid.fromString("00001804-0000-1000-8000-000000000000");
+
+ BleFilter filter = mFilterBuilder.setServiceUuid(uuid).build();
+ assertThat(matches(filter, null, 0, bleRecord)).isFalse();
+ }
+
+ @Test
+ public void serviceUuidNotInBleRecord() {
+ // Verify the name filter does not match
+ byte[] bleRecord = FastPairTestData.eir_1;
+ ParcelUuid uuid = ParcelUuid.fromString("00001804-0000-1000-8000-000000000000");
+
+ BleFilter filter = mFilterBuilder.setServiceUuid(uuid).build();
+ assertThat(matches(filter, null, 0, bleRecord)).isFalse();
+ }
+
+ @Test
+ public void serviceUuidMask() {
+ byte[] bleRecord = FastPairTestData.eddystone_header_and_uuid;
+ ParcelUuid uuid = ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB");
+ ParcelUuid mask = ParcelUuid.fromString("00000000-0000-0000-0000-FFFFFFFFFFFF");
+ BleFilter filter = mFilterBuilder.setServiceUuid(uuid, mask).build();
+ assertMatches(filter, null, 0, bleRecord);
+ }
+
+
+ @Test
+ public void macAddress() {
+ byte[] bleRecord = FastPairTestData.eddystone_header_and_uuid;
+ String macAddress = "00:11:22:33:AA:BB";
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+
+ BluetoothDevice device = adapter.getRemoteDevice(macAddress);
+ BleFilter filter = mFilterBuilder.setDeviceAddress(macAddress).build();
+ assertMatches(filter, device, 0, bleRecord);
+ }
+
+ @Test
+ public void macAddressNoMatch() {
+ byte[] bleRecord = FastPairTestData.eddystone_header_and_uuid;
+ String macAddress = "00:11:22:33:AA:00";
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+
+ BluetoothDevice device = adapter.getRemoteDevice("00:11:22:33:AA:BB");
+ BleFilter filter = mFilterBuilder.setDeviceAddress(macAddress).build();
+ assertThat(matches(filter, device, 0, bleRecord)).isFalse();
+ }
+
+ @Test
+ public void eddystoneIsSuperset() {
+ // Verify eddystone subtypes pass.
+ assertThat(mEddystoneFilter.isSuperset(mEddystoneFilter)).isTrue();
+ assertThat(mEddystoneUidFilter.isSuperset(mEddystoneUidFilter)).isTrue();
+ assertThat(mEddystoneFilter.isSuperset(mEddystoneUidFilter)).isTrue();
+ assertThat(mEddystoneFilter.isSuperset(mEddystoneEidFilter)).isTrue();
+ assertThat(mEddystoneFilter.isSuperset(mEddystoneUrlFilter)).isTrue();
+
+ // Non-eddystone beacon filters should never be supersets.
+ assertThat(mEddystoneFilter.isSuperset(mIBeaconWithoutUuidFilter)).isFalse();
+ assertThat(mEddystoneFilter.isSuperset(mWearFilter)).isFalse();
+ assertThat(mEddystoneFilter.isSuperset(mSmartSetupFilter)).isFalse();
+ assertThat(mEddystoneFilter.isSuperset(mChromecastFilter)).isFalse();
+ assertThat(mEddystoneFilter.isSuperset(mFakeFilter1)).isFalse();
+ assertThat(mEddystoneFilter.isSuperset(mFakeFilter2)).isFalse();
+
+ assertThat(mEddystoneUidFilter.isSuperset(mWearFilter)).isFalse();
+ assertThat(mEddystoneUidFilter.isSuperset(mSmartSetupFilter)).isFalse();
+ assertThat(mEddystoneUidFilter.isSuperset(mChromecastFilter)).isFalse();
+ assertThat(mEddystoneUidFilter.isSuperset(mFakeFilter1)).isFalse();
+ assertThat(mEddystoneUidFilter.isSuperset(mFakeFilter2)).isFalse();
+ }
+
+ @Test
+ public void iBeaconIsSuperset() {
+ // Verify that an iBeacon filter is a superset of itself and any filters that specify UUIDs.
+ assertThat(mIBeaconWithoutUuidFilter.isSuperset(mIBeaconWithoutUuidFilter)).isTrue();
+ assertThat(mIBeaconWithoutUuidFilter.isSuperset(mIBeaconWithUuidFilter)).isTrue();
+
+ // Non-iBeacon filters should never be supersets.
+ assertThat(mIBeaconWithoutUuidFilter.isSuperset(mEddystoneEidFilter)).isFalse();
+ assertThat(mIBeaconWithoutUuidFilter.isSuperset(mEddystoneUrlFilter)).isFalse();
+ assertThat(mIBeaconWithoutUuidFilter.isSuperset(mEddystoneUidFilter)).isFalse();
+ assertThat(mIBeaconWithoutUuidFilter.isSuperset(mWearFilter)).isFalse();
+ assertThat(mIBeaconWithoutUuidFilter.isSuperset(mSmartSetupFilter)).isFalse();
+ assertThat(mIBeaconWithoutUuidFilter.isSuperset(mChromecastFilter)).isFalse();
+ assertThat(mIBeaconWithoutUuidFilter.isSuperset(mFakeFilter1)).isFalse();
+ assertThat(mIBeaconWithoutUuidFilter.isSuperset(mFakeFilter2)).isFalse();
+ }
+
+ @Test
+ public void mixedFilterIsSuperset() {
+ // Compare service data vs manufacturer data filters to verify we detect supersets
+ // correctly in filters that aren't for iBeacon and Eddystone.
+ assertThat(mWearFilter.isSuperset(mIBeaconWithoutUuidFilter)).isFalse();
+ assertThat(mSmartSetupFilter.isSuperset(mIBeaconWithoutUuidFilter)).isFalse();
+ assertThat(mChromecastFilter.isSuperset(mIBeaconWithoutUuidFilter)).isFalse();
+
+ assertThat(mWearFilter.isSuperset(mEddystoneFilter)).isFalse();
+ assertThat(mSmartSetupFilter.isSuperset(mEddystoneFilter)).isFalse();
+ assertThat(mChromecastFilter.isSuperset(mEddystoneFilter)).isFalse();
+
+ assertThat(mWearFilter.isSuperset(mEddystoneUidFilter)).isFalse();
+ assertThat(mSmartSetupFilter.isSuperset(mEddystoneUidFilter)).isFalse();
+ assertThat(mChromecastFilter.isSuperset(mEddystoneUidFilter)).isFalse();
+
+ assertThat(mWearFilter.isSuperset(mEddystoneEidFilter)).isFalse();
+ assertThat(mSmartSetupFilter.isSuperset(mEddystoneEidFilter)).isFalse();
+ assertThat(mChromecastFilter.isSuperset(mEddystoneEidFilter)).isFalse();
+
+ assertThat(mWearFilter.isSuperset(mEddystoneUrlFilter)).isFalse();
+ assertThat(mSmartSetupFilter.isSuperset(mEddystoneUrlFilter)).isFalse();
+ assertThat(mChromecastFilter.isSuperset(mEddystoneUrlFilter)).isFalse();
+
+ assertThat(mWearFilter.isSuperset(mIBeaconWithUuidFilter)).isFalse();
+ assertThat(mSmartSetupFilter.isSuperset(mIBeaconWithUuidFilter)).isFalse();
+ assertThat(mChromecastFilter.isSuperset(mIBeaconWithUuidFilter)).isFalse();
+
+ assertThat(mWearFilter.isSuperset(mChromecastFilter)).isFalse();
+ assertThat(mSmartSetupFilter.isSuperset(mChromecastFilter)).isFalse();
+ assertThat(mSmartSetupFilter.isSuperset(mWearFilter)).isFalse();
+ assertThat(mChromecastFilter.isSuperset(mWearFilter)).isFalse();
+
+ assertThat(mFakeFilter1.isSuperset(mFakeFilter2)).isTrue();
+ assertThat(mFakeFilter2.isSuperset(mFakeFilter1)).isFalse();
+ assertThat(mSmartSetupFilter.isSuperset(mFakeSmartSetupSubsetFilter)).isTrue();
+ assertThat(mSmartSetupFilter.isSuperset(mFakeSmartSetupNotSubsetFilter)).isFalse();
+
+ assertThat(mEddystoneFilter.isSuperset(mEddystoneWithDeviceNameFilter)).isTrue();
+ assertThat(mEddystoneFilter.isSuperset(mEddystoneWithDeviceAddressFilter)).isTrue();
+ assertThat(mEddystoneWithDeviceAddressFilter.isSuperset(mEddystoneFilter)).isFalse();
+
+ assertThat(mChromecastFilter.isSuperset(mServiceUuidWithMaskFilter1)).isTrue();
+ assertThat(mServiceUuidWithMaskFilter2.isSuperset(mServiceUuidWithMaskFilter1)).isFalse();
+ assertThat(mServiceUuidWithMaskFilter1.isSuperset(mServiceUuidWithMaskFilter2)).isTrue();
+ assertThat(mEddystoneFilter.isSuperset(mServiceUuidWithMaskFilter1)).isFalse();
+ }
+
+ @Test
+ public void toOsFilter_getTheSameFilterParameter() {
+ BleFilter nearbyFilter = createTestFilter();
+ ScanFilter osFilter = nearbyFilter.toOsFilter();
+ assertFilterValuesEqual(nearbyFilter, osFilter);
+ }
+
+ @Test
+ public void describeContents() {
+ BleFilter nearbyFilter = createTestFilter();
+ assertThat(nearbyFilter.describeContents()).isEqualTo(0);
+ }
+
+ @Test
+ public void testHashCode() {
+ BleFilter nearbyFilter = createTestFilter();
+ BleFilter compareFilter = new BleFilter("BERT", "00:11:22:33:AA:BB",
+ new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")),
+ new ParcelUuid(UUID.fromString("FFFFFFF-FFFF-FFF-FFFF-FFFFFFFFFFFF")),
+ ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"),
+ new byte[] {0x51, 0x64}, new byte[] {0x00, (byte) 0xFF},
+ BluetoothAssignedNumbers.GOOGLE, new byte[] {0x00, 0x10},
+ new byte[] {0x00, (byte) 0xFF});
+ assertThat(nearbyFilter.hashCode()).isEqualTo(compareFilter.hashCode());
+ }
+
+ @Test
+ public void testToString() {
+ BleFilter nearbyFilter = createTestFilter();
+ assertThat(nearbyFilter.toString()).isEqualTo("BleFilter [deviceName=BERT,"
+ + " deviceAddress=00:11:22:33:AA:BB, uuid=0000fea0-0000-1000-8000-00805f9b34fb,"
+ + " uuidMask=0fffffff-ffff-0fff-ffff-ffffffffffff,"
+ + " serviceDataUuid=0000110b-0000-1000-8000-00805f9b34fb,"
+ + " serviceData=[81, 100], serviceDataMask=[0, -1],"
+ + " manufacturerId=224, manufacturerData=[0, 16], manufacturerDataMask=[0, -1]]");
+ }
+
+ @Test
+ public void testParcel() {
+ BleFilter nearbyFilter = createTestFilter();
+ Parcel parcel = Parcel.obtain();
+ nearbyFilter.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ BleFilter compareFilter = BleFilter.CREATOR.createFromParcel(
+ parcel);
+ parcel.recycle();
+ assertThat(compareFilter.getDeviceName()).isEqualTo("BERT");
+ }
+
+ @Test
+ public void testCreatorNewArray() {
+ BleFilter[] nearbyFilters = BleFilter.CREATOR.newArray(2);
+ assertThat(nearbyFilters.length).isEqualTo(2);
+ }
+
private static boolean matches(
BleFilter filter, BluetoothDevice device, int rssi, byte[] bleRecord) {
return filter.matches(new BleSighting(device,
bleRecord, rssi, 0 /* timestampNanos */));
}
+ private static void assertFilterValuesEqual(BleFilter nearbyFilter, ScanFilter osFilter) {
+ assertThat(osFilter.getDeviceAddress()).isEqualTo(nearbyFilter.getDeviceAddress());
+ assertThat(osFilter.getDeviceName()).isEqualTo(nearbyFilter.getDeviceName());
+
+ assertThat(osFilter.getManufacturerData()).isEqualTo(nearbyFilter.getManufacturerData());
+ assertThat(osFilter.getManufacturerDataMask())
+ .isEqualTo(nearbyFilter.getManufacturerDataMask());
+ assertThat(osFilter.getManufacturerId()).isEqualTo(nearbyFilter.getManufacturerId());
+
+ assertThat(osFilter.getServiceData()).isEqualTo(nearbyFilter.getServiceData());
+ assertThat(osFilter.getServiceDataMask()).isEqualTo(nearbyFilter.getServiceDataMask());
+ assertThat(osFilter.getServiceDataUuid()).isEqualTo(nearbyFilter.getServiceDataUuid());
+
+ assertThat(osFilter.getServiceUuid()).isEqualTo(nearbyFilter.getServiceUuid());
+ assertThat(osFilter.getServiceUuidMask()).isEqualTo(nearbyFilter.getServiceUuidMask());
+ }
private static void assertMatches(
BleFilter filter, BluetoothDevice device, int rssi, byte[] bleRecordBytes) {
@@ -325,10 +657,10 @@
// UUID match.
if (filter.getServiceUuid() != null
- && !matchesServiceUuids(filter.getServiceUuid(), filter.getServiceUuidMask(),
- bleRecord.getServiceUuids())) {
- fail("The filter specifies a service UUID but it doesn't match "
- + "what's in the scan record");
+ && !matchesServiceUuids(filter.getServiceUuid(),
+ filter.getServiceUuidMask(), bleRecord.getServiceUuids())) {
+ fail("The filter specifies a service UUID "
+ + "but it doesn't match what's in the scan record");
}
// Service data match
@@ -401,6 +733,95 @@
}
}
+ private static String byteString(Map<ParcelUuid, byte[]> bytesMap) {
+ StringBuilder builder = new StringBuilder();
+ for (Map.Entry<ParcelUuid, byte[]> entry : bytesMap.entrySet()) {
+ builder.append(builder.toString().isEmpty() ? " " : "\n ");
+ builder.append(entry.getKey().toString());
+ builder.append(" --> ");
+ builder.append(byteString(entry.getValue()));
+ }
+ return builder.toString();
+ }
+
+ private static String byteString(SparseArray<byte[]> bytesArray) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < bytesArray.size(); i++) {
+ builder.append(builder.toString().isEmpty() ? " " : "\n ");
+ builder.append(byteString(bytesArray.valueAt(i)));
+ }
+ return builder.toString();
+ }
+
+ private static BleFilter createTestFilter() {
+ BleFilter.Builder builder = new BleFilter.Builder();
+ builder
+ .setServiceUuid(
+ new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")),
+ new ParcelUuid(UUID.fromString("FFFFFFF-FFFF-FFF-FFFF-FFFFFFFFFFFF")))
+ .setDeviceAddress("00:11:22:33:AA:BB")
+ .setDeviceName("BERT")
+ .setManufacturerData(
+ BluetoothAssignedNumbers.GOOGLE,
+ new byte[] {0x00, 0x10},
+ new byte[] {0x00, (byte) 0xFF})
+ .setServiceData(
+ ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"),
+ new byte[] {0x51, 0x64},
+ new byte[] {0x00, (byte) 0xFF});
+ return builder.build();
+ }
+
+ // ref to beacon.decode.BeaconFilterBuilder.eddystoneFilter()
+ private static BleFilter createEddystoneFilter() {
+ return new BleFilter.Builder().setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID).build();
+ }
+ // ref to beacon.decode.BeaconFilterBuilder.eddystoneUidFilter()
+ private static BleFilter createEddystoneUidFilter() {
+ return new BleFilter.Builder()
+ .setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID)
+ .setServiceData(
+ EDDYSTONE_SERVICE_DATA_PARCELUUID, new byte[] {(short) 0x00},
+ new byte[] {(byte) 0xf0})
+ .build();
+ }
+
+ // ref to beacon.decode.BeaconFilterBuilder.eddystoneUrlFilter()
+ private static BleFilter createEddystoneUrlFilter() {
+ return new BleFilter.Builder()
+ .setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID)
+ .setServiceData(
+ EDDYSTONE_SERVICE_DATA_PARCELUUID,
+ new byte[] {(short) 0x10}, new byte[] {(byte) 0xf0})
+ .build();
+ }
+
+ // ref to beacon.decode.BeaconFilterBuilder.eddystoneEidFilter()
+ private static BleFilter createEddystoneEidFilter() {
+ return new BleFilter.Builder()
+ .setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID)
+ .setServiceData(
+ EDDYSTONE_SERVICE_DATA_PARCELUUID,
+ new byte[] {(short) 0x30}, new byte[] {(byte) 0xf0})
+ .build();
+ }
+
+ // ref to beacon.decode.BeaconFilterBuilder.iBeaconWithoutUuidFilter()
+ private static BleFilter createIBeaconWithoutUuidFilter() {
+ byte[] data = {(byte) 0x02, (byte) 0x15};
+ byte[] mask = {(byte) 0xff, (byte) 0xff};
+
+ return new BleFilter.Builder().setManufacturerData((short) 0x004C, data, mask).build();
+ }
+
+ // ref to beacon.decode.BeaconFilterBuilder.iBeaconWithUuidFilter()
+ private static BleFilter createIBeaconWithUuidFilter() {
+ byte[] data = getFilterData(ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB"));
+ byte[] mask = getFilterMask(ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB"));
+
+ return new BleFilter.Builder().setManufacturerData((short) 0x004C, data, mask).build();
+ }
+
// Ref to beacon.decode.AppleBeaconDecoder.getFilterData
private static byte[] getFilterData(ParcelUuid uuid) {
byte[] data = new byte[18];
@@ -418,6 +839,20 @@
return data;
}
+ // Ref to beacon.decode.AppleBeaconDecoder.getFilterMask
+ private static byte[] getFilterMask(ParcelUuid uuid) {
+ byte[] mask = new byte[18];
+ mask[0] = (byte) 0xff;
+ mask[1] = (byte) 0xff;
+ // Check if UUID is needed in data
+ if (uuid != null) {
+ for (int i = 0; i < 16; i++) {
+ mask[i + 2] = (byte) 0xff;
+ }
+ }
+ return mask;
+ }
+
// Ref to beacon.decode.AppleBeaconDecoder.uuidToByteArray
private static byte[] uuidToByteArray(ParcelUuid uuid) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
@@ -453,24 +888,4 @@
return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits())
== (data.getMostSignificantBits() & mask.getMostSignificantBits()));
}
-
- private static String byteString(Map<ParcelUuid, byte[]> bytesMap) {
- StringBuilder builder = new StringBuilder();
- for (Map.Entry<ParcelUuid, byte[]> entry : bytesMap.entrySet()) {
- builder.append(builder.toString().isEmpty() ? " " : "\n ");
- builder.append(entry.getKey().toString());
- builder.append(" --> ");
- builder.append(byteString(entry.getValue()));
- }
- return builder.toString();
- }
-
- private static String byteString(SparseArray<byte[]> bytesArray) {
- StringBuilder builder = new StringBuilder();
- for (int i = 0; i < bytesArray.size(); i++) {
- builder.append(builder.toString().isEmpty() ? " " : "\n ");
- builder.append(byteString(bytesArray.valueAt(i)));
- }
- return builder.toString();
- }
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java
index 5da98e2..3f9a259 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java
@@ -34,6 +34,9 @@
import static com.google.common.truth.Truth.assertThat;
+import android.os.ParcelUuid;
+import android.util.SparseArray;
+
import androidx.test.filters.SdkSuppress;
import org.junit.Test;
@@ -238,7 +241,6 @@
BleRecord record = BleRecord.parseFromBytes(BEACON);
BleRecord record2 = BleRecord.parseFromBytes(SAME_BEACON);
-
assertThat(record).isEqualTo(record2);
// Different items.
@@ -246,5 +248,48 @@
assertThat(record).isNotEqualTo(record2);
assertThat(record.hashCode()).isNotEqualTo(record2.hashCode());
}
+
+ @Test
+ public void testFields() {
+ BleRecord record = BleRecord.parseFromBytes(BEACON);
+ assertThat(byteString(record.getManufacturerSpecificData()))
+ .isEqualTo(" 0215F7826DA64FA24E988024BC5B71E0893E44D02522B3");
+ assertThat(
+ byteString(record.getServiceData(
+ ParcelUuid.fromString("000000E0-0000-1000-8000-00805F9B34FB"))))
+ .isEqualTo("[null]");
+ assertThat(record.getTxPowerLevel()).isEqualTo(-12);
+ assertThat(record.toString()).isEqualTo(
+ "BleRecord [advertiseFlags=6, serviceUuids=[], "
+ + "manufacturerSpecificData={76=[2, 21, -9, -126, 109, -90, 79, -94, 78,"
+ + " -104, -128, 36, -68, 91, 113, -32, -119, 62, 68, -48, 37, 34, -77]},"
+ + " serviceData={0000d00d-0000-1000-8000-00805f9b34fb"
+ + "=[116, 109, 77, 107, 50, 54, 100]},"
+ + " txPowerLevel=-12, deviceName=Kontakt]");
+ }
+
+ private static String byteString(SparseArray<byte[]> bytesArray) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < bytesArray.size(); i++) {
+ builder.append(builder.toString().isEmpty() ? " " : "\n ");
+ builder.append(byteString(bytesArray.valueAt(i)));
+ }
+ return builder.toString();
+ }
+
+ private static String byteString(byte[] bytes) {
+ if (bytes == null) {
+ return "[null]";
+ } else {
+ final char[] hexArray = "0123456789ABCDEF".toCharArray();
+ char[] hexChars = new char[bytes.length * 2];
+ for (int i = 0; i < bytes.length; i++) {
+ int v = bytes[i] & 0xFF;
+ hexChars[i * 2] = hexArray[v >>> 4];
+ hexChars[i * 2 + 1] = hexArray[v & 0x0F];
+ }
+ return new String(hexChars);
+ }
+ }
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleSightingTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleSightingTest.java
new file mode 100644
index 0000000..b318842
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleSightingTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.ble;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.os.Parcel;
+
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+/** Test for Bluetooth LE {@link BleSighting}. */
+public class BleSightingTest {
+ private static final String DEVICE_NAME = "device1";
+ private static final String OTHER_DEVICE_NAME = "device2";
+ private static final long TIME_EPOCH_MILLIS = 123456;
+ private static final long OTHER_TIME_EPOCH_MILLIS = 456789;
+ private static final int RSSI = 1;
+ private static final int OTHER_RSSI = 2;
+
+ private final BluetoothDevice mBluetoothDevice1 =
+ BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:11:22:33:44:55");
+ private final BluetoothDevice mBluetoothDevice2 =
+ BluetoothAdapter.getDefaultAdapter().getRemoteDevice("AA:BB:CC:DD:EE:FF");
+
+
+ @Test
+ public void testEquals() {
+ BleSighting sighting =
+ buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+ BleSighting sighting2 =
+ buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+ assertThat(sighting.equals(sighting2)).isTrue();
+ assertThat(sighting2.equals(sighting)).isTrue();
+ assertThat(sighting.hashCode()).isEqualTo(sighting2.hashCode());
+
+ // Transitive property.
+ BleSighting sighting3 =
+ buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+ assertThat(sighting2.equals(sighting3)).isTrue();
+ assertThat(sighting.equals(sighting3)).isTrue();
+
+ // Set different values for each field, one at a time.
+ sighting2 = buildBleSighting(mBluetoothDevice2, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+ assertSightingsNotEquals(sighting, sighting2);
+
+ sighting2 = buildBleSighting(mBluetoothDevice1, OTHER_DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+ assertSightingsNotEquals(sighting, sighting2);
+
+ sighting2 = buildBleSighting(mBluetoothDevice1, DEVICE_NAME, OTHER_TIME_EPOCH_MILLIS, RSSI);
+ assertSightingsNotEquals(sighting, sighting2);
+
+ sighting2 = buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, OTHER_RSSI);
+ assertSightingsNotEquals(sighting, sighting2);
+ }
+
+ @Test
+ public void getNormalizedRSSI_usingNearbyRssiOffset_getCorrectValue() {
+ BleSighting sighting =
+ buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+
+ int defaultRssiOffset = 3;
+ assertThat(sighting.getNormalizedRSSI()).isEqualTo(RSSI + defaultRssiOffset);
+ }
+
+ @Test
+ public void testFields() {
+ BleSighting sighting =
+ buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+ assertThat(byteString(sighting.getBleRecordBytes()))
+ .isEqualTo("080964657669636531");
+ assertThat(sighting.getRssi()).isEqualTo(RSSI);
+ assertThat(sighting.getTimestampMillis()).isEqualTo(TIME_EPOCH_MILLIS);
+ assertThat(sighting.getTimestampNanos())
+ .isEqualTo(TimeUnit.MILLISECONDS.toNanos(TIME_EPOCH_MILLIS));
+ assertThat(sighting.toString()).isEqualTo(
+ "BleSighting{device=00:11:22:33:44:55,"
+ + " bleRecord=BleRecord [advertiseFlags=-1,"
+ + " serviceUuids=[],"
+ + " manufacturerSpecificData={}, serviceData={},"
+ + " txPowerLevel=-2147483648,"
+ + " deviceName=device1],"
+ + " rssi=1,"
+ + " timestampNanos=123456000000}");
+ }
+
+ @Test
+ public void testParcelable() {
+ BleSighting sighting =
+ buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI);
+ Parcel dest = Parcel.obtain();
+ sighting.writeToParcel(dest, 0);
+ dest.setDataPosition(0);
+ BleSighting compareSighting = BleSighting.CREATOR.createFromParcel(dest);
+ assertThat(sighting.getRssi()).isEqualTo(RSSI);
+ }
+
+ @Test
+ public void testCreatorNewArray() {
+ BleSighting[] sightings =
+ BleSighting.CREATOR.newArray(2);
+ assertThat(sightings.length).isEqualTo(2);
+ }
+
+ private static String byteString(byte[] bytes) {
+ if (bytes == null) {
+ return "[null]";
+ } else {
+ final char[] hexArray = "0123456789ABCDEF".toCharArray();
+ char[] hexChars = new char[bytes.length * 2];
+ for (int i = 0; i < bytes.length; i++) {
+ int v = bytes[i] & 0xFF;
+ hexChars[i * 2] = hexArray[v >>> 4];
+ hexChars[i * 2 + 1] = hexArray[v & 0x0F];
+ }
+ return new String(hexChars);
+ }
+ }
+
+ /** Builds a BleSighting instance which will correctly match filters by device name. */
+ private static BleSighting buildBleSighting(
+ BluetoothDevice bluetoothDevice, String deviceName, long timeEpochMillis, int rssi) {
+ byte[] nameBytes = deviceName.getBytes(UTF_8);
+ byte[] bleRecordBytes = new byte[nameBytes.length + 2];
+ bleRecordBytes[0] = (byte) (nameBytes.length + 1);
+ bleRecordBytes[1] = 0x09; // Value of private BleRecord.DATA_TYPE_LOCAL_NAME_COMPLETE;
+ System.arraycopy(nameBytes, 0, bleRecordBytes, 2, nameBytes.length);
+
+ return new BleSighting(bluetoothDevice, bleRecordBytes,
+ rssi, TimeUnit.MILLISECONDS.toNanos(timeEpochMillis));
+ }
+
+ private static void assertSightingsNotEquals(BleSighting sighting1, BleSighting sighting2) {
+ assertThat(sighting1.equals(sighting2)).isFalse();
+ assertThat(sighting1.hashCode()).isNotEqualTo(sighting2.hashCode());
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/BeaconDecoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/BeaconDecoderTest.java
new file mode 100644
index 0000000..9a9181d
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/BeaconDecoderTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.ble.decode;
+
+import static com.android.server.nearby.common.ble.BleRecord.parseFromBytes;
+import static com.android.server.nearby.common.ble.testing.FastPairTestData.getFastPairRecord;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class BeaconDecoderTest {
+ private BeaconDecoder mBeaconDecoder = new FastPairDecoder();;
+
+ @Test
+ public void testFields() {
+ assertThat(mBeaconDecoder.getTelemetry(parseFromBytes(getFastPairRecord()))).isNull();
+ assertThat(mBeaconDecoder.getUrl(parseFromBytes(getFastPairRecord()))).isNull();
+ assertThat(mBeaconDecoder.supportsBeaconIdAndTxPower(parseFromBytes(getFastPairRecord())))
+ .isTrue();
+ assertThat(mBeaconDecoder.supportsTxPower()).isTrue();
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java
index 1ad04f8..6552699 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java
@@ -17,15 +17,23 @@
package com.android.server.nearby.common.ble.decode;
import static com.android.server.nearby.common.ble.BleRecord.parseFromBytes;
+import static com.android.server.nearby.common.ble.testing.FastPairTestData.DEVICE_ADDRESS;
import static com.android.server.nearby.common.ble.testing.FastPairTestData.FAST_PAIR_MODEL_ID;
+import static com.android.server.nearby.common.ble.testing.FastPairTestData.FAST_PAIR_SHARED_ACCOUNT_KEY_RECORD;
+import static com.android.server.nearby.common.ble.testing.FastPairTestData.RSSI;
import static com.android.server.nearby.common.ble.testing.FastPairTestData.getFastPairRecord;
import static com.android.server.nearby.common.ble.testing.FastPairTestData.newFastPairRecord;
import static com.google.common.truth.Truth.assertThat;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.server.nearby.common.ble.BleRecord;
+import com.android.server.nearby.common.ble.BleSighting;
+import com.android.server.nearby.common.ble.testing.FastPairTestData;
import com.android.server.nearby.util.Hex;
import com.google.common.primitives.Bytes;
@@ -34,12 +42,13 @@
import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
public class FastPairDecoderTest {
private static final String LONG_MODEL_ID = "1122334455667788";
- private final FastPairDecoder mDecoder = new FastPairDecoder();
// Bits 3-6 are model ID length bits = 0b1000 = 8
private static final byte LONG_MODEL_ID_HEADER = 0b00010000;
private static final String PADDED_LONG_MODEL_ID = "00001111";
@@ -49,74 +58,239 @@
private static final byte MODEL_ID_HEADER = 0b00000110;
private static final String MODEL_ID = "112233";
private static final byte BLOOM_FILTER_HEADER = 0b01100000;
+ private static final byte BLOOM_FILTER_NO_NOTIFICATION_HEADER = 0b01100010;
private static final String BLOOM_FILTER = "112233445566";
+ private static final byte LONG_BLOOM_FILTER_HEADER = (byte) 0b10100000;
+ private static final String LONG_BLOOM_FILTER = "00112233445566778899";
private static final byte BLOOM_FILTER_SALT_HEADER = 0b00010001;
private static final String BLOOM_FILTER_SALT = "01";
+ private static final byte BATTERY_HEADER = 0b00110011;
+ private static final byte BATTERY_NO_NOTIFICATION_HEADER = 0b00110100;
+ private static final String BATTERY = "01048F";
private static final byte RANDOM_RESOLVABLE_DATA_HEADER = 0b01000110;
private static final String RANDOM_RESOLVABLE_DATA = "11223344";
- private static final byte BLOOM_FILTER_NO_NOTIFICATION_HEADER = 0b01100010;
+ private final FastPairDecoder mDecoder = new FastPairDecoder();
+ private final BluetoothDevice mBluetoothDevice =
+ BluetoothAdapter.getDefaultAdapter().getRemoteDevice(DEVICE_ADDRESS);
+
+ @Test
+ public void filter() {
+ assertThat(FastPairDecoder.FILTER.matches(bleSighting(getFastPairRecord()))).isTrue();
+ assertThat(FastPairDecoder.FILTER.matches(bleSighting(FAST_PAIR_SHARED_ACCOUNT_KEY_RECORD)))
+ .isTrue();
+
+ // Any ID is a valid frame.
+ assertThat(FastPairDecoder.FILTER.matches(
+ bleSighting(newFastPairRecord(Hex.stringToBytes("000001"))))).isTrue();
+ assertThat(FastPairDecoder.FILTER.matches(
+ bleSighting(newFastPairRecord(Hex.stringToBytes("098FEC"))))).isTrue();
+ assertThat(FastPairDecoder.FILTER.matches(
+ bleSighting(FastPairTestData.newFastPairRecord(
+ LONG_MODEL_ID_HEADER, Hex.stringToBytes(LONG_MODEL_ID))))).isTrue();
+ }
@Test
public void getModelId() {
assertThat(mDecoder.getBeaconIdBytes(parseFromBytes(getFastPairRecord())))
.isEqualTo(FAST_PAIR_MODEL_ID);
- FastPairServiceData fastPairServiceData1 =
- new FastPairServiceData(LONG_MODEL_ID_HEADER,
- LONG_MODEL_ID);
- assertThat(
- mDecoder.getBeaconIdBytes(
- newBleRecord(fastPairServiceData1.createServiceData())))
- .isEqualTo(Hex.stringToBytes(LONG_MODEL_ID));
FastPairServiceData fastPairServiceData =
- new FastPairServiceData(PADDED_LONG_MODEL_ID_HEADER,
- PADDED_LONG_MODEL_ID);
+ new FastPairServiceData(LONG_MODEL_ID_HEADER, LONG_MODEL_ID);
assertThat(
mDecoder.getBeaconIdBytes(
newBleRecord(fastPairServiceData.createServiceData())))
+ .isEqualTo(Hex.stringToBytes(LONG_MODEL_ID));
+
+ FastPairServiceData fastPairServiceData1 =
+ new FastPairServiceData(PADDED_LONG_MODEL_ID_HEADER, PADDED_LONG_MODEL_ID);
+ assertThat(
+ mDecoder.getBeaconIdBytes(
+ newBleRecord(fastPairServiceData1.createServiceData())))
.isEqualTo(Hex.stringToBytes(TRIMMED_LONG_MODEL_ID));
}
@Test
- public void getBloomFilter() {
- FastPairServiceData fastPairServiceData = new FastPairServiceData(MODEL_ID_HEADER,
- MODEL_ID);
- fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
- fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
- assertThat(FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
- .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+ public void getBeaconIdType() {
+ assertThat(mDecoder.getBeaconIdType()).isEqualTo(1);
}
@Test
- public void getBloomFilter_smallModelId() {
- FastPairServiceData fastPairServiceData = new FastPairServiceData(null, MODEL_ID);
- assertThat(FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+ public void getCalibratedBeaconTxPower() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(LONG_MODEL_ID_HEADER, LONG_MODEL_ID);
+ assertThat(
+ mDecoder.getCalibratedBeaconTxPower(
+ newBleRecord(fastPairServiceData.createServiceData())))
.isNull();
}
@Test
- public void getBloomFilterSalt_modelIdAndMultipleExtraFields() {
- FastPairServiceData fastPairServiceData = new FastPairServiceData(MODEL_ID_HEADER,
- MODEL_ID);
- fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
- fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_SALT_HEADER);
- fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
- fastPairServiceData.mExtraFields.add(BLOOM_FILTER_SALT);
+ public void getServiceDataArray() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(LONG_MODEL_ID_HEADER, LONG_MODEL_ID);
assertThat(
- FastPairDecoder.getBloomFilterSalt(fastPairServiceData.createServiceData()))
- .isEqualTo(Hex.stringToBytes(BLOOM_FILTER_SALT));
+ mDecoder.getServiceDataArray(
+ newBleRecord(fastPairServiceData.createServiceData())))
+ .isEqualTo(Hex.stringToBytes("101122334455667788"));
}
@Test
- public void getRandomResolvableData_whenContainConnectionState() {
- FastPairServiceData fastPairServiceData = new FastPairServiceData(MODEL_ID_HEADER,
- MODEL_ID);
- fastPairServiceData.mExtraFieldHeaders.add(RANDOM_RESOLVABLE_DATA_HEADER);
- fastPairServiceData.mExtraFields.add(RANDOM_RESOLVABLE_DATA);
+ public void hasBloomFilter() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(LONG_MODEL_ID_HEADER, LONG_MODEL_ID);
assertThat(
- FastPairDecoder.getRandomResolvableData(fastPairServiceData
- .createServiceData()))
- .isEqualTo(Hex.stringToBytes(RANDOM_RESOLVABLE_DATA));
+ mDecoder.hasBloomFilter(
+ newBleRecord(fastPairServiceData.createServiceData())))
+ .isFalse();
+ }
+
+ @Test
+ public void hasModelId_allCases() {
+ // One type of the format is just the 3-byte model ID. This format has no header byte (all 3
+ // service data bytes are the model ID in little endian).
+ assertThat(hasModelId("112233", mDecoder)).isTrue();
+
+ // If the model ID is shorter than 3 bytes, then return null.
+ assertThat(hasModelId("11", mDecoder)).isFalse();
+
+ // If the data is longer than 3 bytes,
+ // byte 0 must be 0bVVVLLLLR (version, ID length, reserved).
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData((byte) 0b00001000, "11223344");
+ assertThat(
+ FastPairDecoder.hasBeaconIdBytes(
+ newBleRecord(fastPairServiceData.createServiceData()))).isTrue();
+
+ FastPairServiceData fastPairServiceData1 =
+ new FastPairServiceData((byte) 0b00001010, "1122334455");
+ assertThat(
+ FastPairDecoder.hasBeaconIdBytes(
+ newBleRecord(fastPairServiceData1.createServiceData()))).isTrue();
+
+ // Length bits correct, but version bits != version 0 (only supported version).
+ FastPairServiceData fastPairServiceData2 =
+ new FastPairServiceData((byte) 0b00101000, "11223344");
+ assertThat(
+ FastPairDecoder.hasBeaconIdBytes(
+ newBleRecord(fastPairServiceData2.createServiceData()))).isFalse();
+
+ // Version bits correct, but length bits incorrect (too big, too small).
+ FastPairServiceData fastPairServiceData3 =
+ new FastPairServiceData((byte) 0b00001010, "11223344");
+ assertThat(
+ FastPairDecoder.hasBeaconIdBytes(
+ newBleRecord(fastPairServiceData3.createServiceData()))).isFalse();
+
+ FastPairServiceData fastPairServiceData4 =
+ new FastPairServiceData((byte) 0b00000010, "11223344");
+ assertThat(
+ FastPairDecoder.hasBeaconIdBytes(
+ newBleRecord(fastPairServiceData4.createServiceData()))).isFalse();
+ }
+
+ @Test
+ public void getBatteryLevel() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(BATTERY_HEADER);
+ fastPairServiceData.mExtraFields.add(BATTERY);
+ assertThat(
+ FastPairDecoder.getBatteryLevel(fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BATTERY));
+ }
+
+ @Test
+ public void getBatteryLevel_notIncludedInPacket() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+ assertThat(
+ FastPairDecoder.getBatteryLevel(fastPairServiceData.createServiceData())).isNull();
+ }
+
+ @Test
+ public void getBatteryLevel_noModelId() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData((byte) 0b00000000, null);
+ fastPairServiceData.mExtraFieldHeaders.add(BATTERY_HEADER);
+ fastPairServiceData.mExtraFields.add(BATTERY);
+ assertThat(
+ FastPairDecoder.getBatteryLevel(fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BATTERY));
+ }
+
+ @Test
+ public void getBatteryLevel_multipelExtraField() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(BATTERY_HEADER);
+ fastPairServiceData.mExtraFields.add(BATTERY);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+ assertThat(
+ FastPairDecoder.getBatteryLevel(fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BATTERY));
+ }
+
+ @Test
+ public void getBatteryLevelNoNotification() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(BATTERY_NO_NOTIFICATION_HEADER);
+ fastPairServiceData.mExtraFields.add(BATTERY);
+ assertThat(
+ FastPairDecoder.getBatteryLevelNoNotification(
+ fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BATTERY));
+ }
+
+ @Test
+ public void getBatteryLevelNoNotification_notIncludedInPacket() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+ assertThat(
+ FastPairDecoder.getBatteryLevelNoNotification(
+ fastPairServiceData.createServiceData())).isNull();
+ }
+
+ @Test
+ public void getBatteryLevelNoNotification_noModelId() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData((byte) 0b00000000, null);
+ fastPairServiceData.mExtraFieldHeaders.add(BATTERY_NO_NOTIFICATION_HEADER);
+ fastPairServiceData.mExtraFields.add(BATTERY);
+ assertThat(
+ FastPairDecoder.getBatteryLevelNoNotification(
+ fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BATTERY));
+ }
+
+ @Test
+ public void getBatteryLevelNoNotification_multipleExtraField() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(BATTERY_NO_NOTIFICATION_HEADER);
+ fastPairServiceData.mExtraFields.add(BATTERY);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+ assertThat(
+ FastPairDecoder.getBatteryLevelNoNotification(
+ fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BATTERY));
+ }
+
+ @Test
+ public void getBloomFilter() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+ assertThat(
+ FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
}
@Test
@@ -125,14 +299,222 @@
new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_NO_NOTIFICATION_HEADER);
fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
- assertThat(FastPairDecoder.getBloomFilterNoNotification(fastPairServiceData
- .createServiceData())).isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+ assertThat(
+ FastPairDecoder.getBloomFilterNoNotification(
+ fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+ }
+
+ @Test
+ public void getBloomFilter_smallModelId() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(null, MODEL_ID);
+ assertThat(
+ FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+ .isNull();
+ }
+
+ @Test
+ public void getBloomFilter_headerVersionBitsNotZero() {
+ // Header bits are defined as 0bVVVLLLLR (V=version, L=length, R=reserved), must be zero.
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData((byte) 0b00100000, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+ assertThat(
+ FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+ .isNull();
+ }
+
+ @Test
+ public void getBloomFilter_noExtraFieldBytesIncluded() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(null);
+ fastPairServiceData.mExtraFields.add(null);
+ assertThat(
+ FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+ .isNull();
+ }
+
+ @Test
+ public void getBloomFilter_extraFieldLengthIsZero() {
+ // The extra field header is formatted as 0bLLLLTTTT (L=length, T=type).
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add((byte) 0b00000000);
+ fastPairServiceData.mExtraFields.add(null);
+ assertThat(
+ FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+ .hasLength(0);
+ }
+
+ @Test
+ public void getBloomFilter_extraFieldLengthLongerThanPacket() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add((byte) 0b11110000);
+ fastPairServiceData.mExtraFields.add("1122");
+ assertThat(
+ FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+ .isNull();
+ }
+
+ @Test
+ public void getBloomFilter_secondExtraFieldLengthLongerThanPacket() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add((byte) 0b00100000);
+ fastPairServiceData.mExtraFields.add("1122");
+ fastPairServiceData.mExtraFieldHeaders.add((byte) 0b11110001);
+ fastPairServiceData.mExtraFields.add("3344");
+ assertThat(
+ FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())).isNull();
+ }
+
+ @Test
+ public void getBloomFilter_typeIsWrong() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add((byte) 0b01100001);
+ fastPairServiceData.mExtraFields.add("112233445566");
+ assertThat(
+ FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+ .isNull();
+ }
+
+ @Test
+ public void getBloomFilter_noModelId() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData((byte) 0b00000000, null);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+ assertThat(
+ FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+ }
+
+ @Test
+ public void getBloomFilter_noModelIdAndMultipleExtraFields() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData((byte) 0b00000000, null);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+ fastPairServiceData.mExtraFieldHeaders.add((byte) 0b00010001);
+ fastPairServiceData.mExtraFields.add("00");
+ assertThat(
+ FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+ }
+
+ @Test
+ public void getBloomFilter_modelIdAndMultipleExtraFields() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_SALT_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER_SALT);
+ assertThat(
+ FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+ }
+
+ @Test
+ public void getBloomFilterSalt_modelIdAndMultipleExtraFields() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_SALT_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER_SALT);
+ assertThat(
+ FastPairDecoder.getBloomFilterSalt(fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BLOOM_FILTER_SALT));
+ }
+
+ @Test
+ public void getBloomFilter_modelIdAndMultipleExtraFieldsWithBloomFilterLast() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add((byte) 0b00010001);
+ fastPairServiceData.mExtraFields.add("1A");
+ fastPairServiceData.mExtraFieldHeaders.add((byte) 0b00100010);
+ fastPairServiceData.mExtraFields.add("2CFE");
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+ assertThat(
+ FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+ }
+
+ @Test
+ public void getBloomFilter_modelIdAndMultipleExtraFieldsWithSameType() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add(BLOOM_FILTER);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add("000000000000");
+ assertThat(
+ FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(BLOOM_FILTER));
+ }
+
+ @Test
+ public void getBloomFilter_longExtraField() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(LONG_BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add(LONG_BLOOM_FILTER);
+ fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER);
+ fastPairServiceData.mExtraFields.add("000000000000");
+ assertThat(
+ FastPairDecoder.getBloomFilter(
+ fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(LONG_BLOOM_FILTER));
+ }
+
+ @Test
+ public void getRandomResolvableData_whenNoConnectionState() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ assertThat(
+ FastPairDecoder.getRandomResolvableData(
+ fastPairServiceData.createServiceData()))
+ .isEqualTo(null);
+ }
+
+ @Test
+ public void getRandomResolvableData_whenContainConnectionState() {
+ FastPairServiceData fastPairServiceData =
+ new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID);
+ fastPairServiceData.mExtraFieldHeaders.add(RANDOM_RESOLVABLE_DATA_HEADER);
+ fastPairServiceData.mExtraFields.add(RANDOM_RESOLVABLE_DATA);
+ assertThat(
+ FastPairDecoder.getRandomResolvableData(
+ fastPairServiceData.createServiceData()))
+ .isEqualTo(Hex.stringToBytes(RANDOM_RESOLVABLE_DATA));
}
private static BleRecord newBleRecord(byte[] serviceDataBytes) {
return parseFromBytes(newFastPairRecord(serviceDataBytes));
}
- class FastPairServiceData {
+
+ private static boolean hasModelId(String modelId, FastPairDecoder decoder) {
+ byte[] modelIdBytes = Hex.stringToBytes(modelId);
+ BleRecord bleRecord =
+ parseFromBytes(FastPairTestData.newFastPairRecord((byte) 0, modelIdBytes));
+ return FastPairDecoder.hasBeaconIdBytes(bleRecord)
+ && Arrays.equals(decoder.getBeaconIdBytes(bleRecord), modelIdBytes);
+ }
+
+ private BleSighting bleSighting(byte[] frame) {
+ return new BleSighting(mBluetoothDevice, frame, RSSI,
+ TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()));
+ }
+
+ static class FastPairServiceData {
private Byte mHeader;
private String mModelId;
List<Byte> mExtraFieldHeaders = new ArrayList<>();
@@ -164,6 +546,4 @@
return serviceData;
}
}
-
-
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java
index ebe72b3..29f8d4e 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java
@@ -48,8 +48,10 @@
// RSSI expected at 1 meter based on the calibrated tx field of -41dBm
// Using params: distance (m), int calibratedTxPower (dBm),
int rssi = RangingUtils.rssiFromTargetDistance(1.0, -41);
+ int rssi1 = RangingUtils.rssiFromTargetDistance(0.0, -41);
assertThat(rssi).isEqualTo(-82);
+ assertThat(rssi1).isEqualTo(-41);
}
@Test
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/StringUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/StringUtilsTest.java
new file mode 100644
index 0000000..6b20fdd
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/StringUtilsTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.ble.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.ParcelUuid;
+import android.util.SparseArray;
+
+import com.android.server.nearby.common.ble.BleRecord;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class StringUtilsTest {
+ // iBeacon (Apple) Packet 1
+ private static final byte[] BEACON = {
+ // Flags
+ (byte) 0x02,
+ (byte) 0x01,
+ (byte) 0x06,
+ // Manufacturer-specific data header
+ (byte) 0x1a,
+ (byte) 0xff,
+ (byte) 0x4c,
+ (byte) 0x00,
+ // iBeacon Type
+ (byte) 0x02,
+ // Frame length
+ (byte) 0x15,
+ // iBeacon Proximity UUID
+ (byte) 0xf7,
+ (byte) 0x82,
+ (byte) 0x6d,
+ (byte) 0xa6,
+ (byte) 0x4f,
+ (byte) 0xa2,
+ (byte) 0x4e,
+ (byte) 0x98,
+ (byte) 0x80,
+ (byte) 0x24,
+ (byte) 0xbc,
+ (byte) 0x5b,
+ (byte) 0x71,
+ (byte) 0xe0,
+ (byte) 0x89,
+ (byte) 0x3e,
+ // iBeacon Instance ID (Major/Minor)
+ (byte) 0x44,
+ (byte) 0xd0,
+ (byte) 0x25,
+ (byte) 0x22,
+ // Tx Power
+ (byte) 0xb3,
+ // RSP
+ (byte) 0x08,
+ (byte) 0x09,
+ (byte) 0x4b,
+ (byte) 0x6f,
+ (byte) 0x6e,
+ (byte) 0x74,
+ (byte) 0x61,
+ (byte) 0x6b,
+ (byte) 0x74,
+ (byte) 0x02,
+ (byte) 0x0a,
+ (byte) 0xf4,
+ (byte) 0x0a,
+ (byte) 0x16,
+ (byte) 0x0d,
+ (byte) 0xd0,
+ (byte) 0x74,
+ (byte) 0x6d,
+ (byte) 0x4d,
+ (byte) 0x6b,
+ (byte) 0x32,
+ (byte) 0x36,
+ (byte) 0x64,
+ (byte) 0x00,
+ (byte) 0x00,
+ (byte) 0x00,
+ (byte) 0x00,
+ (byte) 0x00,
+ (byte) 0x00,
+ (byte) 0x00,
+ (byte) 0x00,
+ (byte) 0x00
+ };
+
+ @Test
+ public void testToString() {
+ BleRecord record = BleRecord.parseFromBytes(BEACON);
+ assertThat(StringUtils.toString((SparseArray<byte[]>) null)).isEqualTo("null");
+ assertThat(StringUtils.toString(new SparseArray<byte[]>())).isEqualTo("{}");
+ assertThat(StringUtils
+ .toString((Map<ParcelUuid, byte[]>) null)).isEqualTo("null");
+ assertThat(StringUtils
+ .toString(new HashMap<ParcelUuid, byte[]>())).isEqualTo("{}");
+ assertThat(StringUtils.toString(record.getManufacturerSpecificData()))
+ .isEqualTo("{76=[2, 21, -9, -126, 109, -90, 79, -94, 78, -104, -128,"
+ + " 36, -68, 91, 113, -32, -119, 62, 68, -48, 37, 34, -77]}");
+ assertThat(StringUtils.toString(record.getServiceData()))
+ .isEqualTo("{0000d00d-0000-1000-8000-00805f9b34fb="
+ + "[116, 109, 77, 107, 50, 54, 100]}");
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/BloomFilterTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/BloomFilterTest.java
new file mode 100644
index 0000000..30df81f
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/BloomFilterTest.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bloomfilter;
+
+import static com.google.common.io.BaseEncoding.base16;
+import static com.google.common.primitives.Bytes.concat;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+/**
+ * Unit-tests for the {@link BloomFilter} class.
+ */
+public class BloomFilterTest {
+ private static final int BYTE_ARRAY_LENGTH = 100;
+
+ private final BloomFilter mBloomFilter =
+ new BloomFilter(new byte[BYTE_ARRAY_LENGTH], newHasher());
+
+ public BloomFilter.Hasher newHasher() {
+ return new FastPairBloomFilterHasher();
+ }
+
+ @Test
+ public void emptyFilter_returnsEmptyArray() throws Exception {
+ assertThat(mBloomFilter.asBytes()).isEqualTo(new byte[BYTE_ARRAY_LENGTH]);
+ }
+
+ @Test
+ public void emptyFilter_neverContains() throws Exception {
+ assertThat(mBloomFilter.possiblyContains(element(1))).isFalse();
+ assertThat(mBloomFilter.possiblyContains(element(2))).isFalse();
+ assertThat(mBloomFilter.possiblyContains(element(3))).isFalse();
+ }
+
+ @Test
+ public void add() throws Exception {
+ assertThat(mBloomFilter.possiblyContains(element(1))).isFalse();
+ mBloomFilter.add(element(1));
+ assertThat(mBloomFilter.possiblyContains(element(1))).isTrue();
+ }
+
+ @Test
+ public void add_onlyGivenArgAdded() throws Exception {
+ mBloomFilter.add(element(1));
+ assertThat(mBloomFilter.possiblyContains(element(1))).isTrue();
+ assertThat(mBloomFilter.possiblyContains(element(2))).isFalse();
+ assertThat(mBloomFilter.possiblyContains(element(3))).isFalse();
+ }
+
+ @Test
+ public void add_multipleArgs() throws Exception {
+ mBloomFilter.add(element(1));
+ mBloomFilter.add(element(2));
+ assertThat(mBloomFilter.possiblyContains(element(1))).isTrue();
+ assertThat(mBloomFilter.possiblyContains(element(2))).isTrue();
+ assertThat(mBloomFilter.possiblyContains(element(3))).isFalse();
+ }
+
+ /**
+ * This test was added because of a bug where the BloomFilter doesn't utilize all bits given.
+ * Functionally, the filter still works, but we just have a much higher false positive rate. The
+ * bug was caused by confusing bit length and byte length, which made our BloomFilter only set
+ * bits on the first byteLength (bitLength / 8) bits rather than the whole bitLength bits.
+ *
+ * <p>Here, we're verifying that the bits set are somewhat scattered. So instead of something
+ * like [ 0, 1, 1, 0, 0, 0, 0, ..., 0 ], we should be getting something like
+ * [ 0, 1, 0, 0, 1, 1, 0, 0,0, 1, ..., 1, 0].
+ */
+ @Test
+ public void randomness_noEndBias() throws Exception {
+ // Add one element to our BloomFilter.
+ mBloomFilter.add(element(1));
+
+ // Record the amount of non-zero bytes and the longest streak of zero bytes in the resulting
+ // BloomFilter. This is an approximation of reasonable distribution since we're recording by
+ // bytes instead of bits.
+ int nonZeroCount = 0;
+ int longestZeroStreak = 0;
+ int currentZeroStreak = 0;
+ for (byte b : mBloomFilter.asBytes()) {
+ if (b == 0) {
+ currentZeroStreak++;
+ } else {
+ // Increment the number of non-zero bytes we've seen, update the longest zero
+ // streak, and then reset the current zero streak.
+ nonZeroCount++;
+ longestZeroStreak = Math.max(longestZeroStreak, currentZeroStreak);
+ currentZeroStreak = 0;
+ }
+ }
+ // Update the longest zero streak again for the tail case.
+ longestZeroStreak = Math.max(longestZeroStreak, currentZeroStreak);
+
+ // Since randomness is hard to measure within one unit test, we instead do a valid check.
+ // All non-zero bytes should not be packed into one end of the array.
+ //
+ // In this case, the size of one end is approximated to be:
+ // BYTE_ARRAY_LENGTH / nonZeroCount.
+ // Therefore, the longest zero streak should be less than:
+ // BYTE_ARRAY_LENGTH - one end of the array.
+ int longestAcceptableZeroStreak = BYTE_ARRAY_LENGTH - (BYTE_ARRAY_LENGTH / nonZeroCount);
+ assertThat(longestZeroStreak).isAtMost(longestAcceptableZeroStreak);
+ }
+
+ @Test
+ public void randomness_falsePositiveRate() throws Exception {
+ // Create a new BloomFilter with a length of only 10 bytes.
+ BloomFilter bloomFilter = new BloomFilter(new byte[10], newHasher());
+
+ // Add 5 distinct elements to the BloomFilter.
+ for (int i = 0; i < 5; i++) {
+ bloomFilter.add(element(i));
+ }
+
+ // Now test 100 other elements and record the number of false positives.
+ int falsePositives = 0;
+ for (int i = 5; i < 105; i++) {
+ falsePositives += bloomFilter.possiblyContains(element(i)) ? 1 : 0;
+ }
+
+ // We expect the false positive rate to be 3% with 5 elements in a 10 byte filter. Thus,
+ // we give a little leeway and verify that the false positive rate is no more than 5%.
+ assertWithMessage(
+ String.format(
+ "False positive rate too large. Expected <= 5%%, but got %d%%.",
+ falsePositives))
+ .that(falsePositives <= 5)
+ .isTrue();
+ System.out.printf("False positive rate: %d%%%n", falsePositives);
+ }
+
+
+ private String element(int index) {
+ return "ELEMENT_" + index;
+ }
+
+ @Test
+ public void specificBitPattern() throws Exception {
+ // Create a new BloomFilter along with a fixed set of elements
+ // and bit patterns to verify with.
+ BloomFilter bloomFilter = new BloomFilter(new byte[6], newHasher());
+ // Combine an account key and mac address.
+ byte[] element =
+ concat(
+ base16().decode("11223344556677889900AABBCCDDEEFF"),
+ base16().withSeparator(":", 2).decode("84:68:3E:00:02:11"));
+ byte[] expectedBitPattern = new byte[] {0x50, 0x00, 0x04, 0x15, 0x08, 0x01};
+
+ // Add the fixed elements to the filter.
+ bloomFilter.add(element);
+
+ // Verify that the resulting bytes match the expected one.
+ byte[] bloomFilterBytes = bloomFilter.asBytes();
+ assertWithMessage(
+ "Unexpected bit pattern. Expected %s, but got %s.",
+ base16().encode(expectedBitPattern), base16().encode(bloomFilterBytes))
+ .that(Arrays.equals(expectedBitPattern, bloomFilterBytes))
+ .isTrue();
+
+ // Verify that the expected bit pattern creates a BloomFilter containing all fixed elements.
+ bloomFilter = new BloomFilter(expectedBitPattern, newHasher());
+ assertThat(bloomFilter.possiblyContains(element)).isTrue();
+ }
+
+ // This test case has been on the spec,
+ // https://devsite.googleplex.com/nearby/fast-pair/spec#test_cases.
+ // Explicitly adds it here, and we can easily change the parameters (e.g. account key, ble
+ // address) to clarify test results with partners.
+ @Test
+ public void specificBitPattern_hasOneAccountKey() {
+ BloomFilter bloomFilter1 = new BloomFilter(new byte[4], newHasher());
+ BloomFilter bloomFilter2 = new BloomFilter(new byte[4], newHasher());
+ byte[] accountKey = base16().decode("11223344556677889900AABBCCDDEEFF");
+ byte[] salt1 = base16().decode("C7");
+ byte[] salt2 = base16().decode("C7C8");
+
+ // Add the fixed elements to the filter.
+ bloomFilter1.add(concat(accountKey, salt1));
+ bloomFilter2.add(concat(accountKey, salt2));
+
+ assertThat(bloomFilter1.asBytes()).isEqualTo(base16().decode("0A428810"));
+ assertThat(bloomFilter2.asBytes()).isEqualTo(base16().decode("020C802A"));
+ }
+
+ // Adds this test case to spec. We can easily change the parameters (e.g. account key, ble
+ // address, battery data) to clarify test results with partners.
+ @Test
+ public void specificBitPattern_hasOneAccountKey_withBatteryData() {
+ BloomFilter bloomFilter1 = new BloomFilter(new byte[4], newHasher());
+ BloomFilter bloomFilter2 = new BloomFilter(new byte[4], newHasher());
+ byte[] accountKey = base16().decode("11223344556677889900AABBCCDDEEFF");
+ byte[] salt1 = base16().decode("C7");
+ byte[] salt2 = base16().decode("C7C8");
+ byte[] batteryData = {
+ 0b00110011, // length = 3, show UI indication.
+ 0b01000000, // Left bud: not charging, battery level = 64.
+ 0b01000000, // Right bud: not charging, battery level = 64.
+ 0b01000000 // Case: not charging, battery level = 64.
+ };
+
+ // Adds battery data to build bloom filter.
+ bloomFilter1.add(concat(accountKey, salt1, batteryData));
+ bloomFilter2.add(concat(accountKey, salt2, batteryData));
+
+ assertThat(bloomFilter1.asBytes()).isEqualTo(base16().decode("4A00F000"));
+ assertThat(bloomFilter2.asBytes()).isEqualTo(base16().decode("0101460A"));
+ }
+
+ // This test case has been on the spec,
+ // https://devsite.googleplex.com/nearby/fast-pair/spec#test_cases.
+ // Explicitly adds it here, and we can easily change the parameters (e.g. account key, ble
+ // address) to clarify test results with partners.
+ @Test
+ public void specificBitPattern_hasTwoAccountKeys() {
+ BloomFilter bloomFilter1 = new BloomFilter(new byte[5], newHasher());
+ BloomFilter bloomFilter2 = new BloomFilter(new byte[5], newHasher());
+ byte[] accountKey1 = base16().decode("11223344556677889900AABBCCDDEEFF");
+ byte[] accountKey2 = base16().decode("11112222333344445555666677778888");
+ byte[] salt1 = base16().decode("C7");
+ byte[] salt2 = base16().decode("C7C8");
+
+ // Adds the fixed elements to the filter.
+ bloomFilter1.add(concat(accountKey1, salt1));
+ bloomFilter1.add(concat(accountKey2, salt1));
+ bloomFilter2.add(concat(accountKey1, salt2));
+ bloomFilter2.add(concat(accountKey2, salt2));
+
+ assertThat(bloomFilter1.asBytes()).isEqualTo(base16().decode("2FBA064200"));
+ assertThat(bloomFilter2.asBytes()).isEqualTo(base16().decode("844A62208B"));
+ }
+
+ // Adds this test case to spec. We can easily change the parameters (e.g. account keys, ble
+ // address, battery data) to clarify test results with partners.
+ @Test
+ public void specificBitPattern_hasTwoAccountKeys_withBatteryData() {
+ BloomFilter bloomFilter1 = new BloomFilter(new byte[5], newHasher());
+ BloomFilter bloomFilter2 = new BloomFilter(new byte[5], newHasher());
+ byte[] accountKey1 = base16().decode("11223344556677889900AABBCCDDEEFF");
+ byte[] accountKey2 = base16().decode("11112222333344445555666677778888");
+ byte[] salt1 = base16().decode("C7");
+ byte[] salt2 = base16().decode("C7C8");
+ byte[] batteryData = {
+ 0b00110011, // length = 3, show UI indication.
+ 0b01000000, // Left bud: not charging, battery level = 64.
+ 0b01000000, // Right bud: not charging, battery level = 64.
+ 0b01000000 // Case: not charging, battery level = 64.
+ };
+
+ // Adds battery data to build bloom filter.
+ bloomFilter1.add(concat(accountKey1, salt1, batteryData));
+ bloomFilter1.add(concat(accountKey2, salt1, batteryData));
+ bloomFilter2.add(concat(accountKey1, salt2, batteryData));
+ bloomFilter2.add(concat(accountKey2, salt2, batteryData));
+
+ assertThat(bloomFilter1.asBytes()).isEqualTo(base16().decode("102256C04D"));
+ assertThat(bloomFilter2.asBytes()).isEqualTo(base16().decode("461524D008"));
+ }
+
+ // Adds this test case to spec. We can easily change the parameters (e.g. account keys, ble
+ // address, battery data and battery remaining time) to clarify test results with partners.
+ @Test
+ public void specificBitPattern_hasTwoAccountKeys_withBatteryLevelAndRemainingTime() {
+ BloomFilter bloomFilter1 = new BloomFilter(new byte[5], newHasher());
+ BloomFilter bloomFilter2 = new BloomFilter(new byte[5], newHasher());
+ byte[] accountKey1 = base16().decode("11223344556677889900AABBCCDDEEFF");
+ byte[] accountKey2 = base16().decode("11112222333344445555666677778888");
+ byte[] salt1 = base16().decode("C7");
+ byte[] salt2 = base16().decode("C7C8");
+ byte[] batteryData = {
+ 0b00110011, // length = 3, show UI indication.
+ 0b01000000, // Left bud: not charging, battery level = 64.
+ 0b01000000, // Right bud: not charging, battery level = 64.
+ 0b01000000 // Case: not charging, battery level = 64.
+ };
+ byte[] batteryRemainingTime = {
+ 0b00010101, // length = 1, type = 0b0101 (remaining battery time).
+ 0x1E, // remaining battery time (in minutes) = 30 minutes.
+ };
+
+ // Adds battery data to build bloom filter.
+ bloomFilter1.add(concat(accountKey1, salt1, batteryData, batteryRemainingTime));
+ bloomFilter1.add(concat(accountKey2, salt1, batteryData, batteryRemainingTime));
+ bloomFilter2.add(concat(accountKey1, salt2, batteryData, batteryRemainingTime));
+ bloomFilter2.add(concat(accountKey2, salt2, batteryData, batteryRemainingTime));
+
+ assertThat(bloomFilter1.asBytes()).isEqualTo(base16().decode("32A086B41A"));
+ assertThat(bloomFilter2.asBytes()).isEqualTo(base16().decode("C2A042043E"));
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/FastPairBloomFilterHasherTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/FastPairBloomFilterHasherTest.java
new file mode 100644
index 0000000..0923b95
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/FastPairBloomFilterHasherTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bloomfilter;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import org.junit.Test;
+
+import java.nio.charset.Charset;
+
+public class FastPairBloomFilterHasherTest {
+ private static final int BYTE_ARRAY_LENGTH = 100;
+ private static final Charset CHARSET = UTF_8;
+ private static FastPairBloomFilterHasher sFastPairBloomFilterHasher =
+ new FastPairBloomFilterHasher();
+ @Test
+ public void getHashes() {
+ int[] hashe1 = sFastPairBloomFilterHasher.getHashes(element(1).getBytes(CHARSET));
+ int[] hashe2 = sFastPairBloomFilterHasher.getHashes(element(1).getBytes(CHARSET));
+ int[] hashe3 = sFastPairBloomFilterHasher.getHashes(element(2).getBytes(CHARSET));
+ assertThat(hashe1).isEqualTo(hashe2);
+ assertThat(hashe1).isNotEqualTo(hashe3);
+ }
+
+ private String element(int index) {
+ return "ELEMENT_" + index;
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java
index 28d2fca..cf40fc6 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java
@@ -63,6 +63,21 @@
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void inputNullSecretKeyToEncode_mustThrowException() {
+ byte[] rawData = base16().decode("00112233445566778899AABBCCDDEEFF");
+
+ GeneralSecurityException exception =
+ assertThrows(
+ GeneralSecurityException.class,
+ () -> AdditionalDataEncoder.encodeAdditionalDataPacket(null, rawData));
+
+ assertThat(exception)
+ .hasMessageThat()
+ .contains("Incorrect secret for encoding additional data packet");
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void inputIncorrectKeySizeToEncode_mustThrowException() {
byte[] secret = new byte[KEY_LENGTH - 1];
byte[] rawData = base16().decode("00112233445566778899AABBCCDDEEFF");
@@ -79,6 +94,54 @@
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void inputNullAdditionalDataToEncode_mustThrowException()
+ throws GeneralSecurityException {
+ byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
+
+ GeneralSecurityException exception =
+ assertThrows(
+ GeneralSecurityException.class,
+ () -> AdditionalDataEncoder.encodeAdditionalDataPacket(secret, null));
+
+ assertThat(exception)
+ .hasMessageThat()
+ .contains("Invalid data for encoding additional data packet");
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void inputInvalidAdditionalDataSizeToEncode_mustThrowException()
+ throws GeneralSecurityException {
+ byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
+ byte[] rawData = base16().decode("");
+ GeneralSecurityException exception =
+ assertThrows(
+ GeneralSecurityException.class,
+ () -> AdditionalDataEncoder.encodeAdditionalDataPacket(secret, rawData));
+
+ assertThat(exception)
+ .hasMessageThat()
+ .contains("Invalid data for encoding additional data packet");
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void inputTooLargeAdditionalDataSizeToEncode_mustThrowException()
+ throws GeneralSecurityException {
+ byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
+ byte[] rawData = new byte[MAX_LENGTH_OF_DATA + 1];
+ GeneralSecurityException exception =
+ assertThrows(
+ GeneralSecurityException.class,
+ () -> AdditionalDataEncoder.encodeAdditionalDataPacket(secret, rawData));
+
+ assertThat(exception)
+ .hasMessageThat()
+ .contains("Invalid data for encoding additional data packet");
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void inputIncorrectKeySizeToDecode_mustThrowException() {
byte[] secret = new byte[KEY_LENGTH - 1];
byte[] packet = base16().decode("01234567890123456789");
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairerTest.java
index 0a56f2f..6871024 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairerTest.java
@@ -84,6 +84,23 @@
}
@SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void getDevice() {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ try {
+ assertThat(new BluetoothAudioPairer(
+ context,
+ BLUETOOTH_DEVICE,
+ Preferences.builder().build(),
+ new EventLoggerWrapper(new TestEventLogger()),
+ null /* KeyBasePairingInfo */,
+ null /*PasskeyConfirmationHandler */,
+ new TimingLogger(EVENT_NAME, Preferences.builder().build())).getDevice())
+ .isEqualTo(BLUETOOTH_DEVICE);
+ } catch (PairingException e) {
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
public void testBluetoothAudioPairerUnpairNoCrash() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
try {
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairerTest.java
new file mode 100644
index 0000000..6f55f6a
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairerTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public class BluetoothClassicPairerTest {
+ @Mock
+ PasskeyConfirmationHandler mPasskeyConfirmationHandler;
+ private static final BluetoothDevice BLUETOOTH_DEVICE = BluetoothAdapter.getDefaultAdapter()
+ .getRemoteDevice("11:22:33:44:55:66");
+ private BluetoothClassicPairer mBluetoothClassicPairer;
+
+ @Before
+ public void setUp() {
+ mBluetoothClassicPairer = new BluetoothClassicPairer(
+ ApplicationProvider.getApplicationContext(),
+ BLUETOOTH_DEVICE,
+ Preferences.builder().build(),
+ mPasskeyConfirmationHandler);
+ }
+
+ @Test
+ public void pair() throws PairingException {
+ PairingException exception =
+ assertThrows(
+ PairingException.class,
+ () -> mBluetoothClassicPairer.pair());
+
+ assertThat(exception)
+ .hasMessageThat()
+ .contains("BluetoothClassicPairer, createBond failed");
+ assertThat(mBluetoothClassicPairer.isPaired()).isFalse();
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BytesTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BytesTest.java
new file mode 100644
index 0000000..2e46ef9
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BytesTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SdkSuppress;
+
+import junit.framework.TestCase;
+
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link Bytes}.
+ */
+public class BytesTest extends TestCase {
+
+ private static final Bytes.Value VALUE1 =
+ new Bytes.Value(new byte[]{1, 2}, ByteOrder.BIG_ENDIAN);
+ private static final Bytes.Value VALUE2 =
+ new Bytes.Value(new byte[]{1, 2}, ByteOrder.BIG_ENDIAN);
+ private static final Bytes.Value VALUE3 =
+ new Bytes.Value(new byte[]{1, 3}, ByteOrder.BIG_ENDIAN);
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testEquals_asExpected() {
+ assertThat(VALUE1.equals(VALUE2)).isTrue();
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testNotEquals_asExpected() {
+ assertThat(VALUE1.equals(VALUE3)).isFalse();
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetBytes_asExpected() {
+ assertThat(Arrays.equals(VALUE1.getBytes(ByteOrder.BIG_ENDIAN), new byte[]{1, 2})).isTrue();
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testToString() {
+ assertThat(VALUE1.toString()).isEqualTo("0102");
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java
index f7ffa24..6684fbc 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java
@@ -33,6 +33,8 @@
import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic;
import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection;
+import com.google.common.collect.ImmutableList;
+
import junit.framework.TestCase;
import org.mockito.Mock;
@@ -44,6 +46,8 @@
*/
public class ConstantsTest extends TestCase {
+ private static final int PASSKEY = 32689;
+
@Mock
private BluetoothGattConnection mMockGattConnection;
@@ -78,4 +82,62 @@
assertThat(KeyBasedPairingCharacteristic.getId(mMockGattConnection))
.isEqualTo(OLD_KEY_BASE_PAIRING_CHARACTERISTICS);
}
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void test_accountKeyCharacteristic_notCrash() throws BluetoothException {
+ Constants.FastPairService.AccountKeyCharacteristic.getId(mMockGattConnection);
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void test_additionalDataCharacteristic_notCrash() throws BluetoothException {
+ Constants.FastPairService.AdditionalDataCharacteristic.getId(mMockGattConnection);
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void test_nameCharacteristic_notCrash() throws BluetoothException {
+ Constants.FastPairService.NameCharacteristic.getId(mMockGattConnection);
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void test_passKeyCharacteristic_encryptDecryptSuccessfully()
+ throws java.security.GeneralSecurityException {
+ byte[] secret = AesEcbSingleBlockEncryption.generateKey();
+
+ Constants.FastPairService.PasskeyCharacteristic.getId(mMockGattConnection);
+ assertThat(
+ Constants.FastPairService.PasskeyCharacteristic.decrypt(
+ Constants.FastPairService.PasskeyCharacteristic.Type.SEEKER,
+ secret,
+ Constants.FastPairService.PasskeyCharacteristic.encrypt(
+ Constants.FastPairService.PasskeyCharacteristic.Type.SEEKER,
+ secret,
+ PASSKEY))
+ ).isEqualTo(PASSKEY);
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void test_beaconActionsCharacteristic_notCrash() throws BluetoothException {
+ Constants.FastPairService.BeaconActionsCharacteristic.getId(mMockGattConnection);
+ for (byte b : ImmutableList.of(
+ (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+ .READ_BEACON_PARAMETERS,
+ (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+ .READ_PROVISIONING_STATE,
+ (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+ .SET_EPHEMERAL_IDENTITY_KEY,
+ (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+ .CLEAR_EPHEMERAL_IDENTITY_KEY,
+ (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+ .READ_EPHEMERAL_IDENTITY_KEY,
+ (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+ .RING,
+ (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+ .READ_RINGING_STATE,
+ (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType
+ .UNKNOWN
+ )) {
+ assertThat(Constants.FastPairService.BeaconActionsCharacteristic
+ .valueOf(b)).isEqualTo(b);
+ }
+ }
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/CreateBondExceptionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/CreateBondExceptionTest.java
new file mode 100644
index 0000000..052e696
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/CreateBondExceptionTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SdkSuppress;
+
+import com.android.server.nearby.common.bluetooth.BluetoothException;
+import com.android.server.nearby.intdefs.FastPairEventIntDefs;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link CreateBondException}.
+ */
+public class CreateBondExceptionTest extends TestCase {
+
+ private static final String FORMAT = "FORMAT";
+ private static final int REASON = 0;
+ private static final CreateBondException EXCEPTION = new CreateBondException(
+ FastPairEventIntDefs.CreateBondErrorCode.INCORRECT_VARIANT, REASON, FORMAT);
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void test_getter_asExpected() throws BluetoothException {
+ assertThat(EXCEPTION.getErrorCode()).isEqualTo(
+ FastPairEventIntDefs.CreateBondErrorCode.INCORRECT_VARIANT);
+ assertThat(EXCEPTION.getReason()).isSameInstanceAs(REASON);
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiverTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiverTest.java
new file mode 100644
index 0000000..94bf111
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiverTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import junit.framework.TestCase;
+
+import org.mockito.Mock;
+
+/**
+ * Unit tests for {@link DeviceIntentReceiver}.
+ */
+public class DeviceIntentReceiverTest extends TestCase {
+ @Mock Preferences mPreferences;
+ @Mock BluetoothDevice mBluetoothDevice;
+
+ private DeviceIntentReceiver mDeviceIntentReceiver;
+ private Intent mIntent;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ initMocks(this);
+
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ mDeviceIntentReceiver = DeviceIntentReceiver.oneShotReceiver(
+ context, mPreferences, mBluetoothDevice);
+
+ mIntent = new Intent().putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
+ }
+
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void test_onReceive_notCrash() throws Exception {
+ mDeviceIntentReceiver.onReceive(mIntent);
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java
index 28e925f..1b63ad0 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java
@@ -58,6 +58,13 @@
.setBluetoothDevice(BLUETOOTH_DEVICE)
.setProfile(PROFILE)
.build();
+ assertThat(event.hasBluetoothDevice()).isTrue();
+ assertThat(event.hasProfile()).isTrue();
+ assertThat(event.isFailure()).isTrue();
+ assertThat(event.toString()).isEqualTo(
+ "Event{eventCode=1120, timestamp=1234, profile=1, "
+ + "bluetoothDevice=11:22:33:44:55:66, "
+ + "exception=java.lang.Exception: Test exception}");
Parcel parcel = Parcel.obtain();
event.writeToParcel(parcel, event.describeContents());
@@ -70,5 +77,8 @@
assertThat(result.getEventCode()).isEqualTo(event.getEventCode());
assertThat(result.getBluetoothDevice()).isEqualTo(event.getBluetoothDevice());
assertThat(result.getProfile()).isEqualTo(event.getProfile());
+ assertThat(result.equals(event)).isTrue();
+
+ assertThat(Event.CREATOR.newArray(10)).isNotEmpty();
}
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HandshakeHandlerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HandshakeHandlerTest.java
new file mode 100644
index 0000000..5763d69
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HandshakeHandlerTest.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import static com.android.server.nearby.common.bluetooth.fastpair.Constants.BLUETOOTH_ADDRESS_LENGTH;
+import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.ADDITIONAL_DATA_CHARACTERISTIC;
+import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.DEVICE_ACTION;
+import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.PROVIDER_INITIATES_BONDING;
+import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.REQUEST_DEVICE_NAME;
+import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.REQUEST_DISCOVERABLE;
+import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.REQUEST_RETROACTIVE_PAIR;
+import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.ADDITIONAL_DATA_TYPE_INDEX;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.Nullable;
+import androidx.core.util.Consumer;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.nearby.common.bluetooth.BluetoothException;
+import com.android.server.nearby.common.bluetooth.BluetoothGattException;
+import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AdditionalDataCharacteristic.AdditionalDataType;
+import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request;
+import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection;
+import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter;
+import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor;
+import com.android.server.nearby.intdefs.NearbyEventIntDefs;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.BaseEncoding;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.security.GeneralSecurityException;
+import java.time.Duration;
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link HandshakeHandler}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HandshakeHandlerTest {
+
+ public static final byte[] PUBLIC_KEY =
+ BaseEncoding.base64().decode(
+ "d2JTfvfdS6u7LmGfMOmco3C7ra3lW1k17AOly0LrBydDZURacfTY"
+ + "IMmo5K1ejfD9e8b6qHsDTNzselhifi10kQ==");
+ private static final String SEEKER_ADDRESS = "A1:A2:A3:A4:A5:A6";
+ private static final String PROVIDER_BLE_ADDRESS = "11:22:33:44:55:66";
+ private static final byte[] SHARED_SECRET =
+ BaseEncoding.base16().decode("0123456789ABCDEF0123456789ABCDEF");
+
+ @Mock EventLoggerWrapper mEventLoggerWrapper;
+ @Mock BluetoothGattConnection mBluetoothGattConnection;
+ @Mock BluetoothGattConnection.ChangeObserver mChangeObserver;
+ @Mock private Consumer<Integer> mRescueFromError;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void handshakeGattError_noRetryError_failed() throws BluetoothException {
+ HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
+ new HandshakeHandler.KeyBasedPairingRequest.Builder()
+ .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
+ .build();
+ BluetoothGattException exception =
+ new BluetoothGattException("Exception for no retry", 257);
+ when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception);
+ GattConnectionManager gattConnectionManager =
+ createGattConnectionManager(Preferences.builder(), () -> {});
+ gattConnectionManager.setGattConnection(mBluetoothGattConnection);
+ when(mBluetoothGattConnection.enableNotification(any(), any()))
+ .thenReturn(mChangeObserver);
+ InOrder inOrder = inOrder(mEventLoggerWrapper);
+
+ assertThrows(
+ BluetoothGattException.class,
+ () ->
+ getHandshakeHandler(gattConnectionManager, address -> address)
+ .doHandshakeWithRetryAndSignalLostCheck(
+ PUBLIC_KEY,
+ keyBasedPairingRequest,
+ mRescueFromError));
+
+ inOrder.verify(mEventLoggerWrapper).setCurrentEvent(
+ NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+ inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void handshakeGattError_retryAndNoCount_throwException() throws BluetoothException {
+ HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
+ new HandshakeHandler.KeyBasedPairingRequest.Builder()
+ .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
+ .build();
+ BluetoothGattException exception = new BluetoothGattException("Exception for retry", 133);
+ when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception);
+ GattConnectionManager gattConnectionManager =
+ createGattConnectionManager(Preferences.builder(), () -> {});
+ gattConnectionManager.setGattConnection(mBluetoothGattConnection);
+ when(mBluetoothGattConnection.enableNotification(any(), any()))
+ .thenReturn(mChangeObserver);
+ InOrder inOrder = inOrder(mEventLoggerWrapper);
+
+ HandshakeHandler.HandshakeException handshakeException =
+ assertThrows(
+ HandshakeHandler.HandshakeException.class,
+ () -> getHandshakeHandler(gattConnectionManager, address -> address)
+ .doHandshakeWithRetryAndSignalLostCheck(
+ PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError));
+
+ inOrder.verify(mEventLoggerWrapper)
+ .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+ inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+ inOrder.verify(mEventLoggerWrapper)
+ .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+ inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+ inOrder.verify(mEventLoggerWrapper)
+ .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+ inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+ inOrder.verify(mEventLoggerWrapper)
+ .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+ inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+ inOrder.verifyNoMoreInteractions();
+ assertThat(handshakeException.getOriginalException()).isEqualTo(exception);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void handshakeGattError_noRetryOnTimeout_throwException() throws BluetoothException {
+ HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
+ new HandshakeHandler.KeyBasedPairingRequest.Builder()
+ .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
+ .build();
+ BluetoothOperationExecutor.BluetoothOperationTimeoutException exception =
+ new BluetoothOperationExecutor.BluetoothOperationTimeoutException("Test timeout");
+ when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception);
+ GattConnectionManager gattConnectionManager =
+ createGattConnectionManager(Preferences.builder(), () -> {});
+ gattConnectionManager.setGattConnection(mBluetoothGattConnection);
+ when(mBluetoothGattConnection.enableNotification(any(), any()))
+ .thenReturn(mChangeObserver);
+ InOrder inOrder = inOrder(mEventLoggerWrapper);
+
+ assertThrows(
+ HandshakeHandler.HandshakeException.class,
+ () ->
+ new HandshakeHandler(
+ gattConnectionManager,
+ PROVIDER_BLE_ADDRESS,
+ Preferences.builder().setRetrySecretHandshakeTimeout(false).build(),
+ mEventLoggerWrapper,
+ address -> address)
+ .doHandshakeWithRetryAndSignalLostCheck(
+ PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError));
+
+ inOrder.verify(mEventLoggerWrapper)
+ .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+ inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void handshakeGattError_signalLost() throws BluetoothException {
+ HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
+ new HandshakeHandler.KeyBasedPairingRequest.Builder()
+ .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
+ .build();
+ BluetoothGattException exception = new BluetoothGattException("Exception for retry", 133);
+ when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception);
+ GattConnectionManager gattConnectionManager =
+ createGattConnectionManager(Preferences.builder(), () -> {});
+ gattConnectionManager.setGattConnection(mBluetoothGattConnection);
+ when(mBluetoothGattConnection.enableNotification(any(), any()))
+ .thenReturn(mChangeObserver);
+ InOrder inOrder = inOrder(mEventLoggerWrapper);
+
+ SignalLostException signalLostException =
+ assertThrows(
+ SignalLostException.class,
+ () -> getHandshakeHandler(gattConnectionManager, address -> null)
+ .doHandshakeWithRetryAndSignalLostCheck(
+ PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError));
+
+ inOrder.verify(mEventLoggerWrapper)
+ .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+ inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+ assertThat(signalLostException).hasCauseThat().isEqualTo(exception);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void handshakeGattError_addressRotate() throws BluetoothException {
+ HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
+ new HandshakeHandler.KeyBasedPairingRequest.Builder()
+ .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
+ .build();
+ BluetoothGattException exception = new BluetoothGattException("Exception for retry", 133);
+ when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception);
+ GattConnectionManager gattConnectionManager =
+ createGattConnectionManager(Preferences.builder(), () -> {});
+ gattConnectionManager.setGattConnection(mBluetoothGattConnection);
+ when(mBluetoothGattConnection.enableNotification(any(), any()))
+ .thenReturn(mChangeObserver);
+ InOrder inOrder = inOrder(mEventLoggerWrapper);
+
+ SignalRotatedException signalRotatedException =
+ assertThrows(
+ SignalRotatedException.class,
+ () -> getHandshakeHandler(
+ gattConnectionManager, address -> "AA:BB:CC:DD:EE:FF")
+ .doHandshakeWithRetryAndSignalLostCheck(
+ PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError));
+
+ inOrder.verify(mEventLoggerWrapper).setCurrentEvent(
+ NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION);
+ inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception);
+ assertThat(signalRotatedException.getNewAddress()).isEqualTo("AA:BB:CC:DD:EE:FF");
+ assertThat(signalRotatedException).hasCauseThat().isEqualTo(exception);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void constructBytes_setRetroactiveFlag_decodeCorrectly() throws
+ GeneralSecurityException {
+ HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest =
+ new HandshakeHandler.KeyBasedPairingRequest.Builder()
+ .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS))
+ .addFlag(REQUEST_RETROACTIVE_PAIR)
+ .setSeekerPublicAddress(BluetoothAddress.decode(SEEKER_ADDRESS))
+ .build();
+
+ byte[] encryptedRawMessage =
+ AesEcbSingleBlockEncryption.encrypt(
+ SHARED_SECRET, keyBasedPairingRequest.getBytes());
+ HandshakeRequest handshakeRequest =
+ new HandshakeRequest(SHARED_SECRET, encryptedRawMessage);
+
+ assertThat(handshakeRequest.getType())
+ .isEqualTo(HandshakeRequest.Type.KEY_BASED_PAIRING_REQUEST);
+ assertThat(handshakeRequest.requestRetroactivePair()).isTrue();
+ assertThat(handshakeRequest.getVerificationData())
+ .isEqualTo(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS));
+ assertThat(handshakeRequest.getSeekerPublicAddress())
+ .isEqualTo(BluetoothAddress.decode(SEEKER_ADDRESS));
+ assertThat(handshakeRequest.requestDeviceName()).isFalse();
+ assertThat(handshakeRequest.requestDiscoverable()).isFalse();
+ assertThat(handshakeRequest.requestProviderInitialBonding()).isFalse();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void getTimeout_notOverShortRetryMaxSpentTime_getShort() {
+ Preferences preferences = Preferences.builder().build();
+
+ assertThat(getHandshakeHandler(/* getEnable128BitCustomGattCharacteristicsId= */ true)
+ .getTimeoutMs(
+ preferences.getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs()
+ - 1))
+ .isEqualTo(preferences.getSecretHandshakeShortTimeoutMs());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void getTimeout_overShortRetryMaxSpentTime_getLong() {
+ Preferences preferences = Preferences.builder().build();
+
+ assertThat(getHandshakeHandler(/* getEnable128BitCustomGattCharacteristicsId= */ true)
+ .getTimeoutMs(
+ preferences.getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs()
+ + 1))
+ .isEqualTo(preferences.getSecretHandshakeLongTimeoutMs());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void getTimeout_retryNotEnabled_getOrigin() {
+ Preferences preferences = Preferences.builder().build();
+
+ assertThat(
+ new HandshakeHandler(
+ createGattConnectionManager(Preferences.builder(), () -> {}),
+ PROVIDER_BLE_ADDRESS,
+ Preferences.builder()
+ .setRetryGattConnectionAndSecretHandshake(false).build(),
+ mEventLoggerWrapper,
+ /* fastPairSignalChecker= */ null)
+ .getTimeoutMs(0))
+ .isEqualTo(Duration.ofSeconds(
+ preferences.getGattOperationTimeoutSeconds()).toMillis());
+ }
+
+ private GattConnectionManager createGattConnectionManager(
+ Preferences.Builder prefs, ToggleBluetoothTask toggleBluetooth) {
+ return new GattConnectionManager(
+ ApplicationProvider.getApplicationContext(),
+ prefs.build(),
+ new EventLoggerWrapper(null),
+ BluetoothAdapter.getDefaultAdapter(),
+ toggleBluetooth,
+ PROVIDER_BLE_ADDRESS,
+ new TimingLogger("GattConnectionManager", prefs.build()),
+ /* fastPairSignalChecker= */ null,
+ /* setMtu= */ false);
+ }
+
+ private HandshakeHandler getHandshakeHandler(
+ GattConnectionManager gattConnectionManager,
+ @Nullable FastPairConnection.FastPairSignalChecker fastPairSignalChecker) {
+ return new HandshakeHandler(
+ gattConnectionManager,
+ PROVIDER_BLE_ADDRESS,
+ Preferences.builder()
+ .setGattConnectionAndSecretHandshakeNoRetryGattError(ImmutableSet.of(257))
+ .setRetrySecretHandshakeTimeout(true)
+ .build(),
+ mEventLoggerWrapper,
+ fastPairSignalChecker);
+ }
+
+ private HandshakeHandler getHandshakeHandler(
+ boolean getEnable128BitCustomGattCharacteristicsId) {
+ return new HandshakeHandler(
+ createGattConnectionManager(Preferences.builder(), () -> {}),
+ PROVIDER_BLE_ADDRESS,
+ Preferences.builder()
+ .setGattOperationTimeoutSeconds(5)
+ .setEnable128BitCustomGattCharacteristicsId(
+ getEnable128BitCustomGattCharacteristicsId)
+ .build(),
+ mEventLoggerWrapper,
+ /* fastPairSignalChecker= */ null);
+ }
+
+ private static class HandshakeRequest {
+
+ /**
+ * 16 bytes data: 1-byte for type, 1-byte for flags, 6-bytes for provider's BLE address, 8
+ * bytes optional data.
+ *
+ * @see {go/fast-pair-spec-handshake-message1}
+ */
+ private final byte[] mDecryptedMessage;
+
+ HandshakeRequest(byte[] key, byte[] encryptedPairingRequest)
+ throws GeneralSecurityException {
+ mDecryptedMessage = AesEcbSingleBlockEncryption.decrypt(key, encryptedPairingRequest);
+ }
+
+ /**
+ * Gets the type of this handshake request. Currently, we have 2 types: 0x00 for Key-based
+ * Pairing Request and 0x10 for Action Request.
+ */
+ public Type getType() {
+ return Type.valueOf(mDecryptedMessage[Request.TYPE_INDEX]);
+ }
+
+ /**
+ * Gets verification data of this handshake request.
+ * Currently, we use Provider's BLE address.
+ */
+ public byte[] getVerificationData() {
+ return Arrays.copyOfRange(
+ mDecryptedMessage,
+ Request.VERIFICATION_DATA_INDEX,
+ Request.VERIFICATION_DATA_INDEX + Request.VERIFICATION_DATA_LENGTH);
+ }
+
+ /** Gets Seeker's public address of the handshake request. */
+ public byte[] getSeekerPublicAddress() {
+ return Arrays.copyOfRange(
+ mDecryptedMessage,
+ Request.SEEKER_PUBLIC_ADDRESS_INDEX,
+ Request.SEEKER_PUBLIC_ADDRESS_INDEX + BLUETOOTH_ADDRESS_LENGTH);
+ }
+
+ /** Checks whether the Seeker request discoverability from flags byte. */
+ public boolean requestDiscoverable() {
+ return (getFlags() & REQUEST_DISCOVERABLE) != 0;
+ }
+
+ /**
+ * Checks whether the Seeker requests that the Provider shall initiate bonding from
+ * flags byte.
+ */
+ public boolean requestProviderInitialBonding() {
+ return (getFlags() & PROVIDER_INITIATES_BONDING) != 0;
+ }
+
+ /** Checks whether the Seeker requests that the Provider shall notify the existing name. */
+ public boolean requestDeviceName() {
+ return (getFlags() & REQUEST_DEVICE_NAME) != 0;
+ }
+
+ /** Checks whether this is for retroactively writing account key. */
+ public boolean requestRetroactivePair() {
+ return (getFlags() & REQUEST_RETROACTIVE_PAIR) != 0;
+ }
+
+ /** Gets the flags of this handshake request. */
+ private byte getFlags() {
+ return mDecryptedMessage[Request.FLAGS_INDEX];
+ }
+
+ /** Checks whether the Seeker requests a device action. */
+ public boolean requestDeviceAction() {
+ return (getFlags() & DEVICE_ACTION) != 0;
+ }
+
+ /**
+ * Checks whether the Seeker requests an action which will be followed by an additional
+ * data.
+ */
+ public boolean requestFollowedByAdditionalData() {
+ return (getFlags() & ADDITIONAL_DATA_CHARACTERISTIC) != 0;
+ }
+
+ /** Gets the {@link AdditionalDataType} of this handshake request. */
+ @AdditionalDataType
+ public int getAdditionalDataType() {
+ if (!requestFollowedByAdditionalData()
+ || mDecryptedMessage.length <= ADDITIONAL_DATA_TYPE_INDEX) {
+ return AdditionalDataType.UNKNOWN;
+ }
+ return mDecryptedMessage[ADDITIONAL_DATA_TYPE_INDEX];
+ }
+
+ /** Enumerates the handshake message types. */
+ public enum Type {
+ KEY_BASED_PAIRING_REQUEST(Request.TYPE_KEY_BASED_PAIRING_REQUEST),
+ ACTION_OVER_BLE(Request.TYPE_ACTION_OVER_BLE),
+ UNKNOWN((byte) 0xFF);
+
+ private final byte mValue;
+
+ Type(byte type) {
+ mValue = type;
+ }
+
+ public static Type valueOf(byte value) {
+ for (Type type : Type.values()) {
+ if (type.getValue() == value) {
+ return type;
+ }
+ }
+ return UNKNOWN;
+ }
+
+ public byte getValue() {
+ return mValue;
+ }
+ }
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/LtvTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/LtvTest.java
new file mode 100644
index 0000000..81a5d92
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/LtvTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.fastpair;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Unit tests for {@link Ltv}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LtvTest {
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testParseEmpty_throwsException() throws Ltv.ParseException {
+ assertThrows(Ltv.ParseException.class,
+ () -> Ltv.parse(new byte[]{0}));
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testParse_finishesSuccessfully() throws Ltv.ParseException {
+ assertThat(Ltv.parse(new byte[]{3, 4, 5, 6})).isNotEmpty();
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothAdapterTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothAdapterTest.java
new file mode 100644
index 0000000..6ebe373
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothAdapterTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.testability.android.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link BluetoothAdapter}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothAdapterTest {
+
+ private static final byte[] BYTES = new byte[]{0, 1, 2, 3, 4, 5};
+ private static final String ADDRESS = "00:11:22:33:AA:BB";
+
+ @Mock private android.bluetooth.BluetoothAdapter mBluetoothAdapter;
+ @Mock private android.bluetooth.BluetoothDevice mBluetoothDevice;
+ @Mock private android.bluetooth.le.BluetoothLeAdvertiser mBluetoothLeAdvertiser;
+ @Mock private android.bluetooth.le.BluetoothLeScanner mBluetoothLeScanner;
+
+ BluetoothAdapter mTestabilityBluetoothAdapter;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTestabilityBluetoothAdapter = BluetoothAdapter.wrap(mBluetoothAdapter);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testWrapNullAdapter_isNull() {
+ assertThat(BluetoothAdapter.wrap(null)).isNull();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testWrapNonNullAdapter_isNotNull_unWrapSame() {
+ assertThat(mTestabilityBluetoothAdapter).isNotNull();
+ assertThat(mTestabilityBluetoothAdapter.unwrap()).isSameInstanceAs(mBluetoothAdapter);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testDisable_callsWrapped() {
+ when(mBluetoothAdapter.disable()).thenReturn(true);
+ assertThat(mTestabilityBluetoothAdapter.disable()).isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testEnable_callsWrapped() {
+ when(mBluetoothAdapter.enable()).thenReturn(true);
+ assertThat(mTestabilityBluetoothAdapter.enable()).isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetBluetoothLeAdvertiser_callsWrapped() {
+ when(mBluetoothAdapter.getBluetoothLeAdvertiser()).thenReturn(mBluetoothLeAdvertiser);
+ assertThat(mTestabilityBluetoothAdapter.getBluetoothLeAdvertiser().unwrap())
+ .isSameInstanceAs(mBluetoothLeAdvertiser);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetBluetoothLeScanner_callsWrapped() {
+ when(mBluetoothAdapter.getBluetoothLeScanner()).thenReturn(mBluetoothLeScanner);
+ assertThat(mTestabilityBluetoothAdapter.getBluetoothLeScanner().unwrap())
+ .isSameInstanceAs(mBluetoothLeScanner);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetBondedDevices_callsWrapped() {
+ when(mBluetoothAdapter.getBondedDevices()).thenReturn(null);
+ assertThat(mTestabilityBluetoothAdapter.getBondedDevices()).isNull();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testIsDiscovering_pcallsWrapped() {
+ when(mBluetoothAdapter.isDiscovering()).thenReturn(true);
+ assertThat(mTestabilityBluetoothAdapter.isDiscovering()).isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testStartDiscovery_callsWrapped() {
+ when(mBluetoothAdapter.startDiscovery()).thenReturn(true);
+ assertThat(mTestabilityBluetoothAdapter.startDiscovery()).isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testCancelDiscovery_callsWrapped() {
+ when(mBluetoothAdapter.cancelDiscovery()).thenReturn(true);
+ assertThat(mTestabilityBluetoothAdapter.cancelDiscovery()).isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetRemoteDeviceBytes_callsWrapped() {
+ when(mBluetoothAdapter.getRemoteDevice(BYTES)).thenReturn(mBluetoothDevice);
+ assertThat(mTestabilityBluetoothAdapter.getRemoteDevice(BYTES).unwrap())
+ .isSameInstanceAs(mBluetoothDevice);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetRemoteDeviceString_callsWrapped() {
+ when(mBluetoothAdapter.getRemoteDevice(ADDRESS)).thenReturn(mBluetoothDevice);
+ assertThat(mTestabilityBluetoothAdapter.getRemoteDevice(ADDRESS).unwrap())
+ .isSameInstanceAs(mBluetoothDevice);
+
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDeviceTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDeviceTest.java
new file mode 100644
index 0000000..a962b16
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDeviceTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.testability.android.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.util.UUID;
+
+/**
+ * Unit tests for {@link BluetoothDevice}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothDeviceTest {
+ private static final UUID UUID_CONST = UUID.randomUUID();
+ private static final String ADDRESS = "ADDRESS";
+ private static final String STRING = "STRING";
+
+ @Mock private android.bluetooth.BluetoothDevice mBluetoothDevice;
+ @Mock private android.bluetooth.BluetoothGatt mBluetoothGatt;
+ @Mock private android.bluetooth.BluetoothSocket mBluetoothSocket;
+ @Mock private android.bluetooth.BluetoothClass mBluetoothClass;
+
+ BluetoothDevice mTestabilityBluetoothDevice;
+ BluetoothGattCallback mTestBluetoothGattCallback;
+ Context mContext;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTestabilityBluetoothDevice = BluetoothDevice.wrap(mBluetoothDevice);
+ mTestBluetoothGattCallback = new TestBluetoothGattCallback();
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testWrapNonNullAdapter_isNotNull_unWrapSame() {
+ assertThat(mTestabilityBluetoothDevice).isNotNull();
+ assertThat(mTestabilityBluetoothDevice.unwrap()).isSameInstanceAs(mBluetoothDevice);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testEquality_asExpected() {
+ assertThat(mTestabilityBluetoothDevice.equals(null)).isFalse();
+ assertThat(mTestabilityBluetoothDevice.equals(mTestabilityBluetoothDevice)).isTrue();
+ assertThat(mTestabilityBluetoothDevice.equals(BluetoothDevice.wrap(mBluetoothDevice)))
+ .isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testConnectGattWithThreeParameters_callsWrapped() {
+ when(mBluetoothDevice
+ .connectGatt(mContext, true, mTestBluetoothGattCallback.unwrap()))
+ .thenReturn(mBluetoothGatt);
+ assertThat(mTestabilityBluetoothDevice
+ .connectGatt(mContext, true, mTestBluetoothGattCallback)
+ .unwrap())
+ .isSameInstanceAs(mBluetoothGatt);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testConnectGattWithFourParameters_callsWrapped() {
+ when(mBluetoothDevice
+ .connectGatt(mContext, true, mTestBluetoothGattCallback.unwrap(), 1))
+ .thenReturn(mBluetoothGatt);
+ assertThat(mTestabilityBluetoothDevice
+ .connectGatt(mContext, true, mTestBluetoothGattCallback, 1)
+ .unwrap())
+ .isSameInstanceAs(mBluetoothGatt);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testCreateRfcommSocketToServiceRecord_callsWrapped() throws IOException {
+ when(mBluetoothDevice.createRfcommSocketToServiceRecord(UUID_CONST))
+ .thenReturn(mBluetoothSocket);
+ assertThat(mTestabilityBluetoothDevice.createRfcommSocketToServiceRecord(UUID_CONST))
+ .isSameInstanceAs(mBluetoothSocket);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testCreateInsecureRfcommSocketToServiceRecord_callsWrapped() throws IOException {
+ when(mBluetoothDevice.createInsecureRfcommSocketToServiceRecord(UUID_CONST))
+ .thenReturn(mBluetoothSocket);
+ assertThat(mTestabilityBluetoothDevice
+ .createInsecureRfcommSocketToServiceRecord(UUID_CONST))
+ .isSameInstanceAs(mBluetoothSocket);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testSetPairingConfirmation_callsWrapped() throws IOException {
+ when(mBluetoothDevice.setPairingConfirmation(true)).thenReturn(true);
+ assertThat(mTestabilityBluetoothDevice.setPairingConfirmation(true)).isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testFetchUuidsWithSdp_callsWrapped() throws IOException {
+ when(mBluetoothDevice.fetchUuidsWithSdp()).thenReturn(true);
+ assertThat(mTestabilityBluetoothDevice.fetchUuidsWithSdp()).isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testCreateBond_callsWrapped() throws IOException {
+ when(mBluetoothDevice.createBond()).thenReturn(true);
+ assertThat(mTestabilityBluetoothDevice.createBond()).isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetUuids_callsWrapped() throws IOException {
+ when(mBluetoothDevice.getUuids()).thenReturn(null);
+ assertThat(mTestabilityBluetoothDevice.getUuids()).isNull();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetBondState_callsWrapped() throws IOException {
+ when(mBluetoothDevice.getBondState()).thenReturn(1);
+ assertThat(mTestabilityBluetoothDevice.getBondState()).isEqualTo(1);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetAddress_callsWrapped() throws IOException {
+ when(mBluetoothDevice.getAddress()).thenReturn(ADDRESS);
+ assertThat(mTestabilityBluetoothDevice.getAddress()).isSameInstanceAs(ADDRESS);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetBluetoothClass_callsWrapped() throws IOException {
+ when(mBluetoothDevice.getBluetoothClass()).thenReturn(mBluetoothClass);
+ assertThat(mTestabilityBluetoothDevice.getBluetoothClass())
+ .isSameInstanceAs(mBluetoothClass);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetType_callsWrapped() throws IOException {
+ when(mBluetoothDevice.getType()).thenReturn(1);
+ assertThat(mTestabilityBluetoothDevice.getType()).isEqualTo(1);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetName_callsWrapped() throws IOException {
+ when(mBluetoothDevice.getName()).thenReturn(STRING);
+ assertThat(mTestabilityBluetoothDevice.getName()).isSameInstanceAs(STRING);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testToString_callsWrapped() {
+ when(mBluetoothDevice.toString()).thenReturn(STRING);
+ assertThat(mTestabilityBluetoothDevice.toString()).isSameInstanceAs(STRING);
+ }
+
+ private static class TestBluetoothGattCallback extends BluetoothGattCallback {}
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallbackTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallbackTest.java
new file mode 100644
index 0000000..26ae6d7
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallbackTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.testability.android.bluetooth;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/**
+ * Unit tests for {@link BluetoothGattCallback}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothGattCallbackTest {
+ @Mock private android.bluetooth.BluetoothGatt mBluetoothGatt;
+ @Mock private android.bluetooth.BluetoothGattCharacteristic mBluetoothGattCharacteristic;
+ @Mock private android.bluetooth.BluetoothGattDescriptor mBluetoothGattDescriptor;
+
+ TestBluetoothGattCallback mTestBluetoothGattCallback = new TestBluetoothGattCallback();
+
+ @Test
+ public void testOnConnectionStateChange_notCrash() {
+ mTestBluetoothGattCallback.unwrap()
+ .onConnectionStateChange(mBluetoothGatt, 1, 1);
+ }
+
+ @Test
+ public void testOnServiceDiscovered_notCrash() {
+ mTestBluetoothGattCallback.unwrap().onServicesDiscovered(mBluetoothGatt, 1);
+ }
+
+ @Test
+ public void testOnCharacteristicRead_notCrash() {
+ mTestBluetoothGattCallback.unwrap().onCharacteristicRead(mBluetoothGatt,
+ mBluetoothGattCharacteristic, 1);
+ }
+
+ @Test
+ public void testOnCharacteristicWrite_notCrash() {
+ mTestBluetoothGattCallback.unwrap().onCharacteristicWrite(mBluetoothGatt,
+ mBluetoothGattCharacteristic, 1);
+ }
+
+ @Test
+ public void testOnDescriptionRead_notCrash() {
+ mTestBluetoothGattCallback.unwrap().onDescriptorRead(mBluetoothGatt,
+ mBluetoothGattDescriptor, 1);
+ }
+
+ @Test
+ public void testOnDescriptionWrite_notCrash() {
+ mTestBluetoothGattCallback.unwrap().onDescriptorWrite(mBluetoothGatt,
+ mBluetoothGattDescriptor, 1);
+ }
+
+ @Test
+ public void testOnReadRemoteRssi_notCrash() {
+ mTestBluetoothGattCallback.unwrap().onReadRemoteRssi(mBluetoothGatt, 1, 1);
+ }
+
+ @Test
+ public void testOnReliableWriteCompleted_notCrash() {
+ mTestBluetoothGattCallback.unwrap().onReliableWriteCompleted(mBluetoothGatt, 1);
+ }
+
+ @Test
+ public void testOnMtuChanged_notCrash() {
+ mTestBluetoothGattCallback.unwrap().onMtuChanged(mBluetoothGatt, 1, 1);
+ }
+
+ @Test
+ public void testOnCharacteristicChanged_notCrash() {
+ mTestBluetoothGattCallback.unwrap()
+ .onCharacteristicChanged(mBluetoothGatt, mBluetoothGattCharacteristic);
+ }
+
+ private static class TestBluetoothGattCallback extends BluetoothGattCallback { }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerCallbackTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerCallbackTest.java
new file mode 100644
index 0000000..fb99317
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerCallbackTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.testability.android.bluetooth;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/**
+ * Unit tests for {@link BluetoothGattServerCallback}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothGattServerCallbackTest {
+ @Mock
+ private android.bluetooth.BluetoothDevice mBluetoothDevice;
+ @Mock
+ private android.bluetooth.BluetoothGattService mBluetoothGattService;
+ @Mock
+ private android.bluetooth.BluetoothGattCharacteristic mBluetoothGattCharacteristic;
+ @Mock
+ private android.bluetooth.BluetoothGattDescriptor mBluetoothGattDescriptor;
+
+ TestBluetoothGattServerCallback
+ mTestBluetoothGattServerCallback = new TestBluetoothGattServerCallback();
+
+ @Test
+ public void testOnCharacteristicReadRequest_notCrash() {
+ mTestBluetoothGattServerCallback.unwrap().onCharacteristicReadRequest(
+ mBluetoothDevice, 1, 1, mBluetoothGattCharacteristic);
+ }
+
+ @Test
+ public void testOnCharacteristicWriteRequest_notCrash() {
+ mTestBluetoothGattServerCallback.unwrap().onCharacteristicWriteRequest(
+ mBluetoothDevice,
+ 1,
+ mBluetoothGattCharacteristic,
+ false,
+ true,
+ 1,
+ new byte[]{1});
+ }
+
+ @Test
+ public void testOnConnectionStateChange_notCrash() {
+ mTestBluetoothGattServerCallback.unwrap().onConnectionStateChange(
+ mBluetoothDevice,
+ 1,
+ 2);
+ }
+
+ @Test
+ public void testOnDescriptorReadRequest_notCrash() {
+ mTestBluetoothGattServerCallback.unwrap().onDescriptorReadRequest(
+ mBluetoothDevice,
+ 1,
+ 2, mBluetoothGattDescriptor);
+ }
+
+ @Test
+ public void testOnDescriptorWriteRequest_notCrash() {
+ mTestBluetoothGattServerCallback.unwrap().onDescriptorWriteRequest(
+ mBluetoothDevice,
+ 1,
+ mBluetoothGattDescriptor,
+ false,
+ true,
+ 2,
+ new byte[]{1});
+ }
+
+ @Test
+ public void testOnExecuteWrite_notCrash() {
+ mTestBluetoothGattServerCallback.unwrap().onExecuteWrite(
+ mBluetoothDevice,
+ 1,
+ false);
+ }
+
+ @Test
+ public void testOnMtuChanged_notCrash() {
+ mTestBluetoothGattServerCallback.unwrap().onMtuChanged(
+ mBluetoothDevice,
+ 1);
+ }
+
+ @Test
+ public void testOnNotificationSent_notCrash() {
+ mTestBluetoothGattServerCallback.unwrap().onNotificationSent(
+ mBluetoothDevice,
+ 1);
+ }
+
+ @Test
+ public void testOnServiceAdded_notCrash() {
+ mTestBluetoothGattServerCallback.unwrap().onServiceAdded(1, mBluetoothGattService);
+ }
+
+ private static class TestBluetoothGattServerCallback extends BluetoothGattServerCallback { }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerTest.java
new file mode 100644
index 0000000..48283d1
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.testability.android.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.UUID;
+
+/**
+ * Unit tests for {@link BluetoothGattServer}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothGattServerTest {
+ private static final UUID UUID_CONST = UUID.randomUUID();
+ private static final byte[] BYTES = new byte[]{1, 2, 3};
+
+ @Mock private android.bluetooth.BluetoothDevice mBluetoothDevice;
+ @Mock private android.bluetooth.BluetoothGattServer mBluetoothGattServer;
+ @Mock private android.bluetooth.BluetoothGattService mBluetoothGattService;
+ @Mock private android.bluetooth.BluetoothGattCharacteristic mBluetoothGattCharacteristic;
+
+ BluetoothGattServer mTestabilityBluetoothGattServer;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTestabilityBluetoothGattServer = BluetoothGattServer.wrap(mBluetoothGattServer);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testWrapNonNullAdapter_isNotNull_unWrapSame() {
+ assertThat(mTestabilityBluetoothGattServer).isNotNull();
+ assertThat(mTestabilityBluetoothGattServer.unwrap()).isSameInstanceAs(mBluetoothGattServer);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testConnect_callsWrapped() {
+ when(mBluetoothGattServer
+ .connect(mBluetoothDevice, true))
+ .thenReturn(true);
+ assertThat(mTestabilityBluetoothGattServer
+ .connect(BluetoothDevice.wrap(mBluetoothDevice), true))
+ .isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testAddService_callsWrapped() {
+ when(mBluetoothGattServer
+ .addService(mBluetoothGattService))
+ .thenReturn(true);
+ assertThat(mTestabilityBluetoothGattServer
+ .addService(mBluetoothGattService))
+ .isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testClearServices_callsWrapped() {
+ doNothing().when(mBluetoothGattServer).clearServices();
+ mTestabilityBluetoothGattServer.clearServices();
+ verify(mBluetoothGattServer).clearServices();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testClose_callsWrapped() {
+ doNothing().when(mBluetoothGattServer).close();
+ mTestabilityBluetoothGattServer.close();
+ verify(mBluetoothGattServer).close();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testNotifyCharacteristicChanged_callsWrapped() {
+ when(mBluetoothGattServer
+ .notifyCharacteristicChanged(
+ mBluetoothDevice,
+ mBluetoothGattCharacteristic,
+ true))
+ .thenReturn(true);
+ assertThat(mTestabilityBluetoothGattServer
+ .notifyCharacteristicChanged(
+ BluetoothDevice.wrap(mBluetoothDevice),
+ mBluetoothGattCharacteristic,
+ true))
+ .isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testSendResponse_callsWrapped() {
+ when(mBluetoothGattServer.sendResponse(
+ mBluetoothDevice, 1, 1, 1, BYTES)).thenReturn(true);
+ mTestabilityBluetoothGattServer.sendResponse(
+ BluetoothDevice.wrap(mBluetoothDevice), 1, 1, 1, BYTES);
+ verify(mBluetoothGattServer).sendResponse(
+ mBluetoothDevice, 1, 1, 1, BYTES);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testCancelConnection_callsWrapped() {
+ doNothing().when(mBluetoothGattServer).cancelConnection(mBluetoothDevice);
+ mTestabilityBluetoothGattServer.cancelConnection(BluetoothDevice.wrap(mBluetoothDevice));
+ verify(mBluetoothGattServer).cancelConnection(mBluetoothDevice);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetService_callsWrapped() {
+ when(mBluetoothGattServer.getService(UUID_CONST)).thenReturn(null);
+ assertThat(mTestabilityBluetoothGattServer.getService(UUID_CONST)).isNull();
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattWrapperTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattWrapperTest.java
new file mode 100644
index 0000000..199146d
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattWrapperTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.testability.android.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.UUID;
+
+/**
+ * Unit tests for {@link BluetoothGattWrapper}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothGattWrapperTest {
+ private static final UUID UUID_CONST = UUID.randomUUID();
+ private static final byte[] BYTES = new byte[]{1, 2, 3};
+
+ @Mock private android.bluetooth.BluetoothDevice mBluetoothDevice;
+ @Mock private android.bluetooth.BluetoothGatt mBluetoothGatt;
+ @Mock private android.bluetooth.BluetoothGattService mBluetoothGattService;
+ @Mock private android.bluetooth.BluetoothGattCharacteristic mBluetoothGattCharacteristic;
+ @Mock private android.bluetooth.BluetoothGattDescriptor mBluetoothGattDescriptor;
+
+ BluetoothGattWrapper mBluetoothGattWrapper;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mBluetoothGattWrapper = BluetoothGattWrapper.wrap(mBluetoothGatt);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testWrapNonNullAdapter_isNotNull_unWrapSame() {
+ assertThat(mBluetoothGattWrapper).isNotNull();
+ assertThat(mBluetoothGattWrapper.unwrap()).isSameInstanceAs(mBluetoothGatt);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testEquality_asExpected() {
+ assertThat(mBluetoothGattWrapper.equals(null)).isFalse();
+ assertThat(mBluetoothGattWrapper.equals(mBluetoothGattWrapper)).isTrue();
+ assertThat(mBluetoothGattWrapper.equals(BluetoothGattWrapper.wrap(mBluetoothGatt)))
+ .isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetDevice_callsWrapped() {
+ when(mBluetoothGatt.getDevice()).thenReturn(mBluetoothDevice);
+ assertThat(mBluetoothGattWrapper.getDevice().unwrap()).isSameInstanceAs(mBluetoothDevice);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetServices_callsWrapped() {
+ when(mBluetoothGatt.getServices()).thenReturn(null);
+ assertThat(mBluetoothGattWrapper.getServices()).isNull();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testGetService_callsWrapped() {
+ when(mBluetoothGatt.getService(UUID_CONST)).thenReturn(mBluetoothGattService);
+ assertThat(mBluetoothGattWrapper.getService(UUID_CONST))
+ .isSameInstanceAs(mBluetoothGattService);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testDiscoverServices_callsWrapped() {
+ when(mBluetoothGatt.discoverServices()).thenReturn(true);
+ assertThat(mBluetoothGattWrapper.discoverServices()).isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testReadCharacteristic_callsWrapped() {
+ when(mBluetoothGatt.readCharacteristic(mBluetoothGattCharacteristic)).thenReturn(true);
+ assertThat(mBluetoothGattWrapper.readCharacteristic(mBluetoothGattCharacteristic)).isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testWriteCharacteristic_callsWrapped() {
+ when(mBluetoothGatt.writeCharacteristic(mBluetoothGattCharacteristic, BYTES, 1))
+ .thenReturn(1);
+ assertThat(mBluetoothGattWrapper.writeCharacteristic(
+ mBluetoothGattCharacteristic, BYTES, 1)).isEqualTo(1);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testReadDescriptor_callsWrapped() {
+ when(mBluetoothGatt.readDescriptor(mBluetoothGattDescriptor)).thenReturn(false);
+ assertThat(mBluetoothGattWrapper.readDescriptor(mBluetoothGattDescriptor)).isFalse();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testWriteDescriptor_callsWrapped() {
+ when(mBluetoothGatt.writeDescriptor(mBluetoothGattDescriptor, BYTES)).thenReturn(5);
+ assertThat(mBluetoothGattWrapper.writeDescriptor(mBluetoothGattDescriptor, BYTES))
+ .isEqualTo(5);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testReadRemoteRssi_callsWrapped() {
+ when(mBluetoothGatt.readRemoteRssi()).thenReturn(false);
+ assertThat(mBluetoothGattWrapper.readRemoteRssi()).isFalse();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testRequestConnectionPriority_callsWrapped() {
+ when(mBluetoothGatt.requestConnectionPriority(5)).thenReturn(false);
+ assertThat(mBluetoothGattWrapper.requestConnectionPriority(5)).isFalse();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testRequestMtu_callsWrapped() {
+ when(mBluetoothGatt.requestMtu(5)).thenReturn(false);
+ assertThat(mBluetoothGattWrapper.requestMtu(5)).isFalse();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testSetCharacteristicNotification_callsWrapped() {
+ when(mBluetoothGatt.setCharacteristicNotification(mBluetoothGattCharacteristic, true))
+ .thenReturn(false);
+ assertThat(mBluetoothGattWrapper
+ .setCharacteristicNotification(mBluetoothGattCharacteristic, true)).isFalse();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testDisconnect_callsWrapped() {
+ doNothing().when(mBluetoothGatt).disconnect();
+ mBluetoothGattWrapper.disconnect();
+ verify(mBluetoothGatt).disconnect();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testClose_callsWrapped() {
+ doNothing().when(mBluetoothGatt).close();
+ mBluetoothGattWrapper.close();
+ verify(mBluetoothGatt).close();
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothAdvertiserTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothAdvertiserTest.java
new file mode 100644
index 0000000..8468ed1
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothAdvertiserTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseSettings;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link BluetoothLeAdvertiser}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothAdvertiserTest {
+ @Mock android.bluetooth.le.BluetoothLeAdvertiser mWrappedBluetoothLeAdvertiser;
+ @Mock AdvertiseSettings mAdvertiseSettings;
+ @Mock AdvertiseData mAdvertiseData;
+ @Mock AdvertiseCallback mAdvertiseCallback;
+
+ BluetoothLeAdvertiser mBluetoothLeAdvertiser;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mBluetoothLeAdvertiser = BluetoothLeAdvertiser.wrap(mWrappedBluetoothLeAdvertiser);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testWrapNullAdapter_isNull() {
+ assertThat(BluetoothLeAdvertiser.wrap(null)).isNull();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testWrapNonNullAdapter_isNotNull_unWrapSame() {
+ assertThat(mWrappedBluetoothLeAdvertiser).isNotNull();
+ assertThat(mBluetoothLeAdvertiser.unwrap()).isSameInstanceAs(mWrappedBluetoothLeAdvertiser);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testStartAdvertisingThreeParameters_callsWrapped() {
+ doNothing().when(mWrappedBluetoothLeAdvertiser)
+ .startAdvertising(mAdvertiseSettings, mAdvertiseData, mAdvertiseCallback);
+ mBluetoothLeAdvertiser
+ .startAdvertising(mAdvertiseSettings, mAdvertiseData, mAdvertiseCallback);
+ verify(mWrappedBluetoothLeAdvertiser).startAdvertising(
+ mAdvertiseSettings, mAdvertiseData, mAdvertiseCallback);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testStartAdvertisingFourParameters_callsWrapped() {
+ doNothing().when(mWrappedBluetoothLeAdvertiser).startAdvertising(
+ mAdvertiseSettings, mAdvertiseData, mAdvertiseData, mAdvertiseCallback);
+ mBluetoothLeAdvertiser.startAdvertising(
+ mAdvertiseSettings, mAdvertiseData, mAdvertiseData, mAdvertiseCallback);
+ verify(mWrappedBluetoothLeAdvertiser).startAdvertising(
+ mAdvertiseSettings, mAdvertiseData, mAdvertiseData, mAdvertiseCallback);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testStopAdvertising_callsWrapped() {
+ doNothing().when(mWrappedBluetoothLeAdvertiser).stopAdvertising(mAdvertiseCallback);
+ mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
+ verify(mWrappedBluetoothLeAdvertiser).stopAdvertising(mAdvertiseCallback);
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScannerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScannerTest.java
new file mode 100644
index 0000000..3fce54f
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScannerTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PendingIntent;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanSettings;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link BluetoothLeScanner}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothLeScannerTest {
+ @Mock android.bluetooth.le.BluetoothLeScanner mWrappedBluetoothLeScanner;
+ @Mock PendingIntent mPendingIntent;
+ @Mock ScanSettings mScanSettings;
+ @Mock ScanFilter mScanFilter;
+
+ TestScanCallback mTestScanCallback = new TestScanCallback();
+ BluetoothLeScanner mBluetoothLeScanner;
+ ImmutableList<ScanFilter> mImmutableScanFilterList;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mBluetoothLeScanner = BluetoothLeScanner.wrap(mWrappedBluetoothLeScanner);
+ mImmutableScanFilterList = ImmutableList.of(mScanFilter);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testWrapNullAdapter_isNull() {
+ assertThat(BluetoothLeAdvertiser.wrap(null)).isNull();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testWrapNonNullAdapter_isNotNull_unWrapSame() {
+ assertThat(mWrappedBluetoothLeScanner).isNotNull();
+ assertThat(mBluetoothLeScanner.unwrap()).isSameInstanceAs(mWrappedBluetoothLeScanner);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testStartScan_callsWrapped() {
+ doNothing().when(mWrappedBluetoothLeScanner).startScan(mTestScanCallback.unwrap());
+ mBluetoothLeScanner.startScan(mTestScanCallback);
+ verify(mWrappedBluetoothLeScanner).startScan(mTestScanCallback.unwrap());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testStartScanWithFiltersCallback_callsWrapped() {
+ doNothing().when(mWrappedBluetoothLeScanner)
+ .startScan(mImmutableScanFilterList, mScanSettings, mTestScanCallback.unwrap());
+ mBluetoothLeScanner.startScan(mImmutableScanFilterList, mScanSettings, mTestScanCallback);
+ verify(mWrappedBluetoothLeScanner)
+ .startScan(mImmutableScanFilterList, mScanSettings, mTestScanCallback.unwrap());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testStartScanWithFiltersCallbackIntent_callsWrapped() {
+ when(mWrappedBluetoothLeScanner.startScan(
+ mImmutableScanFilterList, mScanSettings, mPendingIntent)).thenReturn(1);
+ mBluetoothLeScanner.startScan(mImmutableScanFilterList, mScanSettings, mPendingIntent);
+ verify(mWrappedBluetoothLeScanner)
+ .startScan(mImmutableScanFilterList, mScanSettings, mPendingIntent);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testStopScan_callsWrapped() {
+ doNothing().when(mWrappedBluetoothLeScanner).stopScan(mTestScanCallback.unwrap());
+ mBluetoothLeScanner.stopScan(mTestScanCallback);
+ verify(mWrappedBluetoothLeScanner).stopScan(mTestScanCallback.unwrap());
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testStopScanPendingIntent_callsWrapped() {
+ doNothing().when(mWrappedBluetoothLeScanner).stopScan(mPendingIntent);
+ mBluetoothLeScanner.stopScan(mPendingIntent);
+ verify(mWrappedBluetoothLeScanner).stopScan(mPendingIntent);
+ }
+
+ private static class TestScanCallback extends ScanCallback {};
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanCallbackTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanCallbackTest.java
new file mode 100644
index 0000000..6d68486
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanCallbackTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link ScanCallback}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ScanCallbackTest {
+ @Mock android.bluetooth.le.ScanResult mScanResult;
+
+ TestScanCallback mTestScanCallback = new TestScanCallback();
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testOnScanFailed_notCrash() {
+ mTestScanCallback.unwrap().onScanFailed(1);
+ }
+
+ @Test
+ public void testOnScanResult_notCrash() {
+ mTestScanCallback.unwrap().onScanResult(1, mScanResult);
+ }
+
+ @Test
+ public void testOnBatchScanResult_notCrash() {
+ mTestScanCallback.unwrap().onBatchScanResults(ImmutableList.of(mScanResult));
+ }
+
+ private static class TestScanCallback extends ScanCallback { }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanResultTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanResultTest.java
new file mode 100644
index 0000000..255c178
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanResultTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link ScanResult}.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ScanResultTest {
+
+ @Mock android.bluetooth.le.ScanResult mWrappedScanResult;
+ @Mock android.bluetooth.le.ScanRecord mScanRecord;
+ @Mock android.bluetooth.BluetoothDevice mBluetoothDevice;
+ ScanResult mScanResult;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mScanResult = ScanResult.wrap(mWrappedScanResult);
+ }
+
+ @Test
+ public void testGetScanRecord_calledWrapped() {
+ when(mWrappedScanResult.getScanRecord()).thenReturn(mScanRecord);
+ assertThat(mScanResult.getScanRecord()).isSameInstanceAs(mScanRecord);
+ }
+
+ @Test
+ public void testGetRssi_calledWrapped() {
+ when(mWrappedScanResult.getRssi()).thenReturn(3);
+ assertThat(mScanResult.getRssi()).isEqualTo(3);
+ }
+
+ @Test
+ public void testGetTimestampNanos_calledWrapped() {
+ when(mWrappedScanResult.getTimestampNanos()).thenReturn(4L);
+ assertThat(mScanResult.getTimestampNanos()).isEqualTo(4L);
+ }
+
+ @Test
+ public void testGetDevice_calledWrapped() {
+ when(mWrappedScanResult.getDevice()).thenReturn(mBluetoothDevice);
+ assertThat(mScanResult.getDevice().unwrap()).isSameInstanceAs(mBluetoothDevice);
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java
index 70dcec8..ae8258e 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java
@@ -36,7 +36,6 @@
@Rule
public ExpectedException thrown = ExpectedException.none();
- /*
@Test
public void remove() {
mEventLoop.postRunnable(new NumberedRunnable(0));
@@ -44,10 +43,8 @@
mEventLoop.postRunnable(runnableToAddAndRemove);
mEventLoop.removeRunnable(runnableToAddAndRemove);
mEventLoop.postRunnable(new NumberedRunnable(2));
-
- assertThat(mExecutedRunnables).containsExactly(0, 2);
+ assertThat(mExecutedRunnables).doesNotContain(1);
}
- */
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
@@ -88,4 +85,10 @@
mExecutedRunnables.add(mId);
}
}
+
+ @Test
+ public void postEmptyQueueRunnable() {
+ mEventLoop.postEmptyQueueRunnable(new NumberedRunnable(0));
+ assertThat(mExecutedRunnables).isEmpty();
+ }
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/HandlerEventLoopImplTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/HandlerEventLoopImplTest.java
new file mode 100644
index 0000000..6b5eaa8
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/HandlerEventLoopImplTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.eventloop;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class HandlerEventLoopImplTest {
+ private static final String TAG = "HandlerEventLoopImplTest";
+ private final HandlerEventLoopImpl mHandlerEventLoopImpl =
+ new HandlerEventLoopImpl(TAG);
+ private final List<Integer> mExecutedRunnables = new ArrayList<>();
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void remove() {
+ mHandlerEventLoopImpl.postRunnable(new NumberedRunnable(0));
+ NumberedRunnable runnableToAddAndRemove = new NumberedRunnable(1);
+ mHandlerEventLoopImpl.postRunnable(runnableToAddAndRemove);
+ mHandlerEventLoopImpl.removeRunnable(runnableToAddAndRemove);
+ mHandlerEventLoopImpl.postRunnable(new NumberedRunnable(2));
+ assertThat(mExecutedRunnables).doesNotContain(1);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void isPosted() {
+ NumberedRunnable runnable = new HandlerEventLoopImplTest.NumberedRunnable(0);
+ mHandlerEventLoopImpl.postRunnableDelayed(runnable, 10 * 1000L);
+ assertThat(mHandlerEventLoopImpl.isPosted(runnable)).isTrue();
+ mHandlerEventLoopImpl.removeRunnable(runnable);
+ assertThat(mHandlerEventLoopImpl.isPosted(runnable)).isFalse();
+
+ // Let a runnable execute, then verify that it's not posted.
+ mHandlerEventLoopImpl.postRunnable(runnable);
+ assertThat(mHandlerEventLoopImpl.isPosted(runnable)).isTrue();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void postAndWaitAfterDestroy() throws InterruptedException {
+ mHandlerEventLoopImpl.destroy();
+ mHandlerEventLoopImpl.postAndWait(new HandlerEventLoopImplTest.NumberedRunnable(0));
+ assertThat(mExecutedRunnables).isEmpty();
+ }
+
+
+ private class NumberedRunnable extends NamedRunnable {
+ private final int mId;
+
+ private NumberedRunnable(int id) {
+ super("NumberedRunnable:" + id);
+ this.mId = id;
+ }
+
+ @Override
+ public void run() {
+ // Note: when running in robolectric, this is not actually executed on a different
+ // thread, it's executed in the same thread the test runs in, so this is safe.
+ mExecutedRunnables.add(mId);
+ }
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/NamedRunnableTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/NamedRunnableTest.java
new file mode 100644
index 0000000..7005da1
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/NamedRunnableTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.eventloop;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class NamedRunnableTest {
+ private static final String TAG = "NamedRunnableTest";
+
+ @Test
+ public void testToString() {
+ assertThat(mNamedRunnable.toString()).isEqualTo("Runnable[" + TAG + "]");
+ }
+
+ private final NamedRunnable mNamedRunnable = new NamedRunnable(TAG) {
+ @Override
+ public void run() {
+ }
+ };
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/fastpair/IconUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/fastpair/IconUtilsTest.java
new file mode 100644
index 0000000..d39d9cc
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/fastpair/IconUtilsTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.fastpair;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+
+import org.junit.Test;
+import org.mockito.Mock;
+
+public class IconUtilsTest {
+ private static final int MIN_ICON_SIZE = 16;
+ private static final int DESIRED_ICON_SIZE = 32;
+ @Mock
+ Context mContext;
+
+ @Test
+ public void isIconSizedCorrectly() {
+ // Null bitmap is not sized correctly
+ assertThat(IconUtils.isIconSizeCorrect(null)).isFalse();
+
+ int minIconSize = MIN_ICON_SIZE;
+ int desiredIconSize = DESIRED_ICON_SIZE;
+
+ // Bitmap that is 1x1 pixels is not sized correctly
+ Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
+ assertThat(IconUtils.isIconSizeCorrect(icon)).isFalse();
+
+ // Bitmap is categorized as small, and not regular
+ icon = Bitmap.createBitmap(minIconSize + 1,
+ minIconSize + 1, Bitmap.Config.ALPHA_8);
+ assertThat(IconUtils.isIconSizeCorrect(icon)).isTrue();
+ assertThat(IconUtils.isIconSizedSmall(icon)).isTrue();
+ assertThat(IconUtils.isIconSizedRegular(icon)).isFalse();
+
+ // Bitmap is categorized as regular, but not small
+ icon = Bitmap.createBitmap(desiredIconSize + 1,
+ desiredIconSize + 1, Bitmap.Config.ALPHA_8);
+ assertThat(IconUtils.isIconSizeCorrect(icon)).isTrue();
+ assertThat(IconUtils.isIconSizedSmall(icon)).isFalse();
+ assertThat(IconUtils.isIconSizedRegular(icon)).isTrue();
+ }
+
+ @Test
+ public void testAddWhiteCircleBackground() {
+ int minIconSize = MIN_ICON_SIZE;
+ Bitmap icon = Bitmap.createBitmap(minIconSize + 1, minIconSize + 1,
+ Bitmap.Config.ALPHA_8);
+
+ assertThat(
+ IconUtils.isIconSizeCorrect(IconUtils.addWhiteCircleBackground(mContext, icon)))
+ .isTrue();
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/locator/LocatorTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/locator/LocatorTest.java
new file mode 100644
index 0000000..c3a4e55
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/locator/LocatorTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.locator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.nearby.common.eventloop.EventLoop;
+import com.android.server.nearby.fastpair.FastPairAdvHandler;
+import com.android.server.nearby.fastpair.FastPairModule;
+import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
+import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
+import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Clock;
+
+import src.com.android.server.nearby.fastpair.testing.MockingLocator;
+
+public class LocatorTest {
+ private Locator mLocator;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mLocator = src.com.android.server.nearby.fastpair.testing.MockingLocator.withMocksOnly(
+ ApplicationProvider.getApplicationContext());
+ mLocator.bind(new FastPairModule());
+ }
+
+ @Test
+ public void genericConstructor() {
+ assertThat(mLocator.get(FastPairCacheManager.class)).isNotNull();
+ assertThat(mLocator.get(FootprintsDeviceManager.class)).isNotNull();
+ assertThat(mLocator.get(EventLoop.class)).isNotNull();
+ assertThat(mLocator.get(FastPairHalfSheetManager.class)).isNotNull();
+ assertThat(mLocator.get(FastPairAdvHandler.class)).isNotNull();
+ assertThat(mLocator.get(Clock.class)).isNotNull();
+ }
+
+ @Test
+ public void genericDestroy() {
+ mLocator.destroy();
+ }
+
+ @Test
+ public void getOptional() {
+ assertThat(mLocator.getOptional(FastPairModule.class)).isNotNull();
+ mLocator.removeBindingForTest(FastPairModule.class);
+ assertThat(mLocator.getOptional(FastPairModule.class)).isNull();
+ }
+
+ @Test
+ public void getParent() {
+ assertThat(mLocator.getParent()).isNotNull();
+ }
+
+ @Test
+ public void getUnboundErrorMessage() {
+ assertThat(mLocator.getUnboundErrorMessage(FastPairModule.class))
+ .isEqualTo(
+ "Unbound type: com.android.server.nearby.fastpair.FastPairModule\n"
+ + "Searched locators:\n" + "android.app.Application ->\n"
+ + "android.app.Application ->\n" + "android.app.Application");
+ }
+
+ @Test
+ public void getContextForTest() {
+ src.com.android.server.nearby.fastpair.testing.MockingLocator mockingLocator =
+ new MockingLocator(ApplicationProvider.getApplicationContext(), mLocator);
+ assertThat(mockingLocator.getContextForTest()).isNotNull();
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/servicemonitor/PackageWatcherTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/servicemonitor/PackageWatcherTest.java
new file mode 100644
index 0000000..eafc7db
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/common/servicemonitor/PackageWatcherTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.common.servicemonitor;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Test;
+
+public class PackageWatcherTest {
+ private PackageWatcher mPackageWatcher = new PackageWatcher() {
+ @Override
+ public void onSomePackagesChanged() {
+ }
+ };
+
+ @Test
+ public void getPackageName() {
+ Intent intent = new Intent("Action", null);
+ assertThat(mPackageWatcher.getPackageName(intent)).isNull();
+ }
+
+ @Test
+ public void onReceive() {
+ Intent intent = new Intent(Intent.ACTION_PACKAGES_UNSUSPENDED, null);
+ mPackageWatcher.onReceive(ApplicationProvider.getApplicationContext(), intent);
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java
index 346a961..01f2f46 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java
@@ -16,24 +16,33 @@
package com.android.server.nearby.fastpair;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.accounts.Account;
import android.content.Context;
import android.nearby.FastPairDevice;
+import com.android.server.nearby.common.bloomfilter.BloomFilter;
import com.android.server.nearby.common.locator.LocatorContextWrapper;
import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
import com.android.server.nearby.provider.FastPairDataProvider;
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.ByteString;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import service.proto.Cache;
+import service.proto.Data;
import service.proto.Rpcs;
public class FastPairAdvHandlerTest {
@@ -45,11 +54,22 @@
private FastPairHalfSheetManager mFastPairHalfSheetManager;
@Mock
private FastPairNotificationManager mFastPairNotificationManager;
+ @Mock
+ private BloomFilter mBloomFilter;
+ @Mock
+ Cache.StoredDiscoveryItem mStoredDiscoveryItem;
+ @Mock
+ Cache.StoredFastPairItem mStoredFastPairItem;
+ @Mock
+ Data.FastPairDeviceWithAccountKey mFastPairDeviceWithAccountKey;
+ private static final byte[] ACCOUNT_KEY = new byte[] {0, 1, 2};
private static final String BLUETOOTH_ADDRESS = "AA:BB:CC:DD";
private static final int CLOSE_RSSI = -80;
private static final int FAR_AWAY_RSSI = -120;
private static final int TX_POWER = -70;
private static final byte[] INITIAL_BYTE_ARRAY = new byte[]{0x01, 0x02, 0x03};
+ private static final byte[] SALT = new byte[]{0x01};
+ private static final Account ACCOUNT = new Account("abc@google.com", "type1");
LocatorContextWrapper mLocatorContextWrapper;
FastPairAdvHandler mFastPairAdvHandler;
@@ -99,7 +119,7 @@
}
@Test
- public void testSubsequentBroadcast() {
+ public void testSubsequentBroadcast_notShowHalfSheet() {
byte[] fastPairRecordWithBloomFilter =
new byte[]{
(byte) 0x02,
@@ -132,4 +152,44 @@
verify(mFastPairHalfSheetManager, never()).showHalfSheet(any());
}
+
+ @Test
+ public void testFindRecognizedDevice_bloomFilterNotContains_notFound() {
+ when(mFastPairDeviceWithAccountKey.getAccountKey())
+ .thenReturn(ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(ACCOUNT_KEY));
+ when(mBloomFilter.possiblyContains(any(byte[].class))).thenReturn(false);
+
+ assertThat(FastPairAdvHandler.findRecognizedDevice(
+ ImmutableList.of(mFastPairDeviceWithAccountKey), mBloomFilter, SALT)).isNull();
+ }
+
+ @Test
+ public void testFindRecognizedDevice_bloomFilterContains_found() {
+ when(mFastPairDeviceWithAccountKey.getAccountKey())
+ .thenReturn(ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(ACCOUNT_KEY));
+ when(mBloomFilter.possiblyContains(any(byte[].class))).thenReturn(true);
+
+ assertThat(FastPairAdvHandler.findRecognizedDevice(
+ ImmutableList.of(mFastPairDeviceWithAccountKey), mBloomFilter, SALT)).isNotNull();
+ }
+
+ @Test
+ public void testFindRecognizedDeviceFromCachedItem_bloomFilterNotContains_notFound() {
+ when(mStoredFastPairItem.getAccountKey())
+ .thenReturn(ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(ACCOUNT_KEY));
+ when(mBloomFilter.possiblyContains(any(byte[].class))).thenReturn(false);
+
+ assertThat(FastPairAdvHandler.findRecognizedDeviceFromCachedItem(
+ ImmutableList.of(mStoredFastPairItem), mBloomFilter, SALT)).isNull();
+ }
+
+ @Test
+ public void testFindRecognizedDeviceFromCachedItem_bloomFilterContains_found() {
+ when(mStoredFastPairItem.getAccountKey())
+ .thenReturn(ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(ACCOUNT_KEY));
+ when(mBloomFilter.possiblyContains(any(byte[].class))).thenReturn(true);
+
+ assertThat(FastPairAdvHandler.findRecognizedDeviceFromCachedItem(
+ ImmutableList.of(mStoredFastPairItem), mBloomFilter, SALT)).isNotNull();
+ }
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FlagUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FlagUtilsTest.java
new file mode 100644
index 0000000..9cf65f4
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FlagUtilsTest.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair;
+
+import org.junit.Test;
+
+public class FlagUtilsTest {
+
+ @Test
+ public void testGetPreferencesBuilder_notCrash() {
+ FlagUtils.getPreferencesBuilder().build();
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/DiscoveryItemTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/DiscoveryItemTest.java
new file mode 100644
index 0000000..5d4ea22
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/DiscoveryItemTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair.cache;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
+import com.android.server.nearby.fastpair.FastPairManager;
+import com.android.server.nearby.fastpair.testing.FakeDiscoveryItems;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import service.proto.Cache;
+
+/** Unit tests for {@link DiscoveryItem} */
+public class DiscoveryItemTest {
+ private static final String DEFAULT_MAC_ADDRESS = "00:11:22:33:44:55";
+ private static final String DEFAULT_DESCRIPITON = "description";
+ private static final long DEFAULT_TIMESTAMP = 1000000000L;
+ private static final String DEFAULT_TITLE = "title";
+ private static final String APP_NAME = "app_name";
+ private static final String ACTION_URL =
+ "intent:#Intent;action=com.android.server.nearby:ACTION_FAST_PAIR;"
+ + "package=com.google.android.gms;"
+ + "component=com.google.android.gms/"
+ + ".nearby.discovery.service.DiscoveryService;end";
+ private static final String DISPLAY_URL = "DISPLAY_URL";
+ private static final String TRIGGER_ID = "trigger.id";
+ private static final String FAST_PAIR_ID = "id";
+ private static final int RSSI = -80;
+ private static final int TX_POWER = -10;
+
+ @Mock private Context mContext;
+ private LocatorContextWrapper mLocatorContextWrapper;
+ private FastPairCacheManager mFastPairCacheManager;
+ private FastPairManager mFastPairManager;
+ private DiscoveryItem mDiscoveryItem;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mLocatorContextWrapper = new LocatorContextWrapper(mContext);
+ mFastPairManager = new FastPairManager(mLocatorContextWrapper);
+ mFastPairCacheManager = mLocatorContextWrapper.getLocator().get(FastPairCacheManager.class);
+ when(mContext.getContentResolver()).thenReturn(
+ InstrumentationRegistry.getInstrumentation().getContext().getContentResolver());
+ mDiscoveryItem =
+ FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+ }
+
+ @Test
+ public void testMultipleFields() {
+ assertThat(mDiscoveryItem.getId()).isEqualTo(FAST_PAIR_ID);
+ assertThat(mDiscoveryItem.getDescription()).isEqualTo(DEFAULT_DESCRIPITON);
+ assertThat(mDiscoveryItem.getDisplayUrl()).isEqualTo(DISPLAY_URL);
+ assertThat(mDiscoveryItem.getTriggerId()).isEqualTo(TRIGGER_ID);
+ assertThat(mDiscoveryItem.getMacAddress()).isEqualTo(DEFAULT_MAC_ADDRESS);
+ assertThat(
+ mDiscoveryItem.getFirstObservationTimestampMillis()).isEqualTo(DEFAULT_TIMESTAMP);
+ assertThat(
+ mDiscoveryItem.getLastObservationTimestampMillis()).isEqualTo(DEFAULT_TIMESTAMP);
+ assertThat(mDiscoveryItem.getActionUrl()).isEqualTo(ACTION_URL);
+ assertThat(mDiscoveryItem.getAppName()).isEqualTo(APP_NAME);
+ assertThat(mDiscoveryItem.getRssi()).isEqualTo(RSSI);
+ assertThat(mDiscoveryItem.getTxPower()).isEqualTo(TX_POWER);
+ assertThat(mDiscoveryItem.getFastPairInformation()).isNull();
+ assertThat(mDiscoveryItem.getFastPairSecretKey()).isNull();
+ assertThat(mDiscoveryItem.getIcon()).isNull();
+ assertThat(mDiscoveryItem.getIconFifeUrl()).isNotNull();
+ assertThat(mDiscoveryItem.getState()).isNotNull();
+ assertThat(mDiscoveryItem.getTitle()).isNotNull();
+ assertThat(mDiscoveryItem.isApp()).isFalse();
+ assertThat(mDiscoveryItem.isDeletable(
+ 100000L, 0L)).isTrue();
+ assertThat(mDiscoveryItem.isDeviceType(Cache.NearbyType.NEARBY_CHROMECAST)).isTrue();
+ assertThat(mDiscoveryItem.isExpired(
+ 100000L, 0L)).isTrue();
+ assertThat(mDiscoveryItem.isFastPair()).isTrue();
+ assertThat(mDiscoveryItem.isPendingAppInstallValid(5)).isTrue();
+ assertThat(mDiscoveryItem.isPendingAppInstallValid(5,
+ FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, null,
+ TRIGGER_ID, DEFAULT_MAC_ADDRESS, "", RSSI, TX_POWER))).isTrue();
+ assertThat(mDiscoveryItem.isTypeEnabled(Cache.NearbyType.NEARBY_CHROMECAST)).isTrue();
+ assertThat(mDiscoveryItem.toString()).isNotNull();
+ }
+
+ @Test
+ public void isMuted() {
+ assertThat(mDiscoveryItem.isMuted()).isFalse();
+ }
+
+ @Test
+ public void itemWithDefaultDescription_shouldShowUp() {
+ assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse();
+
+ // Null description should not show up.
+ mDiscoveryItem.setStoredItemForTest(DiscoveryItem.newStoredDiscoveryItem());
+ mDiscoveryItem.setStoredItemForTest(
+ FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, null,
+ TRIGGER_ID, DEFAULT_MAC_ADDRESS, "", RSSI, TX_POWER));
+ assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse();
+
+ // Empty description should not show up.
+ mDiscoveryItem.setStoredItemForTest(
+ FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, "",
+ TRIGGER_ID, DEFAULT_MAC_ADDRESS, DEFAULT_TITLE, RSSI, TX_POWER));
+ assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse();
+ }
+
+ @Test
+ public void itemWithEmptyTitle_shouldNotShowUp() {
+ // Null title should not show up.
+ assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse();
+ // Empty title should not show up.
+ mDiscoveryItem.setStoredItemForTest(
+ FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+ TRIGGER_ID, DEFAULT_MAC_ADDRESS, "", RSSI, TX_POWER));
+ assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse();
+
+ // Null title should not show up.
+ mDiscoveryItem.setStoredItemForTest(
+ FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+ TRIGGER_ID, DEFAULT_MAC_ADDRESS, null, RSSI, TX_POWER));
+ assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse();
+ }
+
+ @Test
+ public void itemWithRssiAndTxPower_shouldHaveCorrectEstimatedDistance() {
+ assertThat(mDiscoveryItem.getEstimatedDistance()).isWithin(0.01).of(28.18);
+ }
+
+ @Test
+ public void itemWithoutRssiOrTxPower_shouldNotHaveEstimatedDistance() {
+ mDiscoveryItem.setStoredItemForTest(
+ FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+ TRIGGER_ID, DEFAULT_MAC_ADDRESS, "", 0, 0));
+ assertThat(mDiscoveryItem.getEstimatedDistance()).isWithin(0.01).of(0);
+ }
+
+ @Test
+ public void getUiHashCode_differentAddress_differentHash() {
+ mDiscoveryItem.setStoredItemForTest(
+ FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+ TRIGGER_ID, "00:11:22:33:44:55", "", RSSI, TX_POWER));
+ DiscoveryItem compareTo =
+ FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+ compareTo.setStoredItemForTest(
+ FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+ TRIGGER_ID, "55:44:33:22:11:00", "", RSSI, TX_POWER));
+ assertThat(mDiscoveryItem.getUiHashCode()).isNotEqualTo(compareTo.getUiHashCode());
+ }
+
+ @Test
+ public void getUiHashCode_sameAddress_sameHash() {
+ mDiscoveryItem.setStoredItemForTest(
+ FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+ TRIGGER_ID, "00:11:22:33:44:55", "", RSSI, TX_POWER));
+ DiscoveryItem compareTo =
+ FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+ compareTo.setStoredItemForTest(
+ FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+ TRIGGER_ID, "00:11:22:33:44:55", "", RSSI, TX_POWER));
+ assertThat(mDiscoveryItem.getUiHashCode()).isEqualTo(compareTo.getUiHashCode());
+ }
+
+ @Test
+ public void isFastPair() {
+ DiscoveryItem fastPairItem =
+ FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+ assertThat(fastPairItem.isFastPair()).isTrue();
+ }
+
+ @Test
+ public void testEqual() {
+ DiscoveryItem fastPairItem =
+ FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+ assertThat(mDiscoveryItem.equals(fastPairItem)).isTrue();
+ }
+
+ @Test
+ public void testCompareTo() {
+ DiscoveryItem fastPairItem =
+ FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+ assertThat(mDiscoveryItem.compareTo(fastPairItem)).isEqualTo(0);
+ }
+
+
+ @Test
+ public void testCopyOfStoredItem() {
+ DiscoveryItem fastPairItem =
+ FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+ fastPairItem.setStoredItemForTest(
+ FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+ TRIGGER_ID, "00:11:22:33:44:55", "", RSSI, TX_POWER));
+ assertThat(mDiscoveryItem.equals(fastPairItem)).isFalse();
+ fastPairItem.setStoredItemForTest(mDiscoveryItem.getCopyOfStoredItem());
+ assertThat(mDiscoveryItem.equals(fastPairItem)).isTrue();
+ }
+
+ @Test
+ public void testStoredItemForTest() {
+ DiscoveryItem fastPairItem =
+ FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper);
+ fastPairItem.setStoredItemForTest(
+ FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON,
+ TRIGGER_ID, "00:11:22:33:44:55", "", RSSI, TX_POWER));
+ assertThat(mDiscoveryItem.equals(fastPairItem)).isFalse();
+ fastPairItem.setStoredItemForTest(mDiscoveryItem.getStoredItemForTest());
+ assertThat(mDiscoveryItem.equals(fastPairItem)).isTrue();
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
index adae97d..0f6fb19 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
@@ -20,6 +20,7 @@
import static org.mockito.Mockito.when;
+import android.bluetooth.le.ScanResult;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@@ -43,6 +44,7 @@
private static final ByteString ACCOUNT_KEY = ByteString.copyFromUtf8("axgs");
private static final String MAC_ADDRESS_B = "00:11:22:44";
private static final ByteString ACCOUNT_KEY_B = ByteString.copyFromUtf8("axgb");
+ private static final String ITEM_ID = "ITEM_ID";
@Mock
DiscoveryItem mDiscoveryItem;
@@ -50,6 +52,10 @@
DiscoveryItem mDiscoveryItem2;
@Mock
Cache.StoredFastPairItem mStoredFastPairItem;
+ @Mock
+ ScanResult mScanResult;
+
+ Context mContext;
Cache.StoredDiscoveryItem mStoredDiscoveryItem = Cache.StoredDiscoveryItem.newBuilder()
.setTriggerId(MODEL_ID)
.setAppName(APP_NAME).build();
@@ -60,12 +66,12 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ mContext = ApplicationProvider.getApplicationContext();
}
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void notSaveRetrieveInfo() {
- Context mContext = ApplicationProvider.getApplicationContext();
when(mDiscoveryItem.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem);
when(mDiscoveryItem.getTriggerId()).thenReturn(MODEL_ID);
@@ -78,7 +84,6 @@
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void saveRetrieveInfo() {
- Context mContext = ApplicationProvider.getApplicationContext();
when(mDiscoveryItem.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem);
when(mDiscoveryItem.getTriggerId()).thenReturn(MODEL_ID);
@@ -91,7 +96,6 @@
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void getAllInfo() {
- Context mContext = ApplicationProvider.getApplicationContext();
when(mDiscoveryItem.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem);
when(mDiscoveryItem.getTriggerId()).thenReturn(MODEL_ID);
when(mDiscoveryItem2.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem2);
@@ -105,12 +109,13 @@
fastPairCacheManager.saveDiscoveryItem(mDiscoveryItem2);
assertThat(fastPairCacheManager.getAllSavedStoreDiscoveryItem()).hasSize(3);
+
+ fastPairCacheManager.cleanUp();
}
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void saveRetrieveInfoStoredFastPairItem() {
- Context mContext = ApplicationProvider.getApplicationContext();
Cache.StoredFastPairItem storedFastPairItem = Cache.StoredFastPairItem.newBuilder()
.setMacAddress(MAC_ADDRESS)
.setAccountKey(ACCOUNT_KEY)
@@ -128,7 +133,6 @@
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void checkGetAllFastPairItems() {
- Context mContext = ApplicationProvider.getApplicationContext();
Cache.StoredFastPairItem storedFastPairItem = Cache.StoredFastPairItem.newBuilder()
.setMacAddress(MAC_ADDRESS)
.setAccountKey(ACCOUNT_KEY)
@@ -149,5 +153,15 @@
assertThat(fastPairCacheManager.getAllSavedStoredFastPairItem().size())
.isEqualTo(1);
+
+ fastPairCacheManager.cleanUp();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void getDeviceFromScanResult_notCrash() {
+ FastPairCacheManager fastPairCacheManager = new FastPairCacheManager(mContext);
+ fastPairCacheManager.getDeviceFromScanResult(mScanResult);
+
}
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairDbHelperTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairDbHelperTest.java
new file mode 100644
index 0000000..c5428f5
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairDbHelperTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.server.nearby.fastpair.cache;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteException;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+
+public class FastPairDbHelperTest {
+
+ Context mContext;
+ FastPairDbHelper mFastPairDbHelper;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ mFastPairDbHelper = new FastPairDbHelper(mContext);
+ }
+
+ @After
+ public void teardown() {
+ mFastPairDbHelper.close();
+ }
+
+ @Test
+ public void testUpgrade_notCrash() {
+ mFastPairDbHelper
+ .onUpgrade(mFastPairDbHelper.getWritableDatabase(), 1, 2);
+ }
+
+ @Test
+ public void testDowngrade_throwsException() {
+ assertThrows(
+ SQLiteException.class,
+ () -> mFastPairDbHelper.onDowngrade(
+ mFastPairDbHelper.getWritableDatabase(), 2, 1));
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java
index 58e4c47..b51a295 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java
@@ -16,6 +16,8 @@
package com.android.server.nearby.fastpair.halfsheet;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -25,6 +27,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -32,6 +35,8 @@
import android.content.pm.ResolveInfo;
import android.os.UserHandle;
+import androidx.test.platform.app.InstrumentationRegistry;
+
import com.android.server.nearby.common.locator.Locator;
import com.android.server.nearby.common.locator.LocatorContextWrapper;
import com.android.server.nearby.fastpair.FastPairController;
@@ -50,13 +55,13 @@
public class FastPairHalfSheetManagerTest {
private static final String BLEADDRESS = "11:22:44:66";
private static final String NAME = "device_name";
+ private static final int PASSKEY = 1234;
private FastPairHalfSheetManager mFastPairHalfSheetManager;
private Cache.ScanFastPairStoreItem mScanFastPairStoreItem;
+ @Mock private Context mContext;
@Mock
LocatorContextWrapper mContextWrapper;
@Mock
- ResolveInfo mResolveInfo;
- @Mock
PackageManager mPackageManager;
@Mock
Locator mLocator;
@@ -66,7 +71,8 @@
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
-
+ when(mContext.getContentResolver()).thenReturn(
+ InstrumentationRegistry.getInstrumentation().getContext().getContentResolver());
mScanFastPairStoreItem = Cache.ScanFastPairStoreItem.newBuilder()
.setAddress(BLEADDRESS)
.setDeviceName(NAME)
@@ -133,4 +139,24 @@
verify(mContextWrapper, never())
.startActivityAsUser(intentArgumentCaptor.capture(), eq(UserHandle.CURRENT));
}
+
+ @Test
+ public void getHalfSheetForegroundState() {
+ mFastPairHalfSheetManager =
+ new FastPairHalfSheetManager(mContextWrapper);
+ assertThat(mFastPairHalfSheetManager.getHalfSheetForegroundState()).isTrue();
+ }
+
+ @Test
+ public void testEmptyMethods() {
+ mFastPairHalfSheetManager =
+ new FastPairHalfSheetManager(mContextWrapper);
+ mFastPairHalfSheetManager.destroyBluetoothPairController();
+ mFastPairHalfSheetManager.disableDismissRunnable();
+ mFastPairHalfSheetManager.notifyPairingProcessDone(true, BLEADDRESS, null);
+ mFastPairHalfSheetManager.showPairingFailed();
+ mFastPairHalfSheetManager.showPairingHalfSheet(null);
+ mFastPairHalfSheetManager.showPairingSuccessHalfSheet(BLEADDRESS);
+ mFastPairHalfSheetManager.showPasskeyConfirmation(null, PASSKEY);
+ }
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationManagerTest.java
new file mode 100644
index 0000000..4fb6b37
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationManagerTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair.notification;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class FastPairNotificationManagerTest {
+
+ @Mock private Context mContext;
+ private static final boolean USE_LARGE_ICON = true;
+ private static final int NOTIFICATION_ID = 1;
+ private static final String COMPANION_APP = "companionApp";
+ private static final int BATTERY_LEVEL = 1;
+ private static final String DEVICE_NAME = "deviceName";
+ private static final String ADDRESS = "address";
+ private FastPairNotificationManager mFastPairNotificationManager;
+ private LocatorContextWrapper mLocatorContextWrapper;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mLocatorContextWrapper = new LocatorContextWrapper(mContext);
+ when(mContext.getContentResolver()).thenReturn(
+ InstrumentationRegistry.getInstrumentation().getContext().getContentResolver());
+ mFastPairNotificationManager =
+ new FastPairNotificationManager(mLocatorContextWrapper, null,
+ USE_LARGE_ICON, NOTIFICATION_ID);
+ }
+
+ @Test
+ public void notifyPairingProcessDone() {
+ mFastPairNotificationManager.notifyPairingProcessDone(true, true,
+ "privateAddress", "publicAddress");
+ }
+
+ @Test
+ public void showConnectingNotification() {
+ mFastPairNotificationManager.showConnectingNotification();
+ }
+
+ @Test
+ public void showPairingFailedNotification() {
+ mFastPairNotificationManager.showPairingFailedNotification(new byte[]{1});
+ }
+
+ @Test
+ public void showPairingSucceededNotification() {
+ mFastPairNotificationManager.showPairingSucceededNotification(COMPANION_APP,
+ BATTERY_LEVEL, DEVICE_NAME, ADDRESS);
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandlerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandlerTest.java
new file mode 100644
index 0000000..bfe009c
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandlerTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair.pairinghandler;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+
+import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
+import com.android.server.nearby.common.locator.Locator;
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
+import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
+import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
+import com.android.server.nearby.fastpair.testing.FakeDiscoveryItems;
+
+import com.google.protobuf.ByteString;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Clock;
+
+import service.proto.Cache;
+import service.proto.Rpcs;
+
+public class HalfSheetPairingProgressHandlerTest {
+ @Mock
+ Locator mLocator;
+ @Mock
+ LocatorContextWrapper mContextWrapper;
+ @Mock
+ Clock mClock;
+ @Mock
+ FastPairCacheManager mFastPairCacheManager;
+ @Mock
+ FastPairConnection mFastPairConnection;
+ @Mock
+ FootprintsDeviceManager mFootprintsDeviceManager;
+
+ private static final String MAC_ADDRESS = "00:11:22:33:44:55";
+ private static final byte[] ACCOUNT_KEY = new byte[]{0x01, 0x02};
+ private static final int SUBSEQUENT_PAIR_START = 1310;
+ private static final int SUBSEQUENT_PAIR_END = 1320;
+ private static final int PASSKEY = 1234;
+ private static HalfSheetPairingProgressHandler sHalfSheetPairingProgressHandler;
+ private static DiscoveryItem sDiscoveryItem;
+ private static BluetoothDevice sBluetoothDevice;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mContextWrapper.getLocator()).thenReturn(mLocator);
+ mLocator.overrideBindingForTest(FastPairCacheManager.class, mFastPairCacheManager);
+ mLocator.overrideBindingForTest(Clock.class, mClock);
+ FastPairHalfSheetManager mfastPairHalfSheetManager =
+ new FastPairHalfSheetManager(mContextWrapper);
+ mLocator.bind(FastPairHalfSheetManager.class, mfastPairHalfSheetManager);
+ when(mLocator.get(FastPairHalfSheetManager.class)).thenReturn(mfastPairHalfSheetManager);
+ sDiscoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper);
+ sDiscoveryItem.setStoredItemForTest(
+ sDiscoveryItem.getStoredItemForTest().toBuilder()
+ .setAuthenticationPublicKeySecp256R1(ByteString.copyFrom(ACCOUNT_KEY))
+ .setMacAddress(MAC_ADDRESS)
+ .setFastPairInformation(
+ Cache.FastPairInformation.newBuilder()
+ .setDeviceType(Rpcs.DeviceType.HEADPHONES).build())
+ .build());
+ sHalfSheetPairingProgressHandler =
+ new HalfSheetPairingProgressHandler(mContextWrapper, sDiscoveryItem,
+ sDiscoveryItem.getAppPackageName(), ACCOUNT_KEY);
+
+ sBluetoothDevice =
+ BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:11:22:33:44:55");
+ }
+
+ @Test
+ public void getPairEndEventCode() {
+ assertThat(sHalfSheetPairingProgressHandler
+ .getPairEndEventCode()).isEqualTo(SUBSEQUENT_PAIR_END);
+ }
+
+ @Test
+ public void getPairStartEventCode() {
+ assertThat(sHalfSheetPairingProgressHandler
+ .getPairStartEventCode()).isEqualTo(SUBSEQUENT_PAIR_START);
+ }
+
+ @Test
+ public void testOnHandlePasskeyConfirmation() {
+ sHalfSheetPairingProgressHandler.onHandlePasskeyConfirmation(sBluetoothDevice, PASSKEY);
+ }
+
+ @Test
+ public void testOnPairedCallbackCalled() {
+ sHalfSheetPairingProgressHandler.onPairedCallbackCalled(mFastPairConnection, ACCOUNT_KEY,
+ mFootprintsDeviceManager, MAC_ADDRESS);
+ }
+
+ @Test
+ public void testonPairingFailed() {
+ Throwable e = new Throwable("onPairingFailed");
+ sHalfSheetPairingProgressHandler.onPairingFailed(e);
+ }
+
+ @Test
+ public void testonPairingStarted() {
+ sHalfSheetPairingProgressHandler.onPairingStarted();
+ }
+
+ @Test
+ public void testonPairingSuccess() {
+ sHalfSheetPairingProgressHandler.onPairingSuccess(MAC_ADDRESS);
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandlerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandlerTest.java
new file mode 100644
index 0000000..24f296c
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandlerTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.fastpair.pairinghandler;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothManager;
+
+import androidx.annotation.Nullable;
+
+import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
+import com.android.server.nearby.common.locator.Locator;
+import com.android.server.nearby.common.locator.LocatorContextWrapper;
+import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
+import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
+import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
+import com.android.server.nearby.fastpair.notification.FastPairNotificationManager;
+import com.android.server.nearby.fastpair.testing.FakeDiscoveryItems;
+
+import com.google.protobuf.ByteString;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Clock;
+
+import service.proto.Cache;
+import service.proto.Rpcs;
+
+public class NotificationPairingProgressHandlerTest {
+
+ @Mock
+ Locator mLocator;
+ @Mock
+ LocatorContextWrapper mContextWrapper;
+ @Mock
+ Clock mClock;
+ @Mock
+ FastPairCacheManager mFastPairCacheManager;
+ @Mock
+ FastPairConnection mFastPairConnection;
+ @Mock
+ FootprintsDeviceManager mFootprintsDeviceManager;
+ @Mock
+ android.bluetooth.BluetoothManager mBluetoothManager;
+
+ private static final String MAC_ADDRESS = "00:11:22:33:44:55";
+ private static final byte[] ACCOUNT_KEY = new byte[]{0x01, 0x02};
+ private static final int SUBSEQUENT_PAIR_START = 1310;
+ private static final int SUBSEQUENT_PAIR_END = 1320;
+ private static DiscoveryItem sDiscoveryItem;
+ private static NotificationPairingProgressHandler sNotificationPairingProgressHandler;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mContextWrapper.getSystemService(BluetoothManager.class))
+ .thenReturn(mBluetoothManager);
+ when(mContextWrapper.getLocator()).thenReturn(mLocator);
+ mLocator.overrideBindingForTest(FastPairCacheManager.class,
+ mFastPairCacheManager);
+ mLocator.overrideBindingForTest(Clock.class, mClock);
+ sDiscoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper);
+ sDiscoveryItem.setStoredItemForTest(
+ sDiscoveryItem.getStoredItemForTest().toBuilder()
+ .setAuthenticationPublicKeySecp256R1(ByteString.copyFrom(ACCOUNT_KEY))
+ .setFastPairInformation(
+ Cache.FastPairInformation.newBuilder()
+ .setDeviceType(Rpcs.DeviceType.HEADPHONES).build())
+ .build());
+ sNotificationPairingProgressHandler = createProgressHandler(ACCOUNT_KEY, sDiscoveryItem);
+ }
+
+ @Test
+ public void getPairEndEventCode() {
+ assertThat(sNotificationPairingProgressHandler
+ .getPairEndEventCode()).isEqualTo(SUBSEQUENT_PAIR_END);
+ }
+
+ @Test
+ public void getPairStartEventCode() {
+ assertThat(sNotificationPairingProgressHandler
+ .getPairStartEventCode()).isEqualTo(SUBSEQUENT_PAIR_START);
+ }
+
+ @Test
+ public void onReadyToPair() {
+ sNotificationPairingProgressHandler.onReadyToPair();
+ }
+
+ @Test
+ public void onPairedCallbackCalled() {
+ sNotificationPairingProgressHandler.onPairedCallbackCalled(mFastPairConnection,
+ ACCOUNT_KEY, mFootprintsDeviceManager, MAC_ADDRESS);
+ }
+
+ @Test
+ public void onPairingFailed() {
+ Throwable e = new Throwable("Pairing Failed");
+ sNotificationPairingProgressHandler.onPairingFailed(e);
+ }
+
+ @Test
+ public void onPairingSuccess() {
+ sNotificationPairingProgressHandler.onPairingSuccess(sDiscoveryItem.getMacAddress());
+ }
+
+ private NotificationPairingProgressHandler createProgressHandler(
+ @Nullable byte[] accountKey, DiscoveryItem fastPairItem) {
+ FastPairNotificationManager fastPairNotificationManager =
+ new FastPairNotificationManager(mContextWrapper, fastPairItem, true);
+ FastPairHalfSheetManager fastPairHalfSheetManager =
+ new FastPairHalfSheetManager(mContextWrapper);
+ mLocator.overrideBindingForTest(FastPairHalfSheetManager.class, fastPairHalfSheetManager);
+ NotificationPairingProgressHandler mNotificationPairingProgressHandler =
+ new NotificationPairingProgressHandler(
+ mContextWrapper,
+ fastPairItem,
+ fastPairItem.getAppPackageName(),
+ accountKey,
+ fastPairNotificationManager);
+ return mNotificationPairingProgressHandler;
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java
index 2ade5f2..a3eb50c 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java
@@ -20,8 +20,14 @@
import static org.mockito.Mockito.when;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+
import androidx.annotation.Nullable;
+import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
+import com.android.server.nearby.common.bluetooth.fastpair.Preferences;
import com.android.server.nearby.common.locator.Locator;
import com.android.server.nearby.common.locator.LocatorContextWrapper;
import com.android.server.nearby.fastpair.cache.DiscoveryItem;
@@ -42,8 +48,8 @@
import service.proto.Cache;
import service.proto.Rpcs;
-
public class PairingProgressHandlerBaseTest {
+
@Mock
Locator mLocator;
@Mock
@@ -54,24 +60,46 @@
FastPairCacheManager mFastPairCacheManager;
@Mock
FootprintsDeviceManager mFootprintsDeviceManager;
+ @Mock
+ FastPairConnection mFastPairConnection;
+ @Mock
+ BluetoothManager mBluetoothManager;
+
+ private static final String MAC_ADDRESS = "00:11:22:33:44:55";
private static final byte[] ACCOUNT_KEY = new byte[]{0x01, 0x02};
+ private static final int PASSKEY = 1234;
+ private static DiscoveryItem sDiscoveryItem;
+ private static PairingProgressHandlerBase sPairingProgressHandlerBase;
+ private static BluetoothDevice sBluetoothDevice;
@Before
public void setup() {
-
MockitoAnnotations.initMocks(this);
+ when(mContextWrapper.getSystemService(BluetoothManager.class))
+ .thenReturn(mBluetoothManager);
when(mContextWrapper.getLocator()).thenReturn(mLocator);
mLocator.overrideBindingForTest(FastPairCacheManager.class,
mFastPairCacheManager);
mLocator.overrideBindingForTest(Clock.class, mClock);
+ sBluetoothDevice =
+ BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:11:22:33:44:55");
+ sDiscoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper);
+ sDiscoveryItem.setStoredItemForTest(
+ sDiscoveryItem.getStoredItemForTest().toBuilder()
+ .setAuthenticationPublicKeySecp256R1(ByteString.copyFrom(ACCOUNT_KEY))
+ .setFastPairInformation(
+ Cache.FastPairInformation.newBuilder()
+ .setDeviceType(Rpcs.DeviceType.HEADPHONES).build())
+ .build());
+
+ sPairingProgressHandlerBase =
+ createProgressHandler(ACCOUNT_KEY, sDiscoveryItem, /* isRetroactivePair= */ false);
}
@Test
public void createHandler_halfSheetSubsequentPairing_notificationPairingHandlerCreated() {
-
- DiscoveryItem discoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper);
- discoveryItem.setStoredItemForTest(
- discoveryItem.getStoredItemForTest().toBuilder()
+ sDiscoveryItem.setStoredItemForTest(
+ sDiscoveryItem.getStoredItemForTest().toBuilder()
.setAuthenticationPublicKeySecp256R1(ByteString.copyFrom(ACCOUNT_KEY))
.setFastPairInformation(
Cache.FastPairInformation.newBuilder()
@@ -79,7 +107,7 @@
.build());
PairingProgressHandlerBase progressHandler =
- createProgressHandler(ACCOUNT_KEY, discoveryItem, /* isRetroactivePair= */ false);
+ createProgressHandler(ACCOUNT_KEY, sDiscoveryItem, /* isRetroactivePair= */ false);
assertThat(progressHandler).isInstanceOf(NotificationPairingProgressHandler.class);
}
@@ -87,20 +115,94 @@
@Test
public void createHandler_halfSheetInitialPairing_halfSheetPairingHandlerCreated() {
// No account key
- DiscoveryItem discoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper);
- discoveryItem.setStoredItemForTest(
- discoveryItem.getStoredItemForTest().toBuilder()
+ sDiscoveryItem.setStoredItemForTest(
+ sDiscoveryItem.getStoredItemForTest().toBuilder()
.setFastPairInformation(
Cache.FastPairInformation.newBuilder()
.setDeviceType(Rpcs.DeviceType.HEADPHONES).build())
.build());
PairingProgressHandlerBase progressHandler =
- createProgressHandler(null, discoveryItem, /* isRetroactivePair= */ false);
+ createProgressHandler(null, sDiscoveryItem, /* isRetroactivePair= */ false);
assertThat(progressHandler).isInstanceOf(HalfSheetPairingProgressHandler.class);
}
+ @Test
+ public void onPairingStarted() {
+ sPairingProgressHandlerBase.onPairingStarted();
+ }
+
+ @Test
+ public void onWaitForScreenUnlock() {
+ sPairingProgressHandlerBase.onWaitForScreenUnlock();
+ }
+
+ @Test
+ public void onScreenUnlocked() {
+ sPairingProgressHandlerBase.onScreenUnlocked();
+ }
+
+ @Test
+ public void onReadyToPair() {
+ sPairingProgressHandlerBase.onReadyToPair();
+ }
+
+ @Test
+ public void onSetupPreferencesBuilder() {
+ Preferences.Builder prefsBuilder =
+ Preferences.builder()
+ .setEnableBrEdrHandover(false)
+ .setIgnoreDiscoveryError(true);
+ sPairingProgressHandlerBase.onSetupPreferencesBuilder(prefsBuilder);
+ }
+
+ @Test
+ public void onPairingSetupCompleted() {
+ sPairingProgressHandlerBase.onPairingSetupCompleted();
+ }
+
+ @Test
+ public void onHandlePasskeyConfirmation() {
+ sPairingProgressHandlerBase.onHandlePasskeyConfirmation(sBluetoothDevice, PASSKEY);
+ }
+
+ @Test
+ public void getKeyForLocalCache() {
+ FastPairConnection.SharedSecret sharedSecret =
+ FastPairConnection.SharedSecret.create(ACCOUNT_KEY, sDiscoveryItem.getMacAddress());
+ sPairingProgressHandlerBase
+ .getKeyForLocalCache(ACCOUNT_KEY, mFastPairConnection, sharedSecret);
+ }
+
+ @Test
+ public void onPairedCallbackCalled() {
+ sPairingProgressHandlerBase.onPairedCallbackCalled(mFastPairConnection,
+ ACCOUNT_KEY, mFootprintsDeviceManager, MAC_ADDRESS);
+ }
+
+ @Test
+ public void onPairingFailed() {
+ Throwable e = new Throwable("Pairing Failed");
+ sPairingProgressHandlerBase.onPairingFailed(e);
+ }
+
+ @Test
+ public void onPairingSuccess() {
+ sPairingProgressHandlerBase.onPairingSuccess(sDiscoveryItem.getMacAddress());
+ }
+
+ @Test
+ public void optInFootprintsForInitialPairing() {
+ sPairingProgressHandlerBase.optInFootprintsForInitialPairing(
+ mFootprintsDeviceManager, sDiscoveryItem, ACCOUNT_KEY, null);
+ }
+
+ @Test
+ public void skipWaitingScreenUnlock() {
+ assertThat(sPairingProgressHandlerBase.skipWaitingScreenUnlock()).isFalse();
+ }
+
private PairingProgressHandlerBase createProgressHandler(
@Nullable byte[] accountKey, DiscoveryItem fastPairItem, boolean isRetroactivePair) {
FastPairNotificationManager fastPairNotificationManager =
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java
index c406e47..cdec04d 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java
@@ -22,10 +22,17 @@
import service.proto.Cache;
public class FakeDiscoveryItems {
- public static final String DEFAULT_MAC_ADDRESS = "00:11:22:33:44:55";
- public static final long DEFAULT_TIMESTAMP = 1000000000L;
- public static final String DEFAULT_DESCRIPITON = "description";
- public static final String TRIGGER_ID = "trigger.id";
+ private static final String DEFAULT_MAC_ADDRESS = "00:11:22:33:44:55";
+ private static final long DEFAULT_TIMESTAMP = 1000000000L;
+ private static final String DEFAULT_DESCRIPITON = "description";
+ private static final String APP_NAME = "app_name";
+ private static final String ACTION_URL =
+ "intent:#Intent;action=com.android.server.nearby:ACTION_FAST_PAIR;"
+ + "package=com.google.android.gms;"
+ + "component=com.google.android.gms/"
+ + ".nearby.discovery.service.DiscoveryService;end";
+ private static final String DISPLAY_URL = "DISPLAY_URL";
+ private static final String TRIGGER_ID = "trigger.id";
private static final String FAST_PAIR_ID = "id";
private static final int RSSI = -80;
private static final int TX_POWER = -10;
@@ -46,9 +53,36 @@
item.setMacAddress(DEFAULT_MAC_ADDRESS);
item.setFirstObservationTimestampMillis(DEFAULT_TIMESTAMP);
item.setLastObservationTimestampMillis(DEFAULT_TIMESTAMP);
+ item.setActionUrl(ACTION_URL);
+ item.setAppName(APP_NAME);
item.setRssi(RSSI);
item.setTxPower(TX_POWER);
+ item.setDisplayUrl(DISPLAY_URL);
return item.build();
}
+ public static Cache.StoredDiscoveryItem newFastPairDeviceStoredItem(String id,
+ String description, String triggerId, String macAddress, String title,
+ int rssi, int txPower) {
+ Cache.StoredDiscoveryItem.Builder item = Cache.StoredDiscoveryItem.newBuilder();
+ item.setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED);
+ if (id != null) {
+ item.setId(id);
+ }
+ if (description != null) {
+ item.setDescription(description);
+ }
+ if (triggerId != null) {
+ item.setTriggerId(triggerId);
+ }
+ if (macAddress != null) {
+ item.setMacAddress(macAddress);
+ }
+ if (title != null) {
+ item.setTitle(title);
+ }
+ item.setRssi(rssi);
+ item.setTxPower(txPower);
+ return item.build();
+ }
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingLocator.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingLocator.java
index b261b26..c9a4533 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingLocator.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingLocator.java
@@ -41,7 +41,7 @@
}
@SuppressWarnings("nullness") // due to passing in this before initialized.
- private MockingLocator(Context context, Locator locator) {
+ public MockingLocator(Context context, Locator locator) {
super(context, locator);
this.mLocatorContextWrapper = new LocatorContextWrapper(context, this);
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/injector/ContextHubManagerAdapterTest.java b/nearby/tests/unit/src/com/android/server/nearby/injector/ContextHubManagerAdapterTest.java
new file mode 100644
index 0000000..b577064
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/injector/ContextHubManagerAdapterTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.injector;
+
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class ContextHubManagerAdapterTest {
+ private ContextHubManagerAdapter mContextHubManagerAdapter;
+
+ @Mock
+ ContextHubManager mContextHubManager;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContextHubManagerAdapter = new ContextHubManagerAdapter(mContextHubManager);
+ }
+
+ @Test
+ public void getContextHubs() {
+ mContextHubManagerAdapter.getContextHubs();
+ }
+
+ @Test
+ public void queryNanoApps() {
+ mContextHubManagerAdapter.queryNanoApps(new ContextHubInfo());
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/FastAdvertisementTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/FastAdvertisementTest.java
index 5e0ccbe..8e3e068 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/presence/FastAdvertisementTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/presence/FastAdvertisementTest.java
@@ -75,6 +75,15 @@
assertThat(originalAdvertisement.getVersion()).isEqualTo(
BroadcastRequest.PRESENCE_VERSION_V0);
assertThat(originalAdvertisement.getSalt()).isEqualTo(SALT);
+ assertThat(originalAdvertisement.getTxPower()).isEqualTo(TX_POWER);
+ assertThat(originalAdvertisement.toString())
+ .isEqualTo("FastAdvertisement:<VERSION: 0, length: 19,"
+ + " ltvFieldCount: 4,"
+ + " identityType: 1,"
+ + " identity: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],"
+ + " salt: [2, 3],"
+ + " actions: [123],"
+ + " txPower: 4");
}
@Test
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java
index d06a785..88cd9af 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java
@@ -78,6 +78,11 @@
.onStatusChanged(eq(BroadcastCallback.STATUS_FAILURE));
}
+ @Test
+ public void testStop() {
+ mBleBroadcastProvider.stop();
+ }
+
private static class TestInjector implements Injector {
@Override
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java
index 902cc33..1d485ca 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java
@@ -70,6 +70,7 @@
// Wait for callback to be invoked
Thread.sleep(500);
verify(mListener, times(1)).onNearbyDeviceDiscovered(any());
+ mBleDiscoveryProvider.getScanCallback().onScanFailed(1);
}
@Test
@@ -78,6 +79,11 @@
mBleDiscoveryProvider.onStop();
}
+ @Test
+ public void testInvalidateScanMode() {
+ mBleDiscoveryProvider.invalidateScanMode();
+ }
+
private class TestInjector implements Injector {
@Override
public BluetoothAdapter getBluetoothAdapter() {
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/BroadcastProviderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/BroadcastProviderManagerTest.java
index d45d570..5090cc0 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/BroadcastProviderManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/BroadcastProviderManagerTest.java
@@ -106,6 +106,11 @@
}
@Test
+ public void testStopAdvertising() {
+ mBroadcastProviderManager.stopBroadcast(mBroadcastListener);
+ }
+
+ @Test
public void testStartAdvertising_featureDisabled() throws Exception {
DeviceConfig.setProperty(NAMESPACE_TETHERING, NEARBY_ENABLE_PRESENCE_BROADCAST_LEGACY,
"false", false);
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java
index 1b29b52..c90860e 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java
@@ -16,6 +16,8 @@
package com.android.server.nearby.provider;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@@ -106,6 +108,19 @@
new byte[] {1, 2, 3});
mChreCommunication.onMessageFromNanoApp(mClient, message);
verify(mChreCallback).onMessageFromNanoApp(eq(message));
+
+ }
+
+ @Test
+ public void testContextHubTransactionResultToString() {
+ NanoAppMessage message =
+ NanoAppMessage.createMessageToNanoApp(
+ ChreDiscoveryProvider.NANOAPP_ID,
+ ChreDiscoveryProvider.NANOAPP_MESSAGE_TYPE_FILTER_RESULT,
+ new byte[] {1, 2, 3});
+ assertThat(
+ mChreCommunication.contextHubTransactionResultToString(
+ mClient.sendMessageToNanoApp(message))).isEqualTo("RESULT_SUCCESS");
}
@Test
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/DiscoveryProviderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/DiscoveryProviderManagerTest.java
new file mode 100644
index 0000000..1af959d
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/DiscoveryProviderManagerTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.provider;
+
+import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE;
+import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE;
+
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.nearby.NearbyDeviceParcelable;
+import android.nearby.PresenceScanFilter;
+import android.nearby.PublicCredential;
+import android.nearby.ScanFilter;
+
+import com.android.server.nearby.injector.Injector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DiscoveryProviderManagerTest {
+ @Mock Injector mInjector;
+ @Mock Context mContext;
+ @Mock AppOpsManager mAppOpsManager;
+ private DiscoveryProviderManager mDiscoveryProviderManager;
+
+ private static final int RSSI = -60;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mInjector.getAppOpsManager()).thenReturn(mAppOpsManager);
+ mDiscoveryProviderManager =
+ new DiscoveryProviderManager(mContext, mInjector);
+ }
+
+
+ @Test
+ public void testOnNearbyDeviceDiscovered() {
+ NearbyDeviceParcelable nearbyDeviceParcelable = new NearbyDeviceParcelable.Builder()
+ .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+ .build();
+ mDiscoveryProviderManager.onNearbyDeviceDiscovered(nearbyDeviceParcelable);
+ }
+
+ @Test
+ public void testInvalidateProviderScanMode() {
+ mDiscoveryProviderManager.invalidateProviderScanMode();
+ }
+
+ @Test
+ public void testStartChreProvider() {
+ mDiscoveryProviderManager.startChreProvider();
+ }
+
+ private static PresenceScanFilter getPresenceScanFilter() {
+ final byte[] secretId = new byte[]{1, 2, 3, 4};
+ final byte[] authenticityKey = new byte[]{0, 1, 1, 1};
+ final byte[] publicKey = new byte[]{1, 1, 2, 2};
+ final byte[] encryptedMetadata = new byte[]{1, 2, 3, 4, 5};
+ final byte[] metadataEncryptionKeyTag = new byte[]{1, 1, 3, 4, 5};
+
+ PublicCredential credential = new PublicCredential.Builder(
+ secretId, authenticityKey, publicKey, encryptedMetadata, metadataEncryptionKeyTag)
+ .setIdentityType(IDENTITY_TYPE_PRIVATE)
+ .build();
+
+ final int action = 123;
+ return new PresenceScanFilter.Builder()
+ .addCredential(credential)
+ .setMaxPathLoss(RSSI)
+ .addPresenceAction(action)
+ .build();
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/FastPairDataProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/FastPairDataProviderTest.java
new file mode 100644
index 0000000..300efbd
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/FastPairDataProviderTest.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.provider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.accounts.Account;
+import android.content.Context;
+import android.nearby.FastPairDataProviderService;
+import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel;
+import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel;
+import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel;
+import android.nearby.aidl.FastPairDeviceMetadataParcel;
+import android.nearby.aidl.FastPairEligibleAccountParcel;
+import android.nearby.aidl.FastPairEligibleAccountsRequestParcel;
+import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel;
+import android.nearby.aidl.FastPairManageAccountRequestParcel;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.nearby.fastpair.footprint.FastPairUploadInfo;
+
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.ByteString;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import service.proto.Cache;
+import service.proto.FastPairString;
+import service.proto.Rpcs;
+
+public class FastPairDataProviderTest {
+
+ private static final Account ACCOUNT = new Account("abc@google.com", "type1");
+ private static final byte[] MODEL_ID = new byte[]{7, 9};
+ private static final int BLE_TX_POWER = 5;
+ private static final String CONNECT_SUCCESS_COMPANION_APP_INSTALLED =
+ "CONNECT_SUCCESS_COMPANION_APP_INSTALLED";
+ private static final String CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED =
+ "CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED";
+ private static final int DEVICE_TYPE = 1;
+ private static final String DOWNLOAD_COMPANION_APP_DESCRIPTION =
+ "DOWNLOAD_COMPANION_APP_DESCRIPTION";
+ private static final String FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION =
+ "FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION";
+ private static final byte[] IMAGE = new byte[]{7, 9};
+ private static final String IMAGE_URL = "IMAGE_URL";
+ private static final String INITIAL_NOTIFICATION_DESCRIPTION =
+ "INITIAL_NOTIFICATION_DESCRIPTION";
+ private static final String INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT =
+ "INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT";
+ private static final String INITIAL_PAIRING_DESCRIPTION = "INITIAL_PAIRING_DESCRIPTION";
+ private static final String INTENT_URI = "INTENT_URI";
+ private static final String OPEN_COMPANION_APP_DESCRIPTION = "OPEN_COMPANION_APP_DESCRIPTION";
+ private static final String RETRO_ACTIVE_PAIRING_DESCRIPTION =
+ "RETRO_ACTIVE_PAIRING_DESCRIPTION";
+ private static final String SUBSEQUENT_PAIRING_DESCRIPTION = "SUBSEQUENT_PAIRING_DESCRIPTION";
+ private static final float TRIGGER_DISTANCE = 111;
+ private static final String TRUE_WIRELESS_IMAGE_URL_CASE = "TRUE_WIRELESS_IMAGE_URL_CASE";
+ private static final String TRUE_WIRELESS_IMAGE_URL_LEFT_BUD =
+ "TRUE_WIRELESS_IMAGE_URL_LEFT_BUD";
+ private static final String TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD =
+ "TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD";
+ private static final String UNABLE_TO_CONNECT_DESCRIPTION = "UNABLE_TO_CONNECT_DESCRIPTION";
+ private static final String UNABLE_TO_CONNECT_TITLE = "UNABLE_TO_CONNECT_TITLE";
+ private static final String UPDATE_COMPANION_APP_DESCRIPTION =
+ "UPDATE_COMPANION_APP_DESCRIPTION";
+ private static final String WAIT_LAUNCH_COMPANION_APP_DESCRIPTION =
+ "WAIT_LAUNCH_COMPANION_APP_DESCRIPTION";
+ private static final byte[] ACCOUNT_KEY = new byte[]{3};
+ private static final byte[] SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS = new byte[]{2, 8};
+ private static final byte[] ANTI_SPOOFING_KEY = new byte[]{4, 5, 6};
+ private static final String ACTION_URL = "ACTION_URL";
+ private static final String APP_NAME = "APP_NAME";
+ private static final byte[] AUTHENTICATION_PUBLIC_KEY_SEC_P256R1 = new byte[]{5, 7};
+ private static final String DESCRIPTION = "DESCRIPTION";
+ private static final String DEVICE_NAME = "DEVICE_NAME";
+ private static final String DISPLAY_URL = "DISPLAY_URL";
+ private static final long FIRST_OBSERVATION_TIMESTAMP_MILLIS = 8393L;
+ private static final String ICON_FIFE_URL = "ICON_FIFE_URL";
+ private static final byte[] ICON_PNG = new byte[]{2, 5};
+ private static final String ID = "ID";
+ private static final long LAST_OBSERVATION_TIMESTAMP_MILLIS = 934234L;
+ private static final String MAC_ADDRESS = "MAC_ADDRESS";
+ private static final String NAME = "NAME";
+ private static final String PACKAGE_NAME = "PACKAGE_NAME";
+ private static final long PENDING_APP_INSTALL_TIMESTAMP_MILLIS = 832393L;
+ private static final int RSSI = 9;
+ private static final String TITLE = "TITLE";
+ private static final String TRIGGER_ID = "TRIGGER_ID";
+ private static final int TX_POWER = 63;
+
+ @Mock ProxyFastPairDataProvider mProxyFastPairDataProvider;
+
+ FastPairDataProvider mFastPairDataProvider;
+ FastPairEligibleAccountParcel[] mFastPairEligibleAccountParcels =
+ { genHappyPathFastPairEligibleAccountParcel() };
+ FastPairAntispoofKeyDeviceMetadataParcel mFastPairAntispoofKeyDeviceMetadataParcel =
+ genHappyPathFastPairAntispoofKeyDeviceMetadataParcel();
+ FastPairUploadInfo mFastPairUploadInfo = genHappyPathFastPairUploadInfo();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ mFastPairDataProvider = FastPairDataProvider.init(context);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testFailurePath_throwsException() throws IllegalStateException {
+ mFastPairDataProvider = FastPairDataProvider.getInstance();
+ assertThrows(
+ IllegalStateException.class,
+ () -> {
+ mFastPairDataProvider.loadFastPairEligibleAccounts(); });
+ assertThrows(
+ IllegalStateException.class,
+ () -> {
+ mFastPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(MODEL_ID); });
+ assertThrows(
+ IllegalStateException.class,
+ () -> {
+ mFastPairDataProvider.loadFastPairDeviceWithAccountKey(ACCOUNT); });
+ assertThrows(
+ IllegalStateException.class,
+ () -> {
+ mFastPairDataProvider.loadFastPairDeviceWithAccountKey(
+ ACCOUNT, ImmutableList.of()); });
+ assertThrows(
+ IllegalStateException.class,
+ () -> {
+ mFastPairDataProvider.optIn(ACCOUNT); });
+ assertThrows(
+ IllegalStateException.class,
+ () -> {
+ mFastPairDataProvider.upload(ACCOUNT, mFastPairUploadInfo); });
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testLoadFastPairAntispoofKeyDeviceMetadata_receivesResponse() {
+ mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider);
+ when(mProxyFastPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(any()))
+ .thenReturn(mFastPairAntispoofKeyDeviceMetadataParcel);
+
+ mFastPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(MODEL_ID);
+ ArgumentCaptor<FastPairAntispoofKeyDeviceMetadataRequestParcel> captor =
+ ArgumentCaptor.forClass(FastPairAntispoofKeyDeviceMetadataRequestParcel.class);
+ verify(mProxyFastPairDataProvider).loadFastPairAntispoofKeyDeviceMetadata(captor.capture());
+ assertThat(captor.getValue().modelId).isSameInstanceAs(MODEL_ID);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testOptIn_finishesSuccessfully() {
+ mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider);
+ doNothing().when(mProxyFastPairDataProvider).manageFastPairAccount(any());
+ mFastPairDataProvider.optIn(ACCOUNT);
+ ArgumentCaptor<FastPairManageAccountRequestParcel> captor =
+ ArgumentCaptor.forClass(FastPairManageAccountRequestParcel.class);
+ verify(mProxyFastPairDataProvider).manageFastPairAccount(captor.capture());
+ assertThat(captor.getValue().account).isSameInstanceAs(ACCOUNT);
+ assertThat(captor.getValue().requestType).isEqualTo(
+ FastPairDataProviderService.MANAGE_REQUEST_ADD);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testUpload_finishesSuccessfully() {
+ mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider);
+ doNothing().when(mProxyFastPairDataProvider).manageFastPairAccountDevice(any());
+ mFastPairDataProvider.upload(ACCOUNT, mFastPairUploadInfo);
+ ArgumentCaptor<FastPairManageAccountDeviceRequestParcel> captor =
+ ArgumentCaptor.forClass(FastPairManageAccountDeviceRequestParcel.class);
+ verify(mProxyFastPairDataProvider).manageFastPairAccountDevice(captor.capture());
+ assertThat(captor.getValue().account).isSameInstanceAs(ACCOUNT);
+ assertThat(captor.getValue().requestType).isEqualTo(
+ FastPairDataProviderService.MANAGE_REQUEST_ADD);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testLoadFastPairEligibleAccounts_receivesOneAccount() {
+ mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider);
+ when(mProxyFastPairDataProvider.loadFastPairEligibleAccounts(any()))
+ .thenReturn(mFastPairEligibleAccountParcels);
+ assertThat(mFastPairDataProvider.loadFastPairEligibleAccounts().size())
+ .isEqualTo(1);
+ ArgumentCaptor<FastPairEligibleAccountsRequestParcel> captor =
+ ArgumentCaptor.forClass(FastPairEligibleAccountsRequestParcel.class);
+ verify(mProxyFastPairDataProvider).loadFastPairEligibleAccounts(captor.capture());
+ assertThat(captor.getValue()).isNotNull();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testLoadFastPairDeviceWithAccountKey_finishesSuccessfully() {
+ mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider);
+ when(mProxyFastPairDataProvider.loadFastPairAccountDevicesMetadata(any()))
+ .thenReturn(null);
+
+ mFastPairDataProvider.loadFastPairDeviceWithAccountKey(ACCOUNT);
+ ArgumentCaptor<FastPairAccountDevicesMetadataRequestParcel> captor =
+ ArgumentCaptor.forClass(FastPairAccountDevicesMetadataRequestParcel.class);
+ verify(mProxyFastPairDataProvider).loadFastPairAccountDevicesMetadata(captor.capture());
+ assertThat(captor.getValue().account).isSameInstanceAs(ACCOUNT);
+ assertThat(captor.getValue().deviceAccountKeys).isEmpty();
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testLoadFastPairDeviceWithAccountKeyDeviceAccountKeys_finishesSuccessfully() {
+ mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider);
+ when(mProxyFastPairDataProvider.loadFastPairAccountDevicesMetadata(any()))
+ .thenReturn(null);
+
+ mFastPairDataProvider.loadFastPairDeviceWithAccountKey(
+ ACCOUNT, ImmutableList.of(ACCOUNT_KEY));
+ ArgumentCaptor<FastPairAccountDevicesMetadataRequestParcel> captor =
+ ArgumentCaptor.forClass(FastPairAccountDevicesMetadataRequestParcel.class);
+ verify(mProxyFastPairDataProvider).loadFastPairAccountDevicesMetadata(captor.capture());
+ assertThat(captor.getValue().account).isSameInstanceAs(ACCOUNT);
+ assertThat(captor.getValue().deviceAccountKeys.length).isEqualTo(1);
+ assertThat(captor.getValue().deviceAccountKeys[0].byteArray).isSameInstanceAs(ACCOUNT_KEY);
+ }
+
+ private static FastPairEligibleAccountParcel genHappyPathFastPairEligibleAccountParcel() {
+ FastPairEligibleAccountParcel parcel = new FastPairEligibleAccountParcel();
+ parcel.account = ACCOUNT;
+ parcel.optIn = true;
+
+ return parcel;
+ }
+
+ private static FastPairAntispoofKeyDeviceMetadataParcel
+ genHappyPathFastPairAntispoofKeyDeviceMetadataParcel() {
+ FastPairAntispoofKeyDeviceMetadataParcel parcel =
+ new FastPairAntispoofKeyDeviceMetadataParcel();
+ parcel.antispoofPublicKey = ANTI_SPOOFING_KEY;
+ parcel.deviceMetadata = genHappyPathFastPairDeviceMetadataParcel();
+
+ return parcel;
+ }
+
+ private static FastPairDeviceMetadataParcel genHappyPathFastPairDeviceMetadataParcel() {
+ FastPairDeviceMetadataParcel parcel = new FastPairDeviceMetadataParcel();
+
+ parcel.bleTxPower = BLE_TX_POWER;
+ parcel.connectSuccessCompanionAppInstalled = CONNECT_SUCCESS_COMPANION_APP_INSTALLED;
+ parcel.connectSuccessCompanionAppNotInstalled =
+ CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED;
+ parcel.deviceType = DEVICE_TYPE;
+ parcel.downloadCompanionAppDescription = DOWNLOAD_COMPANION_APP_DESCRIPTION;
+ parcel.failConnectGoToSettingsDescription = FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION;
+ parcel.image = IMAGE;
+ parcel.imageUrl = IMAGE_URL;
+ parcel.initialNotificationDescription = INITIAL_NOTIFICATION_DESCRIPTION;
+ parcel.initialNotificationDescriptionNoAccount =
+ INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT;
+ parcel.initialPairingDescription = INITIAL_PAIRING_DESCRIPTION;
+ parcel.intentUri = INTENT_URI;
+ parcel.name = NAME;
+ parcel.openCompanionAppDescription = OPEN_COMPANION_APP_DESCRIPTION;
+ parcel.retroactivePairingDescription = RETRO_ACTIVE_PAIRING_DESCRIPTION;
+ parcel.subsequentPairingDescription = SUBSEQUENT_PAIRING_DESCRIPTION;
+ parcel.triggerDistance = TRIGGER_DISTANCE;
+ parcel.trueWirelessImageUrlCase = TRUE_WIRELESS_IMAGE_URL_CASE;
+ parcel.trueWirelessImageUrlLeftBud = TRUE_WIRELESS_IMAGE_URL_LEFT_BUD;
+ parcel.trueWirelessImageUrlRightBud = TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD;
+ parcel.unableToConnectDescription = UNABLE_TO_CONNECT_DESCRIPTION;
+ parcel.unableToConnectTitle = UNABLE_TO_CONNECT_TITLE;
+ parcel.updateCompanionAppDescription = UPDATE_COMPANION_APP_DESCRIPTION;
+ parcel.waitLaunchCompanionAppDescription = WAIT_LAUNCH_COMPANION_APP_DESCRIPTION;
+
+ return parcel;
+ }
+
+ private static Cache.StoredDiscoveryItem genHappyPathStoredDiscoveryItem() {
+ Cache.StoredDiscoveryItem.Builder storedDiscoveryItemBuilder =
+ Cache.StoredDiscoveryItem.newBuilder();
+ storedDiscoveryItemBuilder.setActionUrl(ACTION_URL);
+ storedDiscoveryItemBuilder.setActionUrlType(Cache.ResolvedUrlType.WEBPAGE);
+ storedDiscoveryItemBuilder.setAppName(APP_NAME);
+ storedDiscoveryItemBuilder.setAuthenticationPublicKeySecp256R1(
+ ByteString.copyFrom(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1));
+ storedDiscoveryItemBuilder.setDescription(DESCRIPTION);
+ storedDiscoveryItemBuilder.setDeviceName(DEVICE_NAME);
+ storedDiscoveryItemBuilder.setDisplayUrl(DISPLAY_URL);
+ storedDiscoveryItemBuilder.setFirstObservationTimestampMillis(
+ FIRST_OBSERVATION_TIMESTAMP_MILLIS);
+ storedDiscoveryItemBuilder.setIconFifeUrl(ICON_FIFE_URL);
+ storedDiscoveryItemBuilder.setIconPng(ByteString.copyFrom(ICON_PNG));
+ storedDiscoveryItemBuilder.setId(ID);
+ storedDiscoveryItemBuilder.setLastObservationTimestampMillis(
+ LAST_OBSERVATION_TIMESTAMP_MILLIS);
+ storedDiscoveryItemBuilder.setMacAddress(MAC_ADDRESS);
+ storedDiscoveryItemBuilder.setPackageName(PACKAGE_NAME);
+ storedDiscoveryItemBuilder.setPendingAppInstallTimestampMillis(
+ PENDING_APP_INSTALL_TIMESTAMP_MILLIS);
+ storedDiscoveryItemBuilder.setRssi(RSSI);
+ storedDiscoveryItemBuilder.setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED);
+ storedDiscoveryItemBuilder.setTitle(TITLE);
+ storedDiscoveryItemBuilder.setTriggerId(TRIGGER_ID);
+ storedDiscoveryItemBuilder.setTxPower(TX_POWER);
+
+ FastPairString.FastPairStrings.Builder stringsBuilder =
+ FastPairString.FastPairStrings.newBuilder();
+ stringsBuilder.setPairingFinishedCompanionAppInstalled(
+ CONNECT_SUCCESS_COMPANION_APP_INSTALLED);
+ stringsBuilder.setPairingFinishedCompanionAppNotInstalled(
+ CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED);
+ stringsBuilder.setPairingFailDescription(
+ FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION);
+ stringsBuilder.setTapToPairWithAccount(
+ INITIAL_NOTIFICATION_DESCRIPTION);
+ stringsBuilder.setTapToPairWithoutAccount(
+ INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT);
+ stringsBuilder.setInitialPairingDescription(INITIAL_PAIRING_DESCRIPTION);
+ stringsBuilder.setRetroactivePairingDescription(RETRO_ACTIVE_PAIRING_DESCRIPTION);
+ stringsBuilder.setSubsequentPairingDescription(SUBSEQUENT_PAIRING_DESCRIPTION);
+ stringsBuilder.setWaitAppLaunchDescription(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION);
+ storedDiscoveryItemBuilder.setFastPairStrings(stringsBuilder.build());
+
+ Cache.FastPairInformation.Builder fpInformationBuilder =
+ Cache.FastPairInformation.newBuilder();
+ Rpcs.TrueWirelessHeadsetImages.Builder imagesBuilder =
+ Rpcs.TrueWirelessHeadsetImages.newBuilder();
+ imagesBuilder.setCaseUrl(TRUE_WIRELESS_IMAGE_URL_CASE);
+ imagesBuilder.setLeftBudUrl(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD);
+ imagesBuilder.setRightBudUrl(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD);
+ fpInformationBuilder.setTrueWirelessImages(imagesBuilder.build());
+ fpInformationBuilder.setDeviceType(Rpcs.DeviceType.HEADPHONES);
+
+ storedDiscoveryItemBuilder.setFastPairInformation(fpInformationBuilder.build());
+ storedDiscoveryItemBuilder.setTxPower(TX_POWER);
+
+ storedDiscoveryItemBuilder.setIconPng(ByteString.copyFrom(ICON_PNG));
+ storedDiscoveryItemBuilder.setIconFifeUrl(ICON_FIFE_URL);
+ storedDiscoveryItemBuilder.setActionUrl(ACTION_URL);
+
+ return storedDiscoveryItemBuilder.build();
+ }
+
+ private static FastPairUploadInfo genHappyPathFastPairUploadInfo() {
+ return new FastPairUploadInfo(
+ genHappyPathStoredDiscoveryItem(),
+ ByteString.copyFrom(ACCOUNT_KEY),
+ ByteString.copyFrom(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS));
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/ArrayUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/ArrayUtilsTest.java
new file mode 100644
index 0000000..0fe28df
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/ArrayUtilsTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Test;
+
+public final class ArrayUtilsTest {
+
+ private static final byte[] BYTES_ONE = new byte[] {7, 9};
+ private static final byte[] BYTES_TWO = new byte[] {8};
+ private static final byte[] BYTES_EMPTY = new byte[] {};
+ private static final byte[] BYTES_ALL = new byte[] {7, 9, 8};
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testConcatByteArraysNoInput() {
+ assertThat(ArrayUtils.concatByteArrays().length).isEqualTo(0);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testConcatByteArraysOneEmptyArray() {
+ assertThat(ArrayUtils.concatByteArrays(BYTES_EMPTY).length).isEqualTo(0);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testConcatByteArraysOneNonEmptyArray() {
+ assertThat(ArrayUtils.concatByteArrays(BYTES_ONE)).isEqualTo(BYTES_ONE);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testConcatByteArraysMultipleNonEmptyArrays() {
+ assertThat(ArrayUtils.concatByteArrays(BYTES_ONE, BYTES_TWO)).isEqualTo(BYTES_ALL);
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void testConcatByteArraysMultipleArrays() {
+ assertThat(ArrayUtils.concatByteArrays(BYTES_ONE, BYTES_EMPTY, BYTES_TWO))
+ .isEqualTo(BYTES_ALL);
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/BroadcastPermissionsTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/BroadcastPermissionsTest.java
index 1a22412..71ade2a 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/util/BroadcastPermissionsTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/BroadcastPermissionsTest.java
@@ -93,4 +93,9 @@
assertThat(BroadcastPermissions.getPermissionLevel(mMockContext, UID, PID))
.isEqualTo(PERMISSION_BLUETOOTH_ADVERTISE);
}
+
+ @Test
+ public void test_enforceBroadcastPermission() {
+ BroadcastPermissions.enforceBroadcastPermission(mMockContext, mCallerIdentity);
+ }
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java
index f098600..9867a37 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java
@@ -107,6 +107,24 @@
public void test_toString() {
Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem(
createObservedDeviceResponse(), BLUETOOTH_ADDRESS, ACCOUNT);
+
+ assertThat(DataUtils.toString(item))
+ .isEqualTo("ScanFastPairStoreItem=[address:00:11:22:33:FF:EE, "
+ + "actionUrl:intent:#Intent;action=cto_be_set%3AACTION_MAGIC_PAIR;"
+ + "package=to_be_set;component=to_be_set;"
+ + "to_be_set%3AEXTRA_COMPANION_APP=test_package;"
+ + "end, deviceName:My device, "
+ + "iconFifeUrl:device_image_url, "
+ + "fastPairStrings:FastPairStrings[tapToPairWithAccount=message 1, "
+ + "tapToPairWithoutAccount=message 2, "
+ + "initialPairingDescription=message 3 My device, "
+ + "pairingFinishedCompanionAppInstalled=message 4, "
+ + "pairingFinishedCompanionAppNotInstalled=message 5, "
+ + "subsequentPairingDescription=message 6, "
+ + "retroactivePairingDescription=message 7, "
+ + "waitAppLaunchDescription=message 8, "
+ + "pairingFailDescription=message 9]]");
+
FastPairStrings strings = item.getFastPairStrings();
assertThat(DataUtils.toString(strings))
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/EnvironmentTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/EnvironmentTest.java
new file mode 100644
index 0000000..e167cf4
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/EnvironmentTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.util;
+
+import org.junit.Test;
+
+public class EnvironmentTest {
+
+ @Test
+ public void getNearbyDirectory() {
+ Environment.getNearbyDirectory();
+ Environment.getNearbyDirectory(1);
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/identity/CallerIdentityTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/identity/CallerIdentityTest.java
new file mode 100644
index 0000000..a18aa1f
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/identity/CallerIdentityTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.nearby.util.identity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class CallerIdentityTest {
+ private static final int UID = 100;
+ private static final int PID = 10002;
+ private static final String PACKAGE_NAME = "package_name";
+ private static final String ATTRIBUTION_TAG = "attribution_tag";
+
+ @Test
+ public void testToString() {
+ CallerIdentity callerIdentity =
+ CallerIdentity.forTest(UID, PID, PACKAGE_NAME, ATTRIBUTION_TAG);
+ assertThat(callerIdentity.toString()).isEqualTo("100/package_name[attribution_tag]");
+ assertThat(callerIdentity.isSystemServer()).isFalse();
+ }
+}
diff --git a/netd/Android.bp b/netd/Android.bp
index 5ac02d3..c731b8b 100644
--- a/netd/Android.bp
+++ b/netd/Android.bp
@@ -55,7 +55,8 @@
cc_test {
name: "netd_updatable_unit_test",
defaults: ["netd_defaults"],
- test_suites: ["general-tests"],
+ test_suites: ["general-tests", "mts-tethering"],
+ test_config_template: ":net_native_test_config_template",
require_root: true, // required by setrlimitForTest()
header_libs: [
"bpf_connectivity_headers",
@@ -72,6 +73,7 @@
"liblog",
"libnetdutils",
],
+ compile_multilib: "both",
multilib: {
lib32: {
suffix: "32",
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 6ae26c3..fad6bbb 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -73,16 +73,7 @@
}
RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_EGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_EGRESS));
RETURN_IF_NOT_OK(attachProgramToCgroup(BPF_INGRESS_PROG_PATH, cg_fd, BPF_CGROUP_INET_INGRESS));
-
- // For the devices that support cgroup socket filter, the socket filter
- // should be loaded successfully by bpfloader. So we attach the filter to
- // cgroup if the program is pinned properly.
- // TODO: delete the if statement once all devices should support cgroup
- // socket filter (ie. the minimum kernel version required is 4.14).
- if (!access(CGROUP_SOCKET_PROG_PATH, F_OK)) {
- RETURN_IF_NOT_OK(
- attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
- }
+ RETURN_IF_NOT_OK(attachProgramToCgroup(CGROUP_SOCKET_PROG_PATH, cg_fd, BPF_CGROUP_INET_SOCK_CREATE));
return netdutils::status::ok;
}
@@ -111,6 +102,7 @@
RETURN_IF_NOT_OK(mStatsMapB.init(STATS_MAP_B_PATH));
RETURN_IF_NOT_OK(mConfigurationMap.init(CONFIGURATION_MAP_PATH));
RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+ ALOGI("%s successfully", __func__);
return netdutils::status::ok;
}
@@ -241,7 +233,7 @@
if (sock_cookie == NONEXISTENT_COOKIE) return -errno;
base::Result<void> res = mCookieTagMap.deleteValue(sock_cookie);
if (!res.ok()) {
- ALOGE("Failed to untag socket: %s\n", strerror(res.error().code()));
+ ALOGE("Failed to untag socket: %s", strerror(res.error().code()));
return -res.error().code();
}
return 0;
diff --git a/netd/BpfHandlerTest.cpp b/netd/BpfHandlerTest.cpp
index a031dbb..99160da 100644
--- a/netd/BpfHandlerTest.cpp
+++ b/netd/BpfHandlerTest.cpp
@@ -21,7 +21,7 @@
#include <gtest/gtest.h>
-#define TEST_BPF_MAP
+#define BPF_MAP_MAKE_VISIBLE_FOR_TESTING
#include "BpfHandler.h"
using namespace android::bpf; // NOLINT(google-build-using-namespace): exempted
@@ -65,7 +65,7 @@
mFakeConfigurationMap.resetMap(BPF_MAP_TYPE_ARRAY, CONFIGURATION_MAP_SIZE);
ASSERT_VALID(mFakeConfigurationMap);
- mFakeUidPermissionMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
+ mFakeUidPermissionMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeUidPermissionMap);
mBh.mCookieTagMap = mFakeCookieTagMap;
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
index bf56fd5..5b3d314 100644
--- a/service-t/native/libs/libnetworkstats/Android.bp
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -48,7 +48,8 @@
cc_test {
name: "libnetworkstats_test",
- test_suites: ["general-tests"],
+ test_suites: ["general-tests", "mts-tethering"],
+ test_config_template: ":net_native_test_config_template",
require_root: true, // required by setrlimitForTest()
header_libs: ["bpf_connectivity_headers"],
srcs: [
@@ -68,4 +69,13 @@
"libbase",
"liblog",
],
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
}
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
index 9ebef4d..c67821f 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStats.cpp
@@ -205,6 +205,10 @@
configuration.error().message().c_str());
return -configuration.error().code();
}
+ if (configuration.value() != SELECT_MAP_A && configuration.value() != SELECT_MAP_B) {
+ ALOGE("%s unknown configuration value: %d", __func__, configuration.value());
+ return -EINVAL;
+ }
const char* statsMapPath = STATS_MAP_PATH[configuration.value()];
BpfMap<StatsKey, StatsValue> statsMap(statsMapPath);
if (!statsMap.isValid()) {
diff --git a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
index 4974b96..6f9c8c2 100644
--- a/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
+++ b/service-t/native/libs/libnetworkstats/BpfNetworkStatsTest.cpp
@@ -33,6 +33,7 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#define BPF_MAP_MAKE_VISIBLE_FOR_TESTING
#include "bpf/BpfMap.h"
#include "bpf/BpfUtils.h"
#include "netdbpf/BpfNetworkStats.h"
@@ -80,19 +81,19 @@
ASSERT_EQ(0, setrlimitForTest());
mFakeCookieTagMap = BpfMap<uint64_t, UidTagValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
- ASSERT_LE(0, mFakeCookieTagMap.getMap());
+ ASSERT_TRUE(mFakeCookieTagMap.isValid());
mFakeAppUidStatsMap = BpfMap<uint32_t, StatsValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
- ASSERT_LE(0, mFakeAppUidStatsMap.getMap());
+ ASSERT_TRUE(mFakeAppUidStatsMap.isValid());
mFakeStatsMap = BpfMap<StatsKey, StatsValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
- ASSERT_LE(0, mFakeStatsMap.getMap());
+ ASSERT_TRUE(mFakeStatsMap.isValid());
mFakeIfaceIndexNameMap = BpfMap<uint32_t, IfaceValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
- ASSERT_LE(0, mFakeIfaceIndexNameMap.getMap());
+ ASSERT_TRUE(mFakeIfaceIndexNameMap.isValid());
mFakeIfaceStatsMap = BpfMap<uint32_t, StatsValue>(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, 0);
- ASSERT_LE(0, mFakeIfaceStatsMap.getMap());
+ ASSERT_TRUE(mFakeIfaceStatsMap.isValid());
}
void expectUidTag(uint64_t cookie, uid_t uid, uint32_t tag) {
diff --git a/service-t/src/com/android/server/IpSecService.java b/service-t/src/com/android/server/IpSecService.java
index 4bc40ea..16b9f1e 100644
--- a/service-t/src/com/android/server/IpSecService.java
+++ b/service-t/src/com/android/server/IpSecService.java
@@ -1452,6 +1452,11 @@
final ConnectivityManager connectivityManager =
mContext.getSystemService(ConnectivityManager.class);
final LinkProperties lp = connectivityManager.getLinkProperties(underlyingNetwork);
+ if (lp == null) {
+ throw new IllegalArgumentException(
+ "LinkProperties is null. The underlyingNetwork may not be functional");
+ }
+
if (tunnelInterfaceInfo.getInterfaceName().equals(lp.getInterfaceName())) {
throw new IllegalArgumentException(
"Underlying network cannot be the network being exposed by this tunnel");
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 95e6114..7115720 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -101,7 +101,6 @@
private class NsdStateMachine extends StateMachine {
private final DefaultState mDefaultState = new DefaultState();
- private final DisabledState mDisabledState = new DisabledState();
private final EnabledState mEnabledState = new EnabledState();
@Override
@@ -152,7 +151,6 @@
NsdStateMachine(String name, Handler handler) {
super(name, handler);
addState(mDefaultState);
- addState(mDisabledState, mDefaultState);
addState(mEnabledState, mDefaultState);
State initialState = mEnabledState;
setInitialState(initialState);
@@ -250,25 +248,6 @@
}
}
- class DisabledState extends State {
- @Override
- public void enter() {
- sendNsdStateChangeBroadcast(false);
- }
-
- @Override
- public boolean processMessage(Message msg) {
- switch (msg.what) {
- case NsdManager.ENABLE:
- transitionTo(mEnabledState);
- break;
- default:
- return NOT_HANDLED;
- }
- return HANDLED;
- }
- }
-
class EnabledState extends State {
@Override
public void enter() {
@@ -312,10 +291,6 @@
final int clientId = msg.arg2;
final ListenerArgs args;
switch (msg.what) {
- case NsdManager.DISABLE:
- //TODO: cleanup clients
- transitionTo(mDisabledState);
- break;
case NsdManager.DISCOVER_SERVICES:
if (DBG) Log.d(TAG, "Discover services");
args = (ListenerArgs) msg.obj;
diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
index 5e830ad..f058f94 100644
--- a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
+++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java
@@ -16,19 +16,22 @@
package com.android.server.ethernet;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.net.EthernetNetworkSpecifier;
+import android.net.EthernetNetworkUpdateRequest;
import android.net.IEthernetManager;
import android.net.IEthernetServiceListener;
import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.ITetheredInterfaceCallback;
-import android.net.EthernetNetworkUpdateRequest;
import android.net.IpConfiguration;
import android.net.NetworkCapabilities;
+import android.net.NetworkSpecifier;
import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
@@ -216,19 +219,39 @@
"EthernetServiceImpl");
}
- private void maybeValidateTestCapabilities(final String iface,
- @Nullable final NetworkCapabilities nc) {
+ private void validateOrSetNetworkSpecifier(String iface, NetworkCapabilities nc) {
+ final NetworkSpecifier spec = nc.getNetworkSpecifier();
+ if (spec == null) {
+ nc.setNetworkSpecifier(new EthernetNetworkSpecifier(iface));
+ return;
+ }
+ if (!(spec instanceof EthernetNetworkSpecifier)) {
+ throw new IllegalArgumentException("Invalid specifier type for request.");
+ }
+ if (!((EthernetNetworkSpecifier) spec).getInterfaceName().matches(iface)) {
+ throw new IllegalArgumentException("Invalid interface name set on specifier.");
+ }
+ }
+
+ private void maybeValidateTestCapabilities(String iface, NetworkCapabilities nc) {
if (!mTracker.isValidTestInterface(iface)) {
return;
}
- // For test interfaces, only null or capabilities that include TRANSPORT_TEST are
- // allowed.
- if (nc != null && !nc.hasTransport(TRANSPORT_TEST)) {
+ if (!nc.hasTransport(TRANSPORT_TEST)) {
throw new IllegalArgumentException(
"Updates to test interfaces must have NetworkCapabilities.TRANSPORT_TEST.");
}
}
+ private void maybeValidateEthernetTransport(String iface, NetworkCapabilities nc) {
+ if (mTracker.isValidTestInterface(iface)) {
+ return;
+ }
+ if (!nc.hasSingleTransport(TRANSPORT_ETHERNET)) {
+ throw new IllegalArgumentException("Invalid transport type for request.");
+ }
+ }
+
private void enforceAdminPermission(final String iface, boolean enforceAutomotive,
final String logMessage) {
if (mTracker.isValidTestInterface(iface)) {
@@ -251,36 +274,41 @@
// TODO: validate that iface is listed in overlay config_ethernet_interfaces
// only automotive devices are allowed to set the NetworkCapabilities using this API
- enforceAdminPermission(iface, request.getNetworkCapabilities() != null,
- "updateConfiguration() with non-null capabilities");
- maybeValidateTestCapabilities(iface, request.getNetworkCapabilities());
+ final NetworkCapabilities nc = request.getNetworkCapabilities();
+ enforceAdminPermission(
+ iface, nc != null, "updateConfiguration() with non-null capabilities");
+ if (nc != null) {
+ validateOrSetNetworkSpecifier(iface, nc);
+ maybeValidateTestCapabilities(iface, nc);
+ maybeValidateEthernetTransport(iface, nc);
+ }
mTracker.updateConfiguration(
- iface, request.getIpConfiguration(), request.getNetworkCapabilities(), listener);
+ iface, request.getIpConfiguration(), nc, listener);
}
@Override
- public void connectNetwork(@NonNull final String iface,
+ public void enableInterface(@NonNull final String iface,
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
- Log.i(TAG, "connectNetwork called with: iface=" + iface + ", listener=" + listener);
+ Log.i(TAG, "enableInterface called with: iface=" + iface + ", listener=" + listener);
Objects.requireNonNull(iface);
throwIfEthernetNotStarted();
- enforceAdminPermission(iface, true, "connectNetwork()");
+ enforceAdminPermission(iface, false, "enableInterface()");
- mTracker.connectNetwork(iface, listener);
+ mTracker.enableInterface(iface, listener);
}
@Override
- public void disconnectNetwork(@NonNull final String iface,
+ public void disableInterface(@NonNull final String iface,
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
- Log.i(TAG, "disconnectNetwork called with: iface=" + iface + ", listener=" + listener);
+ Log.i(TAG, "disableInterface called with: iface=" + iface + ", listener=" + listener);
Objects.requireNonNull(iface);
throwIfEthernetNotStarted();
- enforceAdminPermission(iface, true, "connectNetwork()");
+ enforceAdminPermission(iface, false, "disableInterface()");
- mTracker.disconnectNetwork(iface, listener);
+ mTracker.disableInterface(iface, listener);
}
@Override
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 1ab7515..31caabc 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -270,6 +270,8 @@
+ ", ipConfig: " + ipConfig);
}
+ // TODO: do the right thing if the interface was in server mode: either fail this operation,
+ // or take the interface out of server mode.
final IpConfiguration localIpConfig = ipConfig == null
? null : new IpConfiguration(ipConfig);
if (ipConfig != null) {
@@ -286,13 +288,13 @@
}
@VisibleForTesting(visibility = PACKAGE)
- protected void connectNetwork(@NonNull final String iface,
+ protected void enableInterface(@NonNull final String iface,
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
mHandler.post(() -> updateInterfaceState(iface, true, listener));
}
@VisibleForTesting(visibility = PACKAGE)
- protected void disconnectNetwork(@NonNull final String iface,
+ protected void disableInterface(@NonNull final String iface,
@Nullable final INetworkInterfaceOutcomeReceiver listener) {
mHandler.post(() -> updateInterfaceState(iface, false, listener));
}
@@ -580,8 +582,8 @@
}
if (DBG) Log.i(TAG, "maybeTrackInterface: " + iface);
- // TODO: avoid making an interface default if it has configured NetworkCapabilities.
- if (mDefaultInterface == null) {
+ // Do not make an interface default if it has configured NetworkCapabilities.
+ if (mDefaultInterface == null && !mNetworkCapabilities.containsKey(iface)) {
mDefaultInterface = iface;
}
diff --git a/service-t/src/com/android/server/net/NetworkStatsRecorder.java b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
index 768f3eb..7c801d7 100644
--- a/service-t/src/com/android/server/net/NetworkStatsRecorder.java
+++ b/service-t/src/com/android/server/net/NetworkStatsRecorder.java
@@ -43,7 +43,6 @@
import libcore.io.IoUtils;
import java.io.ByteArrayOutputStream;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -427,46 +426,6 @@
}
}
- public void importLegacyNetworkLocked(File file) throws IOException {
- Objects.requireNonNull(mRotator, "missing FileRotator");
-
- // legacy file still exists; start empty to avoid double importing
- mRotator.deleteAll();
-
- final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
- collection.readLegacyNetwork(file);
-
- final long startMillis = collection.getStartMillis();
- final long endMillis = collection.getEndMillis();
-
- if (!collection.isEmpty()) {
- // process legacy data, creating active file at starting time, then
- // using end time to possibly trigger rotation.
- mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
- mRotator.maybeRotate(endMillis);
- }
- }
-
- public void importLegacyUidLocked(File file) throws IOException {
- Objects.requireNonNull(mRotator, "missing FileRotator");
-
- // legacy file still exists; start empty to avoid double importing
- mRotator.deleteAll();
-
- final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
- collection.readLegacyUid(file, mOnlyTags);
-
- final long startMillis = collection.getStartMillis();
- final long endMillis = collection.getEndMillis();
-
- if (!collection.isEmpty()) {
- // process legacy data, creating active file at starting time, then
- // using end time to possibly trigger rotation.
- mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
- mRotator.maybeRotate(endMillis);
- }
- }
-
/**
* Import a specified {@link NetworkStatsCollection} instance into this recorder,
* and write it into a standalone file.
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 4f0f341..424dcd9 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -1677,11 +1677,6 @@
@Override
public String[] getMobileIfaces() {
- // TODO (b/192758557): Remove debug log.
- if (CollectionUtils.contains(mMobileIfaces, null)) {
- throw new NullPointerException(
- "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
- }
return mMobileIfaces.clone();
}
@@ -2152,11 +2147,6 @@
}
mMobileIfaces = mobileIfaces.toArray(new String[0]);
- // TODO (b/192758557): Remove debug log.
- if (CollectionUtils.contains(mMobileIfaces, null)) {
- throw new NullPointerException(
- "null element in mMobileIfaces: " + Arrays.toString(mMobileIfaces));
- }
}
private static int getSubIdForMobile(@NonNull NetworkStateSnapshot state) {
diff --git a/service/jarjar-excludes.txt b/service/jarjar-excludes.txt
new file mode 100644
index 0000000..b0d6763
--- /dev/null
+++ b/service/jarjar-excludes.txt
@@ -0,0 +1,9 @@
+# Classes loaded by SystemServer via their hardcoded name, so they can't be jarjared
+com\.android\.server\.ConnectivityServiceInitializer(\$.+)?
+com\.android\.server\.NetworkStatsServiceInitializer(\$.+)?
+
+# Do not jarjar com.android.server, as several unit tests fail because they lose
+# package-private visibility between jarjared and non-jarjared classes.
+# TODO: fix the tests and also jarjar com.android.server, or at least only exclude a package that
+# is specific to the module like com.android.server.connectivity
+com\.android\.server\..+
diff --git a/service/jni/com_android_server_BpfNetMaps.cpp b/service/jni/com_android_server_BpfNetMaps.cpp
index 7b1f59c..2780044 100644
--- a/service/jni/com_android_server_BpfNetMaps.cpp
+++ b/service/jni/com_android_server_BpfNetMaps.cpp
@@ -39,152 +39,132 @@
namespace android {
-static void native_init(JNIEnv* env, jobject clazz) {
+#define CHECK_LOG(status) \
+ do { \
+ if (!isOk(status)) \
+ ALOGE("%s failed, error code = %d", __func__, status.code()); \
+ } while (0)
+
+static void native_init(JNIEnv* env, jclass clazz) {
Status status = mTc.start();
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
}
-static jint native_addNaughtyApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_addNaughtyApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
TrafficController::IptOp::IptOpInsert);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_removeNaughtyApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_removeNaughtyApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, PENALTY_BOX_MATCH,
TrafficController::IptOp::IptOpDelete);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_addNiceApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_addNiceApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
TrafficController::IptOp::IptOpInsert);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_removeNiceApp(JNIEnv* env, jobject clazz, jint uid) {
+static jint native_removeNiceApp(JNIEnv* env, jobject self, jint uid) {
const uint32_t appUids = static_cast<uint32_t>(abs(uid));
Status status = mTc.updateUidOwnerMap(appUids, HAPPY_BOX_MATCH,
TrafficController::IptOp::IptOpDelete);
- if (!isOk(status)) {
- ALOGD("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_setChildChain(JNIEnv* env, jobject clazz, jint childChain, jboolean enable) {
+static jint native_setChildChain(JNIEnv* env, jobject self, jint childChain, jboolean enable) {
auto chain = static_cast<ChildChain>(childChain);
int res = mTc.toggleUidOwnerMap(chain, enable);
- if (res) {
- ALOGE("%s failed, error code = %d", __func__, res);
- }
+ if (res) ALOGE("%s failed, error code = %d", __func__, res);
return (jint)res;
}
-static jint native_replaceUidChain(JNIEnv* env, jobject clazz, jstring name, jboolean isAllowlist,
- jintArray jUids) {
+static jint native_replaceUidChain(JNIEnv* env, jobject self, jstring name, jboolean isAllowlist,
+ jintArray jUids) {
const ScopedUtfChars chainNameUtf8(env, name);
- if (chainNameUtf8.c_str() == nullptr) {
- return -EINVAL;
- }
+ if (chainNameUtf8.c_str() == nullptr) return -EINVAL;
const std::string chainName(chainNameUtf8.c_str());
ScopedIntArrayRO uids(env, jUids);
- if (uids.get() == nullptr) {
- return -EINVAL;
- }
+ if (uids.get() == nullptr) return -EINVAL;
size_t size = uids.size();
static_assert(sizeof(*(uids.get())) == sizeof(int32_t));
std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
int res = mTc.replaceUidOwnerMap(chainName, isAllowlist, data);
- if (res) {
- ALOGE("%s failed, error code = %d", __func__, res);
- }
+ if (res) ALOGE("%s failed, error code = %d", __func__, res);
return (jint)res;
}
-static jint native_setUidRule(JNIEnv* env, jobject clazz, jint childChain, jint uid,
- jint firewallRule) {
+static jint native_setUidRule(JNIEnv* env, jobject self, jint childChain, jint uid,
+ jint firewallRule) {
auto chain = static_cast<ChildChain>(childChain);
auto rule = static_cast<FirewallRule>(firewallRule);
FirewallType fType = mTc.getFirewallType(chain);
int res = mTc.changeUidOwnerRule(chain, uid, rule, fType);
- if (res) {
- ALOGE("%s failed, error code = %d", __func__, res);
- }
+ if (res) ALOGE("%s failed, error code = %d", __func__, res);
return (jint)res;
}
-static jint native_addUidInterfaceRules(JNIEnv* env, jobject clazz, jstring ifName,
- jintArray jUids) {
+static jint native_addUidInterfaceRules(JNIEnv* env, jobject self, jstring ifName,
+ jintArray jUids) {
// Null ifName is a wildcard to allow apps to receive packets on all interfaces and ifIndex is
// set to 0.
- int ifIndex;
+ int ifIndex = 0;
if (ifName != nullptr) {
const ScopedUtfChars ifNameUtf8(env, ifName);
const std::string interfaceName(ifNameUtf8.c_str());
ifIndex = if_nametoindex(interfaceName.c_str());
- } else {
- ifIndex = 0;
}
ScopedIntArrayRO uids(env, jUids);
- if (uids.get() == nullptr) {
- return -EINVAL;
- }
+ if (uids.get() == nullptr) return -EINVAL;
size_t size = uids.size();
static_assert(sizeof(*(uids.get())) == sizeof(int32_t));
std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
Status status = mTc.addUidInterfaceRules(ifIndex, data);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_removeUidInterfaceRules(JNIEnv* env, jobject clazz, jintArray jUids) {
+static jint native_removeUidInterfaceRules(JNIEnv* env, jobject self, jintArray jUids) {
ScopedIntArrayRO uids(env, jUids);
- if (uids.get() == nullptr) {
- return -EINVAL;
- }
+ if (uids.get() == nullptr) return -EINVAL;
size_t size = uids.size();
static_assert(sizeof(*(uids.get())) == sizeof(int32_t));
std::vector<int32_t> data ((int32_t *)&uids[0], (int32_t*)&uids[size]);
Status status = mTc.removeUidInterfaceRules(data);
- if (!isOk(status)) {
- ALOGE("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static jint native_swapActiveStatsMap(JNIEnv* env, jobject clazz) {
+static jint native_updateUidLockdownRule(JNIEnv* env, jobject self, jint uid, jboolean add) {
+ Status status = mTc.updateUidLockdownRule(uid, add);
+ CHECK_LOG(status);
+ return (jint)status.code();
+}
+
+static jint native_swapActiveStatsMap(JNIEnv* env, jobject self) {
Status status = mTc.swapActiveStatsMap();
- if (!isOk(status)) {
- ALOGD("%s failed, error code = %d", __func__, status.code());
- }
+ CHECK_LOG(status);
return (jint)status.code();
}
-static void native_setPermissionForUids(JNIEnv* env, jobject clazz, jint permission,
- jintArray jUids) {
+static void native_setPermissionForUids(JNIEnv* env, jobject self, jint permission,
+ jintArray jUids) {
ScopedIntArrayRO uids(env, jUids);
if (uids.get() == nullptr) return;
@@ -194,7 +174,7 @@
mTc.setPermissionForUids(permission, data);
}
-static void native_dump(JNIEnv* env, jobject clazz, jobject javaFd, jboolean verbose) {
+static void native_dump(JNIEnv* env, jobject self, jobject javaFd, jboolean verbose) {
int fd = netjniutils::GetNativeFileDescriptor(env, javaFd);
if (fd < 0) {
jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor");
@@ -229,6 +209,8 @@
(void*)native_addUidInterfaceRules},
{"native_removeUidInterfaceRules", "([I)I",
(void*)native_removeUidInterfaceRules},
+ {"native_updateUidLockdownRule", "(IZ)I",
+ (void*)native_updateUidLockdownRule},
{"native_swapActiveStatsMap", "()I",
(void*)native_swapActiveStatsMap},
{"native_setPermissionForUids", "(I[I)V",
@@ -239,9 +221,8 @@
// clang-format on
int register_com_android_server_BpfNetMaps(JNIEnv* env) {
- return jniRegisterNativeMethods(env,
- "com/android/server/BpfNetMaps",
- gMethods, NELEM(gMethods));
+ return jniRegisterNativeMethods(env, "com/android/server/BpfNetMaps",
+ gMethods, NELEM(gMethods));
}
}; // namespace android
diff --git a/service/jni/com_android_server_TestNetworkService.cpp b/service/jni/com_android_server_TestNetworkService.cpp
index 4efd0e1..9c7a761 100644
--- a/service/jni/com_android_server_TestNetworkService.cpp
+++ b/service/jni/com_android_server_TestNetworkService.cpp
@@ -51,7 +51,15 @@
jniThrowException(env, "java/lang/IllegalStateException", msg.c_str());
}
-static int createTunTapInterface(JNIEnv* env, bool isTun, const char* iface) {
+// enable or disable carrier on tun / tap interface.
+static void setTunTapCarrierEnabledImpl(JNIEnv* env, const char* iface, int tunFd, bool enabled) {
+ uint32_t carrierOn = enabled;
+ if (ioctl(tunFd, TUNSETCARRIER, &carrierOn)) {
+ throwException(env, errno, "set carrier", iface);
+ }
+}
+
+static int createTunTapImpl(JNIEnv* env, bool isTun, bool hasCarrier, const char* iface) {
base::unique_fd tun(open("/dev/tun", O_RDWR | O_NONBLOCK));
ifreq ifr{};
@@ -63,6 +71,11 @@
return -1;
}
+ if (!hasCarrier) {
+ // disable carrier before setting IFF_UP
+ setTunTapCarrierEnabledImpl(env, iface, tun.get(), hasCarrier);
+ }
+
// Activate interface using an unconnected datagram socket.
base::unique_fd inet6CtrlSock(socket(AF_INET6, SOCK_DGRAM, 0));
ifr.ifr_flags = IFF_UP;
@@ -79,23 +92,31 @@
//------------------------------------------------------------------------------
-static jint create(JNIEnv* env, jobject /* thiz */, jboolean isTun, jstring jIface) {
+static void setTunTapCarrierEnabled(JNIEnv* env, jclass /* clazz */, jstring
+ jIface, jint tunFd, jboolean enabled) {
+ ScopedUtfChars iface(env, jIface);
+ if (!iface.c_str()) {
+ jniThrowNullPointerException(env, "iface");
+ }
+ setTunTapCarrierEnabledImpl(env, iface.c_str(), tunFd, enabled);
+}
+
+static jint createTunTap(JNIEnv* env, jclass /* clazz */, jboolean isTun,
+ jboolean hasCarrier, jstring jIface) {
ScopedUtfChars iface(env, jIface);
if (!iface.c_str()) {
jniThrowNullPointerException(env, "iface");
return -1;
}
- int tun = createTunTapInterface(env, isTun, iface.c_str());
-
- // Any exceptions will be thrown from the createTunTapInterface call
- return tun;
+ return createTunTapImpl(env, isTun, hasCarrier, iface.c_str());
}
//------------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
- {"jniCreateTunTap", "(ZLjava/lang/String;)I", (void*)create},
+ {"nativeSetTunTapCarrierEnabled", "(Ljava/lang/String;IZ)V", (void*)setTunTapCarrierEnabled},
+ {"nativeCreateTunTap", "(ZZLjava/lang/String;)I", (void*)createTunTap},
};
int register_com_android_server_TestNetworkService(JNIEnv* env) {
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index ba836b2..e2c5a63 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -315,10 +315,7 @@
// TODO: use android::base::ScopeGuard.
if (int ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK
-#ifdef POSIX_SPAWN_CLOEXEC_DEFAULT
- | POSIX_SPAWN_CLOEXEC_DEFAULT
-#endif
- )) {
+ | POSIX_SPAWN_CLOEXEC_DEFAULT)) {
posix_spawnattr_destroy(&attr);
throwIOException(env, "posix_spawnattr_setflags failed", ret);
return -1;
diff --git a/service/native/Android.bp b/service/native/Android.bp
index cb26bc3..697fcbd 100644
--- a/service/native/Android.bp
+++ b/service/native/Android.bp
@@ -52,7 +52,8 @@
cc_test {
name: "traffic_controller_unit_test",
- test_suites: ["general-tests"],
+ test_suites: ["general-tests", "mts-tethering"],
+ test_config_template: ":net_native_test_config_template",
require_root: true,
local_include_dirs: ["include"],
header_libs: [
@@ -71,4 +72,13 @@
"libnetd_updatable",
"netd_aidl_interface-lateststable-ndk",
],
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
}
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 4923b00..4dc056d 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -56,7 +56,6 @@
using bpf::BpfMap;
using bpf::synchronizeKernelRCU;
using netdutils::DumpWriter;
-using netdutils::getIfaceList;
using netdutils::NetlinkListener;
using netdutils::NetlinkListenerInterface;
using netdutils::ScopedIndent;
@@ -111,14 +110,6 @@
return matchType;
}
-bool TrafficController::hasUpdateDeviceStatsPermission(uid_t uid) {
- // This implementation is the same logic as method ActivityManager#checkComponentPermission.
- // It implies that the calling uid can never be the same as PER_USER_RANGE.
- uint32_t appId = uid % PER_USER_RANGE;
- return ((appId == AID_ROOT) || (appId == AID_SYSTEM) ||
- mPrivilegedUser.find(appId) != mPrivilegedUser.end());
-}
-
const std::string UidPermissionTypeToString(int permission) {
if (permission == INetd::PERMISSION_NONE) {
return "PERMISSION_NONE";
@@ -190,6 +181,7 @@
RETURN_IF_NOT_OK(mUidOwnerMap.init(UID_OWNER_MAP_PATH));
RETURN_IF_NOT_OK(mUidOwnerMap.clear());
RETURN_IF_NOT_OK(mUidPermissionMap.init(UID_PERMISSION_MAP_PATH));
+ ALOGI("%s successfully", __func__);
return netdutils::status::ok;
}
@@ -197,16 +189,6 @@
Status TrafficController::start() {
RETURN_IF_NOT_OK(initMaps());
- // Fetch the list of currently-existing interfaces. At this point NetlinkHandler is
- // already running, so it will call addInterface() when any new interface appears.
- // TODO: Clean-up addInterface() after interface monitoring is in
- // NetworkStatsService.
- std::map<std::string, uint32_t> ifacePairs;
- ASSIGN_OR_RETURN(ifacePairs, getIfaceList());
- for (const auto& ifacePair:ifacePairs) {
- addInterface(ifacePair.first.c_str(), ifacePair.second);
- }
-
auto result = makeSkDestroyListener();
if (!isOk(result)) {
ALOGE("Unable to create SkDestroyListener: %s", toString(result).c_str());
@@ -244,22 +226,6 @@
return netdutils::status::ok;
}
-int TrafficController::addInterface(const char* name, uint32_t ifaceIndex) {
- IfaceValue iface;
- if (ifaceIndex == 0) {
- ALOGE("Unknown interface %s(%d)", name, ifaceIndex);
- return -1;
- }
-
- strlcpy(iface.name, name, sizeof(IfaceValue));
- Status res = mIfaceIndexNameMap.writeValue(ifaceIndex, iface, BPF_ANY);
- if (!isOk(res)) {
- ALOGE("Failed to add iface %s(%d): %s", name, ifaceIndex, strerror(res.code()));
- return -res.code();
- }
- return 0;
-}
-
Status TrafficController::updateOwnerMapEntry(UidOwnerMatchType match, uid_t uid, FirewallRule rule,
FirewallType type) {
std::lock_guard guard(mMutex);
@@ -339,8 +305,6 @@
return ALLOWLIST;
case LOW_POWER_STANDBY:
return ALLOWLIST;
- case LOCKDOWN:
- return DENYLIST;
case OEM_DENY_1:
return DENYLIST;
case OEM_DENY_2:
@@ -372,9 +336,6 @@
case LOW_POWER_STANDBY:
res = updateOwnerMapEntry(LOW_POWER_STANDBY_MATCH, uid, rule, type);
break;
- case LOCKDOWN:
- res = updateOwnerMapEntry(LOCKDOWN_VPN_MATCH, uid, rule, type);
- break;
case OEM_DENY_1:
res = updateOwnerMapEntry(OEM_DENY_1_MATCH, uid, rule, type);
break;
@@ -446,6 +407,18 @@
return netdutils::status::ok;
}
+Status TrafficController::updateUidLockdownRule(const uid_t uid, const bool add) {
+ std::lock_guard guard(mMutex);
+
+ netdutils::Status result = add ? addRule(uid, LOCKDOWN_VPN_MATCH)
+ : removeRule(uid, LOCKDOWN_VPN_MATCH);
+ if (!isOk(result)) {
+ ALOGW("%s Lockdown rule failed(%d): uid=%d",
+ (add ? "add": "remove"), result.code(), uid);
+ }
+ return result;
+}
+
int TrafficController::replaceUidOwnerMap(const std::string& name, bool isAllowlist __unused,
const std::vector<int32_t>& uids) {
// FirewallRule rule = isAllowlist ? ALLOW : DENY;
@@ -487,8 +460,6 @@
oldConfigure.error().message().c_str());
return -oldConfigure.error().code();
}
- Status res;
- BpfConfig newConfiguration;
uint32_t match;
switch (chain) {
case DOZABLE:
@@ -518,9 +489,9 @@
default:
return -EINVAL;
}
- newConfiguration =
- enable ? (oldConfigure.value() | match) : (oldConfigure.value() & (~match));
- res = mConfigurationMap.writeValue(key, newConfiguration, BPF_EXIST);
+ BpfConfig newConfiguration =
+ enable ? (oldConfigure.value() | match) : (oldConfigure.value() & ~match);
+ Status res = mConfigurationMap.writeValue(key, newConfiguration, BPF_EXIST);
if (!isOk(res)) {
ALOGE("Failed to toggleUidOwnerMap(%d): %s", chain, res.msg().c_str());
}
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index 9e53f11..7730c13 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -30,13 +30,15 @@
#include <gtest/gtest.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <binder/Status.h>
#include <netdutils/MockSyscalls.h>
-#define TEST_BPF_MAP
+#define BPF_MAP_MAKE_VISIBLE_FOR_TESTING
#include "TrafficController.h"
#include "bpf/BpfUtils.h"
#include "NetdUpdatablePublic.h"
@@ -49,6 +51,7 @@
using android::netdutils::Status;
using base::Result;
using netdutils::isOk;
+using netdutils::statusFromErrno;
constexpr int TEST_MAP_SIZE = 10;
constexpr uid_t TEST_UID = 10086;
@@ -56,8 +59,16 @@
constexpr uid_t TEST_UID3 = 98765;
constexpr uint32_t TEST_TAG = 42;
constexpr uint32_t TEST_COUNTERSET = 1;
+constexpr int TEST_COOKIE = 1;
+constexpr char TEST_IFNAME[] = "test0";
+constexpr int TEST_IFINDEX = 999;
+constexpr int RXPACKETS = 1;
+constexpr int RXBYTES = 100;
+constexpr int TXPACKETS = 0;
+constexpr int TXBYTES = 0;
#define ASSERT_VALID(x) ASSERT_TRUE((x).isValid())
+#define ASSERT_INVALID(x) ASSERT_FALSE((x).isValid())
class TrafficControllerTest : public ::testing::Test {
protected:
@@ -66,9 +77,13 @@
BpfMap<uint64_t, UidTagValue> mFakeCookieTagMap;
BpfMap<uint32_t, StatsValue> mFakeAppUidStatsMap;
BpfMap<StatsKey, StatsValue> mFakeStatsMapA;
+ BpfMap<StatsKey, StatsValue> mFakeStatsMapB; // makeTrafficControllerMapsInvalid only
+ BpfMap<uint32_t, StatsValue> mFakeIfaceStatsMap; ; // makeTrafficControllerMapsInvalid only
BpfMap<uint32_t, uint32_t> mFakeConfigurationMap;
BpfMap<uint32_t, UidOwnerValue> mFakeUidOwnerMap;
BpfMap<uint32_t, uint8_t> mFakeUidPermissionMap;
+ BpfMap<uint32_t, uint8_t> mFakeUidCounterSetMap;
+ BpfMap<uint32_t, IfaceValue> mFakeIfaceIndexNameMap;
void SetUp() {
std::lock_guard guard(mTc.mMutex);
@@ -91,6 +106,12 @@
mFakeUidPermissionMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
ASSERT_VALID(mFakeUidPermissionMap);
+ mFakeUidCounterSetMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
+ ASSERT_VALID(mFakeUidCounterSetMap);
+
+ mFakeIfaceIndexNameMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE);
+ ASSERT_VALID(mFakeIfaceIndexNameMap);
+
mTc.mCookieTagMap = mFakeCookieTagMap;
ASSERT_VALID(mTc.mCookieTagMap);
mTc.mAppUidStatsMap = mFakeAppUidStatsMap;
@@ -108,21 +129,38 @@
mTc.mUidPermissionMap = mFakeUidPermissionMap;
ASSERT_VALID(mTc.mUidPermissionMap);
mTc.mPrivilegedUser.clear();
+
+ mTc.mUidCounterSetMap = mFakeUidCounterSetMap;
+ ASSERT_VALID(mTc.mUidCounterSetMap);
+
+ mTc.mIfaceIndexNameMap = mFakeIfaceIndexNameMap;
+ ASSERT_VALID(mTc.mIfaceIndexNameMap);
}
void populateFakeStats(uint64_t cookie, uint32_t uid, uint32_t tag, StatsKey* key) {
UidTagValue cookieMapkey = {.uid = (uint32_t)uid, .tag = tag};
EXPECT_RESULT_OK(mFakeCookieTagMap.writeValue(cookie, cookieMapkey, BPF_ANY));
- *key = {.uid = uid, .tag = tag, .counterSet = TEST_COUNTERSET, .ifaceIndex = 1};
- StatsValue statsMapValue = {.rxPackets = 1, .rxBytes = 100};
- EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
- key->tag = 0;
+ *key = {.uid = uid, .tag = tag, .counterSet = TEST_COUNTERSET, .ifaceIndex = TEST_IFINDEX};
+ StatsValue statsMapValue = {.rxPackets = RXPACKETS, .rxBytes = RXBYTES,
+ .txPackets = TXPACKETS, .txBytes = TXBYTES};
EXPECT_RESULT_OK(mFakeStatsMapA.writeValue(*key, statsMapValue, BPF_ANY));
EXPECT_RESULT_OK(mFakeAppUidStatsMap.writeValue(uid, statsMapValue, BPF_ANY));
// put tag information back to statsKey
key->tag = tag;
}
+ void populateFakeCounterSet(uint32_t uid, uint32_t counterSet) {
+ EXPECT_RESULT_OK(mFakeUidCounterSetMap.writeValue(uid, counterSet, BPF_ANY));
+ }
+
+ void populateFakeIfaceIndexName(const char* name, uint32_t ifaceIndex) {
+ if (name == nullptr || ifaceIndex <= 0) return;
+
+ IfaceValue iface;
+ strlcpy(iface.name, name, sizeof(IfaceValue));
+ EXPECT_RESULT_OK(mFakeIfaceIndexNameMap.writeValue(ifaceIndex, iface, BPF_ANY));
+ }
+
void checkUidOwnerRuleForChain(ChildChain chain, UidOwnerMatchType match) {
uint32_t uid = TEST_UID;
EXPECT_EQ(0, mTc.changeUidOwnerRule(chain, uid, DENY, DENYLIST));
@@ -180,7 +218,7 @@
checkEachUidValue(uids, match);
}
- void expectUidOwnerMapValues(const std::vector<uint32_t>& appUids, uint8_t expectedRule,
+ void expectUidOwnerMapValues(const std::vector<uint32_t>& appUids, uint32_t expectedRule,
uint32_t expectedIif) {
for (uint32_t uid : appUids) {
Result<UidOwnerValue> value = mFakeUidOwnerMap.readValue(uid);
@@ -224,37 +262,6 @@
EXPECT_TRUE(mTc.mPrivilegedUser.empty());
}
- void addPrivilegedUid(uid_t uid) {
- std::vector privilegedUid = {uid};
- mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, privilegedUid);
- }
-
- void removePrivilegedUid(uid_t uid) {
- std::vector privilegedUid = {uid};
- mTc.setPermissionForUids(INetd::PERMISSION_NONE, privilegedUid);
- }
-
- void expectFakeStatsUnchanged(uint64_t cookie, uint32_t tag, uint32_t uid,
- StatsKey tagStatsMapKey) {
- Result<UidTagValue> cookieMapResult = mFakeCookieTagMap.readValue(cookie);
- EXPECT_RESULT_OK(cookieMapResult);
- EXPECT_EQ(uid, cookieMapResult.value().uid);
- EXPECT_EQ(tag, cookieMapResult.value().tag);
- Result<StatsValue> statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
- EXPECT_RESULT_OK(statsMapResult);
- EXPECT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
- EXPECT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
- tagStatsMapKey.tag = 0;
- statsMapResult = mFakeStatsMapA.readValue(tagStatsMapKey);
- EXPECT_RESULT_OK(statsMapResult);
- EXPECT_EQ((uint64_t)1, statsMapResult.value().rxPackets);
- EXPECT_EQ((uint64_t)100, statsMapResult.value().rxBytes);
- auto appStatsResult = mFakeAppUidStatsMap.readValue(uid);
- EXPECT_RESULT_OK(appStatsResult);
- EXPECT_EQ((uint64_t)1, appStatsResult.value().rxPackets);
- EXPECT_EQ((uint64_t)100, appStatsResult.value().rxBytes);
- }
-
Status updateUidOwnerMaps(const std::vector<uint32_t>& appUids,
UidOwnerMatchType matchType, TrafficController::IptOp op) {
Status ret(0);
@@ -265,6 +272,108 @@
return ret;
}
+ Status dump(bool verbose, std::vector<std::string>& outputLines) {
+ if (!outputLines.empty()) return statusFromErrno(EUCLEAN, "Output buffer is not empty");
+
+ android::base::unique_fd localFd, remoteFd;
+ if (!Pipe(&localFd, &remoteFd)) return statusFromErrno(errno, "Failed on pipe");
+
+ // dump() blocks until another thread has consumed all its output.
+ std::thread dumpThread =
+ std::thread([this, remoteFd{std::move(remoteFd)}, verbose]() {
+ mTc.dump(remoteFd, verbose);
+ });
+
+ std::string dumpContent;
+ if (!android::base::ReadFdToString(localFd.get(), &dumpContent)) {
+ return statusFromErrno(errno, "Failed to read dump results from fd");
+ }
+ dumpThread.join();
+
+ std::stringstream dumpStream(std::move(dumpContent));
+ std::string line;
+ while (std::getline(dumpStream, line)) {
+ outputLines.push_back(line);
+ }
+
+ return netdutils::status::ok;
+ }
+
+ // Strings in the |expect| must exist in dump results in order. But no need to be consecutive.
+ bool expectDumpsysContains(std::vector<std::string>& expect) {
+ if (expect.empty()) return false;
+
+ std::vector<std::string> output;
+ Status result = dump(true, output);
+ if (!isOk(result)) {
+ GTEST_LOG_(ERROR) << "TrafficController dump failed: " << netdutils::toString(result);
+ return false;
+ }
+
+ int matched = 0;
+ auto it = expect.begin();
+ for (const auto& line : output) {
+ if (it == expect.end()) break;
+ if (std::string::npos != line.find(*it)) {
+ matched++;
+ ++it;
+ }
+ }
+
+ if (matched != expect.size()) {
+ // dump results for debugging
+ for (const auto& o : output) LOG(INFO) << "output: " << o;
+ for (const auto& e : expect) LOG(INFO) << "expect: " << e;
+ return false;
+ }
+ return true;
+ }
+
+ // Once called, the maps of TrafficController can't recover to valid maps which initialized
+ // in SetUp().
+ void makeTrafficControllerMapsInvalid() {
+ constexpr char INVALID_PATH[] = "invalid";
+
+ mFakeCookieTagMap.init(INVALID_PATH);
+ mTc.mCookieTagMap = mFakeCookieTagMap;
+ ASSERT_INVALID(mTc.mCookieTagMap);
+
+ mFakeAppUidStatsMap.init(INVALID_PATH);
+ mTc.mAppUidStatsMap = mFakeAppUidStatsMap;
+ ASSERT_INVALID(mTc.mAppUidStatsMap);
+
+ mFakeStatsMapA.init(INVALID_PATH);
+ mTc.mStatsMapA = mFakeStatsMapA;
+ ASSERT_INVALID(mTc.mStatsMapA);
+
+ mFakeStatsMapB.init(INVALID_PATH);
+ mTc.mStatsMapB = mFakeStatsMapB;
+ ASSERT_INVALID(mTc.mStatsMapB);
+
+ mFakeIfaceStatsMap.init(INVALID_PATH);
+ mTc.mIfaceStatsMap = mFakeIfaceStatsMap;
+ ASSERT_INVALID(mTc.mIfaceStatsMap);
+
+ mFakeConfigurationMap.init(INVALID_PATH);
+ mTc.mConfigurationMap = mFakeConfigurationMap;
+ ASSERT_INVALID(mTc.mConfigurationMap);
+
+ mFakeUidOwnerMap.init(INVALID_PATH);
+ mTc.mUidOwnerMap = mFakeUidOwnerMap;
+ ASSERT_INVALID(mTc.mUidOwnerMap);
+
+ mFakeUidPermissionMap.init(INVALID_PATH);
+ mTc.mUidPermissionMap = mFakeUidPermissionMap;
+ ASSERT_INVALID(mTc.mUidPermissionMap);
+
+ mFakeUidCounterSetMap.init(INVALID_PATH);
+ mTc.mUidCounterSetMap = mFakeUidCounterSetMap;
+ ASSERT_INVALID(mTc.mUidCounterSetMap);
+
+ mFakeIfaceIndexNameMap.init(INVALID_PATH);
+ mTc.mIfaceIndexNameMap = mFakeIfaceIndexNameMap;
+ ASSERT_INVALID(mTc.mIfaceIndexNameMap);
+ }
};
TEST_F(TrafficControllerTest, TestUpdateOwnerMapEntry) {
@@ -298,7 +407,6 @@
checkUidOwnerRuleForChain(POWERSAVE, POWERSAVE_MATCH);
checkUidOwnerRuleForChain(RESTRICTED, RESTRICTED_MATCH);
checkUidOwnerRuleForChain(LOW_POWER_STANDBY, LOW_POWER_STANDBY_MATCH);
- checkUidOwnerRuleForChain(LOCKDOWN, LOCKDOWN_VPN_MATCH);
checkUidOwnerRuleForChain(OEM_DENY_1, OEM_DENY_1_MATCH);
checkUidOwnerRuleForChain(OEM_DENY_2, OEM_DENY_2_MATCH);
checkUidOwnerRuleForChain(OEM_DENY_3, OEM_DENY_3_MATCH);
@@ -430,6 +538,21 @@
expectMapEmpty(mFakeUidOwnerMap);
}
+TEST_F(TrafficControllerTest, TestUpdateUidLockdownRule) {
+ // Add Lockdown rules
+ ASSERT_TRUE(isOk(mTc.updateUidLockdownRule(1000, true /* add */)));
+ ASSERT_TRUE(isOk(mTc.updateUidLockdownRule(1001, true /* add */)));
+ expectUidOwnerMapValues({1000, 1001}, LOCKDOWN_VPN_MATCH, 0);
+
+ // Remove one of Lockdown rules
+ ASSERT_TRUE(isOk(mTc.updateUidLockdownRule(1000, false /* add */)));
+ expectUidOwnerMapValues({1001}, LOCKDOWN_VPN_MATCH, 0);
+
+ // Remove remaining Lockdown rule
+ ASSERT_TRUE(isOk(mTc.updateUidLockdownRule(1001, false /* add */)));
+ expectMapEmpty(mFakeUidOwnerMap);
+}
+
TEST_F(TrafficControllerTest, TestUidInterfaceFilteringRulesCoexistWithExistingMatches) {
// Set up existing PENALTY_BOX_MATCH rules
ASSERT_TRUE(isOk(updateUidOwnerMaps({1000, 1001, 10012}, PENALTY_BOX_MATCH,
@@ -647,6 +770,148 @@
expectPrivilegedUserSetEmpty();
}
+TEST_F(TrafficControllerTest, TestDumpsys) {
+ StatsKey tagStatsMapKey;
+ populateFakeStats(TEST_COOKIE, TEST_UID, TEST_TAG, &tagStatsMapKey);
+ populateFakeCounterSet(TEST_UID3, TEST_COUNTERSET);
+
+ // Expect: (part of this depends on hard-code values in populateFakeStats())
+ //
+ // mCookieTagMap:
+ // cookie=1 tag=0x2a uid=10086
+ //
+ // mUidCounterSetMap:
+ // 98765 1
+ //
+ // mAppUidStatsMap::
+ // uid rxBytes rxPackets txBytes txPackets
+ // 10086 100 1 0 0
+ //
+ // mStatsMapA:
+ // ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets
+ // 999 test0 0x2a 10086 1 100 1 0 0
+ std::vector<std::string> expectedLines = {
+ "mCookieTagMap:",
+ fmt::format("cookie={} tag={:#x} uid={}", TEST_COOKIE, TEST_TAG, TEST_UID),
+ "mUidCounterSetMap:",
+ fmt::format("{} {}", TEST_UID3, TEST_COUNTERSET),
+ "mAppUidStatsMap::", // TODO@: fix double colon
+ "uid rxBytes rxPackets txBytes txPackets",
+ fmt::format("{} {} {} {} {}", TEST_UID, RXBYTES, RXPACKETS, TXBYTES, TXPACKETS),
+ "mStatsMapA",
+ "ifaceIndex ifaceName tag_hex uid_int cnt_set rxBytes rxPackets txBytes txPackets",
+ fmt::format("{} {} {:#x} {} {} {} {} {} {}",
+ TEST_IFINDEX, TEST_IFNAME, TEST_TAG, TEST_UID, TEST_COUNTERSET, RXBYTES,
+ RXPACKETS, TXBYTES, TXPACKETS)};
+
+ populateFakeIfaceIndexName(TEST_IFNAME, TEST_IFINDEX);
+ expectedLines.emplace_back("mIfaceIndexNameMap:");
+ expectedLines.emplace_back(fmt::format("ifaceIndex={} ifaceName={}",
+ TEST_IFINDEX, TEST_IFNAME));
+
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, HAPPY_BOX_MATCH,
+ TrafficController::IptOpInsert)));
+ expectedLines.emplace_back("mUidOwnerMap:");
+ expectedLines.emplace_back(fmt::format("{} HAPPY_BOX_MATCH", TEST_UID));
+
+ mTc.setPermissionForUids(INetd::PERMISSION_UPDATE_DEVICE_STATS, {TEST_UID2});
+ expectedLines.emplace_back("mUidPermissionMap:");
+ expectedLines.emplace_back(fmt::format("{} BPF_PERMISSION_UPDATE_DEVICE_STATS", TEST_UID2));
+ expectedLines.emplace_back("mPrivilegedUser:");
+ expectedLines.emplace_back(fmt::format("{} ALLOW_UPDATE_DEVICE_STATS", TEST_UID2));
+ EXPECT_TRUE(expectDumpsysContains(expectedLines));
+}
+
+TEST_F(TrafficControllerTest, dumpsysInvalidMaps) {
+ makeTrafficControllerMapsInvalid();
+
+ const std::string kErrIterate = "print end with error: Get firstKey map -1 failed: "
+ "Bad file descriptor";
+ const std::string kErrReadRulesConfig = "read ownerMatch configure failed with error: "
+ "Read value of map -1 failed: Bad file descriptor";
+ const std::string kErrReadStatsMapConfig = "read stats map configure failed with error: "
+ "Read value of map -1 failed: Bad file descriptor";
+
+ std::vector<std::string> expectedLines = {
+ fmt::format("mCookieTagMap {}", kErrIterate),
+ fmt::format("mUidCounterSetMap {}", kErrIterate),
+ fmt::format("mAppUidStatsMap {}", kErrIterate),
+ fmt::format("mStatsMapA {}", kErrIterate),
+ fmt::format("mStatsMapB {}", kErrIterate),
+ fmt::format("mIfaceIndexNameMap {}", kErrIterate),
+ fmt::format("mIfaceStatsMap {}", kErrIterate),
+ fmt::format("mConfigurationMap {}", kErrReadRulesConfig),
+ fmt::format("mConfigurationMap {}", kErrReadStatsMapConfig),
+ fmt::format("mUidOwnerMap {}", kErrIterate),
+ fmt::format("mUidPermissionMap {}", kErrIterate)};
+ EXPECT_TRUE(expectDumpsysContains(expectedLines));
+}
+
+TEST_F(TrafficControllerTest, uidMatchTypeToString) {
+ // NO_MATCH(0) can't be verified because match type flag is added by OR operator.
+ // See TrafficController::addRule()
+ static const struct TestConfig {
+ UidOwnerMatchType uidOwnerMatchType;
+ std::string expected;
+ } testConfigs[] = {
+ // clang-format off
+ {HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"},
+ {DOZABLE_MATCH, "DOZABLE_MATCH"},
+ {STANDBY_MATCH, "STANDBY_MATCH"},
+ {POWERSAVE_MATCH, "POWERSAVE_MATCH"},
+ {HAPPY_BOX_MATCH, "HAPPY_BOX_MATCH"},
+ {RESTRICTED_MATCH, "RESTRICTED_MATCH"},
+ {LOW_POWER_STANDBY_MATCH, "LOW_POWER_STANDBY_MATCH"},
+ {IIF_MATCH, "IIF_MATCH"},
+ {LOCKDOWN_VPN_MATCH, "LOCKDOWN_VPN_MATCH"},
+ {OEM_DENY_1_MATCH, "OEM_DENY_1_MATCH"},
+ {OEM_DENY_2_MATCH, "OEM_DENY_2_MATCH"},
+ {OEM_DENY_3_MATCH, "OEM_DENY_3_MATCH"},
+ // clang-format on
+ };
+
+ for (const auto& config : testConfigs) {
+ SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", config.uidOwnerMatchType,
+ config.expected));
+
+ // Test private function uidMatchTypeToString() via dumpsys.
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, config.uidOwnerMatchType,
+ TrafficController::IptOpInsert)));
+ std::vector<std::string> expectedLines;
+ expectedLines.emplace_back(fmt::format("{} {}", TEST_UID, config.expected));
+ EXPECT_TRUE(expectDumpsysContains(expectedLines));
+
+ // Clean up the stubs.
+ ASSERT_TRUE(isOk(updateUidOwnerMaps({TEST_UID}, config.uidOwnerMatchType,
+ TrafficController::IptOpDelete)));
+ }
+}
+
+TEST_F(TrafficControllerTest, getFirewallType) {
+ static const struct TestConfig {
+ ChildChain childChain;
+ FirewallType firewallType;
+ } testConfigs[] = {
+ // clang-format off
+ {NONE, DENYLIST},
+ {DOZABLE, ALLOWLIST},
+ {STANDBY, DENYLIST},
+ {POWERSAVE, ALLOWLIST},
+ {RESTRICTED, ALLOWLIST},
+ {LOW_POWER_STANDBY, ALLOWLIST},
+ {OEM_DENY_1, DENYLIST},
+ {OEM_DENY_2, DENYLIST},
+ {OEM_DENY_3, DENYLIST},
+ {INVALID_CHAIN, DENYLIST},
+ // clang-format on
+ };
+
+ for (const auto& config : testConfigs) {
+ SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", config.childChain, config.firewallType));
+ EXPECT_EQ(config.firewallType, mTc.getFirewallType(config.childChain));
+ }
+}
+
constexpr uint32_t SOCK_CLOSE_WAIT_US = 30 * 1000;
constexpr uint32_t ENOBUFS_POLL_WAIT_US = 10 * 1000;
@@ -682,7 +947,7 @@
if (res.ok() || (res.error().code() == ENOENT)) {
return Result<void>();
}
- ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s\n", key,
+ ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s", key,
strerror(res.error().code()));
}
// Move forward to next cookie in the map.
diff --git a/service/native/include/Common.h b/service/native/include/Common.h
index c9653ad..03f449a 100644
--- a/service/native/include/Common.h
+++ b/service/native/include/Common.h
@@ -38,7 +38,6 @@
POWERSAVE = 3,
RESTRICTED = 4,
LOW_POWER_STANDBY = 5,
- LOCKDOWN = 6,
OEM_DENY_1 = 7,
OEM_DENY_2 = 8,
OEM_DENY_3 = 9,
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index c019ce7..8512929 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -45,11 +45,6 @@
*/
netdutils::Status swapActiveStatsMap() EXCLUDES(mMutex);
- /*
- * Add the interface name and index pair into the eBPF map.
- */
- int addInterface(const char* name, uint32_t ifaceIndex);
-
int changeUidOwnerRule(ChildChain chain, const uid_t uid, FirewallRule rule, FirewallType type);
int removeUidOwnerRule(const uid_t uid);
@@ -71,6 +66,8 @@
EXCLUDES(mMutex);
netdutils::Status removeUidInterfaceRules(const std::vector<int32_t>& uids) EXCLUDES(mMutex);
+ netdutils::Status updateUidLockdownRule(const uid_t uid, const bool add) EXCLUDES(mMutex);
+
netdutils::Status updateUidOwnerMap(const uint32_t uid,
UidOwnerMatchType matchType, IptOp op) EXCLUDES(mMutex);
@@ -185,8 +182,6 @@
// need to call back to system server for permission check.
std::set<uid_t> mPrivilegedUser GUARDED_BY(mMutex);
- bool hasUpdateDeviceStatsPermission(uid_t uid) REQUIRES(mMutex);
-
// For testing
friend class TrafficControllerTest;
};
diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp
index 68e4dc4..54d40ac 100644
--- a/service/native/libs/libclat/Android.bp
+++ b/service/native/libs/libclat/Android.bp
@@ -35,7 +35,8 @@
cc_test {
name: "libclat_test",
defaults: ["netd_defaults"],
- test_suites: ["device-tests"],
+ test_suites: ["general-tests", "mts-tethering"],
+ test_config_template: ":net_native_test_config_template",
srcs: [
"clatutils_test.cpp",
],
@@ -49,5 +50,14 @@
"liblog",
"libnetutils",
],
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
require_root: true,
}
diff --git a/service/native/libs/libclat/clatutils_test.cpp b/service/native/libs/libclat/clatutils_test.cpp
index 4153e19..8cca1f4 100644
--- a/service/native/libs/libclat/clatutils_test.cpp
+++ b/service/native/libs/libclat/clatutils_test.cpp
@@ -19,6 +19,7 @@
#include <gtest/gtest.h>
#include <linux/if_packet.h>
#include <linux/if_tun.h>
+#include <netinet/in.h>
#include "tun_interface.h"
extern "C" {
@@ -182,6 +183,31 @@
v6Iface.destroy();
}
+// This is not a realistic test because we can't test generateIPv6Address here since it requires
+// manipulating routing, which we can't do without talking to the real netd on the system.
+// See test MakeChecksumNeutral.
+// TODO: remove this test once EthernetTetheringTest can test on mainline test coverage branch.
+TEST_F(ClatUtils, GenerateIpv6AddressFailWithUlaSocketAddress) {
+ // Create an interface for generateIpv6Address to connect to.
+ TunInterface tun;
+ ASSERT_EQ(0, tun.init());
+
+ // Unused because v6 address is ULA and makeChecksumNeutral has never called.
+ in_addr v4 = {inet_addr(kIPv4LocalAddr)};
+ // Not a NAT64 prefix because test can't connect to in generateIpv6Address.
+ // Used to be a fake address from the tun interface for generating an IPv6 socket address.
+ // nat64Prefix won't be used in MakeChecksumNeutral because MakeChecksumNeutral has never
+ // be called.
+ in6_addr nat64Prefix = tun.dstAddr(); // not realistic
+ in6_addr v6;
+ EXPECT_EQ(1, inet_pton(AF_INET6, "::", &v6)); // initialize as zero
+
+ EXPECT_EQ(-ENETUNREACH, generateIpv6Address(tun.name().c_str(), v4, nat64Prefix, &v6));
+ EXPECT_TRUE(IN6_IS_ADDR_ULA(&v6));
+
+ tun.destroy();
+}
+
} // namespace clat
} // namespace net
} // namespace android
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index c006bc6..151d0e3 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -216,6 +216,19 @@
}
/**
+ * Update lockdown rule for uid
+ *
+ * @param uid target uid to add/remove the rule
+ * @param add {@code true} to add the rule, {@code false} to remove the rule.
+ * @throws ServiceSpecificException in case of failure, with an error code indicating the
+ * cause of the failure.
+ */
+ public void updateUidLockdownRule(final int uid, final boolean add) {
+ final int err = native_updateUidLockdownRule(uid, add);
+ maybeThrow(err, "Unable to update lockdown rule");
+ }
+
+ /**
* Request netd to change the current active network stats map.
*
* @throws ServiceSpecificException in case of failure, with an error code indicating the
@@ -271,6 +284,7 @@
private native int native_setUidRule(int childChain, int uid, int firewallRule);
private native int native_addUidInterfaceRules(String ifName, int[] uids);
private native int native_removeUidInterfaceRules(int[] uids);
+ private native int native_updateUidLockdownRule(int uid, boolean add);
private native int native_swapActiveStatsMap();
private native void native_setPermissionForUids(int permissions, int[] uids);
private native void native_dump(FileDescriptor fd, boolean verbose);
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index d0cb294..d734029 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -3428,6 +3428,10 @@
for (NetworkAgentInfo nai : networksSortedById()) {
pw.println(nai.toString());
pw.increaseIndent();
+ pw.println("Nat464Xlat:");
+ pw.increaseIndent();
+ nai.dumpNat464Xlat(pw);
+ pw.decreaseIndent();
pw.println(String.format(
"Requests: REQUEST:%d LISTEN:%d BACKGROUND_REQUEST:%d total:%d",
nai.numForegroundNetworkRequests(),
@@ -3443,10 +3447,6 @@
pw.increaseIndent();
nai.dumpInactivityTimers(pw);
pw.decreaseIndent();
- pw.println("Nat464Xlat:");
- pw.increaseIndent();
- nai.dumpNat464Xlat(pw);
- pw.decreaseIndent();
pw.decreaseIndent();
}
}
@@ -3872,7 +3872,6 @@
}
final boolean wasValidated = nai.lastValidated;
- final boolean wasDefault = isDefaultNetwork(nai);
final boolean wasPartial = nai.partialConnectivity;
nai.partialConnectivity = ((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
final boolean partialConnectivityChanged =
@@ -7767,10 +7766,6 @@
// when the old rules are removed and the time when new rules are added. To fix this,
// make eBPF support two allowlisted interfaces so here new rules can be added before the
// old rules are being removed.
-
- // Null iface given to onVpnUidRangesAdded/Removed is a wildcard to allow apps to receive
- // packets on all interfaces. This is required to accept incoming traffic in Lockdown mode
- // by overriding the Lockdown blocking rule.
if (wasFiltering) {
mPermissionMonitor.onVpnUidRangesRemoved(oldIface, ranges, vpnAppUid);
}
@@ -8096,12 +8091,14 @@
* Returns whether we need to set interface filtering rule or not
*/
private boolean requiresVpnAllowRule(NetworkAgentInfo nai, LinkProperties lp,
- String filterIface) {
- // Only filter if lp has an interface.
- if (lp == null || lp.getInterfaceName() == null) return false;
- // Before T, allow rules are only needed if VPN isolation is enabled.
- // T and After T, allow rules are needed for all VPNs.
- return filterIface != null || (nai.isVPN() && SdkLevel.isAtLeastT());
+ String isolationIface) {
+ // Allow rules are always needed if VPN isolation is enabled.
+ if (isolationIface != null) return true;
+
+ // On T and above, allow rules are needed for all VPNs. Allow rule with null iface is a
+ // wildcard to allow apps to receive packets on all interfaces. This is required to accept
+ // incoming traffic in Lockdown mode by overriding the Lockdown blocking rule.
+ return SdkLevel.isAtLeastT() && nai.isVPN() && lp != null && lp.getInterfaceName() != null;
}
private static UidRangeParcel[] toUidRangeStableParcels(final @NonNull Set<UidRange> ranges) {
@@ -8244,10 +8241,6 @@
// above, where the addition of new ranges happens before the removal of old ranges.
// TODO Fix this window by computing an accurate diff on Set<UidRange>, so the old range
// to be removed will never overlap with the new range to be added.
-
- // Null iface given to onVpnUidRangesAdded/Removed is a wildcard to allow apps to
- // receive packets on all interfaces. This is required to accept incoming traffic in
- // Lockdown mode by overriding the Lockdown blocking rule.
if (wasFiltering && !prevRanges.isEmpty()) {
mPermissionMonitor.onVpnUidRangesRemoved(oldIface, prevRanges,
prevNc.getOwnerUid());
@@ -9266,7 +9259,18 @@
params.networkCapabilities = networkAgent.networkCapabilities;
params.linkProperties = new LinkProperties(networkAgent.linkProperties,
true /* parcelSensitiveFields */);
- networkAgent.networkMonitor().notifyNetworkConnected(params);
+ // isAtLeastT() is conservative here, as recent versions of NetworkStack support the
+ // newer callback even before T. However getInterfaceVersion is a synchronized binder
+ // call that would cause a Log.wtf to be emitted from the system_server process, and
+ // in the absence of a satisfactory, scalable solution which follows an easy/standard
+ // process to check the interface version, just use an SDK check. NetworkStack will
+ // always be new enough when running on T+.
+ if (SdkLevel.isAtLeastT()) {
+ networkAgent.networkMonitor().notifyNetworkConnected(params);
+ } else {
+ networkAgent.networkMonitor().notifyNetworkConnected(params.linkProperties,
+ params.networkCapabilities);
+ }
scheduleUnvalidatedPrompt(networkAgent);
// Whether a particular NetworkRequest listen should cause signal strength thresholds to
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index e12190c..1209579 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -50,6 +50,7 @@
import com.android.net.module.util.NetdUtils;
import com.android.net.module.util.NetworkStackConstants;
+import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
@@ -76,7 +77,11 @@
@NonNull private final NetworkProvider mNetworkProvider;
// Native method stubs
- private static native int jniCreateTunTap(boolean isTun, @NonNull String iface);
+ private static native int nativeCreateTunTap(boolean isTun, boolean hasCarrier,
+ @NonNull String iface);
+
+ private static native void nativeSetTunTapCarrierEnabled(@NonNull String iface, int tunFd,
+ boolean enabled);
@VisibleForTesting
protected TestNetworkService(@NonNull Context context) {
@@ -114,7 +119,7 @@
* interface.
*/
@Override
- public TestNetworkInterface createInterface(boolean isTun, boolean bringUp,
+ public TestNetworkInterface createInterface(boolean isTun, boolean hasCarrier, boolean bringUp,
LinkAddress[] linkAddrs, @Nullable String iface) {
enforceTestNetworkPermissions(mContext);
@@ -130,8 +135,8 @@
final long token = Binder.clearCallingIdentity();
try {
- ParcelFileDescriptor tunIntf =
- ParcelFileDescriptor.adoptFd(jniCreateTunTap(isTun, interfaceName));
+ ParcelFileDescriptor tunIntf = ParcelFileDescriptor.adoptFd(
+ nativeCreateTunTap(isTun, hasCarrier, interfaceName));
for (LinkAddress addr : linkAddrs) {
mNetd.interfaceAddAddress(
interfaceName,
@@ -375,4 +380,20 @@
public static void enforceTestNetworkPermissions(@NonNull Context context) {
context.enforceCallingOrSelfPermission(PERMISSION_NAME, "TestNetworkService");
}
+
+ /** Enable / disable TestNetworkInterface carrier */
+ @Override
+ public void setCarrierEnabled(@NonNull TestNetworkInterface iface, boolean enabled) {
+ enforceTestNetworkPermissions(mContext);
+ nativeSetTunTapCarrierEnabled(iface.getInterfaceName(), iface.getFileDescriptor().getFd(),
+ enabled);
+ // Explicitly close fd after use to prevent StrictMode from complaining.
+ // Also, explicitly referencing iface guarantees that the object is not garbage collected
+ // before nativeSetTunTapCarrierEnabled() executes.
+ try {
+ iface.getFileDescriptor().close();
+ } catch (IOException e) {
+ // if the close fails, there is not much that can be done -- move on.
+ }
+ }
}
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 4a7c77a..498cf63 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -498,6 +498,31 @@
}
}
+ private void maybeCleanUp(ParcelFileDescriptor tunFd, ParcelFileDescriptor readSock6,
+ ParcelFileDescriptor writeSock6) {
+ if (tunFd != null) {
+ try {
+ tunFd.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to close tun file descriptor " + e);
+ }
+ }
+ if (readSock6 != null) {
+ try {
+ readSock6.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to close read socket " + e);
+ }
+ }
+ if (writeSock6 != null) {
+ try {
+ writeSock6.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to close write socket " + e);
+ }
+ }
+ }
+
/**
* Start clatd for a given interface and NAT64 prefix.
*/
@@ -546,8 +571,15 @@
// [3] Open, configure and bring up the tun interface.
// Create the v4-... tun interface.
+
+ // Initialize all required file descriptors with null pointer. This makes the following
+ // error handling easier. Simply always call #maybeCleanUp for closing file descriptors,
+ // if any valid ones, in error handling.
+ ParcelFileDescriptor tunFd = null;
+ ParcelFileDescriptor readSock6 = null;
+ ParcelFileDescriptor writeSock6 = null;
+
final String tunIface = CLAT_PREFIX + iface;
- final ParcelFileDescriptor tunFd;
try {
tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface));
} catch (IOException e) {
@@ -556,7 +588,7 @@
final int tunIfIndex = mDeps.getInterfaceIndex(tunIface);
if (tunIfIndex == INVALID_IFINDEX) {
- tunFd.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("Fail to get interface index for interface " + tunIface);
}
@@ -564,24 +596,27 @@
try {
mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */);
} catch (RemoteException | ServiceSpecificException e) {
- tunFd.close();
Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e);
}
// Detect ipv4 mtu.
final Integer fwmark = getFwmark(netId);
- final int detectedMtu = mDeps.detectMtu(pfx96Str,
+ final int detectedMtu;
+ try {
+ detectedMtu = mDeps.detectMtu(pfx96Str,
ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark);
+ } catch (IOException e) {
+ maybeCleanUp(tunFd, readSock6, writeSock6);
+ throw new IOException("Detect MTU on " + tunIface + " failed: " + e);
+ }
final int mtu = adjustMtu(detectedMtu);
Log.i(TAG, "ipv4 mtu is " + mtu);
- // TODO: add setIptablesDropRule
-
// Config tun interface mtu, address and bring up.
try {
mNetd.interfaceSetMtu(tunIface, mtu);
} catch (RemoteException | ServiceSpecificException e) {
- tunFd.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e);
}
final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
@@ -593,14 +628,13 @@
try {
mNetd.interfaceSetCfg(ifConfig);
} catch (RemoteException | ServiceSpecificException e) {
- tunFd.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/"
+ ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e);
}
// [4] Open and configure local 464xlat read/write sockets.
// Opens a packet socket to receive IPv6 packets in clatd.
- final ParcelFileDescriptor readSock6;
try {
// Use a JNI call to get native file descriptor instead of Os.socket() because we would
// like to use ParcelFileDescriptor to manage file descriptor. But ctor
@@ -608,27 +642,23 @@
// descriptor to initialize ParcelFileDescriptor object instead.
readSock6 = mDeps.adoptFd(mDeps.openPacketSocket());
} catch (IOException e) {
- tunFd.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("Open packet socket failed: " + e);
}
// Opens a raw socket with a given fwmark to send IPv6 packets in clatd.
- final ParcelFileDescriptor writeSock6;
try {
// Use a JNI call to get native file descriptor instead of Os.socket(). See above
// reason why we use jniOpenPacketSocket6().
writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark));
} catch (IOException e) {
- tunFd.close();
- readSock6.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("Open raw socket failed: " + e);
}
final int ifIndex = mDeps.getInterfaceIndex(iface);
if (ifIndex == INVALID_IFINDEX) {
- tunFd.close();
- readSock6.close();
- writeSock6.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("Fail to get interface index for interface " + iface);
}
@@ -636,9 +666,7 @@
try {
mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6Str, ifIndex);
} catch (IOException e) {
- tunFd.close();
- readSock6.close();
- writeSock6.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("add anycast sockopt failed: " + e);
}
@@ -647,9 +675,7 @@
try {
cookie = mDeps.tagSocketAsClat(writeSock6.getFileDescriptor());
} catch (IOException e) {
- tunFd.close();
- readSock6.close();
- writeSock6.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("tag raw socket failed: " + e);
}
@@ -657,9 +683,7 @@
try {
mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6Str, ifIndex);
} catch (IOException e) {
- tunFd.close();
- readSock6.close();
- writeSock6.close();
+ maybeCleanUp(tunFd, readSock6, writeSock6);
throw new IOException("configure packet socket failed: " + e);
}
@@ -673,9 +697,9 @@
mDeps.untagSocket(cookie);
throw new IOException("Error start clatd on " + iface + ": " + e);
} finally {
- tunFd.close();
- readSock6.close();
- writeSock6.close();
+ // The file descriptors have been duplicated (dup2) to clatd in native_startClatd().
+ // Close these file descriptor stubs which are unused anymore.
+ maybeCleanUp(tunFd, readSock6, writeSock6);
}
// [6] Initialize and store clatd tracker object.
diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java
index e8fc06d..e4ad391 100644
--- a/service/src/com/android/server/connectivity/Nat464Xlat.java
+++ b/service/src/com/android/server/connectivity/Nat464Xlat.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static com.android.net.module.util.CollectionUtils.contains;
@@ -127,6 +128,11 @@
final boolean supported = contains(NETWORK_TYPES, nai.networkInfo.getType());
final boolean connected = contains(NETWORK_STATES, nai.networkInfo.getState());
+ // Allow to run clat on test network.
+ // TODO: merge to boolean "supported" once boolean "supported" is migrated to
+ // NetworkCapabilities.TRANSPORT_*.
+ final boolean isTestNetwork = nai.networkCapabilities.hasTransport(TRANSPORT_TEST);
+
// Only run clat on networks that have a global IPv6 address and don't have a native IPv4
// address.
LinkProperties lp = nai.linkProperties;
@@ -137,8 +143,8 @@
final boolean skip464xlat = (nai.netAgentConfig() != null)
&& nai.netAgentConfig().skip464xlat;
- return supported && connected && isIpv6OnlyNetwork && !skip464xlat && !nai.destroyed
- && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
+ return (supported || isTestNetwork) && connected && isIpv6OnlyNetwork && !skip464xlat
+ && !nai.destroyed && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
? isCellular464XlatEnabled() : true);
}
@@ -540,7 +546,7 @@
mClatCoordinator.dump(pw);
pw.decreaseIndent();
} else {
- pw.println("<not start>");
+ pw.println("<not started>");
}
}
}
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index e4a2c20..34c6d2d 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -23,9 +23,6 @@
import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
-import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOCKDOWN_VPN;
-import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
-import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivitySettingsManager.UIDS_ALLOWED_ON_RESTRICTED_NETWORKS;
import static android.net.INetd.PERMISSION_INTERNET;
import static android.net.INetd.PERMISSION_NETWORK;
@@ -684,8 +681,12 @@
}
private synchronized void updateLockdownUid(int uid, boolean add) {
- if (UidRange.containsUid(mVpnLockdownUidRanges.getSet(), uid)
- && !hasRestrictedNetworksPermission(uid)) {
+ // Apps that can use restricted networks can always bypass VPNs.
+ if (hasRestrictedNetworksPermission(uid)) {
+ return;
+ }
+
+ if (UidRange.containsUid(mVpnLockdownUidRanges.getSet(), uid)) {
updateLockdownUidRule(uid, add);
}
}
@@ -790,7 +791,7 @@
mAllApps.add(appId);
// Log package added.
- mPermissionUpdateLogs.log("Package add: name=" + packageName + ", uid=" + uid
+ mPermissionUpdateLogs.log("Package add: uid=" + uid
+ ", nPerm=(" + permissionToString(permission) + "/"
+ permissionToString(currentPermission) + ")"
+ ", tPerm=" + permissionToString(appIdTrafficPerm));
@@ -843,7 +844,7 @@
final int permission = highestUidNetworkPermission(uid);
// Log package removed.
- mPermissionUpdateLogs.log("Package remove: name=" + packageName + ", uid=" + uid
+ mPermissionUpdateLogs.log("Package remove: uid=" + uid
+ ", nPerm=(" + permissionToString(permission) + "/"
+ permissionToString(currentPermission) + ")"
+ ", tPerm=" + permissionToString(appIdTrafficPerm));
@@ -1079,11 +1080,7 @@
private void updateLockdownUidRule(int uid, boolean add) {
try {
- if (add) {
- mBpfNetMaps.setUidRule(FIREWALL_CHAIN_LOCKDOWN_VPN, uid, FIREWALL_RULE_DENY);
- } else {
- mBpfNetMaps.setUidRule(FIREWALL_CHAIN_LOCKDOWN_VPN, uid, FIREWALL_RULE_ALLOW);
- }
+ mBpfNetMaps.updateUidLockdownRule(uid, add);
} catch (ServiceSpecificException e) {
loge("Failed to " + (add ? "add" : "remove") + " Lockdown rule: " + e);
}
@@ -1259,7 +1256,7 @@
pw.println("Lockdown filtering rules:");
pw.increaseIndent();
for (final UidRange range : mVpnLockdownUidRanges.getSet()) {
- pw.println("UIDs: " + range.toString());
+ pw.println("UIDs: " + range);
}
pw.decreaseIndent();
diff --git a/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java b/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java
index 534dbe7..e682026 100644
--- a/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java
+++ b/service/src/com/android/server/connectivity/QosCallbackAgentConnection.java
@@ -30,6 +30,8 @@
import android.telephony.data.NrQosSessionAttributes;
import android.util.Log;
+import com.android.modules.utils.build.SdkLevel;
+
import java.util.Objects;
/**
@@ -149,6 +151,7 @@
void sendEventEpsQosSessionAvailable(final QosSession session,
final EpsBearerQosSessionAttributes attributes) {
+ if (!validateOrSendErrorAndUnregister()) return;
try {
if (DBG) log("sendEventEpsQosSessionAvailable: sending...");
mCallback.onQosEpsBearerSessionAvailable(session, attributes);
@@ -159,6 +162,7 @@
void sendEventNrQosSessionAvailable(final QosSession session,
final NrQosSessionAttributes attributes) {
+ if (!validateOrSendErrorAndUnregister()) return;
try {
if (DBG) log("sendEventNrQosSessionAvailable: sending...");
mCallback.onNrQosSessionAvailable(session, attributes);
@@ -168,6 +172,7 @@
}
void sendEventQosSessionLost(@NonNull final QosSession session) {
+ if (!validateOrSendErrorAndUnregister()) return;
try {
if (DBG) log("sendEventQosSessionLost: sending...");
mCallback.onQosSessionLost(session);
@@ -185,6 +190,21 @@
}
}
+ private boolean validateOrSendErrorAndUnregister() {
+ final int exceptionType = mFilter.validate();
+ if (exceptionType != EX_TYPE_FILTER_NONE) {
+ log("validation fail before sending QosCallback.");
+ // Error callback is returned from Android T to prevent any disruption of application
+ // running on Android S.
+ if (SdkLevel.isAtLeastT()) {
+ sendEventQosCallbackError(exceptionType);
+ mQosCallbackTracker.unregisterCallback(mCallback);
+ }
+ return false;
+ }
+ return true;
+ }
+
private static void log(@NonNull final String msg) {
Log.d(TAG, msg);
}
diff --git a/service/src/com/android/server/net/DelayedDiskWrite.java b/service/src/com/android/server/net/DelayedDiskWrite.java
index 35dc455..41cb419 100644
--- a/service/src/com/android/server/net/DelayedDiskWrite.java
+++ b/service/src/com/android/server/net/DelayedDiskWrite.java
@@ -21,6 +21,8 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
@@ -32,11 +34,42 @@
public class DelayedDiskWrite {
private static final String TAG = "DelayedDiskWrite";
+ private final Dependencies mDeps;
+
private HandlerThread mDiskWriteHandlerThread;
private Handler mDiskWriteHandler;
/* Tracks multiple writes on the same thread */
private int mWriteSequence = 0;
+ public DelayedDiskWrite() {
+ this(new Dependencies());
+ }
+
+ @VisibleForTesting
+ DelayedDiskWrite(Dependencies deps) {
+ mDeps = deps;
+ }
+
+ /**
+ * Dependencies class of DelayedDiskWrite, used for injection in tests.
+ */
+ @VisibleForTesting
+ static class Dependencies {
+ /**
+ * Create a HandlerThread to use in DelayedDiskWrite.
+ */
+ public HandlerThread makeHandlerThread() {
+ return new HandlerThread("DelayedDiskWriteThread");
+ }
+
+ /**
+ * Quit the HandlerThread looper.
+ */
+ public void quitHandlerThread(HandlerThread handlerThread) {
+ handlerThread.getLooper().quit();
+ }
+ }
+
/**
* Used to do a delayed data write to a given {@link OutputStream}.
*/
@@ -65,7 +98,7 @@
/* Do a delayed write to disk on a separate handler thread */
synchronized (this) {
if (++mWriteSequence == 1) {
- mDiskWriteHandlerThread = new HandlerThread("DelayedDiskWriteThread");
+ mDiskWriteHandlerThread = mDeps.makeHandlerThread();
mDiskWriteHandlerThread.start();
mDiskWriteHandler = new Handler(mDiskWriteHandlerThread.getLooper());
}
@@ -99,9 +132,9 @@
// Quit if no more writes sent
synchronized (this) {
if (--mWriteSequence == 0) {
- mDiskWriteHandler.getLooper().quit();
- mDiskWriteHandler = null;
+ mDeps.quitHandlerThread(mDiskWriteHandlerThread);
mDiskWriteHandlerThread = null;
+ mDiskWriteHandler = null;
}
}
}
diff --git a/tests/common/java/android/net/EthernetNetworkManagementExceptionTest.java b/tests/common/java/android/net/EthernetNetworkManagementExceptionTest.java
new file mode 100644
index 0000000..84b6e54
--- /dev/null
+++ b/tests/common/java/android/net/EthernetNetworkManagementExceptionTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+public class EthernetNetworkManagementExceptionTest {
+ private static final String ERROR_MESSAGE = "Test error message";
+
+ @Test
+ public void testEthernetNetworkManagementExceptionParcelable() {
+ final EthernetNetworkManagementException e =
+ new EthernetNetworkManagementException(ERROR_MESSAGE);
+
+ assertParcelingIsLossless(e);
+ }
+
+ @Test
+ public void testEthernetNetworkManagementExceptionHasExpectedErrorMessage() {
+ final EthernetNetworkManagementException e =
+ new EthernetNetworkManagementException(ERROR_MESSAGE);
+
+ assertEquals(ERROR_MESSAGE, e.getMessage());
+ }
+}
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index c47ccbf..ac84e57 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -35,4 +35,12 @@
"general-tests",
"sts"
],
+ data: [
+ ":CtsHostsideNetworkTestsApp",
+ ":CtsHostsideNetworkTestsApp2",
+ ":CtsHostsideNetworkTestsApp3",
+ ":CtsHostsideNetworkTestsApp3PreT",
+ ":CtsHostsideNetworkTestsAppNext",
+ ],
+ per_testcase_directory: true,
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index dc67c70..dd8b523 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -378,10 +378,6 @@
if (mNetwork == null) {
fail("VPN did not become available after " + TIMEOUT_MS + "ms");
}
-
- // Unfortunately, when the available callback fires, the VPN UID ranges are not yet
- // configured. Give the system some time to do so. http://b/18436087 .
- try { Thread.sleep(3000); } catch(InterruptedException e) {}
}
private void stopVpn() {
diff --git a/tests/cts/hostside/app3/Android.bp b/tests/cts/hostside/app3/Android.bp
index 69667ce..141cf03 100644
--- a/tests/cts/hostside/app3/Android.bp
+++ b/tests/cts/hostside/app3/Android.bp
@@ -14,6 +14,10 @@
// limitations under the License.
//
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
java_defaults {
name: "CtsHostsideNetworkTestsApp3Defaults",
srcs: ["src/**/*.java"],
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
index cc07fd1..d0567ae 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -34,8 +34,6 @@
import java.io.FileNotFoundException;
import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
abstract class HostsideNetworkTestCase extends DeviceTestCase implements IAbiReceiver,
IBuildReceiver {
@@ -171,18 +169,19 @@
}
}
- private static final Pattern UID_PATTERN =
- Pattern.compile(".*userId=([0-9]+)$", Pattern.MULTILINE);
-
protected int getUid(String packageName) throws DeviceNotAvailableException {
- final String output = runCommand("dumpsys package " + packageName);
- final Matcher matcher = UID_PATTERN.matcher(output);
- while (matcher.find()) {
- final String match = matcher.group(1);
- return Integer.parseInt(match);
+ final int currentUser = getDevice().getCurrentUser();
+ final String uidLines = runCommand(
+ "cmd package list packages -U --user " + currentUser + " " + packageName);
+ for (String uidLine : uidLines.split("\n")) {
+ if (uidLine.startsWith("package:" + packageName + " uid:")) {
+ final String[] uidLineParts = uidLine.split(":");
+ // 3rd entry is package uid
+ return Integer.parseInt(uidLineParts[2].trim());
+ }
}
- throw new RuntimeException("Did not find regexp '" + UID_PATTERN + "' on adb output\n"
- + output);
+ throw new IllegalStateException("Failed to find the test app on the device; pkg="
+ + packageName + ", u=" + currentUser);
}
protected String runCommand(String command) throws DeviceNotAvailableException {
diff --git a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
index 0344604..1b77d5f 100644
--- a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
+++ b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
@@ -33,7 +33,6 @@
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
import android.net.Uri
-import android.net.cts.NetworkValidationTestUtil.clearValidationTestUrlsDeviceConfig
import android.net.cts.NetworkValidationTestUtil.setHttpUrlDeviceConfig
import android.net.cts.NetworkValidationTestUtil.setHttpsUrlDeviceConfig
import android.net.cts.NetworkValidationTestUtil.setUrlExpirationDeviceConfig
@@ -60,6 +59,8 @@
import org.junit.Assume.assumeTrue
import org.junit.Assume.assumeFalse
import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Rule
import org.junit.runner.RunWith
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
@@ -99,34 +100,42 @@
private val server = TestHttpServer("localhost")
+ @get:Rule
+ val deviceConfigRule = DeviceConfigRule(retryCountBeforeSIfConfigChanged = 5)
+
+ companion object {
+ @JvmStatic @BeforeClass
+ fun setUpClass() {
+ runAsShell(READ_DEVICE_CONFIG) {
+ // Verify that the test URLs are not normally set on the device, but do not fail if
+ // the test URLs are set to what this test uses (URLs on localhost), in case the
+ // test was interrupted manually and rerun.
+ assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL)
+ assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTP_URL)
+ }
+ NetworkValidationTestUtil.clearValidationTestUrlsDeviceConfig()
+ }
+
+ private fun assertEmptyOrLocalhostUrl(urlKey: String) {
+ val url = DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, urlKey)
+ assertTrue(TextUtils.isEmpty(url) || LOCALHOST_HOSTNAME == Uri.parse(url).host,
+ "$urlKey must not be set in production scenarios (current value: $url)")
+ }
+ }
+
@Before
fun setUp() {
- runAsShell(READ_DEVICE_CONFIG) {
- // Verify that the test URLs are not normally set on the device, but do not fail if the
- // test URLs are set to what this test uses (URLs on localhost), in case the test was
- // interrupted manually and rerun.
- assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL)
- assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTP_URL)
- }
- clearValidationTestUrlsDeviceConfig()
server.start()
}
@After
fun tearDown() {
- clearValidationTestUrlsDeviceConfig()
if (pm.hasSystemFeature(FEATURE_WIFI)) {
- reconnectWifi()
+ deviceConfigRule.runAfterNextCleanup { reconnectWifi() }
}
server.stop()
}
- private fun assertEmptyOrLocalhostUrl(urlKey: String) {
- val url = DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, urlKey)
- assertTrue(TextUtils.isEmpty(url) || LOCALHOST_HOSTNAME == Uri.parse(url).host,
- "$urlKey must not be set in production scenarios (current value: $url)")
- }
-
@Test
fun testCaptivePortalIsNotDefaultNetwork() {
assumeTrue(pm.hasSystemFeature(FEATURE_TELEPHONY))
@@ -154,12 +163,13 @@
server.addResponse(Request(TEST_HTTPS_URL_PATH), Status.INTERNAL_ERROR)
val headers = mapOf("Location" to makeUrl(TEST_PORTAL_URL_PATH))
server.addResponse(Request(TEST_HTTP_URL_PATH), Status.REDIRECT, headers)
- setHttpsUrlDeviceConfig(makeUrl(TEST_HTTPS_URL_PATH))
- setHttpUrlDeviceConfig(makeUrl(TEST_HTTP_URL_PATH))
+ setHttpsUrlDeviceConfig(deviceConfigRule, makeUrl(TEST_HTTPS_URL_PATH))
+ setHttpUrlDeviceConfig(deviceConfigRule, makeUrl(TEST_HTTP_URL_PATH))
Log.d(TAG, "Set portal URLs to $TEST_HTTPS_URL_PATH and $TEST_HTTP_URL_PATH")
// URL expiration needs to be in the next 10 minutes
assertTrue(WIFI_CONNECT_TIMEOUT_MS < TimeUnit.MINUTES.toMillis(10))
- setUrlExpirationDeviceConfig(System.currentTimeMillis() + WIFI_CONNECT_TIMEOUT_MS)
+ setUrlExpirationDeviceConfig(deviceConfigRule,
+ System.currentTimeMillis() + WIFI_CONNECT_TIMEOUT_MS)
// Wait for a captive portal to be detected on the network
val wifiNetworkFuture = CompletableFuture<Network>()
@@ -215,4 +225,4 @@
utils.ensureWifiDisconnected(null /* wifiNetworkToCheck */)
utils.ensureWifiConnected()
}
-}
\ No newline at end of file
+}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 0e9c16d..766d62f 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -170,7 +170,6 @@
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
-import android.util.Pair;
import android.util.Range;
import androidx.test.InstrumentationRegistry;
@@ -188,6 +187,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRuleKt;
+import com.android.testutils.DeviceInfoUtils;
import com.android.testutils.DumpTestUtils;
import com.android.testutils.RecorderCallback.CallbackEntry;
import com.android.testutils.TestHttpServer;
@@ -249,6 +249,10 @@
@Rule
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+ @Rule
+ public final DeviceConfigRule mTestValidationConfigRule = new DeviceConfigRule(
+ 5 /* retryCountBeforeSIfConfigChanged */);
+
private static final String TAG = ConnectivityManagerTest.class.getSimpleName();
public static final int TYPE_MOBILE = ConnectivityManager.TYPE_MOBILE;
@@ -1615,51 +1619,7 @@
private static boolean isTcpKeepaliveSupportedByKernel() {
final String kVersionString = VintfRuntimeInfo.getKernelVersion();
- return compareMajorMinorVersion(kVersionString, "4.8") >= 0;
- }
-
- private static Pair<Integer, Integer> getVersionFromString(String version) {
- // Only gets major and minor number of the version string.
- final Pattern versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*");
- final Matcher m = versionPattern.matcher(version);
- if (m.matches()) {
- final int major = Integer.parseInt(m.group(1));
- final int minor = TextUtils.isEmpty(m.group(3)) ? 0 : Integer.parseInt(m.group(3));
- return new Pair<>(major, minor);
- } else {
- return new Pair<>(0, 0);
- }
- }
-
- // TODO: Move to util class.
- private static int compareMajorMinorVersion(final String s1, final String s2) {
- final Pair<Integer, Integer> v1 = getVersionFromString(s1);
- final Pair<Integer, Integer> v2 = getVersionFromString(s2);
-
- if (v1.first == v2.first) {
- return Integer.compare(v1.second, v2.second);
- } else {
- return Integer.compare(v1.first, v2.first);
- }
- }
-
- /**
- * Verifies that version string compare logic returns expected result for various cases.
- * Note that only major and minor number are compared.
- */
- @Test
- public void testMajorMinorVersionCompare() {
- assertEquals(0, compareMajorMinorVersion("4.8.1", "4.8"));
- assertEquals(1, compareMajorMinorVersion("4.9", "4.8.1"));
- assertEquals(1, compareMajorMinorVersion("5.0", "4.8"));
- assertEquals(1, compareMajorMinorVersion("5", "4.8"));
- assertEquals(0, compareMajorMinorVersion("5", "5.0"));
- assertEquals(1, compareMajorMinorVersion("5-beta1", "4.8"));
- assertEquals(0, compareMajorMinorVersion("4.8.0.0", "4.8"));
- assertEquals(0, compareMajorMinorVersion("4.8-RC1", "4.8"));
- assertEquals(0, compareMajorMinorVersion("4.8", "4.8"));
- assertEquals(-1, compareMajorMinorVersion("3.10", "4.8.0"));
- assertEquals(-1, compareMajorMinorVersion("4.7.10.10", "4.8"));
+ return DeviceInfoUtils.compareMajorMinorVersion(kVersionString, "4.8") >= 0;
}
/**
@@ -2809,9 +2769,8 @@
// Accept partial connectivity network should result in a validated network
expectNetworkHasCapability(network, NET_CAPABILITY_VALIDATED, WIFI_CONNECT_TIMEOUT_MS);
} finally {
- resetValidationConfig();
- // Reconnect wifi to reset the wifi status
- reconnectWifi();
+ mHttpServer.stop();
+ mTestValidationConfigRule.runAfterNextCleanup(this::reconnectWifi);
}
}
@@ -2836,11 +2795,13 @@
// Reject partial connectivity network should cause the network being torn down
assertEquals(network, cb.waitForLost());
} finally {
- resetValidationConfig();
+ mHttpServer.stop();
// Wifi will not automatically reconnect to the network. ensureWifiDisconnected cannot
// apply here. Thus, turn off wifi first and restart to restore.
- runShellCommand("svc wifi disable");
- mCtsNetUtils.ensureWifiConnected();
+ mTestValidationConfigRule.runAfterNextCleanup(() -> {
+ runShellCommand("svc wifi disable");
+ mCtsNetUtils.ensureWifiConnected();
+ });
}
}
@@ -2876,11 +2837,13 @@
});
waitForLost(wifiCb);
} finally {
- resetValidationConfig();
+ mHttpServer.stop();
/// Wifi will not automatically reconnect to the network. ensureWifiDisconnected cannot
// apply here. Thus, turn off wifi first and restart to restore.
- runShellCommand("svc wifi disable");
- mCtsNetUtils.ensureWifiConnected();
+ mTestValidationConfigRule.runAfterNextCleanup(() -> {
+ runShellCommand("svc wifi disable");
+ mCtsNetUtils.ensureWifiConnected();
+ });
}
}
@@ -2940,9 +2903,8 @@
wifiCb.assertNoCallbackThat(NO_CALLBACK_TIMEOUT_MS, c -> isValidatedCaps(c));
} finally {
resetAvoidBadWifi(previousAvoidBadWifi);
- resetValidationConfig();
- // Reconnect wifi to reset the wifi status
- reconnectWifi();
+ mHttpServer.stop();
+ mTestValidationConfigRule.runAfterNextCleanup(this::reconnectWifi);
}
}
@@ -2986,11 +2948,6 @@
return future.get(timeout, TimeUnit.MILLISECONDS);
}
- private void resetValidationConfig() {
- NetworkValidationTestUtil.clearValidationTestUrlsDeviceConfig();
- mHttpServer.stop();
- }
-
private void prepareHttpServer() throws Exception {
runAsShell(READ_DEVICE_CONFIG, () -> {
// Verify that the test URLs are not normally set on the device, but do not fail if the
@@ -3063,9 +3020,11 @@
mHttpServer.addResponse(new TestHttpServer.Request(
TEST_HTTP_URL_PATH, Method.GET, "" /* queryParameters */),
httpStatusCode, null /* locationHeader */, "" /* content */);
- NetworkValidationTestUtil.setHttpsUrlDeviceConfig(makeUrl(TEST_HTTPS_URL_PATH));
- NetworkValidationTestUtil.setHttpUrlDeviceConfig(makeUrl(TEST_HTTP_URL_PATH));
- NetworkValidationTestUtil.setUrlExpirationDeviceConfig(
+ NetworkValidationTestUtil.setHttpsUrlDeviceConfig(mTestValidationConfigRule,
+ makeUrl(TEST_HTTPS_URL_PATH));
+ NetworkValidationTestUtil.setHttpUrlDeviceConfig(mTestValidationConfigRule,
+ makeUrl(TEST_HTTP_URL_PATH));
+ NetworkValidationTestUtil.setUrlExpirationDeviceConfig(mTestValidationConfigRule,
System.currentTimeMillis() + WIFI_CONNECT_TIMEOUT_MS);
}
diff --git a/tests/cts/net/src/android/net/cts/DeviceConfigRule.kt b/tests/cts/net/src/android/net/cts/DeviceConfigRule.kt
new file mode 100644
index 0000000..d31a4e0
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/DeviceConfigRule.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts
+
+import android.Manifest.permission.READ_DEVICE_CONFIG
+import android.Manifest.permission.WRITE_DEVICE_CONFIG
+import android.provider.DeviceConfig
+import android.util.Log
+import com.android.modules.utils.build.SdkLevel
+import com.android.testutils.runAsShell
+import com.android.testutils.tryTest
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+private val TAG = DeviceConfigRule::class.simpleName
+
+/**
+ * A [TestRule] that helps set [DeviceConfig] for tests and clean up the test configuration
+ * automatically on teardown.
+ *
+ * The rule can also optionally retry tests when they fail following an external change of
+ * DeviceConfig before S; this typically happens because device config flags are synced while the
+ * test is running, and DisableConfigSyncTargetPreparer is only usable starting from S.
+ *
+ * @param retryCountBeforeSIfConfigChanged if > 0, when the test fails before S, check if
+ * the configs that were set through this rule were changed, and retry the test
+ * up to the specified number of times if yes.
+ */
+class DeviceConfigRule @JvmOverloads constructor(
+ val retryCountBeforeSIfConfigChanged: Int = 0
+) : TestRule {
+ // Maps (namespace, key) -> value
+ private val originalConfig = mutableMapOf<Pair<String, String>, String?>()
+ private val usedConfig = mutableMapOf<Pair<String, String>, String?>()
+
+ /**
+ * Actions to be run after cleanup of the config, for the current test only.
+ */
+ private val currentTestCleanupActions = mutableListOf<Runnable>()
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return TestValidationUrlStatement(base, description)
+ }
+
+ private inner class TestValidationUrlStatement(
+ private val base: Statement,
+ private val description: Description
+ ) : Statement() {
+ override fun evaluate() {
+ var retryCount = if (SdkLevel.isAtLeastS()) 1 else retryCountBeforeSIfConfigChanged + 1
+ while (retryCount > 0) {
+ retryCount--
+ tryTest {
+ base.evaluate()
+ // Can't use break/return out of a loop here because this is a tryTest lambda,
+ // so set retryCount to exit instead
+ retryCount = 0
+ }.catch<Throwable> { e -> // junit AssertionFailedError does not extend Exception
+ if (retryCount == 0) throw e
+ usedConfig.forEach { (key, value) ->
+ val currentValue = runAsShell(READ_DEVICE_CONFIG) {
+ DeviceConfig.getProperty(key.first, key.second)
+ }
+ if (currentValue != value) {
+ Log.w(TAG, "Test failed with unexpected device config change, retrying")
+ return@catch
+ }
+ }
+ throw e
+ } cleanupStep {
+ runAsShell(WRITE_DEVICE_CONFIG) {
+ originalConfig.forEach { (key, value) ->
+ DeviceConfig.setProperty(
+ key.first, key.second, value, false /* makeDefault */)
+ }
+ }
+ } cleanupStep {
+ originalConfig.clear()
+ usedConfig.clear()
+ } cleanup {
+ currentTestCleanupActions.forEach { it.run() }
+ currentTestCleanupActions.clear()
+ }
+ }
+ }
+ }
+
+ /**
+ * Set a configuration key/value. After the test case ends, it will be restored to the value it
+ * had when this method was first called.
+ */
+ fun setConfig(namespace: String, key: String, value: String?) {
+ runAsShell(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG) {
+ val keyPair = Pair(namespace, key)
+ if (!originalConfig.containsKey(keyPair)) {
+ originalConfig[keyPair] = DeviceConfig.getProperty(namespace, key)
+ }
+ usedConfig[keyPair] = value
+ DeviceConfig.setProperty(namespace, key, value, false /* makeDefault */)
+ }
+ }
+
+ /**
+ * Add an action to be run after config cleanup when the current test case ends.
+ */
+ fun runAfterNextCleanup(action: Runnable) {
+ currentTestCleanupActions.add(action)
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index db24b44..cc64239 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -30,11 +30,14 @@
import android.net.EthernetManager.STATE_LINK_UP
import android.net.EthernetManager.TetheredInterfaceCallback
import android.net.EthernetManager.TetheredInterfaceRequest
+import android.net.EthernetNetworkManagementException
import android.net.EthernetNetworkSpecifier
+import android.net.EthernetNetworkUpdateRequest
import android.net.InetAddresses
import android.net.IpConfiguration
import android.net.MacAddress
import android.net.Network
+import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
import android.net.NetworkCapabilities.TRANSPORT_TEST
@@ -44,8 +47,8 @@
import android.net.cts.EthernetManagerTest.EthernetStateListener.CallbackEntry.InterfaceStateChanged
import android.os.Build
import android.os.Handler
-import android.os.HandlerExecutor
import android.os.Looper
+import android.os.OutcomeReceiver
import android.os.SystemProperties
import android.platform.test.annotations.AppModeFull
import android.util.ArraySet
@@ -53,6 +56,8 @@
import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.TrackRecord
import com.android.testutils.anyNetwork
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DeviceInfoUtils
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.RecorderCallback.CallbackEntry.Available
@@ -63,6 +68,7 @@
import com.android.testutils.runAsShell
import com.android.testutils.waitForIdle
import org.junit.After
+import org.junit.Assume.assumeTrue
import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Test
@@ -94,35 +100,42 @@
@AppModeFull(reason = "Instant apps can't access EthernetManager")
// EthernetManager is not updatable before T, so tests do not need to be backwards compatible.
@RunWith(DevSdkIgnoreRunner::class)
+// This test depends on behavior introduced post-T as part of connectivity module updates
+@ConnectivityModuleTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class EthernetManagerTest {
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val em by lazy { context.getSystemService(EthernetManager::class.java) }
private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
+ private val handler by lazy { Handler(Looper.getMainLooper()) }
private val ifaceListener = EthernetStateListener()
private val createdIfaces = ArrayList<EthernetTestInterface>()
private val addedListeners = ArrayList<EthernetStateListener>()
- private val networkRequests = ArrayList<TestableNetworkCallback>()
+ private val registeredCallbacks = ArrayList<TestableNetworkCallback>()
private var tetheredInterfaceRequest: TetheredInterfaceRequest? = null
private class EthernetTestInterface(
context: Context,
- private val handler: Handler
+ private val handler: Handler,
+ hasCarrier: Boolean
) {
private val tapInterface: TestNetworkInterface
private val packetReader: TapPacketReader
private val raResponder: RouterAdvertisementResponder
- val interfaceName get() = tapInterface.interfaceName
+ private val tnm: TestNetworkManager
+ val name get() = tapInterface.interfaceName
init {
- tapInterface = runAsShell(MANAGE_TEST_NETWORKS) {
- val tnm = context.getSystemService(TestNetworkManager::class.java)
- tnm.createTapInterface(false /* bringUp */)
+ tnm = runAsShell(MANAGE_TEST_NETWORKS) {
+ context.getSystemService(TestNetworkManager::class.java)
}
- val mtu = 1500
+ tapInterface = runAsShell(MANAGE_TEST_NETWORKS) {
+ tnm.createTapInterface(hasCarrier, false /* bringUp */)
+ }
+ val mtu = tapInterface.mtu
packetReader = TapPacketReader(handler, tapInterface.fileDescriptor.fileDescriptor, mtu)
raResponder = RouterAdvertisementResponder(packetReader)
raResponder.addRouterEntry(MacAddress.fromString("01:23:45:67:89:ab"),
@@ -132,6 +145,12 @@
raResponder.start()
}
+ fun setCarrierEnabled(enabled: Boolean) {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ tnm.setCarrierEnabled(tapInterface, enabled)
+ }
+ }
+
fun destroy() {
raResponder.stop()
handler.post({ packetReader.stop() })
@@ -172,7 +191,7 @@
}
fun expectCallback(iface: EthernetTestInterface, state: Int, role: Int) {
- expectCallback(createChangeEvent(iface.interfaceName, state, role))
+ expectCallback(createChangeEvent(iface.name, state, role))
}
fun createChangeEvent(iface: String, state: Int, role: Int) =
@@ -190,7 +209,7 @@
}
fun eventuallyExpect(iface: EthernetTestInterface, state: Int, role: Int) {
- eventuallyExpect(iface.interfaceName, state, role)
+ eventuallyExpect(iface.name, state, role)
}
fun assertNoCallback() {
@@ -227,6 +246,35 @@
}
}
+ private class EthernetOutcomeReceiver :
+ OutcomeReceiver<String, EthernetNetworkManagementException> {
+ private val result = CompletableFuture<String>()
+
+ override fun onResult(iface: String) {
+ result.complete(iface)
+ }
+
+ override fun onError(e: EthernetNetworkManagementException) {
+ result.completeExceptionally(e)
+ }
+
+ fun expectResult(expected: String) {
+ assertEquals(expected, result.get(TIMEOUT_MS, TimeUnit.MILLISECONDS))
+ }
+
+ fun expectError() {
+ // Assert that the future fails with EthernetNetworkManagementException from the
+ // completeExceptionally() call inside onUnavailable.
+ assertFailsWith(EthernetNetworkManagementException::class) {
+ try {
+ result.get()
+ } catch (e: ExecutionException) {
+ throw e.cause!!
+ }
+ }
+ }
+ }
+
@Before
fun setUp() {
setIncludeTestInterfaces(true)
@@ -243,26 +291,28 @@
for (listener in addedListeners) {
em.removeInterfaceStateListener(listener)
}
- networkRequests.forEach { cm.unregisterNetworkCallback(it) }
+ registeredCallbacks.forEach { cm.unregisterNetworkCallback(it) }
releaseTetheredInterface()
}
private fun addInterfaceStateListener(listener: EthernetStateListener) {
runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) {
- em.addInterfaceStateListener(HandlerExecutor(Handler(Looper.getMainLooper())), listener)
+ em.addInterfaceStateListener(handler::post, listener)
}
addedListeners.add(listener)
}
- private fun createInterface(): EthernetTestInterface {
+ private fun createInterface(hasCarrier: Boolean = true): EthernetTestInterface {
val iface = EthernetTestInterface(
context,
- Handler(Looper.getMainLooper())
+ handler,
+ hasCarrier
).also { createdIfaces.add(it) }
- with(ifaceListener) {
- // when an interface comes up, we should always see a down cb before an up cb.
- eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
- expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
+
+ // when an interface comes up, we should always see a down cb before an up cb.
+ ifaceListener.eventuallyExpect(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+ if (hasCarrier) {
+ ifaceListener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
}
return iface
}
@@ -282,18 +332,20 @@
private fun requestNetwork(request: NetworkRequest): TestableNetworkCallback {
return TestableNetworkCallback().also {
cm.requestNetwork(request, it)
- networkRequests.add(it)
+ registeredCallbacks.add(it)
}
}
- private fun releaseNetwork(cb: TestableNetworkCallback) {
- cm.unregisterNetworkCallback(cb)
- networkRequests.remove(cb)
+ private fun registerNetworkListener(request: NetworkRequest): TestableNetworkCallback {
+ return TestableNetworkCallback().also {
+ cm.registerNetworkCallback(request, it)
+ registeredCallbacks.add(it)
+ }
}
private fun requestTetheredInterface() = TetheredInterfaceListener().also {
tetheredInterfaceRequest = runAsShell(NETWORK_SETTINGS) {
- em.requestTetheredInterface(HandlerExecutor(Handler(Looper.getMainLooper())), it)
+ em.requestTetheredInterface(handler::post, it)
}
}
@@ -304,22 +356,65 @@
}
}
+ private fun releaseRequest(cb: TestableNetworkCallback) {
+ cm.unregisterNetworkCallback(cb)
+ registeredCallbacks.remove(cb)
+ }
+
+ private fun disableInterface(iface: EthernetTestInterface) = EthernetOutcomeReceiver().also {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ em.disableInterface(iface.name, handler::post, it)
+ }
+ }
+
+ private fun enableInterface(iface: EthernetTestInterface) = EthernetOutcomeReceiver().also {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ em.enableInterface(iface.name, handler::post, it)
+ }
+ }
+
+ private fun updateConfiguration(
+ iface: EthernetTestInterface,
+ ipConfig: IpConfiguration? = null,
+ capabilities: NetworkCapabilities? = null
+ ) = EthernetOutcomeReceiver().also {
+ runAsShell(MANAGE_TEST_NETWORKS) {
+ em.updateConfiguration(
+ iface.name,
+ EthernetNetworkUpdateRequest.Builder()
+ .setIpConfiguration(ipConfig)
+ .setNetworkCapabilities(capabilities).build(),
+ handler::post,
+ it)
+ }
+ }
+
+ // NetworkRequest.Builder does not create a copy of the passed NetworkRequest, so in order to
+ // keep ETH_REQUEST as it is, a defensive copy is created here.
private fun NetworkRequest.createCopyWithEthernetSpecifier(ifaceName: String) =
NetworkRequest.Builder(NetworkRequest(ETH_REQUEST))
.setNetworkSpecifier(EthernetNetworkSpecifier(ifaceName)).build()
// It can take multiple seconds for the network to become available.
private fun TestableNetworkCallback.expectAvailable() =
- expectCallback<Available>(anyNetwork(), 5000/*ms timeout*/).network
+ expectCallback<Available>(anyNetwork(), 5000 /* ms timeout */).network
// b/233534110: eventuallyExpect<Lost>() does not advance ReadHead, use
// eventuallyExpect(Lost::class) instead.
private fun TestableNetworkCallback.eventuallyExpectLost(n: Network? = null) =
eventuallyExpect(Lost::class, TIMEOUT_MS) { n?.equals(it.network) ?: true }
- private fun TestableNetworkCallback.assertNotLost(n: Network? = null) =
+ private fun TestableNetworkCallback.assertNeverLost(n: Network? = null) =
assertNoCallbackThat() { it is Lost && (n?.equals(it.network) ?: true) }
+ private fun TestableNetworkCallback.assertNeverAvailable(n: Network? = null) =
+ assertNoCallbackThat() { it is Available && (n?.equals(it.network) ?: true) }
+
+ private fun TestableNetworkCallback.expectCapabilitiesWithInterfaceName(name: String) =
+ expectCapabilitiesThat(anyNetwork()) {
+ it.networkSpecifier == EthernetNetworkSpecifier(name)
+ }
+
@Test
fun testCallbacks() {
// If an interface exists when the callback is registered, it is reported on registration.
@@ -398,11 +493,8 @@
assertTrue(polledIfaces.add(iface), "Duplicate interface $iface returned")
assertTrue(ifaces.contains(iface), "Untracked interface $iface returned")
// If the event's iface was created in the test, additional criteria can be validated.
- createdIfaces.find { it.interfaceName.equals(iface) }?.let {
- assertEquals(event,
- listener.createChangeEvent(it.interfaceName,
- STATE_LINK_UP,
- ROLE_CLIENT))
+ createdIfaces.find { it.name.equals(iface) }?.let {
+ assertEquals(event, listener.createChangeEvent(it.name, STATE_LINK_UP, ROLE_CLIENT))
}
}
// Assert all callbacks are accounted for.
@@ -411,87 +503,75 @@
@Test
fun testGetInterfaceList() {
- setIncludeTestInterfaces(true)
-
// Create two test interfaces and check the return list contains the interface names.
val iface1 = createInterface()
val iface2 = createInterface()
var ifaces = em.getInterfaceList()
assertTrue(ifaces.size > 0)
- assertTrue(ifaces.contains(iface1.interfaceName))
- assertTrue(ifaces.contains(iface2.interfaceName))
+ assertTrue(ifaces.contains(iface1.name))
+ assertTrue(ifaces.contains(iface2.name))
// Remove one existing test interface and check the return list doesn't contain the
// removed interface name.
removeInterface(iface1)
ifaces = em.getInterfaceList()
- assertFalse(ifaces.contains(iface1.interfaceName))
- assertTrue(ifaces.contains(iface2.interfaceName))
+ assertFalse(ifaces.contains(iface1.name))
+ assertTrue(ifaces.contains(iface2.name))
removeInterface(iface2)
}
@Test
fun testNetworkRequest_withSingleExistingInterface() {
- setIncludeTestInterfaces(true)
createInterface()
// install a listener which will later be used to verify the Lost callback
- val listenerCb = TestableNetworkCallback()
- cm.registerNetworkCallback(ETH_REQUEST, listenerCb)
- networkRequests.add(listenerCb)
+ val listenerCb = registerNetworkListener(ETH_REQUEST)
val cb = requestNetwork(ETH_REQUEST)
val network = cb.expectAvailable()
- cb.assertNotLost()
- releaseNetwork(cb)
+ cb.assertNeverLost()
+ releaseRequest(cb)
listenerCb.eventuallyExpectLost(network)
}
@Test
fun testNetworkRequest_beforeSingleInterfaceIsUp() {
- setIncludeTestInterfaces(true)
-
val cb = requestNetwork(ETH_REQUEST)
- // bring up interface after network has been requested
+ // bring up interface after network has been requested.
+ // Note: there is no guarantee that the NetworkRequest has been processed before the
+ // interface is actually created. That being said, it takes a few seconds between calling
+ // createInterface and the interface actually being properly registered with the ethernet
+ // module, so it is extremely unlikely that the CS handler thread has not run until then.
val iface = createInterface()
val network = cb.expectAvailable()
// remove interface before network request has been removed
- cb.assertNotLost()
+ cb.assertNeverLost()
removeInterface(iface)
cb.eventuallyExpectLost()
-
- releaseNetwork(cb)
}
@Test
fun testNetworkRequest_withMultipleInterfaces() {
- setIncludeTestInterfaces(true)
-
val iface1 = createInterface()
val iface2 = createInterface()
- val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName))
+ val cb = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.name))
val network = cb.expectAvailable()
- cb.expectCapabilitiesThat(network) {
- it.networkSpecifier == EthernetNetworkSpecifier(iface2.interfaceName)
- }
+ cb.expectCapabilitiesWithInterfaceName(iface2.name)
removeInterface(iface1)
- cb.assertNotLost()
+ cb.assertNeverLost()
removeInterface(iface2)
cb.eventuallyExpectLost()
-
- releaseNetwork(cb)
}
@Test
fun testNetworkRequest_withInterfaceBeingReplaced() {
- setIncludeTestInterfaces(true)
val iface1 = createInterface()
val cb = requestNetwork(ETH_REQUEST)
@@ -499,36 +579,71 @@
// create another network and verify the request sticks to the current network
val iface2 = createInterface()
- cb.assertNotLost()
+ cb.assertNeverLost()
// remove iface1 and verify the request brings up iface2
removeInterface(iface1)
cb.eventuallyExpectLost(network)
val network2 = cb.expectAvailable()
-
- releaseNetwork(cb)
}
@Test
fun testNetworkRequest_withMultipleInterfacesAndRequests() {
- setIncludeTestInterfaces(true)
val iface1 = createInterface()
val iface2 = createInterface()
- val cb1 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface1.interfaceName))
- val cb2 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.interfaceName))
+ val cb1 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface1.name))
+ val cb2 = requestNetwork(ETH_REQUEST.createCopyWithEthernetSpecifier(iface2.name))
val cb3 = requestNetwork(ETH_REQUEST)
cb1.expectAvailable()
+ cb1.expectCapabilitiesWithInterfaceName(iface1.name)
cb2.expectAvailable()
+ cb2.expectCapabilitiesWithInterfaceName(iface2.name)
+ // this request can be matched by either network.
cb3.expectAvailable()
- cb1.assertNotLost()
- cb2.assertNotLost()
- cb3.assertNotLost()
+ cb1.assertNeverLost()
+ cb2.assertNeverLost()
+ cb3.assertNeverLost()
+ }
- releaseNetwork(cb1)
- releaseNetwork(cb2)
- releaseNetwork(cb3)
+ @Test
+ fun testNetworkRequest_ensureProperRefcounting() {
+ // create first request before interface is up / exists; create another request after it has
+ // been created; release one of them and check that the network stays up.
+ val listener = registerNetworkListener(ETH_REQUEST)
+ val cb1 = requestNetwork(ETH_REQUEST)
+
+ val iface = createInterface()
+ val network = cb1.expectAvailable()
+
+ val cb2 = requestNetwork(ETH_REQUEST)
+ cb2.expectAvailable()
+
+ // release the first request; this used to trigger b/197548738
+ releaseRequest(cb1)
+
+ cb2.assertNeverLost()
+ releaseRequest(cb2)
+ listener.eventuallyExpectLost(network)
+ }
+
+ @Test
+ fun testNetworkRequest_forInterfaceWhileTogglingCarrier() {
+ // Notice this test case fails on devices running on an older kernel version(e.g. 4.14)
+ // that might not support ioctl new argument. Only run this test on 4.19 kernel or above.
+ assumeTrue(DeviceInfoUtils.isKernelVersionAtLeast("4.19.0"))
+
+ val iface = createInterface(false /* hasCarrier */)
+
+ val cb = requestNetwork(ETH_REQUEST)
+ cb.assertNeverAvailable()
+
+ iface.setCarrierEnabled(true)
+ cb.expectAvailable()
+
+ iface.setCarrierEnabled(false)
+ cb.eventuallyExpectLost()
}
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
index d618915..5ba6d69 100644
--- a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -56,6 +56,7 @@
import android.net.NetworkStatsHistory;
import android.net.TrafficStats;
import android.net.netstats.NetworkStatsDataMigrationUtils;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
@@ -99,7 +100,7 @@
@RunWith(AndroidJUnit4.class)
public class NetworkStatsManagerTest {
@Rule
- public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(SC_V2 /* ignoreClassUpTo */);
+ public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(Build.VERSION_CODES.Q);
private static final String LOG_TAG = "NetworkStatsManagerTest";
private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} {1} {2}";
diff --git a/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt b/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt
index 391d03a..462c8a3 100644
--- a/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt
@@ -16,16 +16,11 @@
package android.net.cts
-import android.Manifest
+import android.Manifest.permission.WRITE_DEVICE_CONFIG
import android.net.util.NetworkStackUtils
import android.provider.DeviceConfig
import android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY
-import android.util.Log
import com.android.testutils.runAsShell
-import com.android.testutils.tryTest
-import java.util.concurrent.CompletableFuture
-import java.util.concurrent.Executor
-import java.util.concurrent.TimeUnit
/**
* Collection of utility methods for configuring network validation.
@@ -38,9 +33,14 @@
* Clear the test network validation URLs.
*/
@JvmStatic fun clearValidationTestUrlsDeviceConfig() {
- setHttpsUrlDeviceConfig(null)
- setHttpUrlDeviceConfig(null)
- setUrlExpirationDeviceConfig(null)
+ runAsShell(WRITE_DEVICE_CONFIG) {
+ DeviceConfig.setProperty(NAMESPACE_CONNECTIVITY,
+ NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL, null, false)
+ DeviceConfig.setProperty(NAMESPACE_CONNECTIVITY,
+ NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL, null, false)
+ DeviceConfig.setProperty(NAMESPACE_CONNECTIVITY,
+ NetworkStackUtils.TEST_URL_EXPIRATION_TIME, null, false)
+ }
}
/**
@@ -48,71 +48,28 @@
*
* @see NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL
*/
- @JvmStatic fun setHttpsUrlDeviceConfig(url: String?) =
- setConfig(NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL, url)
+ @JvmStatic
+ fun setHttpsUrlDeviceConfig(rule: DeviceConfigRule, url: String?) =
+ rule.setConfig(NAMESPACE_CONNECTIVITY,
+ NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL, url)
/**
* Set the test validation HTTP URL.
*
* @see NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL
*/
- @JvmStatic fun setHttpUrlDeviceConfig(url: String?) =
- setConfig(NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL, url)
+ @JvmStatic
+ fun setHttpUrlDeviceConfig(rule: DeviceConfigRule, url: String?) =
+ rule.setConfig(NAMESPACE_CONNECTIVITY,
+ NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL, url)
/**
* Set the test validation URL expiration.
*
* @see NetworkStackUtils.TEST_URL_EXPIRATION_TIME
*/
- @JvmStatic fun setUrlExpirationDeviceConfig(timestamp: Long?) =
- setConfig(NetworkStackUtils.TEST_URL_EXPIRATION_TIME, timestamp?.toString())
-
- private fun setConfig(configKey: String, value: String?): String? {
- Log.i(TAG, "Setting config \"$configKey\" to \"$value\"")
- val readWritePermissions = arrayOf(
- Manifest.permission.READ_DEVICE_CONFIG,
- Manifest.permission.WRITE_DEVICE_CONFIG)
-
- val existingValue = runAsShell(*readWritePermissions) {
- DeviceConfig.getProperty(NAMESPACE_CONNECTIVITY, configKey)
- }
- if (existingValue == value) {
- // Already the correct value. There may be a race if a change is already in flight,
- // but if multiple threads update the config there is no way to fix that anyway.
- Log.i(TAG, "\$configKey\" already had value \"$value\"")
- return value
- }
-
- val future = CompletableFuture<String>()
- val listener = DeviceConfig.OnPropertiesChangedListener {
- // The listener receives updates for any change to any key, so don't react to
- // changes that do not affect the relevant key
- if (!it.keyset.contains(configKey)) return@OnPropertiesChangedListener
- if (it.getString(configKey, null) == value) {
- future.complete(value)
- }
- }
-
- return tryTest {
- runAsShell(*readWritePermissions) {
- DeviceConfig.addOnPropertiesChangedListener(
- NAMESPACE_CONNECTIVITY,
- inlineExecutor,
- listener)
- DeviceConfig.setProperty(
- NAMESPACE_CONNECTIVITY,
- configKey,
- value,
- false /* makeDefault */)
- // Don't drop the permission until the config is applied, just in case
- future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
- }.also {
- Log.i(TAG, "Config \"$configKey\" successfully set to \"$value\"")
- }
- } cleanup {
- DeviceConfig.removeOnPropertiesChangedListener(listener)
- }
- }
-
- private val inlineExecutor get() = Executor { r -> r.run() }
+ @JvmStatic
+ fun setUrlExpirationDeviceConfig(rule: DeviceConfigRule, timestamp: Long?) =
+ rule.setConfig(NAMESPACE_CONNECTIVITY,
+ NetworkStackUtils.TEST_URL_EXPIRATION_TIME, timestamp?.toString())
}
diff --git a/tests/native/Android.bp b/tests/native/Android.bp
index a8d908a..7d43aa8 100644
--- a/tests/native/Android.bp
+++ b/tests/native/Android.bp
@@ -31,3 +31,10 @@
],
compile_multilib: "first",
}
+
+filegroup {
+ name: "net_native_test_config_template",
+ srcs: [
+ "NetNativeTestConfigTemplate.xml",
+ ],
+}
diff --git a/tests/native/NetNativeTestConfigTemplate.xml b/tests/native/NetNativeTestConfigTemplate.xml
new file mode 100644
index 0000000..b71e9aa
--- /dev/null
+++ b/tests/native/NetNativeTestConfigTemplate.xml
@@ -0,0 +1,31 @@
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Configuration for {MODULE} tests">
+ <option name="test-suite-tag" value="mts" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
+ <!-- Only run tests if the device under test is SDK version 33 (Android 13) or above. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk33ModuleController" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="{MODULE}" />
+ </test>
+</configuration>
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 18ace4e..141a251 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -58,42 +58,21 @@
filegroup {
name: "non-connectivity-module-test",
srcs: [
- "java/android/app/usage/*.java",
- "java/android/net/EthernetNetworkUpdateRequestTest.java",
"java/android/net/Ikev2VpnProfileTest.java",
"java/android/net/IpMemoryStoreTest.java",
- "java/android/net/IpSecAlgorithmTest.java",
- "java/android/net/IpSecConfigTest.java",
- "java/android/net/IpSecManagerTest.java",
- "java/android/net/IpSecTransformTest.java",
- "java/android/net/KeepalivePacketDataUtilTest.java",
- "java/android/net/NetworkIdentitySetTest.kt",
- "java/android/net/NetworkIdentityTest.kt",
- "java/android/net/NetworkStats*.java",
- "java/android/net/NetworkTemplateTest.kt",
"java/android/net/TelephonyNetworkSpecifierTest.java",
"java/android/net/VpnManagerTest.java",
"java/android/net/ipmemorystore/*.java",
"java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt",
- "java/android/net/nsd/*.java",
"java/com/android/internal/net/NetworkUtilsInternalTest.java",
"java/com/android/internal/net/VpnProfileTest.java",
- "java/com/android/server/IpSecServiceParameterizedTest.java",
- "java/com/android/server/IpSecServiceRefcountedResourceTest.java",
- "java/com/android/server/IpSecServiceTest.java",
"java/com/android/server/NetworkManagementServiceTest.java",
- "java/com/android/server/NsdServiceTest.java",
"java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java",
"java/com/android/server/connectivity/IpConnectivityMetricsTest.java",
"java/com/android/server/connectivity/MultipathPolicyTrackerTest.java",
"java/com/android/server/connectivity/NetdEventListenerServiceTest.java",
"java/com/android/server/connectivity/VpnTest.java",
- "java/com/android/server/ethernet/*.java",
"java/com/android/server/net/ipmemorystore/*.java",
- "java/com/android/server/net/BpfInterfaceMapUpdaterTest.java",
- "java/com/android/server/net/IpConfigStoreTest.java",
- "java/com/android/server/net/NetworkStats*.java",
- "java/com/android/server/net/TestableUsageCallback.kt",
]
}
diff --git a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
index 561e621..b1b76ec 100644
--- a/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
+++ b/tests/unit/java/android/app/usage/NetworkStatsManagerTest.java
@@ -54,7 +54,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NetworkStatsManagerTest {
private static final String TEST_SUBSCRIBER_ID = "subid";
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index f324630..c327868 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -41,6 +41,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -72,6 +73,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -82,6 +84,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.lang.ref.WeakReference;
+
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R)
@@ -461,4 +465,49 @@
}
fail("expected exception of type " + throwableType);
}
+
+ private static class MockContext extends BroadcastInterceptingContext {
+ MockContext(Context base) {
+ super(base);
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return mock(Context.class);
+ }
+ }
+
+ private WeakReference<Context> makeConnectivityManagerAndReturnContext() {
+ // Mockito may have an internal reference to the mock, creating MockContext for testing.
+ final Context c = new MockContext(mock(Context.class));
+
+ new ConnectivityManager(c, mService);
+
+ return new WeakReference<>(c);
+ }
+
+ private void forceGC() {
+ // First GC ensures that objects are collected for finalization, then second GC ensures
+ // they're garbage-collected after being finalized.
+ System.gc();
+ System.runFinalization();
+ System.gc();
+ }
+
+ @Test
+ public void testConnectivityManagerDoesNotLeakContext() throws Exception {
+ final WeakReference<Context> ref = makeConnectivityManagerAndReturnContext();
+
+ final int attempts = 100;
+ final long waitIntervalMs = 50;
+ for (int i = 0; i < attempts; i++) {
+ forceGC();
+ if (ref.get() == null) break;
+
+ Thread.sleep(waitIntervalMs);
+ }
+
+ assertNull("ConnectivityManager weak reference still not null after " + attempts
+ + " attempts", ref.get());
+ }
}
diff --git a/tests/unit/java/android/net/IpSecAlgorithmTest.java b/tests/unit/java/android/net/IpSecAlgorithmTest.java
index c473e82..1482055 100644
--- a/tests/unit/java/android/net/IpSecAlgorithmTest.java
+++ b/tests/unit/java/android/net/IpSecAlgorithmTest.java
@@ -47,7 +47,7 @@
/** Unit tests for {@link IpSecAlgorithm}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecAlgorithmTest {
private static final byte[] KEY_MATERIAL;
diff --git a/tests/unit/java/android/net/IpSecConfigTest.java b/tests/unit/java/android/net/IpSecConfigTest.java
index b87cb48..9f83036 100644
--- a/tests/unit/java/android/net/IpSecConfigTest.java
+++ b/tests/unit/java/android/net/IpSecConfigTest.java
@@ -36,7 +36,7 @@
/** Unit tests for {@link IpSecConfig}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecConfigTest {
@Test
diff --git a/tests/unit/java/android/net/IpSecManagerTest.java b/tests/unit/java/android/net/IpSecManagerTest.java
index cda8eb7..335f539 100644
--- a/tests/unit/java/android/net/IpSecManagerTest.java
+++ b/tests/unit/java/android/net/IpSecManagerTest.java
@@ -52,7 +52,7 @@
/** Unit tests for {@link IpSecManager}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecManagerTest {
private static final int TEST_UDP_ENCAP_PORT = 34567;
diff --git a/tests/unit/java/android/net/IpSecTransformTest.java b/tests/unit/java/android/net/IpSecTransformTest.java
index 81375f1..c1bd719 100644
--- a/tests/unit/java/android/net/IpSecTransformTest.java
+++ b/tests/unit/java/android/net/IpSecTransformTest.java
@@ -32,7 +32,7 @@
/** Unit tests for {@link IpSecTransform}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecTransformTest {
@Test
diff --git a/tests/unit/java/android/net/NetworkIdentityTest.kt b/tests/unit/java/android/net/NetworkIdentityTest.kt
index bf5568d..d84328c 100644
--- a/tests/unit/java/android/net/NetworkIdentityTest.kt
+++ b/tests/unit/java/android/net/NetworkIdentityTest.kt
@@ -47,7 +47,7 @@
private const val TEST_SUBID2 = 2
@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class NetworkIdentityTest {
private val mockContext = mock(Context::class.java)
diff --git a/tests/unit/java/android/net/NetworkStatsAccessTest.java b/tests/unit/java/android/net/NetworkStatsAccessTest.java
index 97a93ca..a74056b 100644
--- a/tests/unit/java/android/net/NetworkStatsAccessTest.java
+++ b/tests/unit/java/android/net/NetworkStatsAccessTest.java
@@ -19,6 +19,7 @@
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.when;
import android.Manifest;
@@ -66,6 +67,10 @@
when(mContext.getSystemServiceName(DevicePolicyManager.class))
.thenReturn(Context.DEVICE_POLICY_SERVICE);
when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(mDpm);
+ if (mContext.getSystemService(DevicePolicyManager.class) == null) {
+ // Test is using mockito-extended
+ doCallRealMethod().when(mContext).getSystemService(DevicePolicyManager.class);
+ }
setHasCarrierPrivileges(false);
setIsDeviceOwner(false);
diff --git a/tests/unit/java/android/net/NetworkStatsHistoryTest.java b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
index 26079a2..43e331b 100644
--- a/tests/unit/java/android/net/NetworkStatsHistoryTest.java
+++ b/tests/unit/java/android/net/NetworkStatsHistoryTest.java
@@ -60,7 +60,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NetworkStatsHistoryTest {
private static final String TAG = "NetworkStatsHistoryTest";
diff --git a/tests/unit/java/android/net/NetworkStatsTest.java b/tests/unit/java/android/net/NetworkStatsTest.java
index b0cc16c..6d79869 100644
--- a/tests/unit/java/android/net/NetworkStatsTest.java
+++ b/tests/unit/java/android/net/NetworkStatsTest.java
@@ -61,7 +61,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NetworkStatsTest {
private static final String TEST_IFACE = "test0";
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index abd1825..3e9662d 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -70,7 +70,7 @@
private const val TEST_WIFI_KEY2 = "wifiKey2"
@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
class NetworkTemplateTest {
private val mockContext = mock(Context::class.java)
private val mockWifiInfo = mock(WifiInfo::class.java)
diff --git a/tests/unit/java/android/net/QosSocketFilterTest.java b/tests/unit/java/android/net/QosSocketFilterTest.java
index 91f2cdd..6820b40 100644
--- a/tests/unit/java/android/net/QosSocketFilterTest.java
+++ b/tests/unit/java/android/net/QosSocketFilterTest.java
@@ -16,8 +16,17 @@
package android.net;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_NONE;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_BOUND;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_NOT_CONNECTED;
+import static android.net.QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import android.os.Build;
@@ -29,6 +38,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -36,14 +46,14 @@
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
public class QosSocketFilterTest {
-
+ private static final int TEST_NET_ID = 1777;
+ private final Network mNetwork = new Network(TEST_NET_ID);
@Test
public void testPortExactMatch() {
final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4");
final InetAddress addressB = InetAddresses.parseNumericAddress("1.2.3.4");
assertTrue(QosSocketFilter.matchesAddress(
new InetSocketAddress(addressA, 10), addressB, 10, 10));
-
}
@Test
@@ -77,5 +87,90 @@
assertFalse(QosSocketFilter.matchesAddress(
new InetSocketAddress(addressA, 10), addressB, 10, 10));
}
+
+ @Test
+ public void testAddressMatchWithAnyLocalAddresses() {
+ final InetAddress addressA = InetAddresses.parseNumericAddress("1.2.3.4");
+ final InetAddress addressB = InetAddresses.parseNumericAddress("0.0.0.0");
+ assertTrue(QosSocketFilter.matchesAddress(
+ new InetSocketAddress(addressA, 10), addressB, 10, 10));
+ assertFalse(QosSocketFilter.matchesAddress(
+ new InetSocketAddress(addressB, 10), addressA, 10, 10));
+ }
+
+ @Test
+ public void testProtocolMatch() throws Exception {
+ DatagramSocket socket = new DatagramSocket(new InetSocketAddress("127.0.0.1", 0));
+ socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 10));
+ DatagramSocket socketV6 = new DatagramSocket(new InetSocketAddress("::1", 0));
+ socketV6.connect(new InetSocketAddress("::1", socketV6.getLocalPort() + 10));
+ QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+ QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+ QosSocketInfo socketInfo6 = new QosSocketInfo(mNetwork, socketV6);
+ QosSocketFilter socketFilter6 = new QosSocketFilter(socketInfo6);
+ assertTrue(socketFilter.matchesProtocol(IPPROTO_UDP));
+ assertTrue(socketFilter6.matchesProtocol(IPPROTO_UDP));
+ assertFalse(socketFilter.matchesProtocol(IPPROTO_TCP));
+ assertFalse(socketFilter6.matchesProtocol(IPPROTO_TCP));
+ socket.close();
+ socketV6.close();
+ }
+
+ @Test
+ public void testValidate() throws Exception {
+ DatagramSocket socket = new DatagramSocket(new InetSocketAddress("127.0.0.1", 0));
+ socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 7));
+ DatagramSocket socketV6 = new DatagramSocket(new InetSocketAddress("::1", 0));
+
+ QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+ QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+ QosSocketInfo socketInfo6 = new QosSocketInfo(mNetwork, socketV6);
+ QosSocketFilter socketFilter6 = new QosSocketFilter(socketInfo6);
+ assertEquals(EX_TYPE_FILTER_NONE, socketFilter.validate());
+ assertEquals(EX_TYPE_FILTER_NONE, socketFilter6.validate());
+ socket.close();
+ socketV6.close();
+ }
+
+ @Test
+ public void testValidateUnbind() throws Exception {
+ DatagramSocket socket;
+ socket = new DatagramSocket(null);
+ QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+ QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+ assertEquals(EX_TYPE_FILTER_SOCKET_NOT_BOUND, socketFilter.validate());
+ socket.close();
+ }
+
+ @Test
+ public void testValidateLocalAddressChanged() throws Exception {
+ DatagramSocket socket = new DatagramSocket(null);
+ DatagramSocket socket6 = new DatagramSocket(null);
+ QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+ QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+ QosSocketInfo socketInfo6 = new QosSocketInfo(mNetwork, socket6);
+ QosSocketFilter socketFilter6 = new QosSocketFilter(socketInfo6);
+ socket.bind(new InetSocketAddress("127.0.0.1", 0));
+ socket6.bind(new InetSocketAddress("::1", 0));
+ assertEquals(EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED, socketFilter.validate());
+ assertEquals(EX_TYPE_FILTER_SOCKET_LOCAL_ADDRESS_CHANGED, socketFilter6.validate());
+ socket.close();
+ socket6.close();
+ }
+
+ @Test
+ public void testValidateRemoteAddressChanged() throws Exception {
+ DatagramSocket socket;
+ socket = new DatagramSocket(new InetSocketAddress("127.0.0.1", 53137));
+ socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 11));
+ QosSocketInfo socketInfo = new QosSocketInfo(mNetwork, socket);
+ QosSocketFilter socketFilter = new QosSocketFilter(socketInfo);
+ assertEquals(EX_TYPE_FILTER_NONE, socketFilter.validate());
+ socket.disconnect();
+ assertEquals(EX_TYPE_FILTER_SOCKET_NOT_CONNECTED, socketFilter.validate());
+ socket.connect(new InetSocketAddress("127.0.0.1", socket.getLocalPort() + 13));
+ assertEquals(EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED, socketFilter.validate());
+ socket.close();
+ }
}
diff --git a/tests/unit/java/android/net/QosSocketInfoTest.java b/tests/unit/java/android/net/QosSocketInfoTest.java
new file mode 100644
index 0000000..749c182
--- /dev/null
+++ b/tests/unit/java/android/net/QosSocketInfoTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOCK_STREAM;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import android.os.Build;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.net.DatagramSocket;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+public class QosSocketInfoTest {
+ @Mock
+ private Network mMockNetwork = mock(Network.class);
+
+ @Test
+ public void testConstructWithSock() throws Exception {
+ ServerSocket server = new ServerSocket();
+ ServerSocket server6 = new ServerSocket();
+
+ InetSocketAddress clientAddr = new InetSocketAddress("127.0.0.1", 0);
+ InetSocketAddress serverAddr = new InetSocketAddress("127.0.0.1", 0);
+ InetSocketAddress clientAddr6 = new InetSocketAddress("::1", 0);
+ InetSocketAddress serverAddr6 = new InetSocketAddress("::1", 0);
+ server.bind(serverAddr);
+ server6.bind(serverAddr6);
+ Socket socket = new Socket(serverAddr.getAddress(), server.getLocalPort(),
+ clientAddr.getAddress(), clientAddr.getPort());
+ Socket socket6 = new Socket(serverAddr6.getAddress(), server6.getLocalPort(),
+ clientAddr6.getAddress(), clientAddr6.getPort());
+ QosSocketInfo sockInfo = new QosSocketInfo(mMockNetwork, socket);
+ QosSocketInfo sockInfo6 = new QosSocketInfo(mMockNetwork, socket6);
+ assertTrue(sockInfo.getLocalSocketAddress()
+ .equals(new InetSocketAddress(socket.getLocalAddress(), socket.getLocalPort())));
+ assertTrue(sockInfo.getRemoteSocketAddress()
+ .equals((InetSocketAddress) socket.getRemoteSocketAddress()));
+ assertEquals(SOCK_STREAM, sockInfo.getSocketType());
+ assertTrue(sockInfo6.getLocalSocketAddress()
+ .equals(new InetSocketAddress(socket6.getLocalAddress(), socket6.getLocalPort())));
+ assertTrue(sockInfo6.getRemoteSocketAddress()
+ .equals((InetSocketAddress) socket6.getRemoteSocketAddress()));
+ assertEquals(SOCK_STREAM, sockInfo6.getSocketType());
+ socket.close();
+ socket6.close();
+ server.close();
+ server6.close();
+ }
+
+ @Test
+ public void testConstructWithDatagramSock() throws Exception {
+ InetSocketAddress clientAddr = new InetSocketAddress("127.0.0.1", 0);
+ InetSocketAddress serverAddr = new InetSocketAddress("127.0.0.1", 0);
+ InetSocketAddress clientAddr6 = new InetSocketAddress("::1", 0);
+ InetSocketAddress serverAddr6 = new InetSocketAddress("::1", 0);
+ DatagramSocket socket = new DatagramSocket(null);
+ socket.setReuseAddress(true);
+ socket.bind(clientAddr);
+ socket.connect(serverAddr);
+ DatagramSocket socket6 = new DatagramSocket(null);
+ socket6.setReuseAddress(true);
+ socket6.bind(clientAddr);
+ socket6.connect(serverAddr);
+ QosSocketInfo sockInfo = new QosSocketInfo(mMockNetwork, socket);
+ QosSocketInfo sockInfo6 = new QosSocketInfo(mMockNetwork, socket6);
+ assertTrue(sockInfo.getLocalSocketAddress()
+ .equals((InetSocketAddress) socket.getLocalSocketAddress()));
+ assertTrue(sockInfo.getRemoteSocketAddress()
+ .equals((InetSocketAddress) socket.getRemoteSocketAddress()));
+ assertEquals(SOCK_DGRAM, sockInfo.getSocketType());
+ assertTrue(sockInfo6.getLocalSocketAddress()
+ .equals((InetSocketAddress) socket6.getLocalSocketAddress()));
+ assertTrue(sockInfo6.getRemoteSocketAddress()
+ .equals((InetSocketAddress) socket6.getRemoteSocketAddress()));
+ assertEquals(SOCK_DGRAM, sockInfo6.getSocketType());
+ socket.close();
+ }
+}
diff --git a/tests/unit/java/android/net/nsd/NsdManagerTest.java b/tests/unit/java/android/net/nsd/NsdManagerTest.java
index 30b8fcd..32274bc 100644
--- a/tests/unit/java/android/net/nsd/NsdManagerTest.java
+++ b/tests/unit/java/android/net/nsd/NsdManagerTest.java
@@ -51,7 +51,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NsdManagerTest {
static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
diff --git a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
index 892e140..64355ed 100644
--- a/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
+++ b/tests/unit/java/android/net/nsd/NsdServiceInfoTest.java
@@ -42,7 +42,7 @@
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NsdServiceInfoTest {
public final static InetAddress LOCALHOST;
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index b9a18ab..03e1cc4 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -53,7 +53,6 @@
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
-import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOCKDOWN_VPN;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
@@ -153,6 +152,8 @@
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_PROFILE;
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_VPN;
import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
+import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackRegister;
+import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister;
import static com.android.testutils.ConcurrentUtils.await;
import static com.android.testutils.ConcurrentUtils.durationOf;
import static com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
@@ -9516,40 +9517,30 @@
b2.expectBroadcast();
}
- @Test
+ @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
public void testLockdownSetFirewallUidRule() throws Exception {
- // For ConnectivityService#setAlwaysOnVpnPackage.
- mServiceContext.setPermission(
- Manifest.permission.CONTROL_ALWAYS_ON_VPN, PERMISSION_GRANTED);
- // Needed to call Vpn#setAlwaysOnPackage.
- mServiceContext.setPermission(Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
- // Needed to call Vpn#isAlwaysOnPackageSupported.
- mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
-
+ final Set<Range<Integer>> lockdownRange = UidRange.toIntRanges(Set.of(PRIMARY_UIDRANGE));
// Enable Lockdown
- final ArrayList<String> allowList = new ArrayList<>();
- mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE,
- true /* lockdown */, allowList);
+ mCm.setRequireVpnForUids(true /* requireVpn */, lockdownRange);
waitForIdle();
// Lockdown rule is set to apps uids
- verify(mBpfNetMaps).setUidRule(
- eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(APP1_UID), eq(FIREWALL_RULE_DENY));
- verify(mBpfNetMaps).setUidRule(
- eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(APP2_UID), eq(FIREWALL_RULE_DENY));
+ verify(mBpfNetMaps, times(3)).updateUidLockdownRule(anyInt(), eq(true) /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(APP1_UID, true /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(APP2_UID, true /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(VPN_UID, true /* add */);
reset(mBpfNetMaps);
// Disable lockdown
- mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */,
- allowList);
+ mCm.setRequireVpnForUids(false /* requireVPN */, lockdownRange);
waitForIdle();
// Lockdown rule is removed from apps uids
- verify(mBpfNetMaps).setUidRule(
- eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(APP1_UID), eq(FIREWALL_RULE_ALLOW));
- verify(mBpfNetMaps).setUidRule(
- eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(APP2_UID), eq(FIREWALL_RULE_ALLOW));
+ verify(mBpfNetMaps, times(3)).updateUidLockdownRule(anyInt(), eq(false) /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(APP1_UID, false /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(APP2_UID, false /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(VPN_UID, false /* add */);
// Interface rules are not changed by Lockdown mode enable/disable
verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
@@ -10530,69 +10521,67 @@
assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges("tun0"));
}
+ private void checkInterfaceFilteringRuleWithNullInterface(final LinkProperties lp,
+ final int uid) throws Exception {
+ // The uid range needs to cover the test app so the network is visible to it.
+ final Set<UidRange> vpnRange = Collections.singleton(PRIMARY_UIDRANGE);
+ mMockVpn.establish(lp, uid, vpnRange);
+ assertVpnUidRangesUpdated(true, vpnRange, uid);
+
+ if (SdkLevel.isAtLeastT()) {
+ // On T and above, VPN should have rules for null interface. Null Interface is a
+ // wildcard and this accepts traffic from all the interfaces.
+ // There are two expected invocations, one during the VPN initial
+ // connection, one during the VPN LinkProperties update.
+ ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
+ verify(mBpfNetMaps, times(2)).addUidInterfaceRules(
+ eq(null) /* iface */, uidCaptor.capture());
+ if (uid == VPN_UID) {
+ assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
+ assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
+ } else {
+ assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID, VPN_UID);
+ assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID, VPN_UID);
+ }
+ assertEquals(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */),
+ vpnRange);
+
+ mMockVpn.disconnect();
+ waitForIdle();
+
+ // Disconnected VPN should have interface rules removed
+ verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
+ if (uid == VPN_UID) {
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
+ } else {
+ assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID, VPN_UID);
+ }
+ assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */));
+ } else {
+ // Before T, rules are not configured for null interface.
+ verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
+ }
+ }
+
@Test
- public void testLegacyVpnSetInterfaceFilteringRuleWithWildcard() throws Exception {
+ public void testLegacyVpnInterfaceFilteringRule() throws Exception {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("tun0");
lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null));
lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null));
- // The uid range needs to cover the test app so the network is visible to it.
- final Set<UidRange> vpnRange = Collections.singleton(PRIMARY_UIDRANGE);
- mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange);
- assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
-
- // A connected Legacy VPN should have interface rules with null interface.
- // Null Interface is a wildcard and this accepts traffic from all the interfaces.
- // There are two expected invocations, one during the VPN initial connection,
- // one during the VPN LinkProperties update.
- ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
- verify(mBpfNetMaps, times(2)).addUidInterfaceRules(
- eq(null) /* iface */, uidCaptor.capture());
- assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID, VPN_UID);
- assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID, VPN_UID);
- assertEquals(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */),
- vpnRange);
-
- mMockVpn.disconnect();
- waitForIdle();
-
- // Disconnected VPN should have interface rules removed
- verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
- assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID, VPN_UID);
- assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */));
+ // Legacy VPN should have interface filtering with null interface.
+ checkInterfaceFilteringRuleWithNullInterface(lp, Process.SYSTEM_UID);
}
@Test
- public void testLocalIpv4OnlyVpnSetInterfaceFilteringRuleWithWildcard() throws Exception {
+ public void testLocalIpv4OnlyVpnInterfaceFilteringRule() throws Exception {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("tun0");
lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun0"));
lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE));
- // The uid range needs to cover the test app so the network is visible to it.
- final Set<UidRange> vpnRange = Collections.singleton(PRIMARY_UIDRANGE);
- mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange);
- assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID);
-
- // IPv6 unreachable route should not be misinterpreted as a default route
- // A connected VPN should have interface rules with null interface.
- // Null Interface is a wildcard and this accepts traffic from all the interfaces.
- // There are two expected invocations, one during the VPN initial connection,
- // one during the VPN LinkProperties update.
- ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class);
- verify(mBpfNetMaps, times(2)).addUidInterfaceRules(
- eq(null) /* iface */, uidCaptor.capture());
- assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID, VPN_UID);
- assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID, VPN_UID);
- assertEquals(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */),
- vpnRange);
-
- mMockVpn.disconnect();
- waitForIdle();
-
- // Disconnected VPN should have interface rules removed
- verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
- assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID, VPN_UID);
- assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */));
+ // VPN that does not provide a default route should have interface filtering with null
+ // interface.
+ checkInterfaceFilteringRuleWithNullInterface(lp, VPN_UID);
}
@Test
@@ -11797,6 +11786,12 @@
mCm.unregisterNetworkCallback(networkCallback);
}
+ private void verifyDump(String[] args) {
+ final StringWriter stringWriter = new StringWriter();
+ mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), args);
+ assertFalse(stringWriter.toString().isEmpty());
+ }
+
@Test
public void testDumpDoesNotCrash() {
mServiceContext.setPermission(DUMP, PERMISSION_GRANTED);
@@ -11809,11 +11804,26 @@
.addTransportType(TRANSPORT_WIFI).build();
mCm.registerNetworkCallback(genericRequest, genericNetworkCallback);
mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
- final StringWriter stringWriter = new StringWriter();
- mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), new String[0]);
+ verifyDump(new String[0]);
- assertFalse(stringWriter.toString().isEmpty());
+ // Verify dump with arguments.
+ final String dumpPrio = "--dump-priority";
+ final String[] dumpArgs = {dumpPrio};
+ verifyDump(dumpArgs);
+
+ final String[] highDumpArgs = {dumpPrio, "HIGH"};
+ verifyDump(highDumpArgs);
+
+ final String[] normalDumpArgs = {dumpPrio, "NORMAL"};
+ verifyDump(normalDumpArgs);
+
+ // Invalid args should do dumpNormal w/o exception
+ final String[] unknownDumpArgs = {dumpPrio, "UNKNOWN"};
+ verifyDump(unknownDumpArgs);
+
+ final String[] invalidDumpArgs = {"UNKNOWN"};
+ verifyDump(invalidDumpArgs);
}
@Test
@@ -12157,16 +12167,14 @@
mQosCallbackMockHelper.registerQosCallback(
mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
- final NetworkAgentWrapper.CallbackType.OnQosCallbackRegister cbRegister1 =
- (NetworkAgentWrapper.CallbackType.OnQosCallbackRegister)
- wrapper.getCallbackHistory().poll(1000, x -> true);
+ final OnQosCallbackRegister cbRegister1 =
+ (OnQosCallbackRegister) wrapper.getCallbackHistory().poll(1000, x -> true);
assertNotNull(cbRegister1);
final int registerCallbackId = cbRegister1.mQosCallbackId;
mService.unregisterQosCallback(mQosCallbackMockHelper.mCallback);
- final NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister cbUnregister;
- cbUnregister = (NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister)
- wrapper.getCallbackHistory().poll(1000, x -> true);
+ final OnQosCallbackUnregister cbUnregister =
+ (OnQosCallbackUnregister) wrapper.getCallbackHistory().poll(1000, x -> true);
assertNotNull(cbUnregister);
assertEquals(registerCallbackId, cbUnregister.mQosCallbackId);
assertNull(wrapper.getCallbackHistory().poll(200, x -> true));
@@ -12245,6 +12253,86 @@
&& session.getSessionType() == QosSession.TYPE_NR_BEARER));
}
+ @Test @IgnoreUpTo(SC_V2)
+ public void testQosCallbackAvailableOnValidationError() throws Exception {
+ mQosCallbackMockHelper = new QosCallbackMockHelper();
+ final NetworkAgentWrapper wrapper = mQosCallbackMockHelper.mAgentWrapper;
+ final int sessionId = 10;
+ final int qosCallbackId = 1;
+
+ doReturn(QosCallbackException.EX_TYPE_FILTER_NONE)
+ .when(mQosCallbackMockHelper.mFilter).validate();
+ mQosCallbackMockHelper.registerQosCallback(
+ mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
+ OnQosCallbackRegister cbRegister1 =
+ (OnQosCallbackRegister) wrapper.getCallbackHistory().poll(1000, x -> true);
+ assertNotNull(cbRegister1);
+ final int registerCallbackId = cbRegister1.mQosCallbackId;
+
+ waitForIdle();
+
+ doReturn(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED)
+ .when(mQosCallbackMockHelper.mFilter).validate();
+ final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes(
+ 1, 2, 3, 4, 5, new ArrayList<>());
+ mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+ .sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
+ waitForIdle();
+
+ final NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister cbUnregister;
+ cbUnregister = (NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister)
+ wrapper.getCallbackHistory().poll(1000, x -> true);
+ assertNotNull(cbUnregister);
+ assertEquals(registerCallbackId, cbUnregister.mQosCallbackId);
+ waitForIdle();
+ verify(mQosCallbackMockHelper.mCallback)
+ .onError(eq(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED));
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ public void testQosCallbackLostOnValidationError() throws Exception {
+ mQosCallbackMockHelper = new QosCallbackMockHelper();
+ final int sessionId = 10;
+ final int qosCallbackId = 1;
+
+ doReturn(QosCallbackException.EX_TYPE_FILTER_NONE)
+ .when(mQosCallbackMockHelper.mFilter).validate();
+ mQosCallbackMockHelper.registerQosCallback(
+ mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
+ waitForIdle();
+ EpsBearerQosSessionAttributes attributes =
+ sendQosSessionEvent(qosCallbackId, sessionId, true);
+ waitForIdle();
+
+ verify(mQosCallbackMockHelper.mCallback).onQosEpsBearerSessionAvailable(argThat(session ->
+ session.getSessionId() == sessionId
+ && session.getSessionType() == QosSession.TYPE_EPS_BEARER), eq(attributes));
+
+ doReturn(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED)
+ .when(mQosCallbackMockHelper.mFilter).validate();
+
+ sendQosSessionEvent(qosCallbackId, sessionId, false);
+ waitForIdle();
+ verify(mQosCallbackMockHelper.mCallback)
+ .onError(eq(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED));
+ }
+
+ private EpsBearerQosSessionAttributes sendQosSessionEvent(
+ int qosCallbackId, int sessionId, boolean available) {
+ if (available) {
+ final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes(
+ 1, 2, 3, 4, 5, new ArrayList<>());
+ mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+ .sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
+ return attributes;
+ } else {
+ mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+ .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_EPS_BEARER);
+ return null;
+ }
+
+ }
+
@Test
public void testQosCallbackTooManyRequests() throws Exception {
mQosCallbackMockHelper = new QosCallbackMockHelper();
diff --git a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
index 45f3d3c..9401d47 100644
--- a/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -89,7 +89,7 @@
public class IpSecServiceParameterizedTest {
@Rule
public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(
- Build.VERSION_CODES.R /* ignoreClassUpTo */);
+ Build.VERSION_CODES.S_V2 /* ignoreClassUpTo */);
private static final int TEST_SPI = 0xD1201D;
@@ -783,6 +783,23 @@
}
@Test
+ public void testSetNetworkForTunnelInterfaceFailsForNullLp() throws Exception {
+ final IpSecTunnelInterfaceResponse createTunnelResp =
+ createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
+ final Network newFakeNetwork = new Network(1000);
+ final int tunnelIfaceResourceId = createTunnelResp.resourceId;
+
+ try {
+ mIpSecService.setNetworkForTunnelInterface(
+ tunnelIfaceResourceId, newFakeNetwork, BLESSED_PACKAGE);
+ fail(
+ "Expected an IllegalArgumentException for underlying network with null"
+ + " LinkProperties");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
public void testSetNetworkForTunnelInterfaceFailsForInvalidResourceId() throws Exception {
final IpSecTunnelInterfaceResponse createTunnelResp =
createAndValidateTunnel(mSourceAddr, mDestinationAddr, BLESSED_PACKAGE);
diff --git a/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java b/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
index 5c7ca6f..8595ab9 100644
--- a/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceRefcountedResourceTest.java
@@ -54,7 +54,7 @@
/** Unit tests for {@link IpSecService.RefcountedResource}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecServiceRefcountedResourceTest {
Context mMockContext;
IpSecService.Dependencies mMockDeps;
diff --git a/tests/unit/java/com/android/server/IpSecServiceTest.java b/tests/unit/java/com/android/server/IpSecServiceTest.java
index 7e6b157..6955620 100644
--- a/tests/unit/java/com/android/server/IpSecServiceTest.java
+++ b/tests/unit/java/com/android/server/IpSecServiceTest.java
@@ -75,7 +75,7 @@
/** Unit tests for {@link IpSecService}. */
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpSecServiceTest {
private static final int DROID_SPI = 0xD1201D;
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index ed9e930..9365bee 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -25,6 +25,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
@@ -83,7 +84,7 @@
// - test NSD_ON ENABLE/DISABLED listening
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class NsdServiceTest {
static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
@@ -123,6 +124,10 @@
doReturn(MDnsManager.MDNS_SERVICE).when(mContext)
.getSystemServiceName(MDnsManager.class);
doReturn(mMockMDnsM).when(mContext).getSystemService(MDnsManager.MDNS_SERVICE);
+ if (mContext.getSystemService(MDnsManager.class) == null) {
+ // Test is using mockito-extended
+ doCallRealMethod().when(mContext).getSystemService(MDnsManager.class);
+ }
doReturn(true).when(mMockMDnsM).registerService(
anyInt(), anyString(), anyString(), anyInt(), any(), anyInt());
doReturn(true).when(mMockMDnsM).stopOperation(anyInt());
diff --git a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
index f84d10f..8dfe924 100644
--- a/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/ClatCoordinatorTest.java
@@ -30,12 +30,16 @@
import static com.android.testutils.MiscAsserts.assertThrows;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
import android.net.INetd;
@@ -46,6 +50,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.bpf.ClatEgress4Key;
import com.android.net.module.util.bpf.ClatEgress4Value;
@@ -53,6 +58,7 @@
import com.android.net.module.util.bpf.ClatIngress6Value;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.TestBpfMap;
import org.junit.Before;
import org.junit.Test;
@@ -64,6 +70,7 @@
import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.StringWriter;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.util.Objects;
@@ -101,12 +108,12 @@
private static final int RAW_SOCK_FD = 535;
private static final int PACKET_SOCK_FD = 536;
private static final long RAW_SOCK_COOKIE = 27149;
- private static final ParcelFileDescriptor TUN_PFD = new ParcelFileDescriptor(
- new FileDescriptor());
- private static final ParcelFileDescriptor RAW_SOCK_PFD = new ParcelFileDescriptor(
- new FileDescriptor());
- private static final ParcelFileDescriptor PACKET_SOCK_PFD = new ParcelFileDescriptor(
- new FileDescriptor());
+ private static final ParcelFileDescriptor TUN_PFD = spy(new ParcelFileDescriptor(
+ new FileDescriptor()));
+ private static final ParcelFileDescriptor RAW_SOCK_PFD = spy(new ParcelFileDescriptor(
+ new FileDescriptor()));
+ private static final ParcelFileDescriptor PACKET_SOCK_PFD = spy(new ParcelFileDescriptor(
+ new FileDescriptor()));
private static final String EGRESS_PROG_PATH =
"/sys/fs/bpf/net_shared/prog_clatd_schedcls_egress4_clat_rawip";
@@ -121,10 +128,13 @@
private static final ClatIngress6Value INGRESS_VALUE = new ClatIngress6Value(STACKED_IFINDEX,
INET4_LOCAL4);
+ private final TestBpfMap<ClatIngress6Key, ClatIngress6Value> mIngressMap =
+ spy(new TestBpfMap<>(ClatIngress6Key.class, ClatIngress6Value.class));
+ private final TestBpfMap<ClatEgress4Key, ClatEgress4Value> mEgressMap =
+ spy(new TestBpfMap<>(ClatEgress4Key.class, ClatEgress4Value.class));
+
@Mock private INetd mNetd;
@Spy private TestDependencies mDeps = new TestDependencies();
- @Mock private IBpfMap<ClatIngress6Key, ClatIngress6Value> mIngressMap;
- @Mock private IBpfMap<ClatEgress4Key, ClatEgress4Value> mEgressMap;
/**
* The dependency injection class is used to mock the JNI functions and system functions
@@ -505,4 +515,204 @@
// Expected mtu is that CLAT_MAX_MTU(65536) minus MTU_DELTA(28).
assertEquals(65508, ClatCoordinator.adjustMtu(CLAT_MAX_MTU + 1 /* over maximum mtu */));
}
+
+ @Test
+ public void testDump() throws Exception {
+ final ClatCoordinator coordinator = makeClatCoordinator();
+ final StringWriter stringWriter = new StringWriter();
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(stringWriter, " ");
+ coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX);
+ coordinator.dump(ipw);
+
+ final String[] dumpStrings = stringWriter.toString().split("\n");
+ assertEquals(5, dumpStrings.length);
+ assertEquals("Forwarding rules:", dumpStrings[0].trim());
+ assertEquals("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif",
+ dumpStrings[1].trim());
+ assertEquals("1000 /64:ff9b::/96 /2001:db8:0:b11::464 -> /192.0.0.46 1001",
+ dumpStrings[2].trim());
+ assertEquals("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif",
+ dumpStrings[3].trim());
+ assertEquals("1001 /192.0.0.46 -> /2001:db8:0:b11::464 /64:ff9b::/96 1000 ether",
+ dumpStrings[4].trim());
+ }
+
+ @Test
+ public void testNotStartClatWithInvalidPrefix() throws Exception {
+ final ClatCoordinator coordinator = makeClatCoordinator();
+ final IpPrefix invalidPrefix = new IpPrefix("2001:db8::/64");
+ assertThrows(IOException.class,
+ () -> coordinator.clatStart(BASE_IFACE, NETID, invalidPrefix));
+ }
+
+ private void assertStartClat(final TestDependencies deps) throws Exception {
+ final ClatCoordinator coordinator = new ClatCoordinator(deps);
+ assertNotNull(coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
+ }
+
+ private void assertNotStartClat(final TestDependencies deps) {
+ // Expect that the injection function of TestDependencies causes clatStart() failed.
+ final ClatCoordinator coordinator = new ClatCoordinator(deps);
+ assertThrows(IOException.class,
+ () -> coordinator.clatStart(BASE_IFACE, NETID, NAT64_IP_PREFIX));
+ }
+
+ private void checkNotStartClat(final TestDependencies deps, final boolean needToCloseTunFd,
+ final boolean needToClosePacketSockFd, final boolean needToCloseRawSockFd)
+ throws Exception {
+ // [1] Expect that modified TestDependencies can't start clatd.
+ // Use precise check to make sure that there is no unexpected file descriptor closing.
+ clearInvocations(TUN_PFD, RAW_SOCK_PFD, PACKET_SOCK_PFD);
+ assertNotStartClat(deps);
+ if (needToCloseTunFd) {
+ verify(TUN_PFD).close();
+ } else {
+ verify(TUN_PFD, never()).close();
+ }
+ if (needToClosePacketSockFd) {
+ verify(PACKET_SOCK_PFD).close();
+ } else {
+ verify(PACKET_SOCK_PFD, never()).close();
+ }
+ if (needToCloseRawSockFd) {
+ verify(RAW_SOCK_PFD).close();
+ } else {
+ verify(RAW_SOCK_PFD, never()).close();
+ }
+
+ // [2] Expect that unmodified TestDependencies can start clatd.
+ // Used to make sure that the above modified TestDependencies has really broken the
+ // clatd starting.
+ assertStartClat(new TestDependencies());
+ }
+
+ // The following testNotStartClat* tests verifies bunches of code for unwinding the
+ // failure if any.
+ @Test
+ public void testNotStartClatWithNativeFailureSelectIpv4Address() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public String selectIpv4Address(@NonNull String v4addr, int prefixlen)
+ throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
+ false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureGenerateIpv6Address() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public String generateIpv6Address(@NonNull String iface, @NonNull String v4,
+ @NonNull String prefix64) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
+ false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureCreateTunInterface() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public int createTunInterface(@NonNull String tuniface) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), false /* needToCloseTunFd */,
+ false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureDetectMtu() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public int detectMtu(@NonNull String platSubnet, int platSuffix, int mark)
+ throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureOpenPacketSocket() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public int openPacketSocket() throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ false /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureOpenRawSocket6() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public int openRawSocket6(int mark) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ true /* needToClosePacketSockFd */, false /* needToCloseRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureAddAnycastSetsockopt() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public void addAnycastSetsockopt(@NonNull FileDescriptor sock, String v6,
+ int ifindex) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureTagSocketAsClat() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public long tagSocketAsClat(@NonNull FileDescriptor sock) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureConfigurePacketSocket() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public void configurePacketSocket(@NonNull FileDescriptor sock, String v6,
+ int ifindex) throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
+ }
+
+ @Test
+ public void testNotStartClatWithNativeFailureStartClatd() throws Exception {
+ class FailureDependencies extends TestDependencies {
+ @Override
+ public int startClatd(@NonNull FileDescriptor tunfd, @NonNull FileDescriptor readsock6,
+ @NonNull FileDescriptor writesock6, @NonNull String iface,
+ @NonNull String pfx96, @NonNull String v4, @NonNull String v6)
+ throws IOException {
+ throw new IOException();
+ }
+ }
+ checkNotStartClat(new FailureDependencies(), true /* needToCloseTunFd */,
+ true /* needToClosePacketSockFd */, true /* needToCloseRawSockFd */);
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index ecd17ba..354e79a 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -30,9 +30,6 @@
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_REQUIRED;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
-import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOCKDOWN_VPN;
-import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
-import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivitySettingsManager.UIDS_ALLOWED_ON_RESTRICTED_NETWORKS;
import static android.net.INetd.PERMISSION_INTERNET;
import static android.net.INetd.PERMISSION_NETWORK;
@@ -698,7 +695,8 @@
mNetdMonitor.expectNetworkPerm(PERMISSION_SYSTEM, new UserHandle[]{MOCK_USER1},
SYSTEM_APPID1);
- final List<PackageInfo> pkgs = List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID21,
+ final List<PackageInfo> pkgs = List.of(
+ buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID21,
CONNECTIVITY_USE_RESTRICTED_NETWORKS),
buildPackageInfo(SYSTEM_PACKAGE2, SYSTEM_APP_UID21, CHANGE_NETWORK_STATE));
doReturn(pkgs).when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS),
@@ -764,9 +762,10 @@
MOCK_APPID1);
}
- private void doTestuidFilteringDuringVpnConnectDisconnectAndUidUpdates(@Nullable String ifName)
+ private void doTestUidFilteringDuringVpnConnectDisconnectAndUidUpdates(@Nullable String ifName)
throws Exception {
- doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
+ doReturn(List.of(
+ buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
CONNECTIVITY_USE_RESTRICTED_NETWORKS),
buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
buildPackageInfo(MOCK_PACKAGE2, MOCK_UID12),
@@ -774,7 +773,7 @@
.when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID11);
mPermissionMonitor.startMonitoring();
- // Every app on user 0 except MOCK_UID12 are under VPN.
+ // Every app on user 0 except MOCK_UID12 is subject to the VPN.
final Set<UidRange> vpnRange1 = Set.of(
new UidRange(0, MOCK_UID12 - 1),
new UidRange(MOCK_UID12 + 1, UserHandle.PER_USER_RANGE - 1));
@@ -811,18 +810,19 @@
@Test
public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdates() throws Exception {
- doTestuidFilteringDuringVpnConnectDisconnectAndUidUpdates("tun0");
+ doTestUidFilteringDuringVpnConnectDisconnectAndUidUpdates("tun0");
}
@Test
public void testUidFilteringDuringVpnConnectDisconnectAndUidUpdatesWithWildcard()
throws Exception {
- doTestuidFilteringDuringVpnConnectDisconnectAndUidUpdates(null /* ifName */);
+ doTestUidFilteringDuringVpnConnectDisconnectAndUidUpdates(null /* ifName */);
}
private void doTestUidFilteringDuringPackageInstallAndUninstall(@Nullable String ifName) throws
Exception {
- doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
+ doReturn(List.of(
+ buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS),
buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
.when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
@@ -857,155 +857,149 @@
@Test
public void testLockdownUidFilteringWithLockdownEnableDisable() {
- doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
+ doReturn(List.of(
+ buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
CONNECTIVITY_USE_RESTRICTED_NETWORKS),
buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
buildPackageInfo(MOCK_PACKAGE2, MOCK_UID12),
buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
.when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
mPermissionMonitor.startMonitoring();
- // Every app on user 0 except MOCK_UID12 are under VPN.
- final UidRange[] vpnRange1 = {
+ // Every app on user 0 except MOCK_UID12 is subject to the VPN.
+ final UidRange[] lockdownRange = {
new UidRange(0, MOCK_UID12 - 1),
new UidRange(MOCK_UID12 + 1, UserHandle.PER_USER_RANGE - 1)
};
- // Add Lockdown uid range, expect a rule to be set up for user app MOCK_UID11
- mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, vpnRange1);
- verify(mBpfNetMaps)
- .setUidRule(
- eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
- eq(FIREWALL_RULE_DENY));
- assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange1));
+ // Add Lockdown uid range, expect a rule to be set up for MOCK_UID11 and VPN_UID
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, lockdownRange);
+ verify(mBpfNetMaps, times(2)).updateUidLockdownRule(anyInt(), eq(true) /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID11, true /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(VPN_UID, true /* add */);
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(lockdownRange));
reset(mBpfNetMaps);
// Remove Lockdown uid range, expect rules to be torn down
- mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, vpnRange1);
- verify(mBpfNetMaps)
- .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
- eq(FIREWALL_RULE_ALLOW));
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* add */, lockdownRange);
+ verify(mBpfNetMaps, times(2)).updateUidLockdownRule(anyInt(), eq(false) /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID11, false /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(VPN_UID, false /* add */);
assertTrue(mPermissionMonitor.getVpnLockdownUidRanges().isEmpty());
}
@Test
public void testLockdownUidFilteringWithLockdownEnableDisableWithMultiAdd() {
- doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
+ doReturn(List.of(
+ buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
CONNECTIVITY_USE_RESTRICTED_NETWORKS),
buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
.when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
mPermissionMonitor.startMonitoring();
- // MOCK_UID11 is under VPN.
+ // MOCK_UID11 is subject to the VPN.
final UidRange range = new UidRange(MOCK_UID11, MOCK_UID11);
- final UidRange[] vpnRange = {range};
+ final UidRange[] lockdownRange = {range};
// Add Lockdown uid range at 1st time, expect a rule to be set up
- mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, vpnRange);
- verify(mBpfNetMaps)
- .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
- eq(FIREWALL_RULE_DENY));
- assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange));
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, lockdownRange);
+ verify(mBpfNetMaps).updateUidLockdownRule(anyInt(), eq(true) /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID11, true /* add */);
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(lockdownRange));
reset(mBpfNetMaps);
// Add Lockdown uid range at 2nd time, expect a rule not to be set up because the uid
// already has the rule
- mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, vpnRange);
- verify(mBpfNetMaps, never()).setUidRule(anyInt(), anyInt(), anyInt());
- assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange));
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, lockdownRange);
+ verify(mBpfNetMaps, never()).updateUidLockdownRule(anyInt(), anyBoolean());
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(lockdownRange));
reset(mBpfNetMaps);
// Remove Lockdown uid range at 1st time, expect a rule not to be torn down because we added
// the range 2 times.
- mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, vpnRange);
- verify(mBpfNetMaps, never()).setUidRule(anyInt(), anyInt(), anyInt());
- assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange));
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* add */, lockdownRange);
+ verify(mBpfNetMaps, never()).updateUidLockdownRule(anyInt(), anyBoolean());
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(lockdownRange));
reset(mBpfNetMaps);
// Remove Lockdown uid range at 2nd time, expect a rule to be torn down because we added
// twice and we removed twice.
- mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, vpnRange);
- verify(mBpfNetMaps)
- .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
- eq(FIREWALL_RULE_ALLOW));
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* add */, lockdownRange);
+ verify(mBpfNetMaps).updateUidLockdownRule(anyInt(), eq(false) /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID11, false /* add */);
assertTrue(mPermissionMonitor.getVpnLockdownUidRanges().isEmpty());
}
@Test
public void testLockdownUidFilteringWithLockdownEnableDisableWithDuplicates() {
- doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
+ doReturn(List.of(
+ buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
CONNECTIVITY_USE_RESTRICTED_NETWORKS),
buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
.when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
mPermissionMonitor.startMonitoring();
- // MOCK_UID11 is under VPN.
+ // MOCK_UID11 is subject to the VPN.
final UidRange range = new UidRange(MOCK_UID11, MOCK_UID11);
- final UidRange[] vpnRangeDuplicates = {range, range};
- final UidRange[] vpnRange = {range};
+ final UidRange[] lockdownRangeDuplicates = {range, range};
+ final UidRange[] lockdownRange = {range};
// Add Lockdown uid ranges which contains duplicated uid ranges
- mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, vpnRangeDuplicates);
- verify(mBpfNetMaps)
- .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
- eq(FIREWALL_RULE_DENY));
- assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange));
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, lockdownRangeDuplicates);
+ verify(mBpfNetMaps).updateUidLockdownRule(anyInt(), eq(true) /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID11, true /* add */);
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(lockdownRange));
reset(mBpfNetMaps);
// Remove Lockdown uid range at 1st time, expect a rule not to be torn down because uid
// ranges we added contains duplicated uid ranges.
- mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, vpnRange);
- verify(mBpfNetMaps, never()).setUidRule(anyInt(), anyInt(), anyInt());
- assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(vpnRange));
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* add */, lockdownRange);
+ verify(mBpfNetMaps, never()).updateUidLockdownRule(anyInt(), anyBoolean());
+ assertEquals(mPermissionMonitor.getVpnLockdownUidRanges(), Set.of(lockdownRange));
reset(mBpfNetMaps);
// Remove Lockdown uid range at 2nd time, expect a rule to be torn down.
- mPermissionMonitor.updateVpnLockdownUidRanges(false /* false */, vpnRange);
- verify(mBpfNetMaps)
- .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
- eq(FIREWALL_RULE_ALLOW));
+ mPermissionMonitor.updateVpnLockdownUidRanges(false /* add */, lockdownRange);
+ verify(mBpfNetMaps).updateUidLockdownRule(anyInt(), eq(false) /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID11, false /* add */);
assertTrue(mPermissionMonitor.getVpnLockdownUidRanges().isEmpty());
}
@Test
public void testLockdownUidFilteringWithInstallAndUnInstall() {
- doReturn(List.of(buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
+ doReturn(List.of(
+ buildPackageInfo(SYSTEM_PACKAGE1, SYSTEM_APP_UID11, CHANGE_NETWORK_STATE,
NETWORK_STACK, CONNECTIVITY_USE_RESTRICTED_NETWORKS),
buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
.when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
doReturn(List.of(MOCK_USER1, MOCK_USER2)).when(mUserManager).getUserHandles(eq(true));
mPermissionMonitor.startMonitoring();
- final UidRange[] vpnRange = {
+ final UidRange[] lockdownRange = {
UidRange.createForUser(MOCK_USER1),
UidRange.createForUser(MOCK_USER2)
};
- mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, vpnRange);
+ mPermissionMonitor.updateVpnLockdownUidRanges(true /* add */, lockdownRange);
+
+ reset(mBpfNetMaps);
// Installing package should add Lockdown rules
addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_APPID1);
- verify(mBpfNetMaps)
- .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
- eq(FIREWALL_RULE_DENY));
- verify(mBpfNetMaps)
- .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID21),
- eq(FIREWALL_RULE_DENY));
+ verify(mBpfNetMaps, times(2)).updateUidLockdownRule(anyInt(), eq(true) /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID11, true /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID21, true /* add */);
reset(mBpfNetMaps);
// Uninstalling package should remove Lockdown rules
mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID11);
- verify(mBpfNetMaps)
- .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID11),
- eq(FIREWALL_RULE_ALLOW));
- verify(mBpfNetMaps, never())
- .setUidRule(eq(FIREWALL_CHAIN_LOCKDOWN_VPN), eq(MOCK_UID21),
- eq(FIREWALL_RULE_ALLOW));
+ verify(mBpfNetMaps).updateUidLockdownRule(anyInt(), eq(false) /* add */);
+ verify(mBpfNetMaps).updateUidLockdownRule(MOCK_UID11, false /* add */);
}
// Normal package add/remove operations will trigger multiple intent for uids corresponding to
@@ -1329,7 +1323,8 @@
public void testOnExternalApplicationsAvailable() throws Exception {
// Initial the permission state. MOCK_PACKAGE1 and MOCK_PACKAGE2 are installed on external
// and have different uids. There has no permission for both uids.
- doReturn(List.of(buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
+ doReturn(List.of(
+ buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
buildPackageInfo(MOCK_PACKAGE2, MOCK_UID12)))
.when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
mPermissionMonitor.startMonitoring();
@@ -1387,7 +1382,8 @@
throws Exception {
// Initial the permission state. MOCK_PACKAGE1 and MOCK_PACKAGE2 are installed on external
// storage and shared on MOCK_UID11. There has no permission for MOCK_UID11.
- doReturn(List.of(buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
+ doReturn(List.of(
+ buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
buildPackageInfo(MOCK_PACKAGE2, MOCK_UID11)))
.when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
mPermissionMonitor.startMonitoring();
@@ -1413,7 +1409,8 @@
// Initial the permission state. MOCK_PACKAGE1 is installed on external storage and
// MOCK_PACKAGE2 is installed on device. These two packages are shared on MOCK_UID11.
// MOCK_UID11 has NETWORK and INTERNET permissions.
- doReturn(List.of(buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
+ doReturn(List.of(
+ buildPackageInfo(MOCK_PACKAGE1, MOCK_UID11),
buildPackageInfo(MOCK_PACKAGE2, MOCK_UID11, CHANGE_NETWORK_STATE, INTERNET)))
.when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
mPermissionMonitor.startMonitoring();
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index eb35469..e8702b9 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -27,7 +27,9 @@
import static android.net.ConnectivityManager.NetworkCallback;
import static android.net.INetd.IF_STATE_DOWN;
import static android.net.INetd.IF_STATE_UP;
+import static android.net.RouteInfo.RTN_UNREACHABLE;
import static android.net.VpnManager.TYPE_VPN_PLATFORM;
+import static android.net.ipsec.ike.IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE;
import static android.os.Build.VERSION_CODES.S_V2;
import static android.os.UserHandle.PER_USER_RANGE;
@@ -39,6 +41,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@@ -59,6 +62,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -83,7 +87,9 @@
import android.net.InetAddresses;
import android.net.InterfaceConfigurationParcel;
import android.net.IpPrefix;
+import android.net.IpSecConfig;
import android.net.IpSecManager;
+import android.net.IpSecTransform;
import android.net.IpSecTunnelInterfaceResponse;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -100,7 +106,12 @@
import android.net.VpnProfileState;
import android.net.VpnService;
import android.net.VpnTransportInfo;
+import android.net.ipsec.ike.ChildSessionCallback;
+import android.net.ipsec.ike.ChildSessionConfiguration;
import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.IkeSessionConfiguration;
+import android.net.ipsec.ike.IkeSessionConnectionInfo;
+import android.net.ipsec.ike.IkeTrafficSelector;
import android.net.ipsec.ike.exceptions.IkeException;
import android.net.ipsec.ike.exceptions.IkeNetworkLostException;
import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
@@ -121,6 +132,7 @@
import android.security.Credentials;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.Range;
import androidx.test.filters.SmallTest;
@@ -155,6 +167,7 @@
import java.io.FileWriter;
import java.io.IOException;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
@@ -165,6 +178,8 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
@@ -198,11 +213,37 @@
static final Network EGRESS_NETWORK = new Network(101);
static final String EGRESS_IFACE = "wlan0";
static final String TEST_VPN_PKG = "com.testvpn.vpn";
+ private static final String TEST_VPN_CLIENT = "2.4.6.8";
private static final String TEST_VPN_SERVER = "1.2.3.4";
private static final String TEST_VPN_IDENTITY = "identity";
private static final byte[] TEST_VPN_PSK = "psk".getBytes();
+ private static final int IP4_PREFIX_LEN = 32;
+ private static final int MIN_PORT = 0;
+ private static final int MAX_PORT = 65535;
+
+ private static final InetAddress TEST_VPN_CLIENT_IP =
+ InetAddresses.parseNumericAddress(TEST_VPN_CLIENT);
+ private static final InetAddress TEST_VPN_SERVER_IP =
+ InetAddresses.parseNumericAddress(TEST_VPN_SERVER);
+ private static final InetAddress TEST_VPN_CLIENT_IP_2 =
+ InetAddresses.parseNumericAddress("192.0.2.200");
+ private static final InetAddress TEST_VPN_SERVER_IP_2 =
+ InetAddresses.parseNumericAddress("192.0.2.201");
+ private static final InetAddress TEST_VPN_INTERNAL_IP =
+ InetAddresses.parseNumericAddress("198.51.100.10");
+ private static final InetAddress TEST_VPN_INTERNAL_DNS =
+ InetAddresses.parseNumericAddress("8.8.8.8");
+
+ private static final IkeTrafficSelector IN_TS =
+ new IkeTrafficSelector(MIN_PORT, MAX_PORT, TEST_VPN_INTERNAL_IP, TEST_VPN_INTERNAL_IP);
+ private static final IkeTrafficSelector OUT_TS =
+ new IkeTrafficSelector(MIN_PORT, MAX_PORT,
+ InetAddresses.parseNumericAddress("0.0.0.0"),
+ InetAddresses.parseNumericAddress("255.255.255.255"));
+
private static final Network TEST_NETWORK = new Network(Integer.MAX_VALUE);
+ private static final Network TEST_NETWORK_2 = new Network(Integer.MAX_VALUE - 1);
private static final String TEST_IFACE_NAME = "TEST_IFACE";
private static final int TEST_TUNNEL_RESOURCE_ID = 0x2345;
private static final long TEST_TIMEOUT_MS = 500L;
@@ -234,15 +275,21 @@
@Mock private AppOpsManager mAppOps;
@Mock private NotificationManager mNotificationManager;
@Mock private Vpn.SystemServices mSystemServices;
+ @Mock private Vpn.IkeSessionWrapper mIkeSessionWrapper;
@Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
+ @Mock private NetworkAgent mMockNetworkAgent;
@Mock private ConnectivityManager mConnectivityManager;
@Mock private IpSecService mIpSecService;
@Mock private VpnProfileStore mVpnProfileStore;
+ @Mock private ScheduledThreadPoolExecutor mExecutor;
+ @Mock private ScheduledFuture mScheduledFuture;
@Mock DeviceIdleInternal mDeviceIdleInternal;
private final VpnProfile mVpnProfile;
private IpSecManager mIpSecManager;
+ private TestDeps mTestDeps;
+
public VpnTest() throws Exception {
// Build an actual VPN profile that is capable of being converted to and from an
// Ikev2VpnProfile
@@ -257,6 +304,7 @@
MockitoAnnotations.initMocks(this);
mIpSecManager = new IpSecManager(mContext, mIpSecService);
+ mTestDeps = spy(new TestDeps());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
setMockedPackages(mPackages);
@@ -297,6 +345,28 @@
// itself, so set the default value of Context#checkCallingOrSelfPermission to
// PERMISSION_DENIED.
doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(any());
+
+ // Set up mIkev2SessionCreator and mExecutor
+ resetIkev2SessionCreator(mIkeSessionWrapper);
+ resetExecutor(mScheduledFuture);
+ }
+
+ private void resetIkev2SessionCreator(Vpn.IkeSessionWrapper ikeSession) {
+ reset(mIkev2SessionCreator);
+ when(mIkev2SessionCreator.createIkeSession(any(), any(), any(), any(), any(), any()))
+ .thenReturn(ikeSession);
+ }
+
+ private void resetExecutor(ScheduledFuture scheduledFuture) {
+ doAnswer(
+ (invocation) -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ })
+ .when(mExecutor)
+ .execute(any());
+ when(mExecutor.schedule(
+ any(Runnable.class), anyLong(), any())).thenReturn(mScheduledFuture);
}
@After
@@ -1315,7 +1385,7 @@
config -> Arrays.asList(config.flags).contains(flag)));
}
- private void setupPlatformVpnWithSpecificExceptionAndItsErrorCode(IkeException exception,
+ private void doTestPlatformVpnWithException(IkeException exception,
String category, int errorType, int errorCode) throws Exception {
final ArgumentCaptor<IkeSessionCallback> captor =
ArgumentCaptor.forClass(IkeSessionCallback.class);
@@ -1333,6 +1403,7 @@
// state
verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
.createIkeSession(any(), any(), any(), any(), captor.capture(), any());
+ reset(mIkev2SessionCreator);
final IkeSessionCallback ikeCb = captor.getValue();
ikeCb.onClosedWithException(exception);
@@ -1342,15 +1413,45 @@
if (errorType == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
.unregisterNetworkCallback(eq(cb));
+ } else if (errorType == VpnManager.ERROR_CLASS_RECOVERABLE) {
+ int retryIndex = 0;
+ final IkeSessionCallback ikeCb2 = verifyRetryAndGetNewIkeCb(retryIndex++);
+
+ ikeCb2.onClosedWithException(exception);
+ verifyRetryAndGetNewIkeCb(retryIndex++);
}
}
+ private IkeSessionCallback verifyRetryAndGetNewIkeCb(int retryIndex) {
+ final ArgumentCaptor<Runnable> runnableCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+ final ArgumentCaptor<IkeSessionCallback> ikeCbCaptor =
+ ArgumentCaptor.forClass(IkeSessionCallback.class);
+
+ // Verify retry is scheduled
+ final long expectedDelay = mTestDeps.getNextRetryDelaySeconds(retryIndex);
+ verify(mExecutor).schedule(runnableCaptor.capture(), eq(expectedDelay), any());
+
+ // Mock the event of firing the retry task
+ runnableCaptor.getValue().run();
+
+ verify(mIkev2SessionCreator)
+ .createIkeSession(any(), any(), any(), any(), ikeCbCaptor.capture(), any());
+
+ // Forget the mIkev2SessionCreator#createIkeSession call and mExecutor#schedule call
+ // for the next retry verification
+ resetIkev2SessionCreator(mIkeSessionWrapper);
+ resetExecutor(mScheduledFuture);
+
+ return ikeCbCaptor.getValue();
+ }
+
@Test
public void testStartPlatformVpnAuthenticationFailed() throws Exception {
final IkeProtocolException exception = mock(IkeProtocolException.class);
final int errorCode = IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
when(exception.getErrorType()).thenReturn(errorCode);
- setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
errorCode);
}
@@ -1360,7 +1461,7 @@
final IkeProtocolException exception = mock(IkeProtocolException.class);
final int errorCode = IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
when(exception.getErrorType()).thenReturn(errorCode);
- setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE, errorCode);
}
@@ -1370,7 +1471,7 @@
final UnknownHostException unknownHostException = new UnknownHostException();
final int errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
when(exception.getCause()).thenReturn(unknownHostException);
- setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
errorCode);
}
@@ -1382,7 +1483,7 @@
new IkeTimeoutException("IkeTimeoutException");
final int errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
when(exception.getCause()).thenReturn(ikeTimeoutException);
- setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
errorCode);
}
@@ -1391,7 +1492,7 @@
public void testStartPlatformVpnFailedWithIkeNetworkLostException() throws Exception {
final IkeNetworkLostException exception = new IkeNetworkLostException(
new Network(100));
- setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
VpnManager.ERROR_CODE_NETWORK_LOST);
}
@@ -1402,7 +1503,7 @@
final IOException ioException = new IOException();
final int errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
when(exception.getCause()).thenReturn(ioException);
- setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ doTestPlatformVpnWithException(exception,
VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
errorCode);
}
@@ -1473,11 +1574,266 @@
return vpn;
}
+ private IkeSessionConnectionInfo createIkeConnectInfo() {
+ return new IkeSessionConnectionInfo(TEST_VPN_CLIENT_IP, TEST_VPN_SERVER_IP, TEST_NETWORK);
+ }
+
+ private IkeSessionConnectionInfo createIkeConnectInfo_2() {
+ return new IkeSessionConnectionInfo(
+ TEST_VPN_CLIENT_IP_2, TEST_VPN_SERVER_IP_2, TEST_NETWORK_2);
+ }
+
+ private IkeSessionConfiguration createIkeConfig(
+ IkeSessionConnectionInfo ikeConnectInfo, boolean isMobikeEnabled) {
+ final IkeSessionConfiguration.Builder builder =
+ new IkeSessionConfiguration.Builder(ikeConnectInfo);
+
+ if (isMobikeEnabled) {
+ builder.addIkeExtension(EXTENSION_TYPE_MOBIKE);
+ }
+
+ return builder.build();
+ }
+
+ private ChildSessionConfiguration createChildConfig() {
+ return new ChildSessionConfiguration.Builder(Arrays.asList(IN_TS), Arrays.asList(OUT_TS))
+ .addInternalAddress(new LinkAddress(TEST_VPN_INTERNAL_IP, IP4_PREFIX_LEN))
+ .addInternalDnsServer(TEST_VPN_INTERNAL_DNS)
+ .build();
+ }
+
+ private IpSecTransform createIpSecTransform() {
+ return new IpSecTransform(mContext, new IpSecConfig());
+ }
+
+ private void verifyApplyTunnelModeTransforms(int expectedTimes) throws Exception {
+ verify(mIpSecService, times(expectedTimes)).applyTunnelModeTransform(
+ eq(TEST_TUNNEL_RESOURCE_ID), eq(IpSecManager.DIRECTION_IN),
+ anyInt(), anyString());
+ verify(mIpSecService, times(expectedTimes)).applyTunnelModeTransform(
+ eq(TEST_TUNNEL_RESOURCE_ID), eq(IpSecManager.DIRECTION_OUT),
+ anyInt(), anyString());
+ }
+
+ private Pair<IkeSessionCallback, ChildSessionCallback> verifyCreateIkeAndCaptureCbs()
+ throws Exception {
+ final ArgumentCaptor<IkeSessionCallback> ikeCbCaptor =
+ ArgumentCaptor.forClass(IkeSessionCallback.class);
+ final ArgumentCaptor<ChildSessionCallback> childCbCaptor =
+ ArgumentCaptor.forClass(ChildSessionCallback.class);
+
+ verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS)).createIkeSession(
+ any(), any(), any(), any(), ikeCbCaptor.capture(), childCbCaptor.capture());
+
+ return new Pair<>(ikeCbCaptor.getValue(), childCbCaptor.getValue());
+ }
+
+ private static class PlatformVpnSnapshot {
+ public final Vpn vpn;
+ public final NetworkCallback nwCb;
+ public final IkeSessionCallback ikeCb;
+ public final ChildSessionCallback childCb;
+
+ PlatformVpnSnapshot(Vpn vpn, NetworkCallback nwCb,
+ IkeSessionCallback ikeCb, ChildSessionCallback childCb) {
+ this.vpn = vpn;
+ this.nwCb = nwCb;
+ this.ikeCb = ikeCb;
+ this.childCb = childCb;
+ }
+ }
+
+ private PlatformVpnSnapshot verifySetupPlatformVpn(IkeSessionConfiguration ikeConfig)
+ throws Exception {
+ doReturn(mMockNetworkAgent).when(mTestDeps)
+ .newNetworkAgent(
+ any(), any(), anyString(), any(), any(), any(), any(), any());
+
+ final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+ when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+ .thenReturn(mVpnProfile.encode());
+
+ vpn.startVpnProfile(TEST_VPN_PKG);
+ final NetworkCallback nwCb = triggerOnAvailableAndGetCallback();
+
+ // Mock the setup procedure by firing callbacks
+ final Pair<IkeSessionCallback, ChildSessionCallback> cbPair =
+ verifyCreateIkeAndCaptureCbs();
+ final IkeSessionCallback ikeCb = cbPair.first;
+ final ChildSessionCallback childCb = cbPair.second;
+
+ ikeCb.onOpened(ikeConfig);
+ childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_IN);
+ childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_OUT);
+ childCb.onOpened(createChildConfig());
+
+ // Verification VPN setup
+ verifyApplyTunnelModeTransforms(1);
+
+ ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class);
+ ArgumentCaptor<NetworkCapabilities> ncCaptor =
+ ArgumentCaptor.forClass(NetworkCapabilities.class);
+ verify(mTestDeps).newNetworkAgent(
+ any(), any(), anyString(), ncCaptor.capture(), lpCaptor.capture(),
+ any(), any(), any());
+
+ // Check LinkProperties
+ final LinkProperties lp = lpCaptor.getValue();
+ final List<RouteInfo> expectedRoutes = Arrays.asList(
+ new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null /*gateway*/,
+ TEST_IFACE_NAME, RouteInfo.RTN_UNICAST),
+ new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null /*gateway*/,
+ TEST_IFACE_NAME, RTN_UNREACHABLE));
+ assertEquals(expectedRoutes, lp.getRoutes());
+
+ // Check internal addresses
+ final List<LinkAddress> expectedAddresses =
+ Arrays.asList(new LinkAddress(TEST_VPN_INTERNAL_IP, IP4_PREFIX_LEN));
+ assertEquals(expectedAddresses, lp.getLinkAddresses());
+
+ // Check internal DNS
+ assertEquals(Arrays.asList(TEST_VPN_INTERNAL_DNS), lp.getDnsServers());
+
+ // Check NetworkCapabilities
+ assertEquals(Arrays.asList(TEST_NETWORK), ncCaptor.getValue().getUnderlyingNetworks());
+
+ return new PlatformVpnSnapshot(vpn, nwCb, ikeCb, childCb);
+ }
+
@Test
public void testStartPlatformVpn() throws Exception {
- startLegacyVpn(createVpn(primaryUser.id), mVpnProfile);
- // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
- // a subsequent patch.
+ final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+ createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
+ vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
+ }
+
+ @Test
+ public void testStartPlatformVpnMobility_mobikeEnabled() throws Exception {
+ final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+ createIkeConfig(createIkeConnectInfo(), true /* isMobikeEnabled */));
+
+ // Mock network loss and verify a cleanup task is scheduled
+ vpnSnapShot.nwCb.onLost(TEST_NETWORK);
+ verify(mExecutor).schedule(any(Runnable.class), anyLong(), any());
+
+ // Mock new network comes up and the cleanup task is cancelled
+ vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+ verify(mScheduledFuture).cancel(anyBoolean());
+
+ // Verify MOBIKE is triggered
+ verify(mIkeSessionWrapper).setNetwork(TEST_NETWORK_2);
+
+ // Mock the MOBIKE procedure
+ vpnSnapShot.ikeCb.onIkeSessionConnectionInfoChanged(createIkeConnectInfo_2());
+ vpnSnapShot.childCb.onIpSecTransformsMigrated(
+ createIpSecTransform(), createIpSecTransform());
+
+ verify(mIpSecService).setNetworkForTunnelInterface(
+ eq(TEST_TUNNEL_RESOURCE_ID), eq(TEST_NETWORK_2), anyString());
+
+ // Expect 2 times: one for initial setup and one for MOBIKE
+ verifyApplyTunnelModeTransforms(2);
+
+ // Verify mNetworkCapabilities and mNetworkAgent are updated
+ assertEquals(
+ Collections.singletonList(TEST_NETWORK_2),
+ vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());
+ verify(mMockNetworkAgent)
+ .setUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2));
+
+ vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
+ }
+
+ @Test
+ public void testStartPlatformVpnReestablishes_mobikeDisabled() throws Exception {
+ final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+ createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */));
+
+ // Forget the first IKE creation to be prepared to capture callbacks of the second
+ // IKE session
+ resetIkev2SessionCreator(mock(Vpn.IkeSessionWrapper.class));
+
+ // Mock network switch
+ vpnSnapShot.nwCb.onLost(TEST_NETWORK);
+ vpnSnapShot.nwCb.onAvailable(TEST_NETWORK_2);
+
+ // Verify the old IKE Session is killed
+ verify(mIkeSessionWrapper).kill();
+
+ // Capture callbacks of the new IKE Session
+ final Pair<IkeSessionCallback, ChildSessionCallback> cbPair =
+ verifyCreateIkeAndCaptureCbs();
+ final IkeSessionCallback ikeCb = cbPair.first;
+ final ChildSessionCallback childCb = cbPair.second;
+
+ // Mock the IKE Session setup
+ ikeCb.onOpened(createIkeConfig(createIkeConnectInfo_2(), false /* isMobikeEnabled */));
+
+ childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_IN);
+ childCb.onIpSecTransformCreated(createIpSecTransform(), IpSecManager.DIRECTION_OUT);
+ childCb.onOpened(createChildConfig());
+
+ // Expect 2 times since there have been two Session setups
+ verifyApplyTunnelModeTransforms(2);
+
+ // Verify mNetworkCapabilities and mNetworkAgent are updated
+ assertEquals(
+ Collections.singletonList(TEST_NETWORK_2),
+ vpnSnapShot.vpn.mNetworkCapabilities.getUnderlyingNetworks());
+ verify(mMockNetworkAgent)
+ .setUnderlyingNetworks(Collections.singletonList(TEST_NETWORK_2));
+
+ vpnSnapShot.vpn.mVpnRunner.exitVpnRunner();
+ }
+
+ private void verifyHandlingNetworkLoss() throws Exception {
+ final ArgumentCaptor<LinkProperties> lpCaptor =
+ ArgumentCaptor.forClass(LinkProperties.class);
+ verify(mMockNetworkAgent).sendLinkProperties(lpCaptor.capture());
+ final LinkProperties lp = lpCaptor.getValue();
+
+ assertNull(lp.getInterfaceName());
+ final List<RouteInfo> expectedRoutes = Arrays.asList(
+ new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null /*gateway*/,
+ null /*iface*/, RTN_UNREACHABLE),
+ new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null /*gateway*/,
+ null /*iface*/, RTN_UNREACHABLE));
+ assertEquals(expectedRoutes, lp.getRoutes());
+ }
+
+ @Test
+ public void testStartPlatformVpnHandlesNetworkLoss_mobikeEnabled() throws Exception {
+ final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+ createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */));
+
+ // Forget the #sendLinkProperties during first setup.
+ reset(mMockNetworkAgent);
+
+ final ArgumentCaptor<Runnable> runnableCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+
+ // Mock network loss
+ vpnSnapShot.nwCb.onLost(TEST_NETWORK);
+
+ // Mock the grace period expires
+ verify(mExecutor).schedule(runnableCaptor.capture(), anyLong(), any());
+ runnableCaptor.getValue().run();
+
+ verifyHandlingNetworkLoss();
+ }
+
+ @Test
+ public void testStartPlatformVpnHandlesNetworkLoss_mobikeDisabled() throws Exception {
+ final PlatformVpnSnapshot vpnSnapShot = verifySetupPlatformVpn(
+ createIkeConfig(createIkeConnectInfo(), false /* isMobikeEnabled */));
+
+ // Forget the #sendLinkProperties during first setup.
+ reset(mMockNetworkAgent);
+
+ // Mock network loss
+ vpnSnapShot.nwCb.onLost(TEST_NETWORK);
+
+ verifyHandlingNetworkLoss();
}
@Test
@@ -1560,7 +1916,8 @@
}
}
- private final class TestDeps extends Vpn.Dependencies {
+ // Make it public and un-final so as to spy it
+ public class TestDeps extends Vpn.Dependencies {
public final CompletableFuture<String[]> racoonArgs = new CompletableFuture();
public final CompletableFuture<String[]> mtpdArgs = new CompletableFuture();
public final File mStateFile;
@@ -1694,6 +2051,17 @@
public DeviceIdleInternal getDeviceIdleInternal() {
return mDeviceIdleInternal;
}
+
+ @Override
+ public long getNextRetryDelaySeconds(int retryCount) {
+ // Simply return retryCount as the delay seconds for retrying.
+ return retryCount;
+ }
+
+ @Override
+ public ScheduledThreadPoolExecutor newScheduledThreadPoolExecutor() {
+ return mExecutor;
+ }
}
/**
@@ -1705,7 +2073,7 @@
when(mContext.createContextAsUser(eq(UserHandle.of(userId)), anyInt()))
.thenReturn(asUserContext);
final TestLooper testLooper = new TestLooper();
- final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, new TestDeps(), mNetService,
+ final Vpn vpn = new Vpn(testLooper.getLooper(), mContext, mTestDeps, mNetService,
mNetd, userId, mVpnProfileStore, mSystemServices, mIkev2SessionCreator);
verify(mConnectivityManager, times(1)).registerNetworkProvider(argThat(
provider -> provider.getName().contains("VpnNetworkProvider")
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
index 70e6c39..2178b33 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetNetworkFactoryTest.java
@@ -16,8 +16,6 @@
package com.android.server.ethernet;
-import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
@@ -55,16 +53,17 @@
import android.net.StaticIpConfiguration;
import android.net.ip.IpClientCallbacks;
import android.net.ip.IpClientManager;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.test.TestLooper;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.InterfaceParams;
import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.After;
import org.junit.Before;
@@ -79,8 +78,9 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-@RunWith(AndroidJUnit4.class)
@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class EthernetNetworkFactoryTest {
private static final int TIMEOUT_MS = 2_000;
private static final String TEST_IFACE = "test123";
@@ -612,7 +612,6 @@
assertEquals(listener.expectOnResult(), TEST_IFACE);
}
- @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
@Test
public void testUpdateInterfaceAbortsOnConcurrentRemoveInterface() throws Exception {
initEthernetNetworkFactory();
@@ -621,7 +620,6 @@
() -> mNetFactory.removeInterface(TEST_IFACE));
}
- @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
@Test
public void testUpdateInterfaceAbortsOnConcurrentUpdateInterfaceLinkState() throws Exception {
initEthernetNetworkFactory();
@@ -630,7 +628,6 @@
() -> mNetFactory.updateInterfaceLinkState(TEST_IFACE, false, NULL_LISTENER));
}
- @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) // TODO: Use to Build.VERSION_CODES.SC_V2 when available
@Test
public void testUpdateInterfaceAbortsOnNetworkUneededRemovesAllRequests() throws Exception {
initEthernetNetworkFactory();
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java b/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java
index dd1f1ed..a1d93a0 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetServiceImplTest.java
@@ -16,16 +16,17 @@
package com.android.server.ethernet;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
-
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -35,29 +36,37 @@
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.net.INetworkInterfaceOutcomeReceiver;
+import android.net.EthernetNetworkSpecifier;
import android.net.EthernetNetworkUpdateRequest;
+import android.net.INetworkInterfaceOutcomeReceiver;
import android.net.IpConfiguration;
import android.net.NetworkCapabilities;
+import android.net.StringNetworkSpecifier;
+import android.os.Build;
import android.os.Handler;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-@RunWith(AndroidJUnit4.class)
@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class EthernetServiceImplTest {
private static final String TEST_IFACE = "test123";
+ private static final NetworkCapabilities DEFAULT_CAPS = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_ETHERNET)
+ .setNetworkSpecifier(new EthernetNetworkSpecifier(TEST_IFACE))
+ .build();
private static final EthernetNetworkUpdateRequest UPDATE_REQUEST =
new EthernetNetworkUpdateRequest.Builder()
.setIpConfiguration(new IpConfiguration())
- .setNetworkCapabilities(new NetworkCapabilities.Builder().build())
+ .setNetworkCapabilities(DEFAULT_CAPS)
.build();
private static final EthernetNetworkUpdateRequest UPDATE_REQUEST_WITHOUT_CAPABILITIES =
new EthernetNetworkUpdateRequest.Builder()
@@ -65,18 +74,21 @@
.build();
private static final EthernetNetworkUpdateRequest UPDATE_REQUEST_WITHOUT_IP_CONFIG =
new EthernetNetworkUpdateRequest.Builder()
- .setNetworkCapabilities(new NetworkCapabilities.Builder().build())
+ .setNetworkCapabilities(DEFAULT_CAPS)
.build();
private static final INetworkInterfaceOutcomeReceiver NULL_LISTENER = null;
private EthernetServiceImpl mEthernetServiceImpl;
- @Mock private Context mContext;
- @Mock private Handler mHandler;
- @Mock private EthernetTracker mEthernetTracker;
- @Mock private PackageManager mPackageManager;
+ private Context mContext;
+ private Handler mHandler;
+ private EthernetTracker mEthernetTracker;
+ private PackageManager mPackageManager;
@Before
public void setup() {
- MockitoAnnotations.initMocks(this);
+ mContext = mock(Context.class);
+ mHandler = mock(Handler.class);
+ mEthernetTracker = mock(EthernetTracker.class);
+ mPackageManager = mock(PackageManager.class);
doReturn(mPackageManager).when(mContext).getPackageManager();
mEthernetServiceImpl = new EthernetServiceImpl(mContext, mHandler, mEthernetTracker);
mEthernetServiceImpl.mStarted.set(true);
@@ -111,18 +123,18 @@
}
@Test
- public void testConnectNetworkRejectsWhenEthNotStarted() {
+ public void testEnableInterfaceRejectsWhenEthNotStarted() {
mEthernetServiceImpl.mStarted.set(false);
assertThrows(IllegalStateException.class, () -> {
- mEthernetServiceImpl.connectNetwork("" /* iface */, null /* listener */);
+ mEthernetServiceImpl.enableInterface("" /* iface */, null /* listener */);
});
}
@Test
- public void testDisconnectNetworkRejectsWhenEthNotStarted() {
+ public void testDisableInterfaceRejectsWhenEthNotStarted() {
mEthernetServiceImpl.mStarted.set(false);
assertThrows(IllegalStateException.class, () -> {
- mEthernetServiceImpl.disconnectNetwork("" /* iface */, null /* listener */);
+ mEthernetServiceImpl.disableInterface("" /* iface */, null /* listener */);
});
}
@@ -134,16 +146,16 @@
}
@Test
- public void testConnectNetworkRejectsNullIface() {
+ public void testEnableInterfaceRejectsNullIface() {
assertThrows(NullPointerException.class, () -> {
- mEthernetServiceImpl.connectNetwork(null /* iface */, NULL_LISTENER);
+ mEthernetServiceImpl.enableInterface(null /* iface */, NULL_LISTENER);
});
}
@Test
- public void testDisconnectNetworkRejectsNullIface() {
+ public void testDisableInterfaceRejectsNullIface() {
assertThrows(NullPointerException.class, () -> {
- mEthernetServiceImpl.disconnectNetwork(null /* iface */, NULL_LISTENER);
+ mEthernetServiceImpl.disableInterface(null /* iface */, NULL_LISTENER);
});
}
@@ -156,6 +168,41 @@
}
@Test
+ public void testUpdateConfigurationRejectsWithInvalidSpecifierType() {
+ final StringNetworkSpecifier invalidSpecifierType = new StringNetworkSpecifier("123");
+ final EthernetNetworkUpdateRequest request =
+ new EthernetNetworkUpdateRequest.Builder()
+ .setNetworkCapabilities(
+ new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_ETHERNET)
+ .setNetworkSpecifier(invalidSpecifierType)
+ .build()
+ ).build();
+ assertThrows(IllegalArgumentException.class, () -> {
+ mEthernetServiceImpl.updateConfiguration(
+ "" /* iface */, request, null /* listener */);
+ });
+ }
+
+ @Test
+ public void testUpdateConfigurationRejectsWithInvalidSpecifierName() {
+ final String ifaceToUpdate = "eth0";
+ final String ifaceOnSpecifier = "wlan0";
+ EthernetNetworkUpdateRequest request =
+ new EthernetNetworkUpdateRequest.Builder()
+ .setNetworkCapabilities(
+ new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_ETHERNET)
+ .setNetworkSpecifier(
+ new EthernetNetworkSpecifier(ifaceOnSpecifier))
+ .build()
+ ).build();
+ assertThrows(IllegalArgumentException.class, () -> {
+ mEthernetServiceImpl.updateConfiguration(ifaceToUpdate, request, null /* listener */);
+ });
+ }
+
+ @Test
public void testUpdateConfigurationWithCapabilitiesWithAutomotiveFeature() {
toggleAutomotiveFeature(false);
mEthernetServiceImpl.updateConfiguration(TEST_IFACE, UPDATE_REQUEST_WITHOUT_CAPABILITIES,
@@ -165,22 +212,6 @@
eq(UPDATE_REQUEST_WITHOUT_CAPABILITIES.getNetworkCapabilities()), isNull());
}
- @Test
- public void testConnectNetworkRejectsWithoutAutomotiveFeature() {
- toggleAutomotiveFeature(false);
- assertThrows(UnsupportedOperationException.class, () -> {
- mEthernetServiceImpl.connectNetwork("" /* iface */, NULL_LISTENER);
- });
- }
-
- @Test
- public void testDisconnectNetworkRejectsWithoutAutomotiveFeature() {
- toggleAutomotiveFeature(false);
- assertThrows(UnsupportedOperationException.class, () -> {
- mEthernetServiceImpl.disconnectNetwork("" /* iface */, NULL_LISTENER);
- });
- }
-
private void denyManageEthPermission() {
doThrow(new SecurityException("")).when(mContext)
.enforceCallingOrSelfPermission(
@@ -202,18 +233,18 @@
}
@Test
- public void testConnectNetworkRejectsWithoutManageEthPermission() {
+ public void testEnableInterfaceRejectsWithoutManageEthPermission() {
denyManageEthPermission();
assertThrows(SecurityException.class, () -> {
- mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
});
}
@Test
- public void testDisconnectNetworkRejectsWithoutManageEthPermission() {
+ public void testDisableInterfaceRejectsWithoutManageEthPermission() {
denyManageEthPermission();
assertThrows(SecurityException.class, () -> {
- mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
});
}
@@ -231,20 +262,20 @@
}
@Test
- public void testConnectNetworkRejectsTestRequestWithoutTestPermission() {
+ public void testEnableInterfaceRejectsTestRequestWithoutTestPermission() {
enableTestInterface();
denyManageTestNetworksPermission();
assertThrows(SecurityException.class, () -> {
- mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
});
}
@Test
- public void testDisconnectNetworkRejectsTestRequestWithoutTestPermission() {
+ public void testDisableInterfaceRejectsTestRequestWithoutTestPermission() {
enableTestInterface();
denyManageTestNetworksPermission();
assertThrows(SecurityException.class, () -> {
- mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
});
}
@@ -258,15 +289,33 @@
}
@Test
- public void testConnectNetwork() {
- mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).connectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
+ public void testUpdateConfigurationAddsSpecifierWhenNotSet() {
+ final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_ETHERNET).build();
+ final EthernetNetworkUpdateRequest requestSansSpecifier =
+ new EthernetNetworkUpdateRequest.Builder()
+ .setNetworkCapabilities(nc)
+ .build();
+ final NetworkCapabilities ncWithSpecifier = new NetworkCapabilities(nc)
+ .setNetworkSpecifier(new EthernetNetworkSpecifier(TEST_IFACE));
+
+ mEthernetServiceImpl.updateConfiguration(TEST_IFACE, requestSansSpecifier, NULL_LISTENER);
+ verify(mEthernetTracker).updateConfiguration(
+ eq(TEST_IFACE),
+ isNull(),
+ eq(ncWithSpecifier), eq(NULL_LISTENER));
}
@Test
- public void testDisconnectNetwork() {
- mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).disconnectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
+ public void testEnableInterface() {
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).enableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
+ }
+
+ @Test
+ public void testDisableInterface() {
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).disableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
}
@Test
@@ -324,23 +373,23 @@
}
@Test
- public void testConnectNetworkForTestRequestDoesNotRequireAutoOrNetPermission() {
+ public void testEnableInterfaceForTestRequestDoesNotRequireNetPermission() {
enableTestInterface();
toggleAutomotiveFeature(false);
denyManageEthPermission();
- mEthernetServiceImpl.connectNetwork(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).connectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
+ mEthernetServiceImpl.enableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).enableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
}
@Test
- public void testDisconnectNetworkForTestRequestDoesNotRequireAutoOrNetPermission() {
+ public void testDisableInterfaceForTestRequestDoesNotRequireAutoOrNetPermission() {
enableTestInterface();
toggleAutomotiveFeature(false);
denyManageEthPermission();
- mEthernetServiceImpl.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
- verify(mEthernetTracker).disconnectNetwork(eq(TEST_IFACE), eq(NULL_LISTENER));
+ mEthernetServiceImpl.disableInterface(TEST_IFACE, NULL_LISTENER);
+ verify(mEthernetTracker).disableInterface(eq(TEST_IFACE), eq(NULL_LISTENER));
}
private void denyPermissions(String... permissions) {
diff --git a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
index 93789ca..38094ae 100644
--- a/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
+++ b/tests/unit/java/com/android/server/ethernet/EthernetTrackerTest.java
@@ -50,12 +50,14 @@
import android.net.LinkAddress;
import android.net.NetworkCapabilities;
import android.net.StaticIpConfiguration;
+import android.os.Build;
import android.os.HandlerThread;
import android.os.RemoteException;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import com.android.testutils.HandlerUtils;
import org.junit.After;
@@ -71,7 +73,8 @@
import java.util.concurrent.atomic.AtomicBoolean;
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class EthernetTrackerTest {
private static final String TEST_IFACE = "test123";
private static final int TIMEOUT_MS = 1_000;
@@ -354,8 +357,8 @@
}
@Test
- public void testConnectNetworkCorrectlyCallsFactory() {
- tracker.connectNetwork(TEST_IFACE, NULL_LISTENER);
+ public void testEnableInterfaceCorrectlyCallsFactory() {
+ tracker.enableInterface(TEST_IFACE, NULL_LISTENER);
waitForIdle();
verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(true /* up */),
@@ -363,8 +366,8 @@
}
@Test
- public void testDisconnectNetworkCorrectlyCallsFactory() {
- tracker.disconnectNetwork(TEST_IFACE, NULL_LISTENER);
+ public void testDisableInterfaceCorrectlyCallsFactory() {
+ tracker.disableInterface(TEST_IFACE, NULL_LISTENER);
waitForIdle();
verify(mFactory).updateInterfaceLinkState(eq(TEST_IFACE), eq(false /* up */),
diff --git a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
index 987b7b7..c6852d1 100644
--- a/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
+++ b/tests/unit/java/com/android/server/net/BpfInterfaceMapUpdaterTest.java
@@ -24,16 +24,18 @@
import android.content.Context;
import android.net.INetd;
import android.net.MacAddress;
+import android.os.Build;
import android.os.Handler;
import android.os.test.TestLooper;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.Struct.U32;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
import org.junit.Before;
import org.junit.Test;
@@ -42,8 +44,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-@RunWith(AndroidJUnit4.class)
@SmallTest
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public final class BpfInterfaceMapUpdaterTest {
private static final int TEST_INDEX = 1;
private static final int TEST_INDEX2 = 2;
diff --git a/tests/unit/java/com/android/server/net/IpConfigStoreTest.java b/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
index e9a5309..4adc999 100644
--- a/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
+++ b/tests/unit/java/com/android/server/net/IpConfigStoreTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
+import android.content.Context;
import android.net.InetAddresses;
import android.net.IpConfiguration;
import android.net.IpConfiguration.IpAssignment;
@@ -27,9 +28,15 @@
import android.net.LinkAddress;
import android.net.ProxyInfo;
import android.net.StaticIpConfiguration;
+import android.os.Build;
+import android.os.HandlerThread;
import android.util.ArrayMap;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.HandlerUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,17 +44,21 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/**
* Unit tests for {@link IpConfigStore}
*/
-@RunWith(AndroidJUnit4.class)
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
public class IpConfigStoreTest {
+ private static final int TIMEOUT_MS = 2_000;
private static final int KEY_CONFIG = 17;
private static final String IFACE_1 = "eth0";
private static final String IFACE_2 = "eth1";
@@ -56,6 +67,22 @@
private static final String DNS_IP_ADDR_1 = "1.2.3.4";
private static final String DNS_IP_ADDR_2 = "5.6.7.8";
+ private static final ArrayList<InetAddress> DNS_SERVERS = new ArrayList<>(List.of(
+ InetAddresses.parseNumericAddress(DNS_IP_ADDR_1),
+ InetAddresses.parseNumericAddress(DNS_IP_ADDR_2)));
+ private static final StaticIpConfiguration STATIC_IP_CONFIG_1 =
+ new StaticIpConfiguration.Builder()
+ .setIpAddress(new LinkAddress(IP_ADDR_1))
+ .setDnsServers(DNS_SERVERS)
+ .build();
+ private static final StaticIpConfiguration STATIC_IP_CONFIG_2 =
+ new StaticIpConfiguration.Builder()
+ .setIpAddress(new LinkAddress(IP_ADDR_2))
+ .setDnsServers(DNS_SERVERS)
+ .build();
+ private static final ProxyInfo PROXY_INFO =
+ ProxyInfo.buildDirectProxy("10.10.10.10", 88, Arrays.asList("host1", "host2"));
+
@Test
public void backwardCompatibility2to3() throws IOException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
@@ -79,40 +106,73 @@
@Test
public void staticIpMultiNetworks() throws Exception {
- final ArrayList<InetAddress> dnsServers = new ArrayList<>();
- dnsServers.add(InetAddresses.parseNumericAddress(DNS_IP_ADDR_1));
- dnsServers.add(InetAddresses.parseNumericAddress(DNS_IP_ADDR_2));
- final StaticIpConfiguration staticIpConfiguration1 = new StaticIpConfiguration.Builder()
- .setIpAddress(new LinkAddress(IP_ADDR_1))
- .setDnsServers(dnsServers).build();
- final StaticIpConfiguration staticIpConfiguration2 = new StaticIpConfiguration.Builder()
- .setIpAddress(new LinkAddress(IP_ADDR_2))
- .setDnsServers(dnsServers).build();
+ final IpConfiguration expectedConfig1 = newIpConfiguration(IpAssignment.STATIC,
+ ProxySettings.STATIC, STATIC_IP_CONFIG_1, PROXY_INFO);
+ final IpConfiguration expectedConfig2 = newIpConfiguration(IpAssignment.STATIC,
+ ProxySettings.STATIC, STATIC_IP_CONFIG_2, PROXY_INFO);
- ProxyInfo proxyInfo =
- ProxyInfo.buildDirectProxy("10.10.10.10", 88, Arrays.asList("host1", "host2"));
-
- IpConfiguration expectedConfig1 = newIpConfiguration(IpAssignment.STATIC,
- ProxySettings.STATIC, staticIpConfiguration1, proxyInfo);
- IpConfiguration expectedConfig2 = newIpConfiguration(IpAssignment.STATIC,
- ProxySettings.STATIC, staticIpConfiguration2, proxyInfo);
-
- ArrayMap<String, IpConfiguration> expectedNetworks = new ArrayMap<>();
+ final ArrayMap<String, IpConfiguration> expectedNetworks = new ArrayMap<>();
expectedNetworks.put(IFACE_1, expectedConfig1);
expectedNetworks.put(IFACE_2, expectedConfig2);
- MockedDelayedDiskWrite writer = new MockedDelayedDiskWrite();
- IpConfigStore store = new IpConfigStore(writer);
+ final MockedDelayedDiskWrite writer = new MockedDelayedDiskWrite();
+ final IpConfigStore store = new IpConfigStore(writer);
store.writeIpConfigurations("file/path/not/used/", expectedNetworks);
- InputStream in = new ByteArrayInputStream(writer.mByteStream.toByteArray());
- ArrayMap<String, IpConfiguration> actualNetworks = IpConfigStore.readIpConfigurations(in);
+ final InputStream in = new ByteArrayInputStream(writer.mByteStream.toByteArray());
+ final ArrayMap<String, IpConfiguration> actualNetworks =
+ IpConfigStore.readIpConfigurations(in);
assertNotNull(actualNetworks);
assertEquals(2, actualNetworks.size());
assertEquals(expectedNetworks.get(IFACE_1), actualNetworks.get(IFACE_1));
assertEquals(expectedNetworks.get(IFACE_2), actualNetworks.get(IFACE_2));
}
+ @Test
+ public void readIpConfigurationFromFilePath() throws Exception {
+ final HandlerThread testHandlerThread = new HandlerThread("IpConfigStoreTest");
+ final DelayedDiskWrite.Dependencies dependencies =
+ new DelayedDiskWrite.Dependencies() {
+ @Override
+ public HandlerThread makeHandlerThread() {
+ return testHandlerThread;
+ }
+ @Override
+ public void quitHandlerThread(HandlerThread handlerThread) {
+ testHandlerThread.quitSafely();
+ }
+ };
+
+ final IpConfiguration ipConfig = newIpConfiguration(IpAssignment.STATIC,
+ ProxySettings.STATIC, STATIC_IP_CONFIG_1, PROXY_INFO);
+ final ArrayMap<String, IpConfiguration> expectedNetworks = new ArrayMap<>();
+ expectedNetworks.put(IFACE_1, ipConfig);
+
+ // Write IP config to specific file path and read it later.
+ final Context context = InstrumentationRegistry.getContext();
+ final File configFile = new File(context.getFilesDir().getPath(),
+ "IpConfigStoreTest-ipconfig.txt");
+ final DelayedDiskWrite writer = new DelayedDiskWrite(dependencies);
+ final IpConfigStore store = new IpConfigStore(writer);
+ store.writeIpConfigurations(configFile.getPath(), expectedNetworks);
+ HandlerUtils.waitForIdle(testHandlerThread, TIMEOUT_MS);
+
+ // Read IP config from the file path.
+ final ArrayMap<String, IpConfiguration> actualNetworks =
+ IpConfigStore.readIpConfigurations(configFile.getPath());
+ assertNotNull(actualNetworks);
+ assertEquals(1, actualNetworks.size());
+ assertEquals(expectedNetworks.get(IFACE_1), actualNetworks.get(IFACE_1));
+
+ // Return an empty array when reading IP configuration from an non-exist config file.
+ final ArrayMap<String, IpConfiguration> emptyNetworks =
+ IpConfigStore.readIpConfigurations("/dev/null");
+ assertNotNull(emptyNetworks);
+ assertEquals(0, emptyNetworks.size());
+
+ configFile.delete();
+ }
+
private IpConfiguration newIpConfiguration(IpAssignment ipAssignment,
ProxySettings proxySettings, StaticIpConfiguration staticIpConfig, ProxyInfo info) {
final IpConfiguration config = new IpConfiguration();
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 79744b1..5400a00 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -29,6 +29,7 @@
import static android.net.NetworkStats.UID_ALL;
import static com.android.server.net.NetworkStatsFactory.kernelToTag;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -38,7 +39,6 @@
import android.net.NetworkStats;
import android.net.TrafficStats;
import android.net.UnderlyingNetworkInfo;
-import android.os.Build;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -67,7 +67,7 @@
/** Tests for {@link NetworkStatsFactory}. */
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
public class NetworkStatsFactoryTest extends NetworkStatsBaseTest {
private static final String CLAT_PREFIX = "v4-";
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index e03b4fe..f9cbb10 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -147,6 +147,17 @@
import com.android.testutils.TestBpfMap;
import com.android.testutils.TestableNetworkStatsProviderBinder;
+import libcore.testing.io.TestIoUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -162,17 +173,6 @@
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
-import libcore.testing.io.TestIoUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
/**
* Tests for {@link NetworkStatsService}.
*
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
index 0d34609..622f2be 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java
@@ -19,6 +19,8 @@
import static android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
import static android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
@@ -37,7 +39,6 @@
import android.annotation.Nullable;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
-import android.os.Build;
import android.os.Looper;
import android.os.Parcel;
import android.telephony.SubscriptionManager;
@@ -63,7 +64,7 @@
import java.util.concurrent.Executors;
@RunWith(DevSdkIgnoreRunner.class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
public final class NetworkStatsSubscriptionsMonitorTest {
private static final int TEST_SUBID1 = 3;
private static final int TEST_SUBID2 = 5;
diff --git a/tools/Android.bp b/tools/Android.bp
new file mode 100644
index 0000000..1fa93bb
--- /dev/null
+++ b/tools/Android.bp
@@ -0,0 +1,91 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Build tool used to generate jarjar rules for all classes in a jar, except those that are
+// API, UnsupportedAppUsage or otherwise excluded.
+python_binary_host {
+ name: "jarjar-rules-generator",
+ srcs: [
+ "gen_jarjar.py",
+ ],
+ main: "gen_jarjar.py",
+ version: {
+ py2: {
+ enabled: false,
+ },
+ py3: {
+ enabled: true,
+ },
+ },
+ visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
+genrule_defaults {
+ name: "jarjar-rules-combine-defaults",
+ // Concat files with a line break in the middle
+ cmd: "for src in $(in); do cat $${src}; echo; done > $(out)",
+ defaults_visibility: ["//packages/modules/Connectivity:__subpackages__"],
+}
+
+java_library {
+ name: "jarjar-rules-generator-testjavalib",
+ srcs: ["testdata/java/**/*.java"],
+ visibility: ["//visibility:private"],
+}
+
+// TODO(b/233723405) - Remove this workaround.
+// Temporary work around of b/233723405. Using the module_lib stub directly
+// in the test causes it to sometimes get the dex jar and sometimes get the
+// classes jar due to b/233111644. Statically including it here instead
+// ensures that it will always get the classes jar.
+java_library {
+ name: "framework-connectivity.stubs.module_lib-for-test",
+ visibility: ["//visibility:private"],
+ static_libs: [
+ "framework-connectivity.stubs.module_lib",
+ ],
+ // Not strictly necessary but specified as this MUST not have generate
+ // a dex jar as that will break the tests.
+ compile_dex: false,
+}
+
+python_test_host {
+ name: "jarjar-rules-generator-test",
+ srcs: [
+ "gen_jarjar.py",
+ "gen_jarjar_test.py",
+ ],
+ data: [
+ "testdata/test-jarjar-excludes.txt",
+ "testdata/test-unsupportedappusage.txt",
+ ":framework-connectivity.stubs.module_lib-for-test",
+ ":jarjar-rules-generator-testjavalib",
+ ],
+ main: "gen_jarjar_test.py",
+ version: {
+ py2: {
+ enabled: false,
+ },
+ py3: {
+ enabled: true,
+ },
+ },
+}
diff --git a/tools/gen_jarjar.py b/tools/gen_jarjar.py
new file mode 100755
index 0000000..4c2cf54
--- /dev/null
+++ b/tools/gen_jarjar.py
@@ -0,0 +1,133 @@
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+""" This script generates jarjar rule files to add a jarjar prefix to all classes, except those
+that are API, unsupported API or otherwise excluded."""
+
+import argparse
+import io
+import re
+import subprocess
+from xml import sax
+from xml.sax.handler import ContentHandler
+from zipfile import ZipFile
+
+
+def parse_arguments(argv):
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ '--jars', nargs='+',
+ help='Path to pre-jarjar JAR. Can be followed by multiple space-separated paths.')
+ parser.add_argument(
+ '--prefix', required=True,
+ help='Package prefix to use for jarjared classes, '
+ 'for example "com.android.connectivity" (does not end with a dot).')
+ parser.add_argument(
+ '--output', required=True, help='Path to output jarjar rules file.')
+ parser.add_argument(
+ '--apistubs', nargs='*', default=[],
+ help='Path to API stubs jar. Classes that are API will not be jarjared. Can be followed by '
+ 'multiple space-separated paths.')
+ parser.add_argument(
+ '--unsupportedapi', nargs='*', default=[],
+ help='Path to UnsupportedAppUsage hidden API .txt lists. '
+ 'Classes that have UnsupportedAppUsage API will not be jarjared. Can be followed by '
+ 'multiple space-separated paths.')
+ parser.add_argument(
+ '--excludes', nargs='*', default=[],
+ help='Path to files listing classes that should not be jarjared. Can be followed by '
+ 'multiple space-separated paths. '
+ 'Each file should contain one full-match regex per line. Empty lines or lines '
+ 'starting with "#" are ignored.')
+ return parser.parse_args(argv)
+
+
+def _list_toplevel_jar_classes(jar):
+ """List all classes in a .class .jar file that are not inner classes."""
+ return {_get_toplevel_class(c) for c in _list_jar_classes(jar)}
+
+def _list_jar_classes(jar):
+ with ZipFile(jar, 'r') as zip:
+ files = zip.namelist()
+ assert 'classes.dex' not in files, f'Jar file {jar} is dexed, ' \
+ 'expected an intermediate zip of .class files'
+ class_len = len('.class')
+ return [f.replace('/', '.')[:-class_len] for f in files
+ if f.endswith('.class') and not f.endswith('/package-info.class')]
+
+
+def _list_hiddenapi_classes(txt_file):
+ out = set()
+ with open(txt_file, 'r') as f:
+ for line in f:
+ if not line.strip():
+ continue
+ assert line.startswith('L') and ';' in line, f'Class name not recognized: {line}'
+ clazz = line.replace('/', '.').split(';')[0][1:]
+ out.add(_get_toplevel_class(clazz))
+ return out
+
+
+def _get_toplevel_class(clazz):
+ """Return the name of the toplevel (not an inner class) enclosing class of the given class."""
+ if '$' not in clazz:
+ return clazz
+ return clazz.split('$')[0]
+
+
+def _get_excludes(path):
+ out = []
+ with open(path, 'r') as f:
+ for line in f:
+ stripped = line.strip()
+ if not stripped or stripped.startswith('#'):
+ continue
+ out.append(re.compile(stripped))
+ return out
+
+
+def make_jarjar_rules(args):
+ excluded_classes = set()
+ for apistubs_file in args.apistubs:
+ excluded_classes.update(_list_toplevel_jar_classes(apistubs_file))
+
+ for unsupportedapi_file in args.unsupportedapi:
+ excluded_classes.update(_list_hiddenapi_classes(unsupportedapi_file))
+
+ exclude_regexes = []
+ for exclude_file in args.excludes:
+ exclude_regexes.extend(_get_excludes(exclude_file))
+
+ with open(args.output, 'w') as outfile:
+ for jar in args.jars:
+ jar_classes = _list_jar_classes(jar)
+ jar_classes.sort()
+ for clazz in jar_classes:
+ if (_get_toplevel_class(clazz) not in excluded_classes and
+ not any(r.fullmatch(clazz) for r in exclude_regexes)):
+ outfile.write(f'rule {clazz} {args.prefix}.@0\n')
+ # Also include jarjar rules for unit tests of the class, so the package matches
+ outfile.write(f'rule {clazz}Test {args.prefix}.@0\n')
+ outfile.write(f'rule {clazz}Test$* {args.prefix}.@0\n')
+
+
+def _main():
+ # Pass in None to use argv
+ args = parse_arguments(None)
+ make_jarjar_rules(args)
+
+
+if __name__ == '__main__':
+ _main()
diff --git a/tools/gen_jarjar_test.py b/tools/gen_jarjar_test.py
new file mode 100644
index 0000000..8d8e82b
--- /dev/null
+++ b/tools/gen_jarjar_test.py
@@ -0,0 +1,57 @@
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# 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.
+
+import gen_jarjar
+import unittest
+
+
+class TestGenJarjar(unittest.TestCase):
+ def test_gen_rules(self):
+ args = gen_jarjar.parse_arguments([
+ "--jars", "jarjar-rules-generator-testjavalib.jar",
+ "--prefix", "jarjar.prefix",
+ "--output", "test-output-rules.txt",
+ "--apistubs", "framework-connectivity.stubs.module_lib.jar",
+ "--unsupportedapi", "testdata/test-unsupportedappusage.txt",
+ "--excludes", "testdata/test-jarjar-excludes.txt",
+ ])
+ gen_jarjar.make_jarjar_rules(args)
+
+ with open(args.output) as out:
+ lines = out.readlines()
+
+ self.assertListEqual([
+ 'rule test.utils.TestUtilClass jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClassTest jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClassTest$* jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClass$TestInnerClass jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClass$TestInnerClassTest jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClass$TestInnerClassTest$* jarjar.prefix.@0\n'], lines)
+
+
+if __name__ == '__main__':
+ # Need verbosity=2 for the test results parser to find results
+ unittest.main(verbosity=2)
diff --git a/tools/testdata/java/android/net/LinkProperties.java b/tools/testdata/java/android/net/LinkProperties.java
new file mode 100644
index 0000000..bdca377
--- /dev/null
+++ b/tools/testdata/java/android/net/LinkProperties.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Test class with a name matching a public API.
+ */
+public class LinkProperties {
+}
diff --git a/tools/testdata/java/test/jarjarexcluded/JarjarExcludedClass.java b/tools/testdata/java/test/jarjarexcluded/JarjarExcludedClass.java
new file mode 100644
index 0000000..7e3bee1
--- /dev/null
+++ b/tools/testdata/java/test/jarjarexcluded/JarjarExcludedClass.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package test.jarjarexcluded;
+
+/**
+ * Test class that is excluded from jarjar.
+ */
+public class JarjarExcludedClass {
+}
diff --git a/tools/testdata/java/test/unsupportedappusage/TestUnsupportedAppUsageClass.java b/tools/testdata/java/test/unsupportedappusage/TestUnsupportedAppUsageClass.java
new file mode 100644
index 0000000..9d32296
--- /dev/null
+++ b/tools/testdata/java/test/unsupportedappusage/TestUnsupportedAppUsageClass.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package test.unsupportedappusage;
+
+public class TestUnsupportedAppUsageClass {
+ public void testMethod() {}
+}
diff --git a/tools/testdata/java/test/utils/TestUtilClass.java b/tools/testdata/java/test/utils/TestUtilClass.java
new file mode 100644
index 0000000..2162e45
--- /dev/null
+++ b/tools/testdata/java/test/utils/TestUtilClass.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package test.utils;
+
+/**
+ * Sample class to test jarjar rules.
+ */
+public class TestUtilClass {
+ public static class TestInnerClass {}
+}
diff --git a/tools/testdata/test-jarjar-excludes.txt b/tools/testdata/test-jarjar-excludes.txt
new file mode 100644
index 0000000..35d97a2
--- /dev/null
+++ b/tools/testdata/test-jarjar-excludes.txt
@@ -0,0 +1,3 @@
+# Test file for excluded classes
+test\.jarj.rexcluded\.JarjarExcludedCla.s
+test\.jarjarexcluded\.JarjarExcludedClass\$TestInnerCl.ss
diff --git a/tools/testdata/test-unsupportedappusage.txt b/tools/testdata/test-unsupportedappusage.txt
new file mode 100644
index 0000000..331eff9
--- /dev/null
+++ b/tools/testdata/test-unsupportedappusage.txt
@@ -0,0 +1 @@
+Ltest/unsupportedappusage/TestUnsupportedAppUsageClass;->testMethod()V
\ No newline at end of file