Merge "ethernet: rename IpClient callback handler functions" into main
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index cf9b359..d3b01ea 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -203,6 +203,8 @@
// result in a build failure due to inconsistent flags.
package_prefixes: [
"android.nearby.aidl",
+ "android.remoteauth.aidl",
+ "android.remoteauth",
"android.net.apf",
"android.net.connectivity",
"android.net.http.apihelpers",
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index 5e08aba..20f0bc6 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -25,11 +25,12 @@
],
min_sdk_version: "30",
static_libs: [
- "NetworkStackApiStableLib",
+ "DhcpPacketLib",
"androidx.test.rules",
"cts-net-utils",
"mockito-target-extended-minus-junit4",
"net-tests-utils",
+ "net-utils-device-common",
"net-utils-device-common-bpf",
"testables",
"connectivity-net-module-utils-bpf",
diff --git a/Tethering/tests/integration/base/android/net/TetheringTester.java b/Tethering/tests/integration/base/android/net/TetheringTester.java
index 3f3768e..4f3c6e7 100644
--- a/Tethering/tests/integration/base/android/net/TetheringTester.java
+++ b/Tethering/tests/integration/base/android/net/TetheringTester.java
@@ -74,6 +74,7 @@
import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.PacketBuilder;
import com.android.net.module.util.Struct;
+import com.android.net.module.util.arp.ArpPacket;
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Icmpv4Header;
import com.android.net.module.util.structs.Icmpv6Header;
@@ -85,7 +86,6 @@
import com.android.net.module.util.structs.RaHeader;
import com.android.net.module.util.structs.TcpHeader;
import com.android.net.module.util.structs.UdpHeader;
-import com.android.networkstack.arp.ArpPacket;
import com.android.testutils.TapPacketReader;
import java.net.Inet4Address;
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index 05b84c2..dacdaf2 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -43,6 +43,7 @@
":framework-connectivity-tiramisu-updatable-sources",
":framework-nearby-java-sources",
":framework-thread-sources",
+ ":framework-remoteauth-java-sources",
],
libs: [
"unsupportedappusage",
@@ -130,8 +131,10 @@
"android.net",
"android.net.nsd",
"android.nearby",
+ "android.remoteauth",
"com.android.connectivity",
"com.android.nearby",
+ "com.android.remoteauth",
],
hidden_api: {
@@ -153,6 +156,7 @@
"//packages/modules/Connectivity/service", // For R8 only
"//packages/modules/Connectivity/service-t",
"//packages/modules/Connectivity/nearby:__subpackages__",
+ "//packages/modules/Connectivity/remoteauth:__subpackages__",
"//frameworks/base",
// Tests using hidden APIs
diff --git a/framework-t/api/module-lib-current.txt b/framework-t/api/module-lib-current.txt
index 5a8d47b..42c83d8 100644
--- a/framework-t/api/module-lib-current.txt
+++ b/framework-t/api/module-lib-current.txt
@@ -207,3 +207,43 @@
}
+package android.remoteauth {
+
+ public interface DeviceDiscoveryCallback {
+ method public void onDeviceUpdate(@NonNull android.remoteauth.RemoteDevice, int);
+ method public void onTimeout();
+ field public static final int STATE_LOST = 0; // 0x0
+ field public static final int STATE_SEEN = 1; // 0x1
+ }
+
+ public final class RemoteAuthFrameworkInitializer {
+ method public static void registerServiceWrappers();
+ }
+
+ public class RemoteAuthManager {
+ method public boolean isRemoteAuthSupported();
+ method public boolean startDiscovery(int, @NonNull java.util.concurrent.Executor, @NonNull android.remoteauth.DeviceDiscoveryCallback);
+ method public void stopDiscovery(@NonNull android.remoteauth.DeviceDiscoveryCallback);
+ }
+
+ public final class RemoteDevice implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public int getConnectionId();
+ method @Nullable public String getName();
+ method public int getRegistrationState();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.remoteauth.RemoteDevice> CREATOR;
+ field public static final int STATE_NOT_REGISTERED = 0; // 0x0
+ field public static final int STATE_REGISTERED = 1; // 0x1
+ }
+
+ public static final class RemoteDevice.Builder {
+ ctor public RemoteDevice.Builder(int);
+ method @NonNull public android.remoteauth.RemoteDevice build();
+ method @NonNull public android.remoteauth.RemoteDevice.Builder setConnectionId(int);
+ method @NonNull public android.remoteauth.RemoteDevice.Builder setName(@Nullable String);
+ method @NonNull public android.remoteauth.RemoteDevice.Builder setRegistrationState(int);
+ }
+
+}
+
diff --git a/framework/Android.bp b/framework/Android.bp
index 8de8097..813e296 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -62,7 +62,6 @@
":framework-connectivity-sources",
":net-utils-framework-common-srcs",
":framework-connectivity-api-shared-srcs",
- ":framework-remoteauth-java-sources",
],
aidl: {
generate_get_transaction_name: true,
@@ -153,7 +152,6 @@
"//packages/modules/Connectivity/framework-t",
"//packages/modules/Connectivity/service",
"//packages/modules/Connectivity/service-t",
- "//packages/modules/Connectivity/remoteauth:__subpackages__",
"//frameworks/base/packages/Connectivity/service",
"//frameworks/base",
diff --git a/remoteauth/TEST_MAPPING b/remoteauth/TEST_MAPPING
index 5ad8da6..5061319 100644
--- a/remoteauth/TEST_MAPPING
+++ b/remoteauth/TEST_MAPPING
@@ -7,7 +7,7 @@
// TODO(b/193602229): uncomment once it's supported.
//"mainline-presubmit": [
// {
- // "name": "RemoteAuthUnitTests[com.google.android.tethering.apex]"
+ // "name": "RemoteAuthUnitTests[com.google.android.remoteauth.apex]"
// }
//]
}
diff --git a/remoteauth/framework/Android.bp b/remoteauth/framework/Android.bp
index 48d10b6..71b621a 100644
--- a/remoteauth/framework/Android.bp
+++ b/remoteauth/framework/Android.bp
@@ -25,7 +25,7 @@
],
path: "java",
visibility: [
- "//packages/modules/Connectivity/framework:__subpackages__",
+ "//packages/modules/Connectivity/framework-t:__subpackages__",
],
}
@@ -43,7 +43,13 @@
name: "framework-remoteauth-static",
srcs: [":framework-remoteauth-java-sources"],
sdk_version: "module_current",
- libs: [],
- static_libs: [],
+ libs: [
+ "androidx.annotation_annotation",
+ "framework-annotations-lib",
+ "framework-bluetooth",
+ ],
+ static_libs: [
+ "modules-utils-preconditions",
+ ],
visibility: ["//packages/modules/Connectivity/remoteauth/tests:__subpackages__"],
}
diff --git a/remoteauth/framework/java/android/remoteauth/DeviceDiscoveryCallback.java b/remoteauth/framework/java/android/remoteauth/DeviceDiscoveryCallback.java
new file mode 100644
index 0000000..f53e2dc
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/DeviceDiscoveryCallback.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 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.remoteauth;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Reports newly discovered remote devices.
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
+public interface DeviceDiscoveryCallback {
+ /** The device is no longer seen in the discovery process. */
+ int STATE_LOST = 0;
+ /** The device is seen in the discovery process */
+ int STATE_SEEN = 1;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({STATE_LOST, STATE_SEEN})
+ @interface State {}
+
+ /**
+ * Invoked for every change in remote device state.
+ *
+ * @param device remote device
+ * @param state indicates if found or lost
+ */
+ void onDeviceUpdate(@NonNull RemoteDevice device, @State int state);
+
+ /** Invoked when discovery is stopped due to timeout. */
+ void onTimeout();
+}
diff --git a/remoteauth/framework/java/android/remoteauth/IDeviceDiscoveryListener.aidl b/remoteauth/framework/java/android/remoteauth/IDeviceDiscoveryListener.aidl
new file mode 100644
index 0000000..2ad6a6a
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/IDeviceDiscoveryListener.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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.remoteauth;
+
+import android.remoteauth.RemoteDevice;
+
+/**
+ * Binder callback for DeviceDiscoveryCallback.
+ *
+ * {@hide}
+ */
+oneway interface IDeviceDiscoveryListener {
+ /** Reports a {@link RemoteDevice} being discovered. */
+ void onDiscovered(in RemoteDevice remoteDevice);
+
+ /** Reports a {@link RemoteDevice} is no longer within range. */
+ void onLost(in RemoteDevice remoteDevice);
+
+ /** Reports a timeout of {@link RemoteDevice} was reached. */
+ void onTimeout();
+}
diff --git a/remoteauth/framework/java/android/remoteauth/IRemoteAuthService.aidl b/remoteauth/framework/java/android/remoteauth/IRemoteAuthService.aidl
new file mode 100644
index 0000000..f4387e3
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/IRemoteAuthService.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 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.remoteauth;
+
+import android.remoteauth.IDeviceDiscoveryListener;
+
+/**
+ * Interface for communicating with the RemoteAuthService.
+ * These methods are all require MANAGE_REMOTE_AUTH signature permission.
+ * @hide
+ */
+interface IRemoteAuthService {
+ // This is protected by the MANAGE_REMOTE_AUTH signature permission.
+ boolean isRemoteAuthSupported();
+
+ // This is protected by the MANAGE_REMOTE_AUTH signature permission.
+ boolean registerDiscoveryListener(in IDeviceDiscoveryListener deviceDiscoveryListener,
+ int userId,
+ int timeoutMs,
+ String packageName,
+ @nullable String attributionTag);
+
+ // This is protected by the MANAGE_REMOTE_AUTH signature permission.
+ void unregisterDiscoveryListener(in IDeviceDiscoveryListener deviceDiscoveryListener,
+ int userId,
+ String packageName,
+ @nullable String attributionTag);
+}
\ No newline at end of file
diff --git a/remoteauth/framework/java/android/remoteauth/RemoteAuthFrameworkInitializer.java b/remoteauth/framework/java/android/remoteauth/RemoteAuthFrameworkInitializer.java
new file mode 100644
index 0000000..dfd7726
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/RemoteAuthFrameworkInitializer.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2023 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.remoteauth;
+
+import android.annotation.SystemApi;
+import android.app.SystemServiceRegistry;
+import android.content.Context;
+
+/**
+ * Class for initializing RemoteAuth service.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class RemoteAuthFrameworkInitializer {
+ private RemoteAuthFrameworkInitializer() {}
+
+ /**
+ * Called by {@link SystemServiceRegistry}'s static initializer and registers all Nearby
+ * services to {@link Context}, so that {@link Context#getSystemService} can return them.
+ *
+ * @throws IllegalStateException if this is called from anywhere besides {@link
+ * SystemServiceRegistry}
+ */
+ public static void registerServiceWrappers() {
+ // TODO(b/290092977): Change to Context.REMOTE_AUTH_SERVICE after aosp/2681375
+ // is automerges from aosp-main to udc-mainline-prod
+ SystemServiceRegistry.registerContextAwareService(
+ RemoteAuthManager.REMOTE_AUTH_SERVICE,
+ RemoteAuthManager.class,
+ (context, serviceBinder) -> {
+ IRemoteAuthService service = IRemoteAuthService.Stub.asInterface(serviceBinder);
+ return new RemoteAuthManager(context, service);
+ });
+ }
+}
diff --git a/remoteauth/framework/java/android/remoteauth/RemoteAuthManager.java b/remoteauth/framework/java/android/remoteauth/RemoteAuthManager.java
new file mode 100644
index 0000000..c025a55
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/RemoteAuthManager.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2023 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.remoteauth;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+import static android.remoteauth.DeviceDiscoveryCallback.STATE_LOST;
+import static android.remoteauth.DeviceDiscoveryCallback.STATE_SEEN;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+import java.util.WeakHashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * A system service providing a way to perform remote authentication-related operations such as
+ * discovering, registering and authenticating via remote authenticator.
+ *
+ * <p>To get a {@link RemoteAuthManager} instance, call the <code>
+ * Context.getSystemService(Context.REMOTE_AUTH_SERVICE)</code>.
+ *
+ * @hide
+ */
+@SystemApi(client = MODULE_LIBRARIES)
+// TODO(b/290092977): Change to Context.REMOTE_AUTH_SERVICE after aosp/2681375
+// is automerges from aosp-main to udc-mainline-prod
+@SystemService(RemoteAuthManager.REMOTE_AUTH_SERVICE)
+public class RemoteAuthManager {
+ private static final String TAG = "RemoteAuthManager";
+
+ /** @hide */
+ public static final String REMOTE_AUTH_SERVICE = "remote_auth";
+
+ private final Context mContext;
+ private final IRemoteAuthService mService;
+
+ @GuardedBy("mDiscoveryListeners")
+ private final WeakHashMap<
+ DeviceDiscoveryCallback, WeakReference<DeviceDiscoveryListenerTransport>>
+ mDiscoveryListeners = new WeakHashMap<>();
+
+ /** @hide */
+ public RemoteAuthManager(@NonNull Context context, @NonNull IRemoteAuthService service) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(service);
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Returns if this device can be enrolled in the feature.
+ *
+ * @return true if this device can be enrolled
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ // TODO(b/297301535): @RequiresPermission(MANAGE_REMOTE_AUTH)
+ public boolean isRemoteAuthSupported() {
+ try {
+ return mService.isRemoteAuthSupported();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Starts remote authenticator discovery process with timeout. Devices that are capable to
+ * operate as remote authenticators are reported via callback. The discovery stops by calling
+ * stopDiscovery or after a timeout.
+ *
+ * @param timeoutMs the duration in milliseconds after which discovery will stop automatically
+ * @param executor the callback will be executed in the executor thread
+ * @param callback to be used by the caller to get notifications about remote devices
+ * @return {@code true} if discovery began successfully, {@code false} otherwise
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ // TODO(b/297301535): @RequiresPermission(MANAGE_REMOTE_AUTH)
+ public boolean startDiscovery(
+ int timeoutMs,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull DeviceDiscoveryCallback callback) {
+ try {
+ Preconditions.checkNotNull(callback, "invalid null callback");
+ Preconditions.checkArgument(timeoutMs > 0, "invalid timeoutMs, must be > 0");
+ Preconditions.checkNotNull(executor, "invalid null executor");
+ DeviceDiscoveryListenerTransport transport;
+ synchronized (mDiscoveryListeners) {
+ WeakReference<DeviceDiscoveryListenerTransport> reference =
+ mDiscoveryListeners.get(callback);
+ transport = (reference != null) ? reference.get() : null;
+ if (transport == null) {
+ transport =
+ new DeviceDiscoveryListenerTransport(
+ callback, mContext.getUser().getIdentifier(), executor);
+ }
+
+ boolean result =
+ mService.registerDiscoveryListener(
+ transport,
+ mContext.getUser().getIdentifier(),
+ timeoutMs,
+ mContext.getPackageName(),
+ mContext.getAttributionTag());
+ if (result) {
+ mDiscoveryListeners.put(callback, new WeakReference<>(transport));
+ return true;
+ }
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ /**
+ * Removes this listener from device discovery notifications. The given callback is guaranteed
+ * not to receive any invocations that happen after this method is invoked.
+ *
+ * @param callback the callback for the previously started discovery to be ended
+ * @hide
+ */
+ // Suppressed lint: Registration methods should have overload that accepts delivery Executor.
+ // Already have executor in startDiscovery() method.
+ @SuppressLint("ExecutorRegistration")
+ @SystemApi(client = MODULE_LIBRARIES)
+ // TODO(b/297301535): @RequiresPermission(MANAGE_REMOTE_AUTH)
+ public void stopDiscovery(@NonNull DeviceDiscoveryCallback callback) {
+ Preconditions.checkNotNull(callback, "invalid null scanCallback");
+ try {
+ DeviceDiscoveryListenerTransport transport;
+ synchronized (mDiscoveryListeners) {
+ WeakReference<DeviceDiscoveryListenerTransport> reference =
+ mDiscoveryListeners.remove(callback);
+ transport = (reference != null) ? reference.get() : null;
+ }
+ if (transport != null) {
+ mService.unregisterDiscoveryListener(
+ transport,
+ transport.getUserId(),
+ mContext.getPackageName(),
+ mContext.getAttributionTag());
+ } else {
+ Log.d(
+ TAG,
+ "Cannot stop discovery with this callback "
+ + "because it is not registered.");
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private class DeviceDiscoveryListenerTransport extends IDeviceDiscoveryListener.Stub {
+
+ private volatile @NonNull DeviceDiscoveryCallback mDeviceDiscoveryCallback;
+ private Executor mExecutor;
+ private @UserIdInt int mUserId;
+
+ DeviceDiscoveryListenerTransport(
+ DeviceDiscoveryCallback deviceDiscoveryCallback,
+ @UserIdInt int userId,
+ @CallbackExecutor Executor executor) {
+ Preconditions.checkNotNull(deviceDiscoveryCallback, "invalid null callback");
+ mDeviceDiscoveryCallback = deviceDiscoveryCallback;
+ mUserId = userId;
+ mExecutor = executor;
+ }
+
+ @UserIdInt
+ int getUserId() {
+ return mUserId;
+ }
+
+ @Override
+ public void onDiscovered(RemoteDevice remoteDevice) throws RemoteException {
+ if (remoteDevice == null) {
+ Log.w(TAG, "onDiscovered is called with null device");
+ return;
+ }
+ Log.i(TAG, "Notifying the caller about discovered: " + remoteDevice);
+ mExecutor.execute(
+ () -> {
+ mDeviceDiscoveryCallback.onDeviceUpdate(remoteDevice, STATE_SEEN);
+ });
+ }
+
+ @Override
+ public void onLost(RemoteDevice remoteDevice) throws RemoteException {
+ if (remoteDevice == null) {
+ Log.w(TAG, "onLost is called with null device");
+ return;
+ }
+ Log.i(TAG, "Notifying the caller about lost: " + remoteDevice);
+ mExecutor.execute(
+ () -> {
+ mDeviceDiscoveryCallback.onDeviceUpdate(remoteDevice, STATE_LOST);
+ });
+ }
+
+ @Override
+ public void onTimeout() {
+ Log.i(TAG, "Notifying the caller about discovery timeout");
+ mExecutor.execute(
+ () -> {
+ mDeviceDiscoveryCallback.onTimeout();
+ });
+ synchronized (mDiscoveryListeners) {
+ mDiscoveryListeners.remove(mDeviceDiscoveryCallback);
+ }
+ mDeviceDiscoveryCallback = null;
+ }
+ }
+}
diff --git a/remoteauth/framework/java/android/remoteauth/RemoteDevice.aidl b/remoteauth/framework/java/android/remoteauth/RemoteDevice.aidl
new file mode 100644
index 0000000..ea38be2
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/RemoteDevice.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2023 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.remoteauth;
+
+parcelable RemoteDevice;
\ No newline at end of file
diff --git a/remoteauth/framework/java/android/remoteauth/RemoteDevice.java b/remoteauth/framework/java/android/remoteauth/RemoteDevice.java
new file mode 100644
index 0000000..4cd2399
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/RemoteDevice.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2023 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.remoteauth;
+
+import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Remote device that can be registered as remote authenticator.
+ *
+ * @hide
+ */
+// TODO(b/295407748) Change to use @DataClass
+@SystemApi(client = MODULE_LIBRARIES)
+public final class RemoteDevice implements Parcelable {
+ /** The remote device is not registered as remote authenticator. */
+ public static final int STATE_NOT_REGISTERED = 0;
+ /** The remote device is registered as remote authenticator. */
+ public static final int STATE_REGISTERED = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({STATE_NOT_REGISTERED, STATE_REGISTERED})
+ @interface RegistrationState {}
+
+ @NonNull private final String mName;
+ private final @RegistrationState int mRegistrationState;
+ private final int mConnectionId;
+
+ public static final @NonNull Creator<RemoteDevice> CREATOR =
+ new Creator<>() {
+ @Override
+ public RemoteDevice createFromParcel(Parcel in) {
+ RemoteDevice.Builder builder = new RemoteDevice.Builder();
+ builder.setName(in.readString());
+ builder.setRegistrationState(in.readInt());
+ builder.setConnectionId(in.readInt());
+
+ return builder.build();
+ }
+
+ @Override
+ public RemoteDevice[] newArray(int size) {
+ return new RemoteDevice[size];
+ }
+ };
+
+ private RemoteDevice(
+ @Nullable String name,
+ @RegistrationState int registrationState,
+ @NonNull int connectionId) {
+ this.mName = name;
+ this.mRegistrationState = registrationState;
+ this.mConnectionId = connectionId;
+ }
+
+ /** Gets the name of the {@link RemoteDevice} device. */
+ @Nullable
+ public String getName() {
+ return mName;
+ }
+
+ /** Returns registration state of the {@link RemoteDevice}. */
+ public @RegistrationState int getRegistrationState() {
+ return mRegistrationState;
+ }
+
+ /** Returns connection id of the {@link RemoteDevice}. */
+ @NonNull
+ public int getConnectionId() {
+ return mConnectionId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Returns a string representation of {@link RemoteDevice}. */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("RemoteDevice [");
+ sb.append("name=").append(mName).append(", ");
+ sb.append("registered=").append(mRegistrationState).append(", ");
+ sb.append("connectionId=").append(mConnectionId);
+ sb.append("]");
+ return sb.toString();
+ }
+
+ /** Returns true if this {@link RemoteDevice} object is equals to other. */
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof RemoteDevice) {
+ RemoteDevice otherDevice = (RemoteDevice) other;
+ return Objects.equals(this.mName, otherDevice.mName)
+ && this.getRegistrationState() == otherDevice.getRegistrationState()
+ && this.mConnectionId == otherDevice.mConnectionId;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mName, mRegistrationState, mConnectionId);
+ }
+
+ /**
+ * Helper function for writing {@link RemoteDevice} to a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ String name = getName();
+ dest.writeString(name);
+ dest.writeInt(getRegistrationState());
+ dest.writeInt(getConnectionId());
+ }
+
+ /** Builder for {@link RemoteDevice} objects. */
+ public static final class Builder {
+ @Nullable private String mName;
+ // represents if device is already registered
+ private @RegistrationState int mRegistrationState;
+ private int mConnectionId;
+
+ private Builder() {
+ }
+
+ public Builder(final int connectionId) {
+ this.mConnectionId = connectionId;
+ }
+
+ /**
+ * Sets the name of the {@link RemoteDevice} device.
+ *
+ * @param name of the {@link RemoteDevice}. Can be {@code null} if there is no name.
+ */
+ @NonNull
+ public RemoteDevice.Builder setName(@Nullable String name) {
+ this.mName = name;
+ return this;
+ }
+
+ /**
+ * Sets the registration state of the {@link RemoteDevice} device.
+ *
+ * @param registrationState of the {@link RemoteDevice}.
+ */
+ @NonNull
+ public RemoteDevice.Builder setRegistrationState(@RegistrationState int registrationState) {
+ this.mRegistrationState = registrationState;
+ return this;
+ }
+
+ /**
+ * Sets the connectionInfo of the {@link RemoteDevice} device.
+ *
+ * @param connectionId of the RemoteDevice.
+ */
+ @NonNull
+ public RemoteDevice.Builder setConnectionId(int connectionId) {
+ this.mConnectionId = connectionId;
+ return this;
+ }
+
+ /**
+ * Creates the {@link RemoteDevice} instance.
+ *
+ * @return the configured {@link RemoteDevice} instance.
+ */
+ @NonNull
+ public RemoteDevice build() {
+ return new RemoteDevice(mName, mRegistrationState, mConnectionId);
+ }
+ }
+}
diff --git a/remoteauth/service/Android.bp b/remoteauth/service/Android.bp
index 5c5a2fb..c3a9fb3 100644
--- a/remoteauth/service/Android.bp
+++ b/remoteauth/service/Android.bp
@@ -29,8 +29,24 @@
defaults: [
"framework-system-server-module-defaults"
],
- libs: [],
- static_libs: [],
+ libs: [
+ "androidx.annotation_annotation",
+ "framework-bluetooth",
+ "error_prone_annotations",
+ "framework-configinfrastructure",
+ "framework-connectivity-pre-jarjar",
+ "framework-connectivity-t-pre-jarjar",
+ "framework-statsd",
+ ],
+ static_libs: [
+ "libprotobuf-java-lite",
+ "fast-pair-lite-protos",
+ "modules-utils-build",
+ "modules-utils-handlerexecutor",
+ "modules-utils-preconditions",
+ "modules-utils-backgroundthread",
+ "presence-lite-protos",
+ ],
sdk_version: "system_server_current",
// This is included in service-connectivity which is 30+
// TODO (b/293613362): allow APEXes to have service jars with higher min_sdk than the APEX
diff --git a/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java b/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java
new file mode 100644
index 0000000..41ce89a
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2023 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.remoteauth;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.remoteauth.IDeviceDiscoveryListener;
+import android.remoteauth.IRemoteAuthService;
+
+import com.android.internal.util.Preconditions;
+
+/** Service implementing remoteauth functionality. */
+public class RemoteAuthService extends IRemoteAuthService.Stub {
+ public static final String TAG = "RemoteAuthService";
+
+ public RemoteAuthService(Context context) {
+ Preconditions.checkNotNull(context);
+ // TODO(b/290280702): Create here RemoteConnectivityManager and RangingManager
+ }
+
+ @Override
+ public boolean isRemoteAuthSupported() {
+ // TODO(b/297301535): checkPermission(mContext, MANAGE_REMOTE_AUTH);
+ // TODO(b/290676192): integrate with RangingManager
+ // (check if UWB is supported by this device)
+ return true;
+ }
+
+ @Override
+ public boolean registerDiscoveryListener(
+ IDeviceDiscoveryListener deviceDiscoveryListener,
+ @UserIdInt int userId,
+ int timeoutMs,
+ String packageName,
+ @Nullable String attributionTag) {
+ // TODO(b/297301535): checkPermission(mContext, MANAGE_REMOTE_AUTH);
+ // TODO(b/290280702): implement register discovery logic
+ return true;
+ }
+
+ @Override
+ public void unregisterDiscoveryListener(
+ IDeviceDiscoveryListener deviceDiscoveryListener,
+ @UserIdInt int userId,
+ String packageName,
+ @Nullable String attributionTag) {
+ // TODO(b/297301535): checkPermission(mContext, MANAGE_REMOTE_AUTH);
+ // TODO(b/290094221): implement unregister logic
+ }
+
+ private static void checkPermission(Context context, String permission) {
+ context.enforceCallingOrSelfPermission(permission,
+ "Must have " + permission + " permission.");
+ }
+}
diff --git a/remoteauth/tests/unit/Android.bp b/remoteauth/tests/unit/Android.bp
index 8c08a1b..4b92d84 100644
--- a/remoteauth/tests/unit/Android.bp
+++ b/remoteauth/tests/unit/Android.bp
@@ -37,6 +37,7 @@
"androidx.test.rules",
"framework-remoteauth-static",
"junit",
+ "libprotobuf-java-lite",
"platform-test-annotations",
"service-remoteauth-pre-jarjar",
"truth-prebuilt",
diff --git a/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java b/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java
index 5cf3e6b..6b43355 100644
--- a/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java
+++ b/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java
@@ -23,6 +23,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,6 +32,9 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RemoteAuthManagerTest {
+ @Before
+ public void setUp() {}
+
@Test
public void testStub() {
assertTrue(true);
diff --git a/service-t/Android.bp b/service-t/Android.bp
index ce5bb92..83caf35 100644
--- a/service-t/Android.bp
+++ b/service-t/Android.bp
@@ -56,6 +56,7 @@
"service-connectivity-pre-jarjar",
"service-nearby-pre-jarjar",
"service-thread-pre-jarjar",
+ "service-remoteauth-pre-jarjar",
"ServiceConnectivityResources",
"unsupportedappusage",
],
diff --git a/service-t/src/com/android/server/ConnectivityServiceInitializer.java b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
index 626c2eb..2da067a 100644
--- a/service-t/src/com/android/server/ConnectivityServiceInitializer.java
+++ b/service-t/src/com/android/server/ConnectivityServiceInitializer.java
@@ -17,6 +17,7 @@
package com.android.server;
import android.content.Context;
+import android.remoteauth.RemoteAuthManager;
import android.util.Log;
import com.android.modules.utils.build.SdkLevel;
@@ -25,6 +26,7 @@
import com.android.server.ethernet.EthernetService;
import com.android.server.ethernet.EthernetServiceImpl;
import com.android.server.nearby.NearbyService;
+import com.android.server.remoteauth.RemoteAuthService;
/**
* Connectivity service initializer for core networking. This is called by system server to create
@@ -38,6 +40,7 @@
private final NsdService mNsdService;
private final NearbyService mNearbyService;
private final EthernetServiceImpl mEthernetServiceImpl;
+ private final RemoteAuthService mRemoteAuthService;
public ConnectivityServiceInitializer(Context context) {
super(context);
@@ -49,6 +52,7 @@
mConnectivityNative = createConnectivityNativeService(context);
mNsdService = createNsdService(context);
mNearbyService = createNearbyService(context);
+ mRemoteAuthService = createRemoteAuthService(context);
}
@Override
@@ -85,6 +89,11 @@
/* allowIsolated= */ false);
}
+ if (mRemoteAuthService != null) {
+ Log.i(TAG, "Registering " + RemoteAuthManager.REMOTE_AUTH_SERVICE);
+ publishBinderService(RemoteAuthManager.REMOTE_AUTH_SERVICE, mRemoteAuthService,
+ /* allowIsolated= */ false);
+ }
}
@Override
@@ -140,6 +149,20 @@
}
}
+ /** Return RemoteAuth service instance */
+ private RemoteAuthService createRemoteAuthService(final Context context) {
+ if (!SdkLevel.isAtLeastV()) return null;
+ try {
+ return new RemoteAuthService(context);
+ } catch (UnsupportedOperationException e) {
+ // RemoteAuth is not yet supported in all branches
+ // TODO: remove catch clause when it is available.
+ Log.i(TAG, "Skipping unsupported service "
+ + RemoteAuthManager.REMOTE_AUTH_SERVICE);
+ return null;
+ }
+ }
+
/**
* Return EthernetServiceImpl instance or null if current SDK is lower than T or Ethernet
* service isn't necessary.
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index ab5024c..04d0b93 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -318,7 +318,6 @@
import java.io.InterruptedIOException;
import java.io.PrintWriter;
import java.io.Writer;
-import java.lang.IllegalArgumentException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -11108,16 +11107,20 @@
@Override
public void onInterfaceLinkStateChanged(@NonNull String iface, boolean up) {
- for (NetworkAgentInfo nai : mNetworkAgentInfos) {
- nai.clatd.interfaceLinkStateChanged(iface, up);
- }
+ mHandler.post(() -> {
+ for (NetworkAgentInfo nai : mNetworkAgentInfos) {
+ nai.clatd.interfaceLinkStateChanged(iface, up);
+ }
+ });
}
@Override
public void onInterfaceRemoved(@NonNull String iface) {
- for (NetworkAgentInfo nai : mNetworkAgentInfos) {
- nai.clatd.interfaceRemoved(iface);
- }
+ mHandler.post(() -> {
+ for (NetworkAgentInfo nai : mNetworkAgentInfos) {
+ nai.clatd.interfaceRemoved(iface);
+ }
+ });
}
}
diff --git a/tests/common/java/android/net/KeepalivePacketDataTest.kt b/tests/common/java/android/net/KeepalivePacketDataTest.kt
index 92ffc5c..403d6b5 100644
--- a/tests/common/java/android/net/KeepalivePacketDataTest.kt
+++ b/tests/common/java/android/net/KeepalivePacketDataTest.kt
@@ -22,6 +22,7 @@
import androidx.test.runner.AndroidJUnit4
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.NonNullTestUtils
import java.net.InetAddress
import java.util.Arrays
import org.junit.Assert.assertEquals
@@ -50,34 +51,47 @@
// Add for test because constructor of KeepalivePacketData is protected.
private inner class TestKeepalivePacketData(
- srcAddress: InetAddress = TEST_SRC_ADDRV4,
+ srcAddress: InetAddress? = TEST_SRC_ADDRV4,
srcPort: Int = TEST_SRC_PORT,
- dstAddress: InetAddress = TEST_DST_ADDRV4,
+ dstAddress: InetAddress? = TEST_DST_ADDRV4,
dstPort: Int = TEST_DST_PORT,
data: ByteArray = TESTBYTES
- ) : KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, data)
+ ) : KeepalivePacketData(NonNullTestUtils.nullUnsafe(srcAddress), srcPort,
+ NonNullTestUtils.nullUnsafe(dstAddress), dstPort, data)
@Test
@IgnoreUpTo(Build.VERSION_CODES.Q)
fun testConstructor() {
- var data: TestKeepalivePacketData
+ try {
+ TestKeepalivePacketData(srcAddress = null)
+ fail("Null src address should cause exception")
+ } catch (e: InvalidPacketException) {
+ assertEquals(e.error, ERROR_INVALID_IP_ADDRESS)
+ }
try {
- data = TestKeepalivePacketData(dstAddress = TEST_ADDRV6)
+ TestKeepalivePacketData(dstAddress = null)
+ fail("Null dst address should cause exception")
+ } catch (e: InvalidPacketException) {
+ assertEquals(e.error, ERROR_INVALID_IP_ADDRESS)
+ }
+
+ try {
+ TestKeepalivePacketData(dstAddress = TEST_ADDRV6)
fail("Ip family mismatched should cause exception")
} catch (e: InvalidPacketException) {
assertEquals(e.error, ERROR_INVALID_IP_ADDRESS)
}
try {
- data = TestKeepalivePacketData(srcPort = INVALID_PORT)
+ TestKeepalivePacketData(srcPort = INVALID_PORT)
fail("Invalid srcPort should cause exception")
} catch (e: InvalidPacketException) {
assertEquals(e.error, ERROR_INVALID_PORT)
}
try {
- data = TestKeepalivePacketData(dstPort = INVALID_PORT)
+ TestKeepalivePacketData(dstPort = INVALID_PORT)
fail("Invalid dstPort should cause exception")
} catch (e: InvalidPacketException) {
assertEquals(e.error, ERROR_INVALID_PORT)
diff --git a/tests/cts/net/src/android/net/cts/NetworkInfoTest.kt b/tests/cts/net/src/android/net/cts/NetworkInfoTest.kt
index d8ec761..499d97f 100644
--- a/tests/cts/net/src/android/net/cts/NetworkInfoTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkInfoTest.kt
@@ -16,12 +16,12 @@
package android.net.cts
-import android.os.Build
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkInfo
import android.net.NetworkInfo.DetailedState
import android.net.NetworkInfo.State
+import android.os.Build
import android.telephony.TelephonyManager
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
@@ -29,16 +29,17 @@
import com.android.modules.utils.build.SdkLevel
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.NonNullTestUtils
+import kotlin.reflect.jvm.isAccessible
+import kotlin.test.assertFails
+import kotlin.test.assertFailsWith
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Rule
-import org.junit.runner.RunWith
import org.junit.Test
-import kotlin.reflect.jvm.isAccessible
-import kotlin.test.assertFails
-import kotlin.test.assertFailsWith
+import org.junit.runner.RunWith
const val TYPE_MOBILE = ConnectivityManager.TYPE_MOBILE
const val TYPE_WIFI = ConnectivityManager.TYPE_WIFI
@@ -104,6 +105,15 @@
NetworkInfo(ConnectivityManager.MAX_NETWORK_TYPE + 1,
TelephonyManager.NETWORK_TYPE_LTE, MOBILE_TYPE_NAME, LTE_SUBTYPE_NAME)
}
+
+ if (SdkLevel.isAtLeastT()) {
+ assertFailsWith<NullPointerException> {
+ NetworkInfo(NonNullTestUtils.nullUnsafe<NetworkInfo>(null))
+ }
+ } else {
+ // Doesn't immediately crash on S-
+ NetworkInfo(NonNullTestUtils.nullUnsafe<NetworkInfo>(null))
+ }
}
@Test
@@ -126,11 +136,23 @@
constructor.isAccessible = true
val incorrectDetailedState = constructor.newInstance("any", 200) as DetailedState
if (SdkLevel.isAtLeastT()) {
+ assertFailsWith<NullPointerException> {
+ NetworkInfo(NonNullTestUtils.nullUnsafe<NetworkInfo>(null))
+ }
+ assertFailsWith<NullPointerException> {
+ networkInfo.setDetailedState(NonNullTestUtils.nullUnsafe<DetailedState>(null),
+ "reason", "extraInfo")
+ }
// This actually throws ArrayOutOfBoundsException because of the implementation of
// EnumMap, but that's an implementation detail so accept any crash.
assertFails {
networkInfo.setDetailedState(incorrectDetailedState, "reason", "extraInfo")
}
+ } else {
+ // Doesn't immediately crash on S-
+ NetworkInfo(NonNullTestUtils.nullUnsafe<NetworkInfo>(null))
+ networkInfo.setDetailedState(NonNullTestUtils.nullUnsafe<DetailedState>(null),
+ "reason", "extraInfo")
}
}
}
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index f0da89f..a8414ca 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -49,6 +49,7 @@
import android.telephony.TelephonyManager
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.NonNullTestUtils
import com.android.testutils.assertParcelSane
import kotlin.test.assertEquals
import kotlin.test.assertFalse
@@ -218,6 +219,19 @@
templateNullWifiKey.assertDoesNotMatch(identWifiNullKey)
}
+ @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.TIRAMISU)
+ @Test
+ fun testBuildTemplateMobileAll_nullSubscriberId() {
+ val templateMobileAllWithNullImsi =
+ buildTemplateMobileAll(NonNullTestUtils.nullUnsafe<String>(null))
+ val setWithNull = HashSet<String?>().apply {
+ add(null)
+ }
+ val templateFromBuilder = NetworkTemplate.Builder(MATCH_MOBILE).setMeteredness(METERED_YES)
+ .setSubscriberIds(setWithNull).build()
+ assertEquals(templateFromBuilder, templateMobileAllWithNullImsi)
+ }
+
@Test
fun testMobileMatches() {
val templateMobileImsi1 = buildTemplateMobileAll(TEST_IMSI1)