Merge "Add jarjar rules for service-nearby." into tm-dev
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index e73b7d5..3699f7a 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -59,6 +59,7 @@
import android.os.HandlerThread;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.VintfRuntimeInfo;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
@@ -84,6 +85,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DeviceInfoUtils;
import com.android.testutils.DumpTestUtils;
import com.android.testutils.HandlerUtils;
import com.android.testutils.TapPacketReader;
@@ -1058,19 +1060,33 @@
}
@Test
- @IgnoreAfter(Build.VERSION_CODES.Q)
- public void testTetherUdpV4WithoutBpf() throws Exception {
+ @IgnoreAfter(Build.VERSION_CODES.R)
+ public void testTetherUdpV4UpToR() throws Exception {
initializeTethering();
runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
false /* usingBpf */);
}
+ private static boolean isUdpOffloadSupportedByKernel() {
+ final String kVersionString = VintfRuntimeInfo.getKernelVersion();
+ // Kernel version which is older than 4.14 doesn't support UDP offload absolutely. Kernel
+ // version which is between 4.14 and 5.8 support UDP offload probably. Simply apply kernel
+ // 4.14 to be threshold first and monitor on what devices tests fail for improving the
+ // offload support checking.
+ return DeviceInfoUtils.compareMajorMinorVersion(kVersionString, "4.14") >= 0;
+ }
+
@Test
@IgnoreUpTo(Build.VERSION_CODES.R)
- public void testTetherUdpV4WithBpf() throws Exception {
+ public void testTetherUdpV4AfterR() throws Exception {
initializeTethering();
+ boolean usingBpf = isUdpOffloadSupportedByKernel();
+ if (!usingBpf) {
+ Log.i(TAG, "testTetherUdpV4AfterR will skip BPF offload test for kernel "
+ + VintfRuntimeInfo.getKernelVersion());
+ }
runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader),
- true /* usingBpf */);
+ usingBpf);
}
@Nullable
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index 92a774c..896bc09 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -355,88 +355,10 @@
DEFINE_BPF_MAP_GRW(tether_upstream4_map, HASH, Tether4Key, Tether4Value, 1024, AID_NETWORK_STACK)
-static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool is_ethernet,
- const bool downstream, const bool updatetime) {
- // Require ethernet dst mac address to be our unicast address.
- if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
-
- // Must be meta-ethernet IPv4 frame
- if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
-
- const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
-
- // Since the program never writes via DPA (direct packet access) auto-pull/unclone logic does
- // not trigger and thus we need to manually make sure we can read packet headers via DPA.
- // Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter.
- // It has to be done early cause it will invalidate any skb->data/data_end derived pointers.
- try_make_writable(skb, l2_header_size + IP4_HLEN + TCP_HLEN);
-
- void* data = (void*)(long)skb->data;
- const void* data_end = (void*)(long)skb->data_end;
- struct ethhdr* eth = is_ethernet ? data : NULL; // used iff is_ethernet
- struct iphdr* ip = is_ethernet ? (void*)(eth + 1) : data;
-
- // Must have (ethernet and) ipv4 header
- if (data + l2_header_size + sizeof(*ip) > data_end) return TC_ACT_PIPE;
-
- // Ethertype - if present - must be IPv4
- if (is_ethernet && (eth->h_proto != htons(ETH_P_IP))) return TC_ACT_PIPE;
-
- // IP version must be 4
- if (ip->version != 4) TC_PUNT(INVALID_IP_VERSION);
-
- // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
- if (ip->ihl != 5) TC_PUNT(HAS_IP_OPTIONS);
-
- // Calculate the IPv4 one's complement checksum of the IPv4 header.
- __wsum sum4 = 0;
- for (int i = 0; i < sizeof(*ip) / sizeof(__u16); ++i) {
- sum4 += ((__u16*)ip)[i];
- }
- // Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
- sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse u32 into range 1 .. 0x1FFFE
- sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse any potential carry into u16
- // for a correct checksum we should get *a* zero, but sum4 must be positive, ie 0xFFFF
- if (sum4 != 0xFFFF) TC_PUNT(CHECKSUM);
-
- // Minimum IPv4 total length is the size of the header
- if (ntohs(ip->tot_len) < sizeof(*ip)) TC_PUNT(TRUNCATED_IPV4);
-
- // We are incapable of dealing with IPv4 fragments
- if (ip->frag_off & ~htons(IP_DF)) TC_PUNT(IS_IP_FRAG);
-
- // Cannot decrement during forward if already zero or would be zero,
- // Let the kernel's stack handle these cases and generate appropriate ICMP errors.
- if (ip->ttl <= 1) TC_PUNT(LOW_TTL);
-
- // If we cannot update the 'last_used' field due to lack of bpf_ktime_get_boot_ns() helper,
- // then it is not safe to offload UDP due to the small conntrack timeouts, as such,
- // in such a situation we can only support TCP. This also has the added nice benefit of
- // using a separate error counter, and thus making it obvious which version of the program
- // is loaded.
- if (!updatetime && ip->protocol != IPPROTO_TCP) TC_PUNT(NON_TCP);
-
- // We do not support offloading anything besides IPv4 TCP and UDP, due to need for NAT,
- // but no need to check this if !updatetime due to check immediately above.
- if (updatetime && (ip->protocol != IPPROTO_TCP) && (ip->protocol != IPPROTO_UDP))
- TC_PUNT(NON_TCP_UDP);
-
- // We want to make sure that the compiler will, in the !updatetime case, entirely optimize
- // out all the non-tcp logic. Also note that at this point is_udp === !is_tcp.
- const bool is_tcp = !updatetime || (ip->protocol == IPPROTO_TCP);
-
- // This is a bit of a hack to make things easier on the bpf verifier.
- // (In particular I believe the Linux 4.14 kernel's verifier can get confused later on about
- // what offsets into the packet are valid and can spuriously reject the program, this is
- // because it fails to realize that is_tcp && !is_tcp is impossible)
- //
- // For both TCP & UDP we'll need to read and modify the src/dst ports, which so happen to
- // always be in the first 4 bytes of the L4 header. Additionally for UDP we'll need access
- // to the checksum field which is in bytes 7 and 8. While for TCP we'll need to read the
- // TCP flags (at offset 13) and access to the checksum field (2 bytes at offset 16).
- // As such we *always* need access to at least 8 bytes.
- if (data + l2_header_size + sizeof(*ip) + 8 > data_end) TC_PUNT(SHORT_L4_HEADER);
-
+static inline __always_inline int do_forward4_bottom(struct __sk_buff* skb,
+ const int l2_header_size, void* data, const void* data_end,
+ struct ethhdr* eth, struct iphdr* ip, const bool is_ethernet,
+ const bool downstream, const bool updatetime, const bool is_tcp) {
struct tcphdr* tcph = is_tcp ? (void*)(ip + 1) : NULL;
struct udphdr* udph = is_tcp ? NULL : (void*)(ip + 1);
@@ -625,6 +547,102 @@
return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
}
+static inline __always_inline int do_forward4(struct __sk_buff* skb, const bool is_ethernet,
+ const bool downstream, const bool updatetime) {
+ // Require ethernet dst mac address to be our unicast address.
+ if (is_ethernet && (skb->pkt_type != PACKET_HOST)) return TC_ACT_PIPE;
+
+ // Must be meta-ethernet IPv4 frame
+ if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
+
+ const int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
+
+ // Since the program never writes via DPA (direct packet access) auto-pull/unclone logic does
+ // not trigger and thus we need to manually make sure we can read packet headers via DPA.
+ // Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter.
+ // It has to be done early cause it will invalidate any skb->data/data_end derived pointers.
+ try_make_writable(skb, l2_header_size + IP4_HLEN + TCP_HLEN);
+
+ void* data = (void*)(long)skb->data;
+ const void* data_end = (void*)(long)skb->data_end;
+ struct ethhdr* eth = is_ethernet ? data : NULL; // used iff is_ethernet
+ struct iphdr* ip = is_ethernet ? (void*)(eth + 1) : data;
+
+ // Must have (ethernet and) ipv4 header
+ if (data + l2_header_size + sizeof(*ip) > data_end) return TC_ACT_PIPE;
+
+ // Ethertype - if present - must be IPv4
+ if (is_ethernet && (eth->h_proto != htons(ETH_P_IP))) return TC_ACT_PIPE;
+
+ // IP version must be 4
+ if (ip->version != 4) TC_PUNT(INVALID_IP_VERSION);
+
+ // We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
+ if (ip->ihl != 5) TC_PUNT(HAS_IP_OPTIONS);
+
+ // Calculate the IPv4 one's complement checksum of the IPv4 header.
+ __wsum sum4 = 0;
+ for (int i = 0; i < sizeof(*ip) / sizeof(__u16); ++i) {
+ sum4 += ((__u16*)ip)[i];
+ }
+ // Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse u32 into range 1 .. 0x1FFFE
+ sum4 = (sum4 & 0xFFFF) + (sum4 >> 16); // collapse any potential carry into u16
+ // for a correct checksum we should get *a* zero, but sum4 must be positive, ie 0xFFFF
+ if (sum4 != 0xFFFF) TC_PUNT(CHECKSUM);
+
+ // Minimum IPv4 total length is the size of the header
+ if (ntohs(ip->tot_len) < sizeof(*ip)) TC_PUNT(TRUNCATED_IPV4);
+
+ // We are incapable of dealing with IPv4 fragments
+ if (ip->frag_off & ~htons(IP_DF)) TC_PUNT(IS_IP_FRAG);
+
+ // Cannot decrement during forward if already zero or would be zero,
+ // Let the kernel's stack handle these cases and generate appropriate ICMP errors.
+ if (ip->ttl <= 1) TC_PUNT(LOW_TTL);
+
+ // If we cannot update the 'last_used' field due to lack of bpf_ktime_get_boot_ns() helper,
+ // then it is not safe to offload UDP due to the small conntrack timeouts, as such,
+ // in such a situation we can only support TCP. This also has the added nice benefit of
+ // using a separate error counter, and thus making it obvious which version of the program
+ // is loaded.
+ if (!updatetime && ip->protocol != IPPROTO_TCP) TC_PUNT(NON_TCP);
+
+ // We do not support offloading anything besides IPv4 TCP and UDP, due to need for NAT,
+ // but no need to check this if !updatetime due to check immediately above.
+ if (updatetime && (ip->protocol != IPPROTO_TCP) && (ip->protocol != IPPROTO_UDP))
+ TC_PUNT(NON_TCP_UDP);
+
+ // We want to make sure that the compiler will, in the !updatetime case, entirely optimize
+ // out all the non-tcp logic. Also note that at this point is_udp === !is_tcp.
+ const bool is_tcp = !updatetime || (ip->protocol == IPPROTO_TCP);
+
+ // This is a bit of a hack to make things easier on the bpf verifier.
+ // (In particular I believe the Linux 4.14 kernel's verifier can get confused later on about
+ // what offsets into the packet are valid and can spuriously reject the program, this is
+ // because it fails to realize that is_tcp && !is_tcp is impossible)
+ //
+ // For both TCP & UDP we'll need to read and modify the src/dst ports, which so happen to
+ // always be in the first 4 bytes of the L4 header. Additionally for UDP we'll need access
+ // to the checksum field which is in bytes 7 and 8. While for TCP we'll need to read the
+ // TCP flags (at offset 13) and access to the checksum field (2 bytes at offset 16).
+ // As such we *always* need access to at least 8 bytes.
+ if (data + l2_header_size + sizeof(*ip) + 8 > data_end) TC_PUNT(SHORT_L4_HEADER);
+
+ // We're forcing the compiler to emit two copies of the following code, optimized
+ // separately for is_tcp being true or false. This simplifies the resulting bpf
+ // byte code sufficiently that the 4.14 bpf verifier is able to keep track of things.
+ // Without this (updatetime == true) case would fail to bpf verify on 4.14 even
+ // if the underlying requisite kernel support (bpf_ktime_get_boot_ns) was backported.
+ if (is_tcp) {
+ return do_forward4_bottom(skb, l2_header_size, data, data_end, eth, ip,
+ is_ethernet, downstream, updatetime, /* is_tcp */ true);
+ } else {
+ return do_forward4_bottom(skb, l2_header_size, data, data_end, eth, ip,
+ is_ethernet, downstream, updatetime, /* is_tcp */ false);
+ }
+}
+
// Full featured (required) implementations for 5.8+ kernels (these are S+ by definition)
DEFINE_BPF_PROG_KVER("schedcls/tether_downstream4_rawip$5_8", AID_ROOT, AID_NETWORK_STACK,
diff --git a/framework/src/android/net/LinkProperties.java b/framework/src/android/net/LinkProperties.java
index 8782b33..4ce2593 100644
--- a/framework/src/android/net/LinkProperties.java
+++ b/framework/src/android/net/LinkProperties.java
@@ -1366,6 +1366,21 @@
}
/**
+ * Returns true if this link has a throw route.
+ *
+ * @return {@code true} if there is an exclude route, {@code false} otherwise.
+ * @hide
+ */
+ public boolean hasExcludeRoute() {
+ for (RouteInfo r : mRoutes) {
+ if (r.getType() == RouteInfo.RTN_THROW) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Compares this {@code LinkProperties} interface name against the target
*
* @param target LinkProperties to compare.
diff --git a/framework/src/android/net/ProfileNetworkPreference.java b/framework/src/android/net/ProfileNetworkPreference.java
index fb271e3..fdcab02 100644
--- a/framework/src/android/net/ProfileNetworkPreference.java
+++ b/framework/src/android/net/ProfileNetworkPreference.java
@@ -120,8 +120,8 @@
public String toString() {
return "ProfileNetworkPreference{"
+ "mPreference=" + getPreference()
- + "mIncludedUids=" + mIncludedUids.toString()
- + "mExcludedUids=" + mExcludedUids.toString()
+ + "mIncludedUids=" + Arrays.toString(mIncludedUids)
+ + "mExcludedUids=" + Arrays.toString(mExcludedUids)
+ "mPreferenceEnterpriseId=" + mPreferenceEnterpriseId
+ '}';
}
diff --git a/nearby/TEST_MAPPING b/nearby/TEST_MAPPING
index dbaca33..d68bcc9 100644
--- a/nearby/TEST_MAPPING
+++ b/nearby/TEST_MAPPING
@@ -8,6 +8,9 @@
},
{
"name": "NearbyIntegrationUntrustedTests"
+ },
+ {
+ "name": "NearbyIntegrationUiTests"
}
],
"postsubmit": [
diff --git a/nearby/tests/integration/ui/Android.bp b/nearby/tests/integration/ui/Android.bp
new file mode 100644
index 0000000..524c838
--- /dev/null
+++ b/nearby/tests/integration/ui/Android.bp
@@ -0,0 +1,40 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "NearbyIntegrationUiTests",
+ defaults: ["mts-target-sdk-version-current"],
+ sdk_version: "test_current",
+ static_libs: ["NearbyIntegrationUiTestsLib"],
+ test_suites: ["device-tests"],
+}
+
+android_library {
+ name: "NearbyIntegrationUiTestsLib",
+ srcs: ["src/**/*.kt"],
+ sdk_version: "test_current",
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "androidx.test.uiautomator_uiautomator",
+ "junit",
+ "platform-test-rules",
+ "service-nearby-pre-jarjar",
+ "truth-prebuilt",
+ ],
+}
diff --git a/nearby/tests/integration/ui/AndroidManifest.xml b/nearby/tests/integration/ui/AndroidManifest.xml
new file mode 100644
index 0000000..9aea0c1
--- /dev/null
+++ b/nearby/tests/integration/ui/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.nearby.integration.ui">
+
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.nearby.integration.ui"
+ android:label="Nearby Mainline Module Integration UI Tests" />
+
+</manifest>
diff --git a/nearby/tests/integration/ui/AndroidTest.xml b/nearby/tests/integration/ui/AndroidTest.xml
new file mode 100644
index 0000000..9dfcf7b
--- /dev/null
+++ b/nearby/tests/integration/ui/AndroidTest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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="Runs Nearby Mainline Module Integration UI Tests">
+ <!-- Needed for pulling the screen record files. -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="NearbyIntegrationUiTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="NearbyIntegrationUiTests" />
+ <option name="config-descriptor:metadata" key="mainline-param"
+ value="com.google.android.tethering.next.apex" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.nearby.integration.ui" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ <!-- test-timeout unit is ms, value = 5 min -->
+ <option name="test-timeout" value="300000" />
+ </test>
+
+ <!-- Only run NearbyIntegrationUiTests in MTS if the Nearby Mainline module is installed. -->
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.tethering" />
+ </object>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/user/0/android.nearby.integration.ui/files" />
+ <option name="collect-on-run-ended-only" value="true" />
+ </metrics_collector>
+</configuration>
diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/BaseUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/BaseUiTest.kt
new file mode 100644
index 0000000..658775b
--- /dev/null
+++ b/nearby/tests/integration/ui/src/android/nearby/integration/ui/BaseUiTest.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.integration.ui
+
+import android.platform.test.rule.ArtifactSaver
+import android.platform.test.rule.ScreenRecordRule
+import android.platform.test.rule.TestWatcher
+import org.junit.Rule
+import org.junit.rules.TestRule
+import org.junit.rules.Timeout
+import org.junit.runner.Description
+
+abstract class BaseUiTest {
+ @get:Rule
+ var mGlobalTimeout: Timeout = Timeout.seconds(100) // Test times out in 1.67 minutes
+
+ @get:Rule
+ val mTestWatcherRule: TestRule = object : TestWatcher() {
+ override fun failed(throwable: Throwable?, description: Description?) {
+ super.failed(throwable, description)
+ ArtifactSaver.onError(description, throwable)
+ }
+ }
+
+ @get:Rule
+ val mScreenRecordRule: TestRule = ScreenRecordRule()
+}
\ No newline at end of file
diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/CheckNearbyHalfSheetUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/CheckNearbyHalfSheetUiTest.kt
new file mode 100644
index 0000000..5a3538e
--- /dev/null
+++ b/nearby/tests/integration/ui/src/android/nearby/integration/ui/CheckNearbyHalfSheetUiTest.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.integration.ui
+
+import android.content.Context
+import android.os.Bundle
+import android.platform.test.rule.ScreenRecordRule.ScreenRecord
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.nearby.common.eventloop.EventLoop
+import com.android.server.nearby.common.locator.Locator
+import com.android.server.nearby.common.locator.LocatorContextWrapper
+import com.android.server.nearby.fastpair.FastPairController
+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.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.AfterClass
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import service.proto.Cache
+import service.proto.FastPairString.FastPairStrings
+import java.time.Clock
+
+/** An instrumented test to check Nearby half sheet UI showed correctly.
+ *
+ * To run this test directly:
+ * am instrument -w -r \
+ * -e class android.nearby.integration.ui.CheckNearbyHalfSheetUiTest \
+ * android.nearby.integration.ui/androidx.test.runner.AndroidJUnitRunner
+ */
+@RunWith(AndroidJUnit4::class)
+class CheckNearbyHalfSheetUiTest : BaseUiTest() {
+ private var waitHalfSheetPopupTimeoutMs: Long
+ private var halfSheetTitleText: String
+ private var halfSheetSubtitleText: String
+
+ init {
+ val arguments: Bundle = InstrumentationRegistry.getArguments()
+ waitHalfSheetPopupTimeoutMs = arguments.getLong(
+ WAIT_HALF_SHEET_POPUP_TIMEOUT_KEY,
+ DEFAULT_WAIT_HALF_SHEET_POPUP_TIMEOUT_MS
+ )
+ halfSheetTitleText =
+ arguments.getString(HALF_SHEET_TITLE_KEY, DEFAULT_HALF_SHEET_TITLE_TEXT)
+ halfSheetSubtitleText =
+ arguments.getString(HALF_SHEET_SUBTITLE_KEY, DEFAULT_HALF_SHEET_SUBTITLE_TEXT)
+
+ device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ }
+
+ /** For multidevice test snippet only. Force overwrites the test arguments. */
+ fun updateTestArguments(
+ waitHalfSheetPopupTimeoutSeconds: Int,
+ halfSheetTitleText: String,
+ halfSheetSubtitleText: String
+ ) {
+ this.waitHalfSheetPopupTimeoutMs = waitHalfSheetPopupTimeoutSeconds * 1000L
+ this.halfSheetTitleText = halfSheetTitleText
+ this.halfSheetSubtitleText = halfSheetSubtitleText
+ }
+
+ @Before
+ fun setUp() {
+ val appContext = ApplicationProvider.getApplicationContext<Context>()
+ val locator = Locator(appContext).apply {
+ overrideBindingForTest(EventLoop::class.java, EventLoop.newInstance("test"))
+ overrideBindingForTest(
+ FastPairCacheManager::class.java,
+ FastPairCacheManager(appContext)
+ )
+ overrideBindingForTest(FootprintsDeviceManager::class.java, FootprintsDeviceManager())
+ overrideBindingForTest(Clock::class.java, Clock.systemDefaultZone())
+ }
+ val locatorContextWrapper = LocatorContextWrapper(appContext, locator)
+ locator.overrideBindingForTest(
+ FastPairController::class.java,
+ FastPairController(locatorContextWrapper)
+ )
+ val scanFastPairStoreItem = Cache.ScanFastPairStoreItem.newBuilder()
+ .setDeviceName(DEFAULT_HALF_SHEET_TITLE_TEXT)
+ .setFastPairStrings(
+ FastPairStrings.newBuilder()
+ .setInitialPairingDescription(DEFAULT_HALF_SHEET_SUBTITLE_TEXT).build()
+ )
+ .build()
+ FastPairHalfSheetManager(locatorContextWrapper).showHalfSheet(scanFastPairStoreItem)
+ }
+
+ @Test
+ @ScreenRecord
+ fun checkNearbyHalfSheetUi() {
+ // Check Nearby half sheet showed by checking button "Connect" on the DevicePairingFragment.
+ val isConnectButtonShowed = device.wait(
+ Until.hasObject(NearbyHalfSheetUiMap.DevicePairingFragment.connectButton),
+ waitHalfSheetPopupTimeoutMs
+ )
+ assertWithMessage("Nearby half sheet didn't show within $waitHalfSheetPopupTimeoutMs ms.")
+ .that(isConnectButtonShowed).isTrue()
+
+ val halfSheetTitle =
+ device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.halfSheetTitle)
+ assertThat(halfSheetTitle).isNotNull()
+ assertThat(halfSheetTitle.text).isEqualTo(halfSheetTitleText)
+
+ val halfSheetSubtitle =
+ device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.halfSheetSubtitle)
+ assertThat(halfSheetSubtitle).isNotNull()
+ assertThat(halfSheetSubtitle.text).isEqualTo(halfSheetSubtitleText)
+
+ val deviceImage = device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.deviceImage)
+ assertThat(deviceImage).isNotNull()
+
+ val infoButton = device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.infoButton)
+ assertThat(infoButton).isNotNull()
+ }
+
+ companion object {
+ private const val DEFAULT_WAIT_HALF_SHEET_POPUP_TIMEOUT_MS = 30 * 1000L
+ private const val DEFAULT_HALF_SHEET_TITLE_TEXT = "Fast Pair Provider Simulator"
+ private const val DEFAULT_HALF_SHEET_SUBTITLE_TEXT = "Fast Pair Provider Simulator will " +
+ "appear on devices linked with nearby-mainline-fpseeker@google.com"
+ private const val WAIT_HALF_SHEET_POPUP_TIMEOUT_KEY = "WAIT_HALF_SHEET_POPUP_TIMEOUT_MS"
+ private const val HALF_SHEET_TITLE_KEY = "HALF_SHEET_TITLE"
+ private const val HALF_SHEET_SUBTITLE_KEY = "HALF_SHEET_SUBTITLE"
+ private lateinit var device: UiDevice
+
+ @AfterClass
+ @JvmStatic
+ fun teardownClass() {
+ // Cleans up after saving screenshot in TestWatcher, leaves nothing dirty behind.
+ DismissNearbyHalfSheetUiTest().dismissHalfSheet()
+ }
+ }
+}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/DismissNearbyHalfSheetUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/DismissNearbyHalfSheetUiTest.kt
similarity index 81%
rename from nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/DismissNearbyHalfSheetUiTest.kt
rename to nearby/tests/integration/ui/src/android/nearby/integration/ui/DismissNearbyHalfSheetUiTest.kt
index 1d99d26..52d202a 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/DismissNearbyHalfSheetUiTest.kt
+++ b/nearby/tests/integration/ui/src/android/nearby/integration/ui/DismissNearbyHalfSheetUiTest.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package android.nearby.multidevices.fastpair.seeker.ui
+package android.nearby.integration.ui
+import android.platform.test.rule.ScreenRecordRule.ScreenRecord
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
@@ -27,14 +28,15 @@
*
* To run this test directly:
* am instrument -w -r \
- * -e class android.nearby.multidevices.fastpair.seeker.ui.DismissNearbyHalfSheetUiTest \
- * android.nearby.multidevices/androidx.test.runner.AndroidJUnitRunner
+ * -e class android.nearby.integration.ui.DismissNearbyHalfSheetUiTest \
+ * android.nearby.integration.ui/androidx.test.runner.AndroidJUnitRunner
*/
@RunWith(AndroidJUnit4::class)
-class DismissNearbyHalfSheetUiTest {
+class DismissNearbyHalfSheetUiTest : BaseUiTest() {
private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
@Test
+ @ScreenRecord
fun dismissHalfSheet() {
device.pressHome()
device.waitForIdle()
diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt
new file mode 100644
index 0000000..8b19d5c
--- /dev/null
+++ b/nearby/tests/integration/ui/src/android/nearby/integration/ui/NearbyHalfSheetUiMap.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.integration.ui
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager.MATCH_SYSTEM_ONLY
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import android.util.Log
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import com.android.server.nearby.fastpair.FastPairManager
+import com.android.server.nearby.util.Environment
+import com.google.common.truth.Truth.assertThat
+
+/** UiMap for Nearby Mainline Half Sheet. */
+object NearbyHalfSheetUiMap {
+ private val PACKAGE_NAME: String = getHalfSheetApkPkgName()
+ private const val ANDROID_WIDGET_BUTTON = "android.widget.Button"
+ private const val ANDROID_WIDGET_IMAGE_VIEW = "android.widget.ImageView"
+ private const val ANDROID_WIDGET_TEXT_VIEW = "android.widget.TextView"
+
+ object DevicePairingFragment {
+ val halfSheetTitle: BySelector =
+ By.res(PACKAGE_NAME, "toolbar_title").clazz(ANDROID_WIDGET_TEXT_VIEW)
+ val halfSheetSubtitle: BySelector =
+ By.res(PACKAGE_NAME, "header_subtitle").clazz(ANDROID_WIDGET_TEXT_VIEW)
+ val deviceImage: BySelector =
+ By.res(PACKAGE_NAME, "pairing_pic").clazz(ANDROID_WIDGET_IMAGE_VIEW)
+ val connectButton: BySelector =
+ By.res(PACKAGE_NAME, "connect_btn").clazz(ANDROID_WIDGET_BUTTON).text("Connect")
+ val infoButton: BySelector =
+ By.res(PACKAGE_NAME, "info_icon").clazz(ANDROID_WIDGET_IMAGE_VIEW)
+ }
+
+ // Vendors might override HalfSheetUX in their vendor partition, query the package name
+ // instead of hard coding. ex: Google overrides it in vendor/google/modules/TetheringGoogle.
+ fun getHalfSheetApkPkgName(): String {
+ val appContext = ApplicationProvider.getApplicationContext<Context>()
+ val resolveInfos: MutableList<ResolveInfo> =
+ appContext.packageManager.queryIntentActivities(
+ Intent(FastPairManager.ACTION_RESOURCES_APK),
+ ResolveInfoFlags.of(MATCH_SYSTEM_ONLY.toLong())
+ )
+
+ // remove apps that don't live in the nearby apex
+ resolveInfos.removeIf { !Environment.isAppInNearbyApex(it.activityInfo.applicationInfo) }
+
+ assertThat(resolveInfos).hasSize(1)
+
+ val halfSheetApkPkgName: String = resolveInfos[0].activityInfo.applicationInfo.packageName
+ Log.i("NearbyHalfSheetUiMap", "Found half-sheet APK at: $halfSheetApkPkgName")
+ return halfSheetApkPkgName
+ }
+}
\ No newline at end of file
diff --git a/nearby/tests/integration/ui/src/android/nearby/integration/ui/PairByNearbyHalfSheetUiTest.kt b/nearby/tests/integration/ui/src/android/nearby/integration/ui/PairByNearbyHalfSheetUiTest.kt
new file mode 100644
index 0000000..27264b51
--- /dev/null
+++ b/nearby/tests/integration/ui/src/android/nearby/integration/ui/PairByNearbyHalfSheetUiTest.kt
@@ -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 android.nearby.integration.ui
+
+import android.platform.test.rule.ScreenRecordRule.ScreenRecord
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import org.junit.AfterClass
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** An instrumented test to start pairing by interacting with Nearby half sheet UI.
+ *
+ * To run this test directly:
+ * am instrument -w -r \
+ * -e class android.nearby.integration.ui.PairByNearbyHalfSheetUiTest \
+ * android.nearby.integration.ui/androidx.test.runner.AndroidJUnitRunner
+ */
+@RunWith(AndroidJUnit4::class)
+class PairByNearbyHalfSheetUiTest : BaseUiTest() {
+ init {
+ device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ }
+
+ @Before
+ fun setUp() {
+ CheckNearbyHalfSheetUiTest().apply {
+ setUp()
+ checkNearbyHalfSheetUi()
+ }
+ }
+
+ @Test
+ @ScreenRecord
+ fun clickConnectButton() {
+ val connectButton = NearbyHalfSheetUiMap.DevicePairingFragment.connectButton
+ device.findObject(connectButton).click()
+ device.wait(Until.gone(connectButton), CONNECT_BUTTON_TIMEOUT_MILLS)
+ }
+
+ companion object {
+ private const val CONNECT_BUTTON_TIMEOUT_MILLS = 3000L
+ private lateinit var device: UiDevice
+
+ @AfterClass
+ @JvmStatic
+ fun teardownClass() {
+ // Cleans up after saving screenshot in TestWatcher, leaves nothing dirty behind.
+ device.pressBack()
+ DismissNearbyHalfSheetUiTest().dismissHalfSheet()
+ }
+ }
+}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/Android.bp b/nearby/tests/multidevices/clients/Android.bp
index 49bc2e9..b1bf9a7 100644
--- a/nearby/tests/multidevices/clients/Android.bp
+++ b/nearby/tests/multidevices/clients/Android.bp
@@ -24,9 +24,9 @@
"MoblySnippetHelperLib",
"NearbyFastPairProviderLib",
"NearbyFastPairSeekerSharedLib",
+ "NearbyIntegrationUiTestsLib",
"androidx.test.core",
"androidx.test.ext.junit",
- "androidx.test.uiautomator_uiautomator",
"kotlin-stdlib",
"mobly-snippet-lib",
"truth-prebuilt",
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt
index bfb7a50..a2c39f7 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt
@@ -22,12 +22,12 @@
import android.nearby.ScanCallback
import android.nearby.ScanRequest
import android.nearby.fastpair.seeker.FAKE_TEST_ACCOUNT_NAME
+import android.nearby.integration.ui.CheckNearbyHalfSheetUiTest
+import android.nearby.integration.ui.DismissNearbyHalfSheetUiTest
+import android.nearby.integration.ui.PairByNearbyHalfSheetUiTest
import android.nearby.multidevices.fastpair.seeker.data.FastPairTestDataManager
import android.nearby.multidevices.fastpair.seeker.events.PairingCallbackEvents
import android.nearby.multidevices.fastpair.seeker.events.ScanCallbackEvents
-import android.nearby.multidevices.fastpair.seeker.ui.CheckNearbyHalfSheetUiTest
-import android.nearby.multidevices.fastpair.seeker.ui.DismissNearbyHalfSheetUiTest
-import android.nearby.multidevices.fastpair.seeker.ui.PairByNearbyHalfSheetUiTest
import android.provider.Settings
import androidx.test.core.app.ApplicationProvider
import com.google.android.mobly.snippet.Snippet
@@ -86,14 +86,17 @@
val deviceName = deviceMetadata.name!!
val initialPairingDescriptionTemplateText = deviceMetadata.initialPairingDescription!!
- CheckNearbyHalfSheetUiTest(
- waitHalfSheetPopupTimeoutSeconds = timeout,
- halfSheetTitleText = deviceName,
- halfSheetSubtitleText = initialPairingDescriptionTemplateText.format(
- deviceName,
- FAKE_TEST_ACCOUNT_NAME
+ CheckNearbyHalfSheetUiTest().apply {
+ updateTestArguments(
+ waitHalfSheetPopupTimeoutSeconds = timeout,
+ halfSheetTitleText = deviceName,
+ halfSheetSubtitleText = initialPairingDescriptionTemplateText.format(
+ deviceName,
+ FAKE_TEST_ACCOUNT_NAME
+ )
)
- ).checkNearbyHalfSheetUi()
+ checkNearbyHalfSheetUi()
+ }
}
/** Puts a model id to FastPairAntispoofKeyDeviceMetadata pair into test data cache.
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/CheckNearbyHalfSheetUiTest.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/CheckNearbyHalfSheetUiTest.kt
deleted file mode 100644
index 84b5e89..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/CheckNearbyHalfSheetUiTest.kt
+++ /dev/null
@@ -1,101 +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 android.nearby.multidevices.fastpair.seeker.ui
-
-import android.os.Bundle
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.Until
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/** An instrumented test to check Nearby half sheet UI showed correctly.
- *
- * To run this test directly:
- * am instrument -w -r \
- * -e class android.nearby.multidevices.fastpair.seeker.ui.CheckNearbyHalfSheetUiTest \
- * android.nearby.multidevices/androidx.test.runner.AndroidJUnitRunner
- */
-@RunWith(AndroidJUnit4::class)
-class CheckNearbyHalfSheetUiTest {
- private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
- private val waitHalfSheetPopupTimeoutMs: Long
- private val halfSheetTitleText: String
- private val halfSheetSubtitleText: String
-
- constructor() {
- val arguments: Bundle = InstrumentationRegistry.getArguments()
- waitHalfSheetPopupTimeoutMs = arguments.getLong(
- WAIT_HALF_SHEET_POPUP_TIMEOUT_KEY,
- DEFAULT_WAIT_HALF_SHEET_POPUP_TIMEOUT_MS
- )
- halfSheetTitleText =
- arguments.getString(HALF_SHEET_TITLE_KEY, DEFAULT_HALF_SHEET_TITLE_TEXT)
- halfSheetSubtitleText =
- arguments.getString(HALF_SHEET_SUBTITLE_KEY, DEFAULT_HALF_SHEET_SUBTITLE_TEXT)
- }
-
- constructor(
- waitHalfSheetPopupTimeoutSeconds: Int,
- halfSheetTitleText: String,
- halfSheetSubtitleText: String
- ) {
- this.waitHalfSheetPopupTimeoutMs = waitHalfSheetPopupTimeoutSeconds * 1000L
- this.halfSheetTitleText = halfSheetTitleText
- this.halfSheetSubtitleText = halfSheetSubtitleText
- }
-
- @Test
- fun checkNearbyHalfSheetUi() {
- // Check Nearby half sheet showed by checking button "Connect" on the DevicePairingFragment.
- val isConnectButtonShowed = device.wait(
- Until.hasObject(NearbyHalfSheetUiMap.DevicePairingFragment.connectButton),
- waitHalfSheetPopupTimeoutMs
- )
- assertWithMessage("Nearby half sheet didn't show within $waitHalfSheetPopupTimeoutMs ms.")
- .that(isConnectButtonShowed).isTrue()
-
- val halfSheetTitle =
- device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.halfSheetTitle)
- assertThat(halfSheetTitle).isNotNull()
- assertThat(halfSheetTitle.text).isEqualTo(halfSheetTitleText)
-
- val halfSheetSubtitle =
- device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.halfSheetSubtitle)
- assertThat(halfSheetSubtitle).isNotNull()
- assertThat(halfSheetSubtitle.text).isEqualTo(halfSheetSubtitleText)
-
- val deviceImage = device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.deviceImage)
- assertThat(deviceImage).isNotNull()
-
- val infoButton = device.findObject(NearbyHalfSheetUiMap.DevicePairingFragment.infoButton)
- assertThat(infoButton).isNotNull()
- }
-
- companion object {
- private const val DEFAULT_WAIT_HALF_SHEET_POPUP_TIMEOUT_MS = 1000L
- private const val DEFAULT_HALF_SHEET_TITLE_TEXT = "Fast Pair Provider Simulator"
- private const val DEFAULT_HALF_SHEET_SUBTITLE_TEXT = "Fast Pair Provider Simulator will " +
- "appear on devices linked with nearby-mainline-fpseeker@google.com"
- private const val WAIT_HALF_SHEET_POPUP_TIMEOUT_KEY = "WAIT_HALF_SHEET_POPUP_TIMEOUT_MS"
- private const val HALF_SHEET_TITLE_KEY = "HALF_SHEET_TITLE"
- private const val HALF_SHEET_SUBTITLE_KEY = "HALF_SHEET_SUBTITLE"
- }
-}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/NearbyHalfSheetUiMap.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/NearbyHalfSheetUiMap.kt
deleted file mode 100644
index c94ff01..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/NearbyHalfSheetUiMap.kt
+++ /dev/null
@@ -1,41 +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 android.nearby.multidevices.fastpair.seeker.ui
-
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.BySelector
-
-/** UiMap for Nearby Mainline Half Sheet. */
-object NearbyHalfSheetUiMap {
- private const val PACKAGE_NAME = "com.google.android.nearby.halfsheet"
- private const val ANDROID_WIDGET_BUTTON = "android.widget.Button"
- private const val ANDROID_WIDGET_IMAGE_VIEW = "android.widget.ImageView"
- private const val ANDROID_WIDGET_TEXT_VIEW = "android.widget.TextView"
-
- object DevicePairingFragment {
- val halfSheetTitle: BySelector =
- By.res(PACKAGE_NAME, "toolbar_title").clazz(ANDROID_WIDGET_TEXT_VIEW)
- val halfSheetSubtitle: BySelector =
- By.res(PACKAGE_NAME, "header_subtitle").clazz(ANDROID_WIDGET_TEXT_VIEW)
- val deviceImage: BySelector =
- By.res(PACKAGE_NAME, "pairing_pic").clazz(ANDROID_WIDGET_IMAGE_VIEW)
- val connectButton: BySelector =
- By.res(PACKAGE_NAME, "connect_btn").clazz(ANDROID_WIDGET_BUTTON).text("Connect")
- val infoButton: BySelector =
- By.res(PACKAGE_NAME, "info_icon").clazz(ANDROID_WIDGET_IMAGE_VIEW)
- }
-}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/PairByNearbyHalfSheetUiTest.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/PairByNearbyHalfSheetUiTest.kt
deleted file mode 100644
index 9028668..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/ui/PairByNearbyHalfSheetUiTest.kt
+++ /dev/null
@@ -1,47 +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 android.nearby.multidevices.fastpair.seeker.ui
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.Until
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/** An instrumented test to start pairing by interacting with Nearby half sheet UI.
- *
- * To run this test directly:
- * am instrument -w -r \
- * -e class android.nearby.multidevices.fastpair.seeker.ui.PairByNearbyHalfSheetUiTest \
- * android.nearby.multidevices/androidx.test.runner.AndroidJUnitRunner
- */
-@RunWith(AndroidJUnit4::class)
-class PairByNearbyHalfSheetUiTest {
- private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
-
- @Test
- fun clickConnectButton() {
- val connectButton = NearbyHalfSheetUiMap.DevicePairingFragment.connectButton
- device.findObject(connectButton).click()
- device.wait(Until.gone(connectButton), CONNECT_BUTTON_TIMEOUT_MILLS)
- }
-
- companion object {
- const val CONNECT_BUTTON_TIMEOUT_MILLS = 3000L
- }
-}
\ No newline at end of file
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 6de6625..c19bb11 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -108,6 +108,7 @@
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
import android.app.usage.NetworkStatsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -8092,7 +8093,8 @@
&& nc.getOwnerUid() != Process.SYSTEM_UID
&& lp.getInterfaceName() != null
&& (lp.hasIpv4DefaultRoute() || lp.hasIpv4UnreachableDefaultRoute())
- && (lp.hasIpv6DefaultRoute() || lp.hasIpv6UnreachableDefaultRoute());
+ && (lp.hasIpv6DefaultRoute() || lp.hasIpv6UnreachableDefaultRoute())
+ && !lp.hasExcludeRoute();
}
private static UidRangeParcel[] toUidRangeStableParcels(final @NonNull Set<UidRange> ranges) {
@@ -10645,13 +10647,29 @@
mQosCallbackTracker.unregisterCallback(callback);
}
+ private boolean isNetworkPreferenceAllowedForProfile(@NonNull UserHandle profile) {
+ // UserManager.isManagedProfile returns true for all apps in managed user profiles.
+ // Enterprise device can be fully managed like device owner and such use case
+ // also should be supported. Calling app check for work profile and fully managed device
+ // is already done in DevicePolicyManager.
+ // This check is an extra caution to be sure device is fully managed or not.
+ final UserManager um = mContext.getSystemService(UserManager.class);
+ final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ if (um.isManagedProfile(profile.getIdentifier())) {
+ return true;
+ }
+ if (SdkLevel.isAtLeastT() && dpm.getDeviceOwner() != null) return true;
+ return false;
+ }
+
/**
- * Request that a user profile is put by default on a network matching a given preference.
+ * Set a list of default network selection policies for a user profile or device owner.
*
* See the documentation for the individual preferences for a description of the supported
* behaviors.
*
- * @param profile the user profile for whih the preference is being set.
+ * @param profile If the device owner is set, any profile is allowed.
+ Otherwise, the given profile can only be managed profile.
* @param preferences the list of profile network preferences for the
* provided profile.
* @param listener an optional listener to listen for completion of the operation.
@@ -10676,9 +10694,9 @@
throw new IllegalArgumentException("Must explicitly specify a user handle ("
+ "UserHandle.CURRENT not supported)");
}
- final UserManager um = mContext.getSystemService(UserManager.class);
- if (!um.isManagedProfile(profile.getIdentifier())) {
- throw new IllegalArgumentException("Profile must be a managed profile");
+ if (!isNetworkPreferenceAllowedForProfile(profile)) {
+ throw new IllegalArgumentException("Profile must be a managed profile "
+ + "or the device owner must be set. ");
}
final List<ProfileNetworkPreferenceList.Preference> preferenceList =
@@ -10821,10 +10839,20 @@
private void handleSetProfileNetworkPreference(
@NonNull final List<ProfileNetworkPreferenceList.Preference> preferenceList,
@Nullable final IOnCompleteListener listener) {
+ /*
+ * handleSetProfileNetworkPreference is always called for single user.
+ * preferenceList only contains preferences for different uids within the same user
+ * (enforced by getUidListToBeAppliedForNetworkPreference).
+ * Clear all the existing preferences for the user before applying new preferences.
+ *
+ */
+ mProfileNetworkPreferences = mProfileNetworkPreferences.clearUser(
+ preferenceList.get(0).user);
for (final ProfileNetworkPreferenceList.Preference preference : preferenceList) {
validateNetworkCapabilitiesOfProfileNetworkPreference(preference.capabilities);
mProfileNetworkPreferences = mProfileNetworkPreferences.plus(preference);
}
+
removeDefaultNetworkRequestsForPreference(PREFERENCE_ORDER_PROFILE);
addPerAppDefaultNetworkRequests(
createNrisFromProfileNetworkPreferences(mProfileNetworkPreferences));
diff --git a/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java b/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
index 71f342d..473a115 100644
--- a/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
+++ b/service/src/com/android/server/connectivity/ProfileNetworkPreferenceList.java
@@ -70,23 +70,33 @@
/**
* Returns a new object consisting of this object plus the passed preference.
*
- * If a preference already exists for the same user, it will be replaced by the passed
- * preference. Passing a Preference object containing a null capabilities object is equivalent
- * to (and indeed, implemented as) removing the preference for this user.
+ * It is not expected that unwanted preference already exists for the same user.
+ * All preferences for the user that were previously configured should be cleared before
+ * adding a new preference.
+ * Passing a Preference object containing a null capabilities object is equivalent
+ * to removing the preference for this user.
*/
public ProfileNetworkPreferenceList plus(@NonNull final Preference pref) {
- final ArrayList<Preference> newPrefs = new ArrayList<>();
- for (final Preference existingPref : preferences) {
- if (!existingPref.user.equals(pref.user)) {
- newPrefs.add(existingPref);
- }
- }
+ final ArrayList<Preference> newPrefs = new ArrayList<>(preferences);
if (null != pref.capabilities) {
newPrefs.add(pref);
}
return new ProfileNetworkPreferenceList(newPrefs);
}
+ /**
+ * Remove all preferences corresponding to a user.
+ */
+ public ProfileNetworkPreferenceList clearUser(UserHandle user) {
+ final ArrayList<Preference> newPrefs = new ArrayList<>();
+ for (final Preference existingPref : preferences) {
+ if (!existingPref.user.equals(user)) {
+ newPrefs.add(existingPref);
+ }
+ }
+ return new ProfileNetworkPreferenceList(newPrefs);
+ }
+
public boolean isEmpty() {
return preferences.isEmpty();
}
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index 8fc636a..345a78d 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -1261,6 +1261,17 @@
assertFalse(lp.hasIpv4UnreachableDefaultRoute());
}
+ @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testHasExcludeRoute() {
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("VPN");
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV4, 2), RTN_UNICAST));
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 0), RTN_UNICAST));
+ assertFalse(lp.hasExcludeRoute());
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 2), RTN_THROW));
+ assertTrue(lp.hasExcludeRoute());
+ }
+
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
@EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
public void testRouteAddWithSameKey() throws Exception {
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index 01c8cd2..edfaf9f 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -23,6 +23,7 @@
defaults: ["cts_support_defaults"],
sdk_version: "test_current",
static_libs: [
+ "androidx.annotation_annotation",
"CtsHostsideNetworkTestsAidl",
"NetworkStackApiStableShims",
],
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
index a337fe2..bffa7b0 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
@@ -28,11 +28,14 @@
import android.os.RemoteCallback;
import android.util.Log;
+import androidx.annotation.GuardedBy;
+
/**
* Activity used to bring process to foreground.
*/
public class MyActivity extends Activity {
+ @GuardedBy("this")
private BroadcastReceiver finishCommandReceiver = null;
@Override
@@ -43,8 +46,11 @@
@Override
public void finish() {
- if (finishCommandReceiver != null) {
- unregisterReceiver(finishCommandReceiver);
+ synchronized (this) {
+ if (finishCommandReceiver != null) {
+ unregisterReceiver(finishCommandReceiver);
+ finishCommandReceiver = null;
+ }
}
super.finish();
}
@@ -67,15 +73,17 @@
super.onResume();
Log.d(TAG, "MyActivity.onResume(): " + getIntent());
Common.notifyNetworkStateObserver(this, getIntent(), TYPE_COMPONENT_ACTIVTY);
- finishCommandReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.d(TAG, "Finishing MyActivity");
- MyActivity.this.finish();
- }
- };
- registerReceiver(finishCommandReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY),
- Context.RECEIVER_EXPORTED);
+ synchronized (this) {
+ finishCommandReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "Finishing MyActivity");
+ MyActivity.this.finish();
+ }
+ };
+ registerReceiver(finishCommandReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY),
+ Context.RECEIVER_EXPORTED);
+ }
final RemoteCallback callback = getIntent().getParcelableExtra(
Intent.EXTRA_REMOTE_CALLBACK);
if (callback != null) {
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 6316c72..f96732d 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -105,6 +105,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
+import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_2;
import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION;
import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS;
import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS;
@@ -195,6 +196,7 @@
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
import android.app.usage.NetworkStatsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -467,6 +469,7 @@
private static final int TEST_APP_ID_2 = 104;
private static final int TEST_WORK_PROFILE_APP_UID_2 =
UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID_2);
+ private static final int TEST_APP_ID_3 = 105;
private static final String CLAT_PREFIX = "v4-";
private static final String MOBILE_IFNAME = "test_rmnet_data0";
@@ -542,6 +545,7 @@
@Mock NetworkPolicyManager mNetworkPolicyManager;
@Mock VpnProfileStore mVpnProfileStore;
@Mock SystemConfigManager mSystemConfigManager;
+ @Mock DevicePolicyManager mDevicePolicyManager;
@Mock Resources mResources;
@Mock ClatCoordinator mClatCoordinator;
@Mock PacProxyManager mPacProxyManager;
@@ -664,6 +668,7 @@
if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
if (Context.ETHERNET_SERVICE.equals(name)) return mEthernetManager;
if (Context.NETWORK_POLICY_SERVICE.equals(name)) return mNetworkPolicyManager;
+ if (Context.DEVICE_POLICY_SERVICE.equals(name)) return mDevicePolicyManager;
if (Context.SYSTEM_CONFIG_SERVICE.equals(name)) return mSystemConfigManager;
if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager;
if (Context.BATTERY_STATS_SERVICE.equals(name)) return mBatteryStatsManager;
@@ -693,6 +698,14 @@
doReturn(value).when(mUserManager).isManagedProfile(eq(userHandle.getIdentifier()));
}
+ public void setDeviceOwner(@NonNull final UserHandle userHandle, String value) {
+ // This relies on all contexts for a given user returning the same UM mock
+ final DevicePolicyManager dpmMock = createContextAsUser(userHandle, 0 /* flags */)
+ .getSystemService(DevicePolicyManager.class);
+ doReturn(value).when(dpmMock).getDeviceOwner();
+ doReturn(value).when(mDevicePolicyManager).getDeviceOwner();
+ }
+
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
@@ -14539,7 +14552,7 @@
profileNetworkPreferenceBuilder.setPreference(
PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(
- NetworkCapabilities.NET_ENTERPRISE_ID_2);
+ NET_ENTERPRISE_ID_2);
registerDefaultNetworkCallbacks();
testPreferenceForUserNetworkUpDownForGivenPreference(
profileNetworkPreferenceBuilder.build(), true,
@@ -14564,6 +14577,146 @@
}
/**
+ * Make sure per profile network preferences behave as expected when two slices with
+ * two different apps within same user profile is configured
+ * Make sure per profile network preferences overrides with latest preference when
+ * same user preference is set twice
+ */
+ @Test
+ public void testSetPreferenceWithOverridingPreference()
+ throws Exception {
+ final InOrder inOrder = inOrder(mMockNetd);
+ final UserHandle testHandle = setupEnterpriseNetwork();
+ mServiceContext.setWorkProfile(testHandle, true);
+ registerDefaultNetworkCallbacks();
+
+ final TestNetworkCallback appCb1 = new TestNetworkCallback();
+ final TestNetworkCallback appCb2 = new TestNetworkCallback();
+ final TestNetworkCallback appCb3 = new TestNetworkCallback();
+
+ final int testWorkProfileAppUid1 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID);
+ final int testWorkProfileAppUid2 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_2);
+ final int testWorkProfileAppUid3 =
+ UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_3);
+
+ registerDefaultNetworkCallbackAsUid(appCb1, testWorkProfileAppUid1);
+ registerDefaultNetworkCallbackAsUid(appCb2, testWorkProfileAppUid2);
+ registerDefaultNetworkCallbackAsUid(appCb3, testWorkProfileAppUid3);
+
+ // Connect both a regular cell agent and an enterprise network first.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+
+ final TestNetworkAgentWrapper workAgent1 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_1);
+ final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_2);
+ workAgent1.connect(true);
+ workAgent2.connect(true);
+
+ mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb1.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb2.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ appCb3.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent1.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+ verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
+ workAgent2.getNetwork().netId, INetd.PERMISSION_SYSTEM));
+
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+
+ // Set preferences for testHandle to map testWorkProfileAppUid1 to
+ // NET_ENTERPRISE_ID_1 and testWorkProfileAppUid2 to NET_ENTERPRISE_ID_2.
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder1 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder1.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder1.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ profileNetworkPreferenceBuilder1.setIncludedUids(new int[]{testWorkProfileAppUid1});
+
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder2.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_2);
+ profileNetworkPreferenceBuilder2.setIncludedUids(new int[]{testWorkProfileAppUid2});
+
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder1.build(),
+ profileNetworkPreferenceBuilder2.build()),
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent2.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent1.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
+ PREFERENCE_ORDER_PROFILE));
+
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+ appCb1.expectAvailableCallbacksValidated(workAgent1);
+ appCb2.expectAvailableCallbacksValidated(workAgent2);
+
+ // Set preferences for testHandle to map testWorkProfileAppUid3 to
+ // to NET_ENTERPRISE_ID_1.
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder3 =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder3.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder3.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ profileNetworkPreferenceBuilder3.setIncludedUids(new int[]{testWorkProfileAppUid3});
+
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder3.build()),
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
+ workAgent1.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+ workAgent2.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
+ PREFERENCE_ORDER_PROFILE));
+ verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+ workAgent1.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
+ PREFERENCE_ORDER_PROFILE));
+
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
+ appCb3.expectAvailableCallbacksValidated(workAgent1);
+ appCb2.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ appCb1.expectAvailableCallbacksValidated(mCellNetworkAgent);
+
+ // Set the preferences for testHandle to default.
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
+
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder.build()),
+ r -> r.run(), listener);
+ listener.expectOnComplete();
+ verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
+ workAgent1.getNetwork().netId,
+ uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
+ PREFERENCE_ORDER_PROFILE));
+
+ assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, appCb1, appCb2);
+ appCb3.expectAvailableCallbacksValidated(mCellNetworkAgent);
+ workAgent2.disconnect();
+ mCellNetworkAgent.disconnect();
+
+ mCm.unregisterNetworkCallback(appCb1);
+ mCm.unregisterNetworkCallback(appCb2);
+ mCm.unregisterNetworkCallback(appCb3);
+ // Other callbacks will be unregistered by tearDown()
+ }
+
+ /**
* Test that, in a given networking context, calling setPreferenceForUser to set per-profile
* defaults on then off works as expected.
*/
@@ -14733,12 +14886,42 @@
public void testProfileNetworkPrefWrongProfile() throws Exception {
final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
mServiceContext.setWorkProfile(testHandle, false);
- assertThrows("Should not be able to set a user pref for a non-work profile",
+ mServiceContext.setDeviceOwner(testHandle, null);
+ assertThrows("Should not be able to set a user pref for a non-work profile "
+ + "and non device owner",
IllegalArgumentException.class , () ->
mCm.setProfileNetworkPreference(testHandle,
PROFILE_NETWORK_PREFERENCE_ENTERPRISE, null, null));
}
+ /**
+ * Make sure requests for per-profile default networking for a device owner is
+ * accepted on T and not accepted on S
+ */
+ @Test
+ public void testProfileNetworkDeviceOwner() throws Exception {
+ final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
+ mServiceContext.setWorkProfile(testHandle, false);
+ mServiceContext.setDeviceOwner(testHandle, "deviceOwnerPackage");
+ ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+ new ProfileNetworkPreference.Builder();
+ profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+ profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+ final TestOnCompleteListener listener = new TestOnCompleteListener();
+ if (SdkLevel.isAtLeastT()) {
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder.build()),
+ r -> r.run(), listener);
+ } else {
+ // S should not allow setting preference on device owner
+ assertThrows("Should not be able to set a user pref for a non-work profile on S",
+ IllegalArgumentException.class , () ->
+ mCm.setProfileNetworkPreferences(testHandle,
+ List.of(profileNetworkPreferenceBuilder.build()),
+ r -> r.run(), listener));
+ }
+ }
+
@Test
public void testSubIdsClearedWithoutNetworkFactoryPermission() throws Exception {
mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED);