Merge "Make a defensive copy when sending NetworkInfo change" 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-t/src/android/net/EthernetManager.java b/framework-t/src/android/net/EthernetManager.java
index 2b76dd9..886d194 100644
--- a/framework-t/src/android/net/EthernetManager.java
+++ b/framework-t/src/android/net/EthernetManager.java
@@ -32,13 +32,13 @@
import android.os.Build;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
+import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.modules.utils.BackgroundThread;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -56,37 +56,12 @@
private final IEthernetManager mService;
@GuardedBy("mListenerLock")
- private final ArrayList<ListenerInfo<InterfaceStateListener>> mIfaceListeners =
- new ArrayList<>();
+ private final ArrayMap<InterfaceStateListener, IEthernetServiceListener>
+ mIfaceServiceListeners = new ArrayMap<>();
@GuardedBy("mListenerLock")
- private final ArrayList<ListenerInfo<IntConsumer>> mEthernetStateListeners =
- new ArrayList<>();
+ private final ArrayMap<IntConsumer, IEthernetServiceListener> mStateServiceListeners =
+ new ArrayMap<>();
final Object mListenerLock = new Object();
- private final IEthernetServiceListener.Stub mServiceListener =
- new IEthernetServiceListener.Stub() {
- @Override
- public void onEthernetStateChanged(int state) {
- synchronized (mListenerLock) {
- for (ListenerInfo<IntConsumer> li : mEthernetStateListeners) {
- li.executor.execute(() -> {
- li.listener.accept(state);
- });
- }
- }
- }
-
- @Override
- public void onInterfaceStateChanged(String iface, int state, int role,
- IpConfiguration configuration) {
- synchronized (mListenerLock) {
- for (ListenerInfo<InterfaceStateListener> li : mIfaceListeners) {
- li.executor.execute(() ->
- li.listener.onInterfaceStateChanged(iface, state, role,
- configuration));
- }
- }
- }
- };
/**
* Indicates that Ethernet is disabled.
@@ -104,18 +79,6 @@
@SystemApi(client = MODULE_LIBRARIES)
public static final int ETHERNET_STATE_ENABLED = 1;
- private static class ListenerInfo<T> {
- @NonNull
- public final Executor executor;
- @NonNull
- public final T listener;
-
- private ListenerInfo(@NonNull Executor executor, @NonNull T listener) {
- this.executor = executor;
- this.listener = listener;
- }
- }
-
/**
* The interface is absent.
* @hide
@@ -323,18 +286,28 @@
if (listener == null || executor == null) {
throw new NullPointerException("listener and executor must not be null");
}
+
+ final IEthernetServiceListener.Stub serviceListener = new IEthernetServiceListener.Stub() {
+ @Override
+ public void onEthernetStateChanged(int state) {}
+
+ @Override
+ public void onInterfaceStateChanged(String iface, int state, int role,
+ IpConfiguration configuration) {
+ executor.execute(() ->
+ listener.onInterfaceStateChanged(iface, state, role, configuration));
+ }
+ };
synchronized (mListenerLock) {
- maybeAddServiceListener();
- mIfaceListeners.add(new ListenerInfo<InterfaceStateListener>(executor, listener));
+ addServiceListener(serviceListener);
+ mIfaceServiceListeners.put(listener, serviceListener);
}
}
@GuardedBy("mListenerLock")
- private void maybeAddServiceListener() {
- if (!mIfaceListeners.isEmpty() || !mEthernetStateListeners.isEmpty()) return;
-
+ private void addServiceListener(@NonNull final IEthernetServiceListener listener) {
try {
- mService.addListener(mServiceListener);
+ mService.addListener(listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -364,17 +337,16 @@
public void removeInterfaceStateListener(@NonNull InterfaceStateListener listener) {
Objects.requireNonNull(listener);
synchronized (mListenerLock) {
- mIfaceListeners.removeIf(l -> l.listener == listener);
- maybeRemoveServiceListener();
+ maybeRemoveServiceListener(mIfaceServiceListeners.remove(listener));
}
}
@GuardedBy("mListenerLock")
- private void maybeRemoveServiceListener() {
- if (!mIfaceListeners.isEmpty() || !mEthernetStateListeners.isEmpty()) return;
+ private void maybeRemoveServiceListener(@Nullable final IEthernetServiceListener listener) {
+ if (listener == null) return;
try {
- mService.removeListener(mServiceListener);
+ mService.removeListener(listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -687,9 +659,19 @@
@NonNull IntConsumer listener) {
Objects.requireNonNull(executor);
Objects.requireNonNull(listener);
+ final IEthernetServiceListener.Stub serviceListener = new IEthernetServiceListener.Stub() {
+ @Override
+ public void onEthernetStateChanged(int state) {
+ executor.execute(() -> listener.accept(state));
+ }
+
+ @Override
+ public void onInterfaceStateChanged(String iface, int state, int role,
+ IpConfiguration configuration) {}
+ };
synchronized (mListenerLock) {
- maybeAddServiceListener();
- mEthernetStateListeners.add(new ListenerInfo<IntConsumer>(executor, listener));
+ addServiceListener(serviceListener);
+ mStateServiceListeners.put(listener, serviceListener);
}
}
@@ -705,8 +687,7 @@
public void removeEthernetStateListener(@NonNull IntConsumer listener) {
Objects.requireNonNull(listener);
synchronized (mListenerLock) {
- mEthernetStateListeners.removeIf(l -> l.listener == listener);
- maybeRemoveServiceListener();
+ maybeRemoveServiceListener(mStateServiceListeners.remove(listener));
}
}
diff --git a/framework/src/android/net/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/framework/java/android/nearby/INearbyManager.aidl b/nearby/framework/java/android/nearby/INearbyManager.aidl
index 62e109e..3fd5ecc 100644
--- a/nearby/framework/java/android/nearby/INearbyManager.aidl
+++ b/nearby/framework/java/android/nearby/INearbyManager.aidl
@@ -28,12 +28,13 @@
*/
interface INearbyManager {
- int registerScanListener(in ScanRequest scanRequest, in IScanListener listener);
+ int registerScanListener(in ScanRequest scanRequest, in IScanListener listener,
+ String packageName, @nullable String attributionTag);
void unregisterScanListener(in IScanListener listener);
void startBroadcast(in BroadcastRequestParcelable broadcastRequest,
- in IBroadcastListener callback);
+ in IBroadcastListener callback, String packageName, @nullable String attributionTag);
void stopBroadcast(in IBroadcastListener callback);
}
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/IScanListener.aidl b/nearby/framework/java/android/nearby/IScanListener.aidl
index 54033aa..3e3b107 100644
--- a/nearby/framework/java/android/nearby/IScanListener.aidl
+++ b/nearby/framework/java/android/nearby/IScanListener.aidl
@@ -32,4 +32,7 @@
/** Reports a {@link NearbyDevice} is no longer within range. */
void onLost(in NearbyDeviceParcelable nearbyDeviceParcelable);
+
+ /** Reports when there is an error during scanning. */
+ void onError();
}
diff --git a/nearby/framework/java/android/nearby/NearbyFrameworkInitializer.java b/nearby/framework/java/android/nearby/NearbyFrameworkInitializer.java
index 3780fbb..b732d67 100644
--- a/nearby/framework/java/android/nearby/NearbyFrameworkInitializer.java
+++ b/nearby/framework/java/android/nearby/NearbyFrameworkInitializer.java
@@ -43,7 +43,7 @@
NearbyManager.class,
(context, serviceBinder) -> {
INearbyManager service = INearbyManager.Stub.asInterface(serviceBinder);
- return new NearbyManager(service);
+ return new NearbyManager(context, service);
}
);
}
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index 4177f6a..9073f78 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -28,6 +28,7 @@
import android.content.Context;
import android.os.RemoteException;
import android.provider.Settings;
+import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
@@ -85,6 +86,7 @@
private static final WeakHashMap<BroadcastCallback, WeakReference<BroadcastListenerTransport>>
sBroadcastListeners = new WeakHashMap<>();
+ private final Context mContext;
private final INearbyManager mService;
/**
@@ -92,7 +94,10 @@
*
* @param service the service object
*/
- NearbyManager(@NonNull INearbyManager service) {
+ NearbyManager(@NonNull Context context, @NonNull INearbyManager service) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(service);
+ mContext = context;
mService = service;
}
@@ -163,7 +168,8 @@
Preconditions.checkState(transport.isRegistered());
transport.setExecutor(executor);
}
- @ScanStatus int status = mService.registerScanListener(scanRequest, transport);
+ @ScanStatus int status = mService.registerScanListener(scanRequest, transport,
+ mContext.getPackageName(), mContext.getAttributionTag());
if (status != ScanStatus.SUCCESS) {
return status;
}
@@ -228,8 +234,8 @@
Preconditions.checkState(transport.isRegistered());
transport.setExecutor(executor);
}
- mService.startBroadcast(new BroadcastRequestParcelable(broadcastRequest),
- transport);
+ mService.startBroadcast(new BroadcastRequestParcelable(broadcastRequest), transport,
+ mContext.getPackageName(), mContext.getAttributionTag());
sBroadcastListeners.put(callback, new WeakReference<>(transport));
}
} catch (RemoteException e) {
@@ -350,6 +356,15 @@
}
});
}
+
+ @Override
+ public void onError() {
+ mExecutor.execute(() -> {
+ if (mScanCallback != null) {
+ Log.e("NearbyManager", "onError: There is an error in scan.");
+ }
+ });
+ }
}
private static class BroadcastListenerTransport extends IBroadcastListener.Stub {
diff --git a/nearby/service/Android.bp b/nearby/service/Android.bp
index 7112bb1..0c2395c 100644
--- a/nearby/service/Android.bp
+++ b/nearby/service/Android.bp
@@ -84,15 +84,14 @@
"framework-system-server-module-defaults"
],
libs: [
+ "androidx.annotation_annotation",
"framework-bluetooth.stubs.module_lib", // TODO(b/215722418): Change to framework-bluetooth once fixed
"error_prone_annotations",
"framework-connectivity-t.impl",
"framework-statsd.stubs.module_lib",
],
static_libs: [
- "androidx.annotation_annotation",
"androidx.core_core",
- "androidx.localbroadcastmanager_localbroadcastmanager",
"guava",
"libprotobuf-java-lite",
"fast-pair-lite-protos",
diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java
index e3e5b5d..2dee835 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyService.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyService.java
@@ -17,9 +17,12 @@
package com.android.server.nearby;
import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.SystemService.PHASE_THIRD_PARTY_APPS_CAN_START;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.app.AppOpsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
@@ -35,6 +38,7 @@
import android.nearby.ScanRequest;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.nearby.common.locator.LocatorContextWrapper;
import com.android.server.nearby.fastpair.FastPairManager;
import com.android.server.nearby.injector.ContextHubManagerAdapter;
@@ -43,13 +47,16 @@
import com.android.server.nearby.provider.BroadcastProviderManager;
import com.android.server.nearby.provider.DiscoveryProviderManager;
import com.android.server.nearby.provider.FastPairDataProvider;
+import com.android.server.nearby.util.identity.CallerIdentity;
+import com.android.server.nearby.util.permissions.BroadcastPermissions;
+import com.android.server.nearby.util.permissions.DiscoveryPermissions;
/** Service implementing nearby functionality. */
public class NearbyService extends INearbyManager.Stub {
public static final String TAG = "NearbyService";
private final Context mContext;
- private final SystemInjector mSystemInjector;
+ private Injector mInjector;
private final FastPairManager mFastPairManager;
private final PresenceManager mPresenceManager;
private final BroadcastReceiver mBluetoothReceiver =
@@ -60,11 +67,11 @@
intent.getIntExtra(
BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
if (state == BluetoothAdapter.STATE_ON) {
- if (mSystemInjector != null) {
+ if (mInjector != null && mInjector instanceof SystemInjector) {
// Have to do this logic in listener. Even during PHASE_BOOT_COMPLETED
// phase, BluetoothAdapter is not null, the BleScanner is null.
Log.v(TAG, "Initiating BluetoothAdapter when Bluetooth is turned on.");
- mSystemInjector.initializeBluetoothAdapter();
+ ((SystemInjector) mInjector).initializeBluetoothAdapter();
}
}
}
@@ -74,18 +81,29 @@
public NearbyService(Context context) {
mContext = context;
- mSystemInjector = new SystemInjector(context);
- mProviderManager = new DiscoveryProviderManager(context, mSystemInjector);
- mBroadcastProviderManager = new BroadcastProviderManager(context, mSystemInjector);
+ mInjector = new SystemInjector(context);
+ mProviderManager = new DiscoveryProviderManager(context, mInjector);
+ mBroadcastProviderManager = new BroadcastProviderManager(context, mInjector);
final LocatorContextWrapper lcw = new LocatorContextWrapper(context, null);
mFastPairManager = new FastPairManager(lcw);
mPresenceManager = new PresenceManager(lcw);
}
+ @VisibleForTesting
+ void setInjector(Injector injector) {
+ this.mInjector = injector;
+ }
+
@Override
@NearbyManager.ScanStatus
- public int registerScanListener(ScanRequest scanRequest, IScanListener listener) {
- if (mProviderManager.registerScanListener(scanRequest, listener)) {
+ public int registerScanListener(ScanRequest scanRequest, IScanListener listener,
+ String packageName, @Nullable String attributionTag) {
+ // Permissions check
+ enforceBluetoothPrivilegedPermission(mContext);
+ CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
+ DiscoveryPermissions.enforceDiscoveryPermission(mContext, identity);
+
+ if (mProviderManager.registerScanListener(scanRequest, listener, identity)) {
return NearbyManager.ScanStatus.SUCCESS;
}
return NearbyManager.ScanStatus.ERROR;
@@ -98,10 +116,12 @@
@Override
public void startBroadcast(BroadcastRequestParcelable broadcastRequestParcelable,
- IBroadcastListener listener) {
+ IBroadcastListener listener, String packageName, @Nullable String attributionTag) {
+ enforceBluetoothPrivilegedPermission(mContext);
+ BroadcastPermissions.enforceBroadcastPermission(
+ mContext, CallerIdentity.fromBinder(mContext, packageName, attributionTag));
mBroadcastProviderManager.startBroadcast(
- broadcastRequestParcelable.getBroadcastRequest(),
- listener);
+ broadcastRequestParcelable.getBroadcastRequest(), listener);
}
@Override
@@ -116,28 +136,48 @@
*/
public void onBootPhase(int phase) {
switch (phase) {
+ case PHASE_SYSTEM_SERVICES_READY:
+ if (mInjector instanceof SystemInjector) {
+ ((SystemInjector) mInjector).initializeAppOpsManager();
+ }
+ break;
case PHASE_THIRD_PARTY_APPS_CAN_START:
// Ensures that a fast pair data provider exists which will work in direct boot.
FastPairDataProvider.init(mContext);
break;
case PHASE_BOOT_COMPLETED:
- // The nearby service must be functioning after this boot phase.
- mSystemInjector.initializeBluetoothAdapter();
+ if (mInjector instanceof SystemInjector) {
+ // The nearby service must be functioning after this boot phase.
+ ((SystemInjector) mInjector).initializeBluetoothAdapter();
+ // Initialize ContextManager for CHRE scan.
+ ((SystemInjector) mInjector).initializeContextHubManagerAdapter();
+ }
mContext.registerReceiver(
mBluetoothReceiver,
new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
mFastPairManager.initiate();
- // Initialize ContextManager for CHRE scan.
- mSystemInjector.initializeContextHubManagerAdapter();
mPresenceManager.initiate();
break;
}
}
+ /**
+ * If the calling process of has not been granted
+ * {@link android.Manifest.permission.BLUETOOTH_PRIVILEGED} permission,
+ * throw a {@link SecurityException}.
+ */
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+ private static void enforceBluetoothPrivilegedPermission(Context context) {
+ context.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ "Need BLUETOOTH PRIVILEGED permission");
+ }
+
private static final class SystemInjector implements Injector {
private final Context mContext;
@Nullable private BluetoothAdapter mBluetoothAdapter;
@Nullable private ContextHubManagerAdapter mContextHubManagerAdapter;
+ @Nullable private AppOpsManager mAppOpsManager;
SystemInjector(Context context) {
mContext = context;
@@ -155,6 +195,12 @@
return mContextHubManagerAdapter;
}
+ @Override
+ @Nullable
+ public AppOpsManager getAppOpsManager() {
+ return mAppOpsManager;
+ }
+
synchronized void initializeBluetoothAdapter() {
if (mBluetoothAdapter != null) {
return;
@@ -176,5 +222,12 @@
}
mContextHubManagerAdapter = new ContextHubManagerAdapter(manager);
}
+
+ synchronized void initializeAppOpsManager() {
+ if (mAppOpsManager != null) {
+ return;
+ }
+ mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+ }
}
}
diff --git a/nearby/service/java/com/android/server/nearby/injector/Injector.java b/nearby/service/java/com/android/server/nearby/injector/Injector.java
index f990dc9..57784a9 100644
--- a/nearby/service/java/com/android/server/nearby/injector/Injector.java
+++ b/nearby/service/java/com/android/server/nearby/injector/Injector.java
@@ -16,6 +16,7 @@
package com.android.server.nearby.injector;
+import android.app.AppOpsManager;
import android.bluetooth.BluetoothAdapter;
/**
@@ -29,4 +30,7 @@
/** Get the ContextHubManagerAdapter for ChreDiscoveryProvider to scan. */
ContextHubManagerAdapter getContextHubManagerAdapter();
+
+ /** Get the AppOpsManager to control access. */
+ AppOpsManager getAppOpsManager();
}
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 53d61c2..bdeab51 100644
--- a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
+++ b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
@@ -21,6 +21,7 @@
import static com.android.server.nearby.NearbyService.TAG;
import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.content.Context;
import android.nearby.IScanListener;
import android.nearby.NearbyDeviceParcelable;
@@ -35,6 +36,8 @@
import com.android.server.nearby.injector.Injector;
import com.android.server.nearby.metrics.NearbyMetrics;
import com.android.server.nearby.presence.PresenceDiscoveryResult;
+import com.android.server.nearby.util.identity.CallerIdentity;
+import com.android.server.nearby.util.permissions.DiscoveryPermissions;
import java.util.ArrayList;
import java.util.HashMap;
@@ -53,6 +56,7 @@
private final BleDiscoveryProvider mBleDiscoveryProvider;
@Nullable private final ChreDiscoveryProvider mChreDiscoveryProvider;
private @ScanRequest.ScanMode int mScanMode;
+ private final Injector mInjector;
@GuardedBy("mLock")
private Map<IBinder, ScanListenerRecord> mScanTypeScanListenerRecordMap;
@@ -60,12 +64,26 @@
@Override
public void onNearbyDeviceDiscovered(NearbyDeviceParcelable nearbyDevice) {
synchronized (mLock) {
+ AppOpsManager appOpsManager = Objects.requireNonNull(mInjector.getAppOpsManager());
for (IBinder listenerBinder : mScanTypeScanListenerRecordMap.keySet()) {
ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listenerBinder);
if (record == null) {
Log.w(TAG, "DiscoveryProviderManager cannot find the scan record.");
continue;
}
+ CallerIdentity callerIdentity = record.getCallerIdentity();
+ if (!DiscoveryPermissions.noteDiscoveryResultDelivery(
+ appOpsManager, callerIdentity)) {
+ Log.w(TAG, "[DiscoveryProviderManager] scan permission revoked "
+ + "- not forwarding results");
+ try {
+ record.getScanListener().onError();
+ } catch (RemoteException e) {
+ Log.w(TAG, "DiscoveryProviderManager failed to report error.", e);
+ }
+ return;
+ }
+
if (nearbyDevice.getScanType() == SCAN_TYPE_NEARBY_PRESENCE) {
List<ScanFilter> presenceFilters =
record.getScanRequest().getScanFilters().stream()
@@ -103,13 +121,14 @@
new ChreDiscoveryProvider(
mContext, new ChreCommunication(injector, executor), executor);
mScanTypeScanListenerRecordMap = new HashMap<>();
+ mInjector = injector;
}
/**
* Registers the listener in the manager and starts scan according to the requested scan mode.
*/
- public boolean registerScanListener(ScanRequest scanRequest, IScanListener listener) {
- Log.i(TAG, "DiscoveryProviderManager registerScanListener");
+ public boolean registerScanListener(ScanRequest scanRequest, IScanListener listener,
+ CallerIdentity callerIdentity) {
synchronized (mLock) {
IBinder listenerBinder = listener.asBinder();
if (mScanTypeScanListenerRecordMap.containsKey(listener.asBinder())) {
@@ -120,7 +139,8 @@
return true;
}
}
- ScanListenerRecord scanListenerRecord = new ScanListenerRecord(scanRequest, listener);
+ ScanListenerRecord scanListenerRecord =
+ new ScanListenerRecord(scanRequest, listener, callerIdentity);
mScanTypeScanListenerRecordMap.put(listenerBinder, scanListenerRecord);
if (!startProviders(scanRequest)) {
@@ -273,9 +293,13 @@
private final IScanListener mScanListener;
- ScanListenerRecord(ScanRequest scanRequest, IScanListener iScanListener) {
+ private final CallerIdentity mCallerIdentity;
+
+ ScanListenerRecord(ScanRequest scanRequest, IScanListener iScanListener,
+ CallerIdentity callerIdentity) {
mScanListener = iScanListener;
mScanRequest = scanRequest;
+ mCallerIdentity = callerIdentity;
}
IScanListener getScanListener() {
@@ -286,6 +310,10 @@
return mScanRequest;
}
+ CallerIdentity getCallerIdentity() {
+ return mCallerIdentity;
+ }
+
@Override
public boolean equals(Object other) {
if (other instanceof ScanListenerRecord) {
diff --git a/nearby/service/java/com/android/server/nearby/util/identity/CallerIdentity.java b/nearby/service/java/com/android/server/nearby/util/identity/CallerIdentity.java
new file mode 100644
index 0000000..b5c80b9
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/identity/CallerIdentity.java
@@ -0,0 +1,169 @@
+/*
+ * 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 android.annotation.Nullable;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Process;
+
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+/**
+ * Identifying information on a caller.
+ *
+ * @hide
+ */
+public final class CallerIdentity {
+
+ /**
+ * Creates a CallerIdentity from the current binder identity, using the given package, feature
+ * id, and listener id. The package will be checked to enforce it belongs to the calling uid,
+ * and a security exception will be thrown if it is invalid.
+ */
+ public static CallerIdentity fromBinder(Context context, String packageName,
+ @Nullable String attributionTag) {
+ int uid = Binder.getCallingUid();
+ if (!contains(context.getPackageManager().getPackagesForUid(uid), packageName)) {
+ throw new SecurityException("invalid package \"" + packageName + "\" for uid " + uid);
+ }
+ return fromBinderUnsafe(packageName, attributionTag);
+ }
+
+ /**
+ * Construct a CallerIdentity for test purposes.
+ */
+ @VisibleForTesting
+ public static CallerIdentity forTest(int uid, int pid, String packageName,
+ @Nullable String attributionTag) {
+ return new CallerIdentity(uid, pid, packageName, attributionTag);
+ }
+
+ /**
+ * Creates a CallerIdentity from the current binder identity, using the given package, feature
+ * id, and listener id. The package will not be checked to enforce that it belongs to the
+ * calling uid - this method should only be used if the package will be validated by some other
+ * means, such as an appops call.
+ */
+ public static CallerIdentity fromBinderUnsafe(String packageName,
+ @Nullable String attributionTag) {
+ return new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(),
+ packageName, attributionTag);
+ }
+
+ private final int mUid;
+
+ private final int mPid;
+
+ private final String mPackageName;
+
+ private final @Nullable String mAttributionTag;
+
+
+ private CallerIdentity(int uid, int pid, String packageName,
+ @Nullable String attributionTag) {
+ this.mUid = uid;
+ this.mPid = pid;
+ this.mPackageName = Objects.requireNonNull(packageName);
+ this.mAttributionTag = attributionTag;
+ }
+
+ /** The calling UID. */
+ public int getUid() {
+ return mUid;
+ }
+
+ /** The calling PID. */
+ public int getPid() {
+ return mPid;
+ }
+
+ /** The calling package name. */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /** The calling attribution tag. */
+ public String getAttributionTag() {
+ return mAttributionTag;
+ }
+
+ /** Returns true if this represents a system server identity. */
+ public boolean isSystemServer() {
+ return mUid == Process.SYSTEM_UID;
+ }
+
+ @Override
+ public String toString() {
+ int length = 10 + mPackageName.length();
+ if (mAttributionTag != null) {
+ length += mAttributionTag.length();
+ }
+
+ StringBuilder builder = new StringBuilder(length);
+ builder.append(mUid).append("/").append(mPackageName);
+ if (mAttributionTag != null) {
+ builder.append("[");
+ if (mAttributionTag.startsWith(mPackageName)) {
+ builder.append(mAttributionTag.substring(mPackageName.length()));
+ } else {
+ builder.append(mAttributionTag);
+ }
+ builder.append("]");
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof CallerIdentity)) {
+ return false;
+ }
+ CallerIdentity that = (CallerIdentity) o;
+ return mUid == that.mUid
+ && mPid == that.mPid
+ && mPackageName.equals(that.mPackageName)
+ && Objects.equals(mAttributionTag, that.mAttributionTag);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUid, mPid, mPackageName, mAttributionTag);
+ }
+
+ private static <T> boolean contains(@Nullable T[] array, T value) {
+ return indexOf(array, value) != -1;
+ }
+
+ /**
+ * Return first index of {@code value} in {@code array}, or {@code -1} if
+ * not found.
+ */
+ private static <T> int indexOf(@Nullable T[] array, T value) {
+ if (array == null) return -1;
+ for (int i = 0; i < array.length; i++) {
+ if (Objects.equals(array[i], value)) return i;
+ }
+ return -1;
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/util/permissions/BroadcastPermissions.java b/nearby/service/java/com/android/server/nearby/util/permissions/BroadcastPermissions.java
new file mode 100644
index 0000000..c11c234
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/permissions/BroadcastPermissions.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 com.android.server.nearby.util.permissions;
+
+import static android.Manifest.permission.BLUETOOTH_ADVERTISE;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static java.lang.annotation.ElementType.TYPE_USE;
+
+import android.content.Context;
+
+import androidx.annotation.IntDef;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.nearby.util.identity.CallerIdentity;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Utilities for handling presence broadcast runtime permissions. */
+public class BroadcastPermissions {
+
+ /** Indicates no permissions are present, or no permissions are required. */
+ public static final int PERMISSION_NONE = 0;
+
+ /** Indicates only the Bluetooth advertise permission is present, or is required. */
+ public static final int PERMISSION_BLUETOOTH_ADVERTISE = 1;
+
+ /** Broadcast permission levels. */
+ @IntDef({
+ PERMISSION_NONE,
+ PERMISSION_BLUETOOTH_ADVERTISE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({TYPE_USE})
+ public @interface BroadcastPermissionLevel {}
+
+ /**
+ * Throws a security exception if the caller does not hold the required broadcast permissions.
+ */
+ public static void enforceBroadcastPermission(Context context, CallerIdentity callerIdentity) {
+ if (!checkCallerBroadcastPermission(context, callerIdentity)) {
+ throw new SecurityException("uid " + callerIdentity.getUid()
+ + " does not have " + BLUETOOTH_ADVERTISE + ".");
+ }
+ }
+
+ /**
+ * Checks if the app has the permission to broadcast.
+ *
+ * @return true if the app does have the permission, false otherwise.
+ */
+ public static boolean checkCallerBroadcastPermission(Context context,
+ CallerIdentity callerIdentity) {
+ int uid = callerIdentity.getUid();
+ int pid = callerIdentity.getPid();
+
+ if (!checkBroadcastPermission(
+ getPermissionLevel(context, uid, pid), PERMISSION_BLUETOOTH_ADVERTISE)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /** Returns the permission level of the caller. */
+ @VisibleForTesting
+ @BroadcastPermissionLevel
+ public static int getPermissionLevel(
+ Context context, int uid, int pid) {
+ boolean isBluetoothAdvertiseGranted =
+ context.checkPermission(BLUETOOTH_ADVERTISE, pid, uid)
+ == PERMISSION_GRANTED;
+ if (isBluetoothAdvertiseGranted) {
+ return PERMISSION_BLUETOOTH_ADVERTISE;
+ }
+
+ return PERMISSION_NONE;
+ }
+
+ /** Returns false if the given permission level does not meet the required permission level. */
+ private static boolean checkBroadcastPermission(
+ @BroadcastPermissionLevel int permissionLevel,
+ @BroadcastPermissionLevel int requiredPermissionLevel) {
+ return permissionLevel >= requiredPermissionLevel;
+ }
+
+ private BroadcastPermissions() {}
+}
+
diff --git a/nearby/service/java/com/android/server/nearby/util/permissions/DiscoveryPermissions.java b/nearby/service/java/com/android/server/nearby/util/permissions/DiscoveryPermissions.java
new file mode 100644
index 0000000..b0888ba
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/permissions/DiscoveryPermissions.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.util.permissions;
+
+import static android.Manifest.permission.BLUETOOTH_SCAN;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static java.lang.annotation.ElementType.TYPE_USE;
+
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.Context;
+
+import androidx.annotation.IntDef;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.nearby.util.identity.CallerIdentity;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Utilities for handling presence discovery runtime permissions. */
+public class DiscoveryPermissions {
+
+ /** Indicates no permissions are present, or no permissions are required. */
+ public static final int PERMISSION_NONE = 0;
+
+ /** Indicates only the Bluetooth scan permission is present, or is required. */
+ public static final int PERMISSION_BLUETOOTH_SCAN = 1;
+
+ // String in AppOpsManager
+ @VisibleForTesting
+ public static final String OPSTR_BLUETOOTH_SCAN = "android:bluetooth_scan";
+
+ /** Discovery permission levels. */
+ @IntDef({
+ PERMISSION_NONE,
+ PERMISSION_BLUETOOTH_SCAN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @Target({TYPE_USE})
+ public @interface DiscoveryPermissionLevel {}
+
+ /**
+ * Throws a security exception if the caller does not hold the required scan permissions.
+ */
+ public static void enforceDiscoveryPermission(Context context, CallerIdentity callerIdentity) {
+ if (!checkCallerDiscoveryPermission(context, callerIdentity)) {
+ throw new SecurityException("uid " + callerIdentity.getUid() + " does not have "
+ + BLUETOOTH_SCAN + ".");
+ }
+ }
+
+ /**
+ * Checks if the caller has the permission to scan.
+ */
+ public static boolean checkCallerDiscoveryPermission(Context context,
+ CallerIdentity callerIdentity) {
+ int uid = callerIdentity.getUid();
+ int pid = callerIdentity.getPid();
+
+ return checkDiscoveryPermission(
+ getPermissionLevel(context, uid, pid), PERMISSION_BLUETOOTH_SCAN);
+ }
+
+ /**
+ * Checks if the caller is allowed by AppOpsManager to scan.
+ */
+ public static boolean noteDiscoveryResultDelivery(AppOpsManager appOpsManager,
+ CallerIdentity callerIdentity) {
+ return noteAppOpAllowed(appOpsManager, callerIdentity, /* message= */ null);
+ }
+
+ private static boolean noteAppOpAllowed(AppOpsManager appOpsManager,
+ CallerIdentity identity, @Nullable String message) {
+ return appOpsManager.noteOp(asAppOp(PERMISSION_BLUETOOTH_SCAN),
+ identity.getUid(), identity.getPackageName(), identity.getAttributionTag(), message)
+ == AppOpsManager.MODE_ALLOWED;
+ }
+
+ /** Returns the permission level of the caller. */
+ public static @DiscoveryPermissionLevel int getPermissionLevel(
+ Context context, int uid, int pid) {
+ boolean isBluetoothScanGranted =
+ context.checkPermission(BLUETOOTH_SCAN, pid, uid) == PERMISSION_GRANTED;
+ if (isBluetoothScanGranted) {
+ return PERMISSION_BLUETOOTH_SCAN;
+ }
+ return PERMISSION_NONE;
+ }
+
+ /** Returns false if the given permission lev`el does not meet the required permission level. */
+ private static boolean checkDiscoveryPermission(
+ @DiscoveryPermissionLevel int permissionLevel,
+ @DiscoveryPermissionLevel int requiredPermissionLevel) {
+ return permissionLevel >= requiredPermissionLevel;
+ }
+
+ /** Returns the app op string according to the permission level. */
+ private static String asAppOp(@DiscoveryPermissionLevel int permissionLevel) {
+ if (permissionLevel == PERMISSION_BLUETOOTH_SCAN) {
+ return "android:bluetooth_scan";
+ }
+ throw new IllegalArgumentException();
+ }
+
+ private DiscoveryPermissions() {}
+}
diff --git a/nearby/tests/cts/fastpair/AndroidManifest.xml b/nearby/tests/cts/fastpair/AndroidManifest.xml
index ce841f2..96e2783 100644
--- a/nearby/tests/cts/fastpair/AndroidManifest.xml
+++ b/nearby/tests/cts/fastpair/AndroidManifest.xml
@@ -19,6 +19,8 @@
package="android.nearby.cts">
<uses-sdk android:minSdkVersion="32" android:targetSdkVersion="32" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application>
diff --git a/nearby/tests/cts/fastpair/AndroidTest.xml b/nearby/tests/cts/fastpair/AndroidTest.xml
index 360bbf3..2800069 100644
--- a/nearby/tests/cts/fastpair/AndroidTest.xml
+++ b/nearby/tests/cts/fastpair/AndroidTest.xml
@@ -14,6 +14,9 @@
limitations under the License.
-->
<configuration description="Config for CTS Nearby Fast Pair test cases">
+ <!-- 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" />
+
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="location" />
<!-- Instant cannot access NearbyManager. -->
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 9720865..6824ca6 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
@@ -16,6 +16,7 @@
package android.nearby.cts;
+import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
import static android.Manifest.permission.READ_DEVICE_CONFIG;
import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE;
@@ -23,6 +24,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
import android.app.UiAutomation;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
@@ -51,6 +54,7 @@
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@@ -73,10 +77,32 @@
private UiAutomation mUiAutomation =
InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ private ScanRequest mScanRequest = new ScanRequest.Builder()
+ .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR)
+ .setScanMode(ScanRequest.SCAN_MODE_LOW_LATENCY)
+ .setBleEnabled(true)
+ .build();
+ private ScanCallback mScanCallback = new ScanCallback() {
+ @Override
+ public void onDiscovered(@NonNull NearbyDevice device) {
+ }
+
+ @Override
+ public void onUpdated(@NonNull NearbyDevice device) {
+ }
+
+ @Override
+ public void onLost(@NonNull NearbyDevice device) {
+ }
+ };
+ private static final Executor EXECUTOR = Executors.newSingleThreadExecutor();
+
@Before
public void setUp() {
- mUiAutomation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG);
- DeviceConfig.setProperty(NAMESPACE_TETHERING, "nearby_enable_presence_broadcast_legacy",
+ mUiAutomation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, WRITE_DEVICE_CONFIG,
+ BLUETOOTH_PRIVILEGED);
+ DeviceConfig.setProperty(NAMESPACE_TETHERING,
+ "nearby_enable_presence_broadcast_legacy",
"true", false);
mContext = InstrumentationRegistry.getContext();
@@ -88,28 +114,16 @@
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void test_startAndStopScan() {
- ScanRequest scanRequest = new ScanRequest.Builder()
- .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR)
- .setScanMode(ScanRequest.SCAN_MODE_LOW_LATENCY)
- .setBleEnabled(true)
- .build();
- ScanCallback scanCallback = new ScanCallback() {
- @Override
- public void onDiscovered(@NonNull NearbyDevice device) {
- }
+ mNearbyManager.startScan(mScanRequest, EXECUTOR, mScanCallback);
+ mNearbyManager.stopScan(mScanCallback);
+ }
- @Override
- public void onUpdated(@NonNull NearbyDevice device) {
-
- }
-
- @Override
- public void onLost(@NonNull NearbyDevice device) {
-
- }
- };
- mNearbyManager.startScan(scanRequest, Executors.newSingleThreadExecutor(), scanCallback);
- mNearbyManager.stopScan(scanCallback);
+ @Test
+ @SdkSuppress(minSdkVersion = 32, codeName = "T")
+ public void test_startScan_noPrivilegedPermission() {
+ mUiAutomation.dropShellPermissionIdentity();
+ assertThrows(SecurityException.class, () -> mNearbyManager
+ .startScan(mScanRequest, EXECUTOR, mScanCallback));
}
@Test
diff --git a/nearby/tests/integration/privileged/AndroidManifest.xml b/nearby/tests/integration/privileged/AndroidManifest.xml
index 00845f1..86ec111 100644
--- a/nearby/tests/integration/privileged/AndroidManifest.xml
+++ b/nearby/tests/integration/privileged/AndroidManifest.xml
@@ -18,6 +18,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.nearby.integration.privileged">
+ <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
+ <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
+ <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<instrumentation
diff --git a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt
index 3b6337a..66bab23 100644
--- a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt
+++ b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt
@@ -17,22 +17,81 @@
package android.nearby.integration.privileged
import android.content.Context
+import android.nearby.BroadcastCallback
+import android.nearby.BroadcastRequest
+import android.nearby.NearbyDevice
import android.nearby.NearbyManager
+import android.nearby.PresenceBroadcastRequest
+import android.nearby.PresenceCredential
+import android.nearby.PrivateCredential
+import android.nearby.ScanCallback
+import android.nearby.ScanRequest
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class NearbyManagerTest {
+ private lateinit var appContext: Context
+
+ @Before
+ fun setUp() {
+ appContext = ApplicationProvider.getApplicationContext<Context>()
+ }
/** Verify privileged app can get Nearby service. */
@Test
fun testContextGetNearbySystemService_fromPrivilegedApp_returnsNoneNull() {
- val appContext = ApplicationProvider.getApplicationContext<Context>()
- val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+ assertThat(appContext.getSystemService(Context.NEARBY_SERVICE)).isNotNull()
+ }
- assertThat(nearbyManager).isNotNull()
+ /** Verify privileged app can start/stop scan without exception. */
+ @Test
+ fun testNearbyManagerStartScanStopScan_fromPrivilegedApp_succeed() {
+ val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+ val scanRequest = ScanRequest.Builder()
+ .setScanMode(ScanRequest.SCAN_MODE_LOW_LATENCY)
+ .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR)
+ .setBleEnabled(true)
+ .build()
+ val scanCallback = object : ScanCallback {
+ override fun onDiscovered(device: NearbyDevice) {}
+
+ override fun onUpdated(device: NearbyDevice) {}
+
+ override fun onLost(device: NearbyDevice) {}
+ }
+
+ nearbyManager.startScan(scanRequest, /* executor */ { it.run() }, scanCallback)
+ nearbyManager.stopScan(scanCallback)
+ }
+
+ /** Verify privileged app can start/stop broadcast without exception. */
+ @Test
+ fun testNearbyManagerStartBroadcastStopBroadcast_fromPrivilegedApp_succeed() {
+ val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+ val salt = byteArrayOf(1, 2)
+ val secreteId = byteArrayOf(1, 2, 3, 4)
+ val metadataEncryptionKey = ByteArray(14)
+ val authenticityKey = byteArrayOf(0, 1, 1, 1)
+ val deviceName = "test_device"
+ val mediums = listOf(BroadcastRequest.MEDIUM_BLE)
+ val credential =
+ PrivateCredential.Builder(secreteId, authenticityKey, metadataEncryptionKey, deviceName)
+ .setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE)
+ .build()
+ val broadcastRequest: BroadcastRequest =
+ PresenceBroadcastRequest.Builder(mediums, salt, credential)
+ .addAction(123)
+ .build()
+ val broadcastCallback = BroadcastCallback { }
+
+ nearbyManager.startBroadcast(
+ broadcastRequest, /* executor */ { it.run() }, broadcastCallback
+ )
+ nearbyManager.stopBroadcast(broadcastCallback)
}
}
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/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt b/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt
index 04c5e30..3bfac6d 100644
--- a/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt
+++ b/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt
@@ -17,20 +17,127 @@
package android.nearby.integration.untrusted
import android.content.Context
+import android.nearby.BroadcastCallback
+import android.nearby.BroadcastRequest
+import android.nearby.NearbyDevice
import android.nearby.NearbyManager
+import android.nearby.PresenceBroadcastRequest
+import android.nearby.PresenceCredential
+import android.nearby.PrivateCredential
+import android.nearby.ScanCallback
+import android.nearby.ScanRequest
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class NearbyManagerTest {
+ private lateinit var appContext: Context
+
+ @Before
+ fun setUp() {
+ appContext = ApplicationProvider.getApplicationContext<Context>()
+ }
/** Verify untrusted app can get Nearby service. */
@Test
fun testContextGetNearbyService_fromUnTrustedApp_returnsNotNull() {
- val appContext = ApplicationProvider.getApplicationContext<Context>()
assertThat(appContext.getSystemService(Context.NEARBY_SERVICE)).isNotNull()
}
+
+ /**
+ * Verify untrusted app can't start scan because it needs BLUETOOTH_PRIVILEGED
+ * permission which is not for use by third-party applications.
+ */
+ @Test
+ fun testNearbyManagerStartScan_fromUnTrustedApp_throwsException() {
+ val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+ val scanRequest = ScanRequest.Builder()
+ .setScanMode(ScanRequest.SCAN_MODE_LOW_LATENCY)
+ .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR)
+ .setBleEnabled(true)
+ .build()
+ val scanCallback = object : ScanCallback {
+ override fun onDiscovered(device: NearbyDevice) {}
+
+ override fun onUpdated(device: NearbyDevice) {}
+
+ override fun onLost(device: NearbyDevice) {}
+ }
+
+ assertThrows(SecurityException::class.java) {
+ nearbyManager.startScan(scanRequest, /* executor */ { it.run() }, scanCallback)
+ }
+ }
+
+ /**
+ * Verify untrusted app can't stop scan because it needs BLUETOOTH_PRIVILEGED
+ * permission which is not for use by third-party applications.
+ */
+ @Test
+ @Ignore("Permission check for stopXXX not yet implement: b/229338477#comment24")
+ fun testNearbyManagerStopScan_fromUnTrustedApp_throwsException() {
+ val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+ val scanCallback = object : ScanCallback {
+ override fun onDiscovered(device: NearbyDevice) {}
+
+ override fun onUpdated(device: NearbyDevice) {}
+
+ override fun onLost(device: NearbyDevice) {}
+ }
+
+ assertThrows(SecurityException::class.java) {
+ nearbyManager.stopScan(scanCallback)
+ }
+ }
+
+ /**
+ * Verify untrusted app can't start broadcast because it needs BLUETOOTH_PRIVILEGED
+ * permission which is not for use by third-party applications.
+ */
+ @Test
+ fun testNearbyManagerStartBroadcast_fromUnTrustedApp_throwsException() {
+ val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+ val salt = byteArrayOf(1, 2)
+ val secreteId = byteArrayOf(1, 2, 3, 4)
+ val metadataEncryptionKey = ByteArray(14)
+ val authenticityKey = byteArrayOf(0, 1, 1, 1)
+ val deviceName = "test_device"
+ val mediums = listOf(BroadcastRequest.MEDIUM_BLE)
+ val credential =
+ PrivateCredential.Builder(secreteId, authenticityKey, metadataEncryptionKey, deviceName)
+ .setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE)
+ .build()
+ val broadcastRequest: BroadcastRequest =
+ PresenceBroadcastRequest.Builder(mediums, salt, credential)
+ .addAction(123)
+ .build()
+ val broadcastCallback = BroadcastCallback { }
+
+ assertThrows(SecurityException::class.java) {
+ nearbyManager.startBroadcast(
+ broadcastRequest, /* executor */ { it.run() }, broadcastCallback
+ )
+ }
+ }
+
+ /**
+ * Verify untrusted app can't stop broadcast because it needs BLUETOOTH_PRIVILEGED
+ * permission which is not for use by third-party applications.
+ */
+ @Test
+ @Ignore("Permission check for stopXXX not yet implement: b/229338477#comment24")
+ fun testNearbyManagerStopBroadcast_fromUnTrustedApp_throwsException() {
+ val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+ val broadcastCallback = BroadcastCallback { }
+
+ assertThrows(SecurityException::class.java) {
+ nearbyManager.stopBroadcast(broadcastCallback)
+ }
+ }
}
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/nearby/tests/multidevices/host/AndroidTest.xml b/nearby/tests/multidevices/host/AndroidTest.xml
index 5926cc1..43cf136 100644
--- a/nearby/tests/multidevices/host/AndroidTest.xml
+++ b/nearby/tests/multidevices/host/AndroidTest.xml
@@ -36,7 +36,6 @@
<!-- Any python dependencies can be specified and will be installed with pip -->
<!-- TODO(b/225958696): Import python dependencies -->
<option name="dep-module" value="mobly" />
- <option name="dep-module" value="retry" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
diff --git a/nearby/tests/multidevices/host/test_helper/fast_pair_provider_simulator.py b/nearby/tests/multidevices/host/test_helper/fast_pair_provider_simulator.py
index d6484fb..592c4f1 100644
--- a/nearby/tests/multidevices/host/test_helper/fast_pair_provider_simulator.py
+++ b/nearby/tests/multidevices/host/test_helper/fast_pair_provider_simulator.py
@@ -14,10 +14,12 @@
"""Fast Pair provider simulator role."""
+import time
+
from mobly import asserts
from mobly.controllers import android_device
+from mobly.controllers.android_device_lib import jsonrpc_client_base
from mobly.controllers.android_device_lib import snippet_event
-import retry
from typing import Optional
from test_helper import event_helper
@@ -104,7 +106,6 @@
"""Tears down the Fast Pair provider simulator."""
self._ad.fp.teardownProviderSimulator()
- @retry.retry(tries=3)
def get_ble_mac_address(self) -> str:
"""Gets Bluetooth low energy mac address of the provider simulator.
@@ -115,7 +116,11 @@
Returns:
The BLE mac address of the Fast Pair provider simulator.
"""
- return self._ad.fp.getBluetoothLeAddress()
+ for _ in range(3):
+ try:
+ return self._ad.fp.getBluetoothLeAddress()
+ except jsonrpc_client_base.ApiError:
+ time.sleep(1)
def wait_for_discoverable_mode(self, timeout_seconds: int) -> None:
"""Waits onScanModeChange event to ensure provider is discoverable.
diff --git a/nearby/tests/unit/AndroidManifest.xml b/nearby/tests/unit/AndroidManifest.xml
index 88c0f5f..9f58baf 100644
--- a/nearby/tests/unit/AndroidManifest.xml
+++ b/nearby/tests/unit/AndroidManifest.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
diff --git a/nearby/tests/unit/AndroidTest.xml b/nearby/tests/unit/AndroidTest.xml
index fdf665d..ad52316 100644
--- a/nearby/tests/unit/AndroidTest.xml
+++ b/nearby/tests/unit/AndroidTest.xml
@@ -15,6 +15,9 @@
~ limitations under the License.
-->
<configuration description="Runs Nearby Mainline API Tests.">
+ <!-- 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.suite.SuiteApkInstaller">
<option name="test-file-name" value="NearbyUnitTests.apk" />
</target_preparer>
diff --git a/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java b/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java
index 31965a4..e250254 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java
@@ -16,10 +16,18 @@
package com.android.server.nearby;
+import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
import static android.Manifest.permission.READ_DEVICE_CONFIG;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
+import android.app.AppOpsManager;
import android.app.UiAutomation;
import android.content.Context;
import android.nearby.IScanListener;
@@ -27,12 +35,17 @@
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.server.nearby.injector.Injector;
+import com.android.server.nearby.util.permissions.DiscoveryPermissions;
+
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
public final class NearbyServiceTest {
+ private static final String PACKAGE_NAME = "android.nearby.test";
private Context mContext;
private NearbyService mService;
private ScanRequest mScanRequest;
@@ -41,19 +54,36 @@
@Mock
private IScanListener mScanListener;
+ @Mock
+ private AppOpsManager mMockAppOpsManager;
@Before
- public void setup() {
+ public void setUp() {
initMocks(this);
- mUiAutomation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG);
+ mUiAutomation.adoptShellPermissionIdentity(READ_DEVICE_CONFIG, BLUETOOTH_PRIVILEGED);
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mService = new NearbyService(mContext);
mScanRequest = createScanRequest();
}
+ @After
+ public void tearDown() {
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+
@Test
public void test_register() {
- mService.registerScanListener(mScanRequest, mScanListener);
+ setMockInjector(/* isMockOpsAllowed= */ true);
+ mService.registerScanListener(mScanRequest, mScanListener, PACKAGE_NAME,
+ /* attributionTag= */ null);
+ }
+
+ @Test
+ public void test_register_noPrivilegedPermission_throwsException() {
+ mUiAutomation.dropShellPermissionIdentity();
+ assertThrows(java.lang.SecurityException.class,
+ () -> mService.registerScanListener(mScanRequest, mScanListener, PACKAGE_NAME,
+ /* attributionTag= */ null));
}
@Test
@@ -67,4 +97,14 @@
.setBleEnabled(true)
.build();
}
+
+ private void setMockInjector(boolean isMockOpsAllowed) {
+ Injector injector = mock(Injector.class);
+ when(injector.getAppOpsManager()).thenReturn(mMockAppOpsManager);
+ when(mMockAppOpsManager.noteOp(eq(DiscoveryPermissions.OPSTR_BLUETOOTH_SCAN),
+ anyInt(), eq(PACKAGE_NAME), nullable(String.class), nullable(String.class)))
+ .thenReturn(isMockOpsAllowed
+ ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED);
+ mService.setInjector(injector);
+ }
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceManagerTest.java
index d32e325..3b34655 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceManagerTest.java
@@ -16,9 +16,6 @@
package com.android.server.nearby.presence;
-
-
-
import androidx.test.filters.SdkSuppress;
import org.junit.Before;
@@ -26,7 +23,6 @@
import org.mockito.MockitoAnnotations;
public class PresenceManagerTest {
- private PresenceManager mPresenceManager;
@Before
public void setup() {
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 f485e18..d06a785 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
@@ -20,6 +20,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.app.AppOpsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.AdvertiseSettings;
@@ -61,7 +62,6 @@
public void testOnStatus_success() {
byte[] advertiseBytes = new byte[]{1, 2, 3, 4};
mBleBroadcastProvider.start(advertiseBytes, mBroadcastListener);
- verify(mBroadcastListener).onStatusChanged(eq(BroadcastCallback.STATUS_FAILURE));
AdvertiseSettings settings = new AdvertiseSettings.Builder().build();
mBleBroadcastProvider.onStartSuccess(settings);
@@ -74,7 +74,8 @@
mBleBroadcastProvider.start(advertiseBytes, mBroadcastListener);
mBleBroadcastProvider.onStartFailure(BroadcastCallback.STATUS_FAILURE);
- verify(mBroadcastListener, times(2)).onStatusChanged(eq(BroadcastCallback.STATUS_FAILURE));
+ verify(mBroadcastListener, times(1))
+ .onStatusChanged(eq(BroadcastCallback.STATUS_FAILURE));
}
private static class TestInjector implements Injector {
@@ -90,5 +91,10 @@
public ContextHubManagerAdapter getContextHubManagerAdapter() {
return null;
}
+
+ @Override
+ public AppOpsManager getAppOpsManager() {
+ return null;
+ }
}
}
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 8e97443..902cc33 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
@@ -23,6 +23,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
+import android.app.AppOpsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
@@ -87,6 +88,11 @@
public ContextHubManagerAdapter getContextHubManagerAdapter() {
return null;
}
+
+ @Override
+ public AppOpsManager getAppOpsManager() {
+ return null;
+ }
}
private ScanResult createScanResult() {
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
new file mode 100644
index 0000000..1a22412
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/BroadcastPermissionsTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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 android.Manifest.permission.BLUETOOTH_ADVERTISE;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static com.android.server.nearby.util.permissions.BroadcastPermissions.PERMISSION_BLUETOOTH_ADVERTISE;
+import static com.android.server.nearby.util.permissions.BroadcastPermissions.PERMISSION_NONE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.content.Context;
+
+import com.android.server.nearby.util.identity.CallerIdentity;
+import com.android.server.nearby.util.permissions.BroadcastPermissions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+/**
+ * Unit test for {@link BroadcastPermissions}
+ */
+public final class BroadcastPermissionsTest {
+
+ private static final String PACKAGE_NAME = "android.nearby.test";
+ private static final int UID = 1234;
+ private static final int PID = 5678;
+ private CallerIdentity mCallerIdentity;
+
+ @Mock private Context mMockContext;
+
+ @Before
+ public void setup() {
+ initMocks(this);
+ mCallerIdentity = CallerIdentity
+ .forTest(UID, PID, PACKAGE_NAME, /* attributionTag= */ null);
+ }
+
+ @Test
+ public void test_checkCallerBroadcastPermission_granted() {
+ when(mMockContext.checkPermission(BLUETOOTH_ADVERTISE, PID, UID))
+ .thenReturn(PERMISSION_GRANTED);
+
+ assertThat(BroadcastPermissions
+ .checkCallerBroadcastPermission(mMockContext, mCallerIdentity))
+ .isTrue();
+ }
+
+ @Test
+ public void test_checkCallerBroadcastPermission_deniedPermission() {
+ when(mMockContext.checkPermission(BLUETOOTH_ADVERTISE, PID, UID))
+ .thenReturn(PERMISSION_DENIED);
+
+ assertThat(BroadcastPermissions
+ .checkCallerBroadcastPermission(mMockContext, mCallerIdentity))
+ .isFalse();
+ }
+
+ @Test
+ public void test_getPermissionLevel_none() {
+ when(mMockContext.checkPermission(BLUETOOTH_ADVERTISE, PID, UID))
+ .thenReturn(PERMISSION_DENIED);
+
+ assertThat(BroadcastPermissions.getPermissionLevel(mMockContext, UID, PID))
+ .isEqualTo(PERMISSION_NONE);
+ }
+
+ @Test
+ public void test_getPermissionLevel_advertising() {
+ when(mMockContext.checkPermission(BLUETOOTH_ADVERTISE, PID, UID))
+ .thenReturn(PERMISSION_GRANTED);
+
+ assertThat(BroadcastPermissions.getPermissionLevel(mMockContext, UID, PID))
+ .isEqualTo(PERMISSION_BLUETOOTH_ADVERTISE);
+ }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/DiscoveryPermissionsTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/DiscoveryPermissionsTest.java
new file mode 100644
index 0000000..d953a60
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/DiscoveryPermissionsTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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 android.Manifest.permission.BLUETOOTH_SCAN;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import static com.android.server.nearby.util.permissions.DiscoveryPermissions.PERMISSION_BLUETOOTH_SCAN;
+import static com.android.server.nearby.util.permissions.DiscoveryPermissions.PERMISSION_NONE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+
+import com.android.server.nearby.util.identity.CallerIdentity;
+import com.android.server.nearby.util.permissions.DiscoveryPermissions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+/**
+ * Unit test for {@link DiscoveryPermissions}
+ */
+public final class DiscoveryPermissionsTest {
+
+ private static final String PACKAGE_NAME = "android.nearby.test";
+ private static final int UID = 1234;
+ private static final int PID = 5678;
+ private CallerIdentity mCallerIdentity;
+
+ @Mock
+ private Context mMockContext;
+ @Mock private AppOpsManager mMockAppOps;
+
+ @Before
+ public void setup() {
+ initMocks(this);
+ mCallerIdentity = CallerIdentity
+ .forTest(UID, PID, PACKAGE_NAME, /* attributionTag= */ null);
+ }
+
+ @Test
+ public void test_enforceCallerDiscoveryPermission_exception() {
+ when(mMockContext.checkPermission(BLUETOOTH_SCAN, PID, UID)).thenReturn(PERMISSION_DENIED);
+
+ assertThrows(SecurityException.class,
+ () -> DiscoveryPermissions
+ .enforceDiscoveryPermission(mMockContext, mCallerIdentity));
+ }
+
+ @Test
+ public void test_checkCallerDiscoveryPermission_granted() {
+ when(mMockContext.checkPermission(BLUETOOTH_SCAN, PID, UID)).thenReturn(PERMISSION_GRANTED);
+
+ assertThat(DiscoveryPermissions
+ .checkCallerDiscoveryPermission(mMockContext, mCallerIdentity))
+ .isTrue();
+ }
+
+ @Test
+ public void test_checkCallerDiscoveryPermission_denied() {
+ when(mMockContext.checkPermission(BLUETOOTH_SCAN, PID, UID)).thenReturn(PERMISSION_DENIED);
+
+ assertThat(DiscoveryPermissions
+ .checkCallerDiscoveryPermission(mMockContext, mCallerIdentity))
+ .isFalse();
+ }
+
+ @Test
+ public void test_checkNoteOpPermission_granted() {
+ when(mMockAppOps.noteOp(DiscoveryPermissions.OPSTR_BLUETOOTH_SCAN, UID, PACKAGE_NAME,
+ null, null)).thenReturn(AppOpsManager.MODE_ALLOWED);
+
+ assertThat(DiscoveryPermissions
+ .noteDiscoveryResultDelivery(mMockAppOps, mCallerIdentity))
+ .isTrue();
+ }
+
+ @Test
+ public void test_checkNoteOpPermission_denied() {
+ when(mMockAppOps.noteOp(DiscoveryPermissions.OPSTR_BLUETOOTH_SCAN, UID, PACKAGE_NAME,
+ null, null)).thenReturn(AppOpsManager.MODE_ERRORED);
+
+ assertThat(DiscoveryPermissions
+ .noteDiscoveryResultDelivery(mMockAppOps, mCallerIdentity))
+ .isFalse();
+ }
+
+ @Test
+ public void test_getPermissionLevel_none() {
+ when(mMockContext.checkPermission(BLUETOOTH_SCAN, PID, UID)).thenReturn(PERMISSION_DENIED);
+
+ assertThat(DiscoveryPermissions
+ .getPermissionLevel(mMockContext, UID, PID))
+ .isEqualTo(PERMISSION_NONE);
+ }
+
+ @Test
+ public void test_getPermissionLevel_scan() {
+ when(mMockContext.checkPermission(BLUETOOTH_SCAN, PID, UID))
+ .thenReturn(PERMISSION_GRANTED);
+
+ assertThat(DiscoveryPermissions
+ .getPermissionLevel(mMockContext, UID, PID)).isEqualTo(PERMISSION_BLUETOOTH_SCAN);
+ }
+}
diff --git a/service-t/src/com/android/server/net/NetworkStatsObservers.java b/service-t/src/com/android/server/net/NetworkStatsObservers.java
index fdfc893..c51a886 100644
--- a/service-t/src/com/android/server/net/NetworkStatsObservers.java
+++ b/service-t/src/com/android/server/net/NetworkStatsObservers.java
@@ -18,6 +18,7 @@
import static android.app.usage.NetworkStatsManager.MIN_THRESHOLD_BYTES;
+import android.annotation.NonNull;
import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -38,6 +39,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.SparseArray;
@@ -52,12 +54,15 @@
*/
class NetworkStatsObservers {
private static final String TAG = "NetworkStatsObservers";
+ private static final boolean LOG = true;
private static final boolean LOGV = false;
private static final int MSG_REGISTER = 1;
private static final int MSG_UNREGISTER = 2;
private static final int MSG_UPDATE_STATS = 3;
+ private static final int DUMP_USAGE_REQUESTS_COUNT = 200;
+
// All access to this map must be done from the handler thread.
// indexed by DataUsageRequest#requestId
private final SparseArray<RequestInfo> mDataUsageRequests = new SparseArray<>();
@@ -77,13 +82,15 @@
*
* @return the normalized request wrapped within {@link RequestInfo}.
*/
- public DataUsageRequest register(Context context, DataUsageRequest inputRequest,
- IUsageCallback callback, int callingUid, @NetworkStatsAccess.Level int accessLevel) {
+ public DataUsageRequest register(@NonNull Context context,
+ @NonNull DataUsageRequest inputRequest, @NonNull IUsageCallback callback,
+ int callingPid, int callingUid, @NonNull String callingPackage,
+ @NetworkStatsAccess.Level int accessLevel) {
DataUsageRequest request = buildRequest(context, inputRequest, callingUid);
- RequestInfo requestInfo = buildRequestInfo(request, callback, callingUid,
- accessLevel);
+ RequestInfo requestInfo = buildRequestInfo(request, callback, callingPid, callingUid,
+ callingPackage, accessLevel);
- if (LOGV) Log.v(TAG, "Registering observer for " + request);
+ if (LOG) Log.d(TAG, "Registering observer for " + requestInfo);
getHandler().sendMessage(mHandler.obtainMessage(MSG_REGISTER, requestInfo));
return request;
}
@@ -172,7 +179,7 @@
RequestInfo requestInfo;
requestInfo = mDataUsageRequests.get(request.requestId);
if (requestInfo == null) {
- if (LOGV) Log.v(TAG, "Trying to unregister unknown request " + request);
+ if (LOG) Log.d(TAG, "Trying to unregister unknown request " + request);
return;
}
if (Process.SYSTEM_UID != callingUid && requestInfo.mCallingUid != callingUid) {
@@ -180,7 +187,7 @@
return;
}
- if (LOGV) Log.v(TAG, "Unregistering " + request);
+ if (LOG) Log.d(TAG, "Unregistering " + requestInfo);
mDataUsageRequests.remove(request.requestId);
requestInfo.unlinkDeathRecipient();
requestInfo.callCallback(NetworkStatsManager.CALLBACK_RELEASED);
@@ -214,18 +221,19 @@
}
private RequestInfo buildRequestInfo(DataUsageRequest request, IUsageCallback callback,
- int callingUid, @NetworkStatsAccess.Level int accessLevel) {
+ int callingPid, int callingUid, @NonNull String callingPackage,
+ @NetworkStatsAccess.Level int accessLevel) {
if (accessLevel <= NetworkStatsAccess.Level.USER) {
- return new UserUsageRequestInfo(this, request, callback, callingUid,
- accessLevel);
+ return new UserUsageRequestInfo(this, request, callback, callingPid,
+ callingUid, callingPackage, accessLevel);
} else {
// Safety check in case a new access level is added and we forgot to update this
if (accessLevel < NetworkStatsAccess.Level.DEVICESUMMARY) {
throw new IllegalArgumentException(
"accessLevel " + accessLevel + " is less than DEVICESUMMARY.");
}
- return new NetworkUsageRequestInfo(this, request, callback, callingUid,
- accessLevel);
+ return new NetworkUsageRequestInfo(this, request, callback, callingPid,
+ callingUid, callingPackage, accessLevel);
}
}
@@ -237,18 +245,22 @@
private final NetworkStatsObservers mStatsObserver;
protected final DataUsageRequest mRequest;
private final IUsageCallback mCallback;
+ protected final int mCallingPid;
protected final int mCallingUid;
+ protected final String mCallingPackage;
protected final @NetworkStatsAccess.Level int mAccessLevel;
protected NetworkStatsRecorder mRecorder;
protected NetworkStatsCollection mCollection;
RequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
- IUsageCallback callback, int callingUid,
- @NetworkStatsAccess.Level int accessLevel) {
+ IUsageCallback callback, int callingPid, int callingUid,
+ @NonNull String callingPackage, @NetworkStatsAccess.Level int accessLevel) {
mStatsObserver = statsObserver;
mRequest = request;
mCallback = callback;
+ mCallingPid = callingPid;
mCallingUid = callingUid;
+ mCallingPackage = callingPackage;
mAccessLevel = accessLevel;
try {
@@ -269,7 +281,8 @@
@Override
public String toString() {
- return "RequestInfo from uid:" + mCallingUid
+ return "RequestInfo from pid/uid:" + mCallingPid + "/" + mCallingUid
+ + "(" + mCallingPackage + ")"
+ " for " + mRequest + " accessLevel:" + mAccessLevel;
}
@@ -338,9 +351,10 @@
private static class NetworkUsageRequestInfo extends RequestInfo {
NetworkUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
- IUsageCallback callback, int callingUid,
- @NetworkStatsAccess.Level int accessLevel) {
- super(statsObserver, request, callback, callingUid, accessLevel);
+ IUsageCallback callback, int callingPid, int callingUid,
+ @NonNull String callingPackage, @NetworkStatsAccess.Level int accessLevel) {
+ super(statsObserver, request, callback, callingPid, callingUid, callingPackage,
+ accessLevel);
}
@Override
@@ -380,9 +394,10 @@
private static class UserUsageRequestInfo extends RequestInfo {
UserUsageRequestInfo(NetworkStatsObservers statsObserver, DataUsageRequest request,
- IUsageCallback callback, int callingUid,
- @NetworkStatsAccess.Level int accessLevel) {
- super(statsObserver, request, callback, callingUid, accessLevel);
+ IUsageCallback callback, int callingPid, int callingUid,
+ @NonNull String callingPackage, @NetworkStatsAccess.Level int accessLevel) {
+ super(statsObserver, request, callback, callingPid, callingUid,
+ callingPackage, accessLevel);
}
@Override
@@ -448,4 +463,10 @@
mCurrentTime = currentTime;
}
}
+
+ public void dump(IndentingPrintWriter pw) {
+ for (int i = 0; i < Math.min(mDataUsageRequests.size(), DUMP_USAGE_REQUESTS_COUNT); i++) {
+ pw.println(mDataUsageRequests.valueAt(i));
+ }
+ }
}
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 82b1fb5..217a9a6 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -1279,13 +1279,14 @@
Objects.requireNonNull(request.template, "NetworkTemplate is null");
Objects.requireNonNull(callback, "callback is null");
- int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
@NetworkStatsAccess.Level int accessLevel = checkAccessLevel(callingPackage);
DataUsageRequest normalizedRequest;
final long token = Binder.clearCallingIdentity();
try {
normalizedRequest = mStatsObservers.register(mContext,
- request, callback, callingUid, accessLevel);
+ request, callback, callingPid, callingUid, callingPackage, accessLevel);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -2098,6 +2099,13 @@
}
});
pw.decreaseIndent();
+ pw.println();
+
+ pw.println("Stats Observers:");
+ pw.increaseIndent();
+ mStatsObservers.dump(pw);
+ pw.decreaseIndent();
+ pw.println();
pw.println("Dev stats:");
pw.increaseIndent();
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index c1c2e9d..c7223fc 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -105,14 +105,19 @@
# From the API shims
rule com.android.networkstack.apishim.** com.android.connectivity.@0
-# From fast-pair-lite-protos
-rule service.proto.** com.android.server.nearby.@0
-
# From filegroup framework-connectivity-protos
rule android.service.*Proto com.android.connectivity.@0
# From mdns-aidl-interface
rule android.net.mdns.aidl.** android.net.connectivity.@0
+# From nearby-service, including proto
+rule service.proto.** com.android.server.nearby.@0
+rule androidx.annotation.Keep* com.android.server.nearby.@0
+rule androidx.collection.** com.android.server.nearby.@0
+rule androidx.core.** com.android.server.nearby.@0
+rule androidx.versionedparcelable.** com.android.server.nearby.@0
+rule com.google.common.** com.android.server.nearby.@0
+
# Remaining are connectivity sources in com.android.server and com.android.server.connectivity:
# TODO: move to a subpackage of com.android.connectivity (such as com.android.connectivity.server)
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index 500c696..ba836b2 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -314,7 +314,11 @@
}
// TODO: use android::base::ScopeGuard.
- if (int ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK)) {
+ if (int ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK
+#ifdef POSIX_SPAWN_CLOEXEC_DEFAULT
+ | POSIX_SPAWN_CLOEXEC_DEFAULT
+#endif
+ )) {
posix_spawnattr_destroy(&attr);
throwIOException(env, "posix_spawnattr_setflags failed", ret);
return -1;
diff --git a/service/proguard.flags b/service/proguard.flags
index 2b20ddd..94397ab 100644
--- a/service/proguard.flags
+++ b/service/proguard.flags
@@ -4,14 +4,11 @@
-keep class android.net.** { *; }
-keep class com.android.connectivity.** { *; }
-keep class com.android.net.** { *; }
--keep class com.android.server.** { *; }
+-keep class !com.android.server.nearby.**,com.android.server.** { *; }
# Prevent proguard from stripping out any nearby-service and fast-pair-lite-protos fields.
-# TODO: This could be optimized in the future to only keep the critical
-# entry points and then let proguard strip out any unused code within
-# the service. "com.android.server.nearby.service.proto" must be kept to prevent proguard
-# from stripping out any fast-pair-lite-protos fields.
--keep class com.android.server.nearby.** { *; }
+-keep class com.android.server.nearby.NearbyService { *; }
+-keep class com.android.server.nearby.service.proto { *; }
# The lite proto runtime uses reflection to access fields based on the names in
# the schema, keep all the fields.
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 843d33d..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;
@@ -3431,6 +3432,10 @@
pw.increaseIndent();
nai.dumpInactivityTimers(pw);
pw.decreaseIndent();
+ pw.println("Nat464Xlat:");
+ pw.increaseIndent();
+ nai.dumpNat464Xlat(pw);
+ pw.decreaseIndent();
pw.decreaseIndent();
}
}
@@ -8088,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) {
@@ -10641,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.
@@ -10672,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 =
@@ -10817,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/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index cc81522..4a7c77a 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -36,6 +36,7 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap;
@@ -742,6 +743,69 @@
mClatdTracker = null;
}
+ private void dumpBpfIngress(@NonNull IndentingPrintWriter pw) {
+ if (mIngressMap == null) {
+ pw.println("No BPF ingress6 map");
+ return;
+ }
+
+ try {
+ if (mIngressMap.isEmpty()) {
+ pw.println("<empty>");
+ }
+ pw.println("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif");
+ pw.increaseIndent();
+ mIngressMap.forEach((k, v) -> {
+ // TODO: print interface name
+ pw.println(String.format("%d %s/96 %s -> %s %d", k.iif, k.pfx96, k.local6,
+ v.local4, v.oif));
+ });
+ pw.decreaseIndent();
+ } catch (ErrnoException e) {
+ pw.println("Error dumping BPF ingress6 map: " + e);
+ }
+ }
+
+ private void dumpBpfEgress(@NonNull IndentingPrintWriter pw) {
+ if (mEgressMap == null) {
+ pw.println("No BPF egress4 map");
+ return;
+ }
+
+ try {
+ if (mEgressMap.isEmpty()) {
+ pw.println("<empty>");
+ }
+ pw.println("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif");
+ pw.increaseIndent();
+ mEgressMap.forEach((k, v) -> {
+ // TODO: print interface name
+ pw.println(String.format("%d %s -> %s %s/96 %d %s", k.iif, k.local4, v.local6,
+ v.pfx96, v.oif, v.oifIsEthernet != 0 ? "ether" : "rawip"));
+ });
+ pw.decreaseIndent();
+ } catch (ErrnoException e) {
+ pw.println("Error dumping BPF egress4 map: " + e);
+ }
+ }
+
+ /**
+ * Dump the cordinator information.
+ *
+ * @param pw print writer.
+ */
+ public void dump(@NonNull IndentingPrintWriter pw) {
+ // TODO: dump ClatdTracker
+ // TODO: move map dump to a global place to avoid duplicate dump while there are two or
+ // more IPv6 only networks.
+ pw.println("Forwarding rules:");
+ pw.increaseIndent();
+ dumpBpfIngress(pw);
+ dumpBpfEgress(pw);
+ pw.decreaseIndent();
+ pw.println();
+ }
+
/**
* Get clatd tracker. For test only.
*/
diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java
index 35e02ca..e8fc06d 100644
--- a/service/src/com/android/server/connectivity/Nat464Xlat.java
+++ b/service/src/com/android/server/connectivity/Nat464Xlat.java
@@ -36,6 +36,7 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.NetworkStackConstants;
import com.android.server.ConnectivityService;
@@ -526,6 +527,24 @@
mNetwork.handler().post(() -> handleInterfaceRemoved(iface));
}
+ /**
+ * Dump the NAT64 xlat information.
+ *
+ * @param pw print writer.
+ */
+ public void dump(IndentingPrintWriter pw) {
+ if (SdkLevel.isAtLeastT()) {
+ if (isStarted()) {
+ pw.println("ClatCoordinator:");
+ pw.increaseIndent();
+ mClatCoordinator.dump(pw);
+ pw.decreaseIndent();
+ } else {
+ pw.println("<not start>");
+ }
+ }
+ }
+
@Override
public String toString() {
return "mBaseIface: " + mBaseIface + ", mIface: " + mIface + ", mState: " + mState;
diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
index 1fc5a8f..323888a 100644
--- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java
@@ -59,6 +59,7 @@
import android.util.Pair;
import android.util.SparseArray;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.WakeupMessage;
import com.android.modules.utils.build.SdkLevel;
import com.android.server.ConnectivityService;
@@ -1186,6 +1187,15 @@
}
/**
+ * Dump the NAT64 xlat information.
+ *
+ * @param pw print writer.
+ */
+ public void dumpNat464Xlat(IndentingPrintWriter pw) {
+ clatd.dump(pw);
+ }
+
+ /**
* Sets the most recent ConnectivityReport for this network.
*
* <p>This should only be called from the ConnectivityService thread.
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/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index eb7d1ea..e4a9ebe 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -916,7 +916,7 @@
final Intent intent = new Intent();
if (type == TYPE_COMPONENT_ACTIVTIY) {
intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_ACTIVITY_CLASS))
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
} else if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS))
.setFlags(1);
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
new file mode 100644
index 0000000..098f295
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.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.cts.net.hostside;
+
+
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getUiDevice;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
+import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.net.hostside.Property.DOZE_MODE;
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+@RequiredProperties({NON_METERED_NETWORK})
+public class ConnOnActivityStartTest extends AbstractRestrictBackgroundNetworkTestCase {
+ private static final int TEST_ITERATION_COUNT = 5;
+
+ @Before
+ public final void setUp() throws Exception {
+ super.setUp();
+ resetDeviceState();
+ }
+
+ @After
+ public final void tearDown() throws Exception {
+ super.tearDown();
+ resetDeviceState();
+ }
+
+ private void resetDeviceState() throws Exception {
+ resetBatteryState();
+ setBatterySaverMode(false);
+ setRestrictBackground(false);
+ setAppIdle(false);
+ setDozeMode(false);
+ }
+
+
+ @Test
+ @RequiredProperties({BATTERY_SAVER_MODE})
+ public void testStartActivity_batterySaver() throws Exception {
+ setBatterySaverMode(true);
+ assertLaunchedActivityHasNetworkAccess("testStartActivity_batterySaver");
+ }
+
+ @Test
+ @RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
+ public void testStartActivity_dataSaver() throws Exception {
+ setRestrictBackground(true);
+ assertLaunchedActivityHasNetworkAccess("testStartActivity_dataSaver");
+ }
+
+ @Test
+ @RequiredProperties({DOZE_MODE})
+ public void testStartActivity_doze() throws Exception {
+ setDozeMode(true);
+ assertLaunchedActivityHasNetworkAccess("testStartActivity_doze");
+ }
+
+ @Test
+ @RequiredProperties({APP_STANDBY_MODE})
+ public void testStartActivity_appStandby() throws Exception {
+ turnBatteryOn();
+ setAppIdle(true);
+ assertLaunchedActivityHasNetworkAccess("testStartActivity_appStandby");
+ }
+
+ private void assertLaunchedActivityHasNetworkAccess(String testName) throws Exception {
+ for (int i = 0; i < TEST_ITERATION_COUNT; ++i) {
+ Log.i(TAG, testName + " start #" + i);
+ launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
+ getUiDevice().pressHome();
+ assertBackgroundState();
+ Log.i(TAG, testName + " end #" + i);
+ }
+ }
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index 0a0f24b..7842eec 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -57,6 +57,7 @@
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
import com.android.compatibility.common.util.AppStandbyUtils;
import com.android.compatibility.common.util.BatteryUtils;
@@ -438,6 +439,10 @@
return InstrumentationRegistry.getInstrumentation();
}
+ public static UiDevice getUiDevice() {
+ return UiDevice.getInstance(getInstrumentation());
+ }
+
// When power saver mode or restrict background enabled or adding any white/black list into
// those modes, NetworkPolicy may need to take some time to update the rules of uids. So having
// this function and using PollingCheck to try to make sure the uid has updated and reduce the
diff --git a/tests/cts/hostside/app2/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 eb7dca7..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,38 +28,29 @@
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
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "MyActivity.onCreate()");
- 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);
- final RemoteCallback callback = getIntent().getParcelableExtra(
- Intent.EXTRA_REMOTE_CALLBACK);
- if (callback != null) {
- callback.sendResult(null);
- }
}
@Override
public void finish() {
- if (finishCommandReceiver != null) {
- unregisterReceiver(finishCommandReceiver);
+ synchronized (this) {
+ if (finishCommandReceiver != null) {
+ unregisterReceiver(finishCommandReceiver);
+ finishCommandReceiver = null;
+ }
}
super.finish();
}
@@ -71,6 +62,36 @@
}
@Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ Log.d(TAG, "MyActivity.onNewIntent()");
+ setIntent(intent);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Log.d(TAG, "MyActivity.onResume(): " + getIntent());
+ Common.notifyNetworkStateObserver(this, getIntent(), TYPE_COMPONENT_ACTIVTY);
+ 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) {
+ callback.sendResult(null);
+ }
+ }
+
+ @Override
protected void onDestroy() {
Log.d(TAG, "MyActivity.onDestroy()");
super.onDestroy();
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
new file mode 100644
index 0000000..3387fd7
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net;
+
+public class HostsideConnOnActivityStartTest extends HostsideNetworkTestCase {
+ private static final String TEST_CLASS = TEST_PKG + ".ConnOnActivityStartTest";
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ uninstallPackage(TEST_APP2_PKG, false);
+ installPackage(TEST_APP2_APK);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ uninstallPackage(TEST_APP2_PKG, true);
+ }
+
+ public void testStartActivity_batterySaver() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_batterySaver");
+ }
+
+ public void testStartActivity_dataSaver() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_dataSaver");
+ }
+
+ public void testStartActivity_doze() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_doze");
+ }
+
+ public void testStartActivity_appStandby() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_appStandby");
+ }
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
index f633df4..7a613b3 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
@@ -16,7 +16,6 @@
package com.android.cts.net;
-import android.platform.test.annotations.FlakyTest;
import android.platform.test.annotations.SecurityTest;
import com.android.ddmlib.Log;
@@ -155,7 +154,6 @@
"testBackgroundNetworkAccess_disabled");
}
- @FlakyTest(bugId=170180675)
public void testAppIdleMetered_whitelisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
@@ -186,7 +184,6 @@
"testBackgroundNetworkAccess_disabled");
}
- @FlakyTest(bugId=170180675)
public void testAppIdleNonMetered_whitelisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index f7a2421..04434e5 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -75,7 +75,7 @@
private val em by lazy { EthernetManagerShimImpl.newInstance(context) }
private val createdIfaces = ArrayList<EthernetTestInterface>()
- private val addedListeners = ArrayList<InterfaceStateListener>()
+ private val addedListeners = ArrayList<EthernetStateListener>()
private class EthernetTestInterface(
context: Context,
@@ -171,7 +171,7 @@
}
}
- private fun addInterfaceStateListener(executor: Executor, listener: InterfaceStateListener) {
+ private fun addInterfaceStateListener(executor: Executor, listener: EthernetStateListener) {
em.addInterfaceStateListener(executor, listener)
addedListeners.add(listener)
}
@@ -212,15 +212,25 @@
listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ // Register a new listener, it should see state of all existing interfaces immediately.
+ val listener2 = EthernetStateListener()
+ addInterfaceStateListener(executor, listener2)
+ listener2.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
+ listener2.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+
// Removing interfaces first sends link down, then STATE_ABSENT/ROLE_NONE.
removeInterface(iface)
- listener.expectCallback(iface, STATE_LINK_DOWN, ROLE_CLIENT)
- listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
+ for (listener in addedListeners) {
+ listener.expectCallback(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+ listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
+ }
removeInterface(iface2)
- listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
- listener.expectCallback(iface2, STATE_ABSENT, ROLE_NONE)
- listener.assertNoCallback()
+ for (listener in addedListeners) {
+ listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
+ listener.expectCallback(iface2, STATE_ABSENT, ROLE_NONE)
+ listener.assertNoCallback()
+ }
}
@Test
diff --git a/tests/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);
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
index 5f9d1ff..13a85e8 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -86,11 +86,19 @@
private static NetworkTemplate sTemplateImsi1 = buildTemplateMobileAll(IMSI_1);
private static NetworkTemplate sTemplateImsi2 = buildTemplateMobileAll(IMSI_2);
+ private static final int PID_SYSTEM = 1234;
+ private static final int PID_RED = 1235;
+ private static final int PID_BLUE = 1236;
+
private static final int UID_RED = UserHandle.PER_USER_RANGE + 1;
private static final int UID_BLUE = UserHandle.PER_USER_RANGE + 2;
private static final int UID_GREEN = UserHandle.PER_USER_RANGE + 3;
private static final int UID_ANOTHER_USER = 2 * UserHandle.PER_USER_RANGE + 4;
+ private static final String PACKAGE_SYSTEM = "android";
+ private static final String PACKAGE_RED = "RED";
+ private static final String PACKAGE_BLUE = "BLUE";
+
private static final long WAIT_TIMEOUT_MS = 500;
private static final long THRESHOLD_BYTES = 2 * MB_IN_BYTES;
private static final long BASE_BYTES = 7 * MB_IN_BYTES;
@@ -131,14 +139,15 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, thresholdTooLowBytes);
final DataUsageRequest requestByApp = mStatsObservers.register(mContext, inputRequest,
- mUsageCallback, UID_RED, NetworkStatsAccess.Level.DEVICE);
+ mUsageCallback, PID_RED , UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE);
assertTrue(requestByApp.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, requestByApp.template));
assertEquals(thresholdTooLowBytes, requestByApp.thresholdInBytes);
// Verify the threshold requested by system uid won't be overridden.
final DataUsageRequest requestBySystem = mStatsObservers.register(mContext, inputRequest,
- mUsageCallback, Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ mUsageCallback, PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM,
+ NetworkStatsAccess.Level.DEVICE);
assertTrue(requestBySystem.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, requestBySystem.template));
assertEquals(1, requestBySystem.thresholdInBytes);
@@ -151,7 +160,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, highThresholdBytes);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request.template));
assertEquals(highThresholdBytes, request.thresholdInBytes);
@@ -163,13 +172,13 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateWifi, THRESHOLD_BYTES);
DataUsageRequest request1 = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request1.requestId > 0);
assertTrue(Objects.equals(sTemplateWifi, request1.template));
assertEquals(THRESHOLD_BYTES, request1.thresholdInBytes);
DataUsageRequest request2 = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request2.requestId > request1.requestId);
assertTrue(Objects.equals(sTemplateWifi, request2.template));
assertEquals(THRESHOLD_BYTES, request2.thresholdInBytes);
@@ -189,7 +198,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -209,7 +218,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- UID_RED, NetworkStatsAccess.Level.DEVICE);
+ PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -237,7 +246,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -261,7 +270,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -291,7 +300,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- Process.SYSTEM_UID, NetworkStatsAccess.Level.DEVICE);
+ PID_SYSTEM, Process.SYSTEM_UID, PACKAGE_SYSTEM, NetworkStatsAccess.Level.DEVICE);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -322,7 +331,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- UID_RED, NetworkStatsAccess.Level.DEFAULT);
+ PID_RED, UID_RED, PACKAGE_SYSTEM , NetworkStatsAccess.Level.DEFAULT);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -355,7 +364,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- UID_BLUE, NetworkStatsAccess.Level.DEFAULT);
+ PID_BLUE, UID_BLUE, PACKAGE_BLUE, NetworkStatsAccess.Level.DEFAULT);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -387,7 +396,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- UID_BLUE, NetworkStatsAccess.Level.USER);
+ PID_BLUE, UID_BLUE, PACKAGE_BLUE, NetworkStatsAccess.Level.USER);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);
@@ -420,7 +429,7 @@
DataUsageRequest.REQUEST_ID_UNSET, sTemplateImsi1, THRESHOLD_BYTES);
DataUsageRequest request = mStatsObservers.register(mContext, inputRequest, mUsageCallback,
- UID_RED, NetworkStatsAccess.Level.USER);
+ PID_RED, UID_RED, PACKAGE_RED, NetworkStatsAccess.Level.USER);
assertTrue(request.requestId > 0);
assertTrue(Objects.equals(sTemplateImsi1, request.template));
assertEquals(THRESHOLD_BYTES, request.thresholdInBytes);