Merge "[Thread] move Thread into com.android.tethering" into main
diff --git a/OWNERS_core_networking_xts b/OWNERS_core_networking_xts
index 1844334..7612210 100644
--- a/OWNERS_core_networking_xts
+++ b/OWNERS_core_networking_xts
@@ -4,4 +4,6 @@
 # For cherry-picks of CLs that are already merged in aosp/master, or flaky test fixes.
 jchalard@google.com #{LAST_RESORT_SUGGESTION}
 maze@google.com #{LAST_RESORT_SUGGESTION}
+# In addition to cherry-picks and flaky test fixes, also for incremental changes on NsdManager tests
+# to increase coverage for existing behavior, and testing of bug fixes in NsdManager
 reminv@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index b0aa668..fe70820 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -112,8 +112,8 @@
      * config_tether_upstream_automatic when set to true.
      *
      * This flag is enabled if !=0 and less than the module APEX version: see
-     * {@link DeviceConfigUtils#isFeatureEnabled}. It is also ignored after R, as later devices
-     * should just set config_tether_upstream_automatic to true instead.
+     * {@link DeviceConfigUtils#isTetheringFeatureEnabled}. It is also ignored after R, as later
+     * devices should just set config_tether_upstream_automatic to true instead.
      */
     public static final String TETHER_FORCE_UPSTREAM_AUTOMATIC_VERSION =
             "tether_force_upstream_automatic_version";
@@ -181,7 +181,7 @@
     public static class Dependencies {
         boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
                 @NonNull String name, @NonNull String moduleName, boolean defaultEnabled) {
-            return DeviceConfigUtils.isFeatureEnabled(context, namespace, name,
+            return DeviceConfigUtils.isTetheringFeatureEnabled(context, namespace, name,
                     moduleName, defaultEnabled);
         }
 
diff --git a/framework-t/src/android/net/TrafficStats.java b/framework-t/src/android/net/TrafficStats.java
index dc4ac55..a69b38d 100644
--- a/framework-t/src/android/net/TrafficStats.java
+++ b/framework-t/src/android/net/TrafficStats.java
@@ -683,33 +683,13 @@
     /** {@hide} */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static long getMobileTcpRxPackets() {
-        long total = 0;
-        for (String iface : getMobileIfaces()) {
-            long stat = UNSUPPORTED;
-            try {
-                stat = getStatsService().getIfaceStats(iface, TYPE_TCP_RX_PACKETS);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-            total += addIfSupported(stat);
-        }
-        return total;
+        return UNSUPPORTED;
     }
 
     /** {@hide} */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public static long getMobileTcpTxPackets() {
-        long total = 0;
-        for (String iface : getMobileIfaces()) {
-            long stat = UNSUPPORTED;
-            try {
-                stat = getStatsService().getIfaceStats(iface, TYPE_TCP_TX_PACKETS);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-            total += addIfSupported(stat);
-        }
-        return total;
+        return UNSUPPORTED;
     }
 
     /**
@@ -1141,8 +1121,4 @@
     public static final int TYPE_TX_BYTES = 2;
     /** {@hide} */
     public static final int TYPE_TX_PACKETS = 3;
-    /** {@hide} */
-    public static final int TYPE_TCP_RX_PACKETS = 4;
-    /** {@hide} */
-    public static final int TYPE_TCP_TX_PACKETS = 5;
 }
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index d239277..73feee4 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -210,8 +210,8 @@
     };
     auto configuration = mConfigurationMap.readValue(CURRENT_STATS_MAP_CONFIGURATION_KEY);
     if (!configuration.ok()) {
-        ALOGE("Failed to get current configuration: %s, fd: %d",
-              strerror(configuration.error().code()), mConfigurationMap.getMap().get());
+        ALOGE("Failed to get current configuration: %s",
+              strerror(configuration.error().code()));
         return -configuration.error().code();
     }
     if (configuration.value() != SELECT_MAP_A && configuration.value() != SELECT_MAP_B) {
@@ -224,7 +224,7 @@
     // HACK: mStatsMapB becomes RW BpfMap here, but countUidStatsEntries doesn't modify so it works
     base::Result<void> res = currentMap.iterate(countUidStatsEntries);
     if (!res.ok()) {
-        ALOGE("Failed to count the stats entry in map %d: %s", currentMap.getMap().get(),
+        ALOGE("Failed to count the stats entry in map: %s",
               strerror(res.error().code()));
         return -res.error().code();
     }
@@ -243,8 +243,7 @@
     // should be fine to concurrently update the map while eBPF program is running.
     res = mCookieTagMap.writeValue(sock_cookie, newKey, BPF_ANY);
     if (!res.ok()) {
-        ALOGE("Failed to tag the socket: %s, fd: %d", strerror(res.error().code()),
-              mCookieTagMap.getMap().get());
+        ALOGE("Failed to tag the socket: %s", strerror(res.error().code()));
         return -res.error().code();
     }
     ALOGD("Socket with cookie %" PRIu64 " tagged successfully with tag %" PRIu32 " uid %u "
diff --git a/remoteauth/README.md b/remoteauth/README.md
index 384fcf7..f28154d 100644
--- a/remoteauth/README.md
+++ b/remoteauth/README.md
@@ -23,7 +23,6 @@
 
 ### AIDEGen
 
-AIDEGen is deprecated, prefer ASfP [go/asfp](http://go/asfp)
 ```sh
 $ source build/envsetup.sh && lunch <TARGET>
 $ cd packages/modules/Connectivity
@@ -31,17 +30,13 @@
 # This will launch Intellij project for RemoteAuth module.
 ```
 
-### ASfP
-
-See full instructions for ASfP at [go/asfp-getting-started](http://go/asfp-getting-started)
-
 ## Build and Install
 
 ```sh
 $ source build/envsetup.sh && lunch <TARGET>
-$ m com.google.android.tethering deapexer
+$ m com.android.tethering deapexer
 $ $ANDROID_BUILD_TOP/out/host/linux-x86/bin/deapexer decompress --input \
-    ${ANDROID_PRODUCT_OUT}/system/apex/com.google.android.tethering.capex \
+    ${ANDROID_PRODUCT_OUT}/system/apex/com.android.tethering.capex \
     --output /tmp/tethering.apex
 $ adb install -r /tmp/tethering.apex
 ```
diff --git a/remoteauth/service/java/com/android/server/remoteauth/README.md b/remoteauth/service/java/com/android/server/remoteauth/README.md
index 423ab45..2f8b096 100644
--- a/remoteauth/service/java/com/android/server/remoteauth/README.md
+++ b/remoteauth/service/java/com/android/server/remoteauth/README.md
@@ -1 +1,4 @@
-This is the source root for the RemoteAuthService
\ No newline at end of file
+This is the source root for the RemoteAuthService
+
+## Remote connectivity manager
+Provides the connectivity manager to manage connections with the peer device.
diff --git a/remoteauth/service/java/com/android/server/remoteauth/connectivity/CdmConnectionInfo.java b/remoteauth/service/java/com/android/server/remoteauth/connectivity/CdmConnectionInfo.java
new file mode 100644
index 0000000..8bfdd36
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/connectivity/CdmConnectionInfo.java
@@ -0,0 +1,108 @@
+/*
+ * 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 com.android.server.remoteauth.connectivity;
+
+import android.annotation.NonNull;
+import android.annotation.TargetApi;
+import android.companion.AssociationInfo;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Encapsulates the connection information for companion device manager connections.
+ *
+ * <p>Connection information captures the details of underlying connection such as connection id,
+ * type of connection and peer device mac address.
+ */
+// TODO(b/295407748): Change to use @DataClass.
+// TODO(b/296625303): Change to VANILLA_ICE_CREAM when AssociationInfo is available in V.
+@TargetApi(Build.VERSION_CODES.TIRAMISU)
+public final class CdmConnectionInfo extends ConnectionInfo {
+    @NonNull private final AssociationInfo mAssociationInfo;
+
+    public CdmConnectionInfo(int connectionId, @NonNull AssociationInfo associationInfo) {
+       super(connectionId);
+        mAssociationInfo = associationInfo;
+    }
+
+    private CdmConnectionInfo(@NonNull Parcel in) {
+        super(in);
+        mAssociationInfo = in.readTypedObject(AssociationInfo.CREATOR);
+    }
+
+    /** Used to read CdmConnectionInfo from a Parcel */
+    @NonNull
+    public static final Parcelable.Creator<CdmConnectionInfo> CREATOR =
+            new Parcelable.Creator<CdmConnectionInfo>() {
+                public CdmConnectionInfo createFromParcel(@NonNull Parcel in) {
+                    return new CdmConnectionInfo(in);
+                }
+
+                public CdmConnectionInfo[] newArray(int size) {
+                    return new CdmConnectionInfo[size];
+                }
+            };
+
+    /** No special parcel contents. */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Flatten this CdmConnectionInfo in to a Parcel.
+     *
+     * @param out 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 out, int flags) {
+        super.writeToParcel(out, flags);
+        out.writeTypedObject(mAssociationInfo, 0);
+    }
+
+    public AssociationInfo getAssociationInfo() {
+        return mAssociationInfo;
+    }
+
+    /** Returns a string representation of ConnectionInfo. */
+    @Override
+    public String toString() {
+        return super.toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || !(o instanceof CdmConnectionInfo)) {
+            return false;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+
+        CdmConnectionInfo other = (CdmConnectionInfo) o;
+        return mAssociationInfo.equals(other.getAssociationInfo());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAssociationInfo);
+    }
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/connectivity/Connection.java b/remoteauth/service/java/com/android/server/remoteauth/connectivity/Connection.java
new file mode 100644
index 0000000..eb5458d
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/connectivity/Connection.java
@@ -0,0 +1,86 @@
+/*
+ * 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 com.android.server.remoteauth.connectivity;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A connection with the peer device.
+ *
+ * <p>Connections are used to exchange data with the peer device.
+ *
+ */
+public interface Connection {
+    /** Unknown error. */
+    int ERROR_UNKNOWN = 0;
+
+    /** Message was sent successfully. */
+    int ERROR_OK = 1;
+
+    /** Timeout occurred while waiting for response from the peer. */
+    int ERROR_DEADLINE_EXCEEDED = 2;
+
+    /** Device became unavailable while sending the message. */
+    int ERROR_DEVICE_UNAVAILABLE = 3;
+
+    /** Represents error code. */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ERROR_UNKNOWN, ERROR_OK, ERROR_DEADLINE_EXCEEDED, ERROR_DEVICE_UNAVAILABLE})
+    @interface ErrorCode {}
+
+    /**
+     * Callback for clients to get the response of sendRequest. {@link onSuccess} is called if the
+     * peer device responds with Status::OK, otherwise runs the {@link onFailure} callback.
+     */
+    abstract class MessageResponseCallback {
+        /**
+         * Called on a success.
+         *
+         * @param buffer response from the device.
+         */
+        public void onSuccess(byte[] buffer) {}
+
+        /**
+         * Called when message sending fails.
+         *
+         * @param errorCode indicating the error.
+         */
+        public void onFailure(@ErrorCode int errorCode) {}
+    }
+
+    /**
+     * Sends a request to the peer.
+     *
+     * @param request byte array to be sent to the peer device.
+     * @param messageResponseCallback callback to be run when the peer response is received or if an
+     *     error occurred.
+     */
+    void sendRequest(byte[] request, MessageResponseCallback messageResponseCallback);
+
+    /** Triggers a disconnect from the peer device. */
+    void disconnect();
+
+    /**
+     * Returns the connection information.
+     *
+     * @return A connection information object.
+     */
+    ConnectionInfo getConnectionInfo();
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/connectivity/ConnectionException.java b/remoteauth/service/java/com/android/server/remoteauth/connectivity/ConnectionException.java
new file mode 100644
index 0000000..a8c7860
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/connectivity/ConnectionException.java
@@ -0,0 +1,56 @@
+/*
+ * 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 com.android.server.remoteauth.connectivity;
+
+import static com.android.server.remoteauth.connectivity.ConnectivityManager.ReasonCode;
+
+import android.annotation.Nullable;
+
+/** Exception that signals that the connection request failed. */
+public final class ConnectionException extends RuntimeException {
+    private final @ReasonCode int mReasonCode;
+
+    public ConnectionException(@ReasonCode int reasonCode) {
+        super();
+        this.mReasonCode = reasonCode;
+    }
+
+    public ConnectionException(@ReasonCode int reasonCode, @Nullable String message) {
+        super(message);
+        this.mReasonCode = reasonCode;
+    }
+
+    public ConnectionException(@ReasonCode int reasonCode, @Nullable Throwable cause) {
+        super(cause);
+        this.mReasonCode = reasonCode;
+    }
+
+    public ConnectionException(
+            @ReasonCode int reasonCode, @Nullable String message, @Nullable Throwable cause) {
+        super(message, cause);
+        this.mReasonCode = reasonCode;
+    }
+
+    public @ReasonCode int getReasonCode() {
+        return this.mReasonCode;
+    }
+
+    @Override
+    public String getMessage() {
+        return super.getMessage() + " Reason code: " + this.mReasonCode;
+    }
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/connectivity/ConnectionInfo.java b/remoteauth/service/java/com/android/server/remoteauth/connectivity/ConnectionInfo.java
new file mode 100644
index 0000000..39bfa8d
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/connectivity/ConnectionInfo.java
@@ -0,0 +1,88 @@
+/*
+ * 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 com.android.server.remoteauth.connectivity;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Encapsulates the connection information.
+ *
+ * <p>Connection information captures the details of underlying connection such as connection id,
+ * type of connection and peer device mac address.
+ *
+ */
+// TODO(b/295407748) Change to use @DataClass.
+public abstract class ConnectionInfo implements Parcelable {
+    int mConnectionId;
+
+    public ConnectionInfo(int connectionId) {
+        mConnectionId = connectionId;
+    }
+
+    /** Create object from Parcel */
+    public ConnectionInfo(@NonNull Parcel in) {
+        mConnectionId = in.readInt();
+    }
+
+    public int getConnectionId() {
+        return mConnectionId;
+    }
+
+    /** No special parcel contents. */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Flattens this ConnectionInfo in to a Parcel.
+     *
+     * @param out 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 out, int flags) {
+        out.writeInt(mConnectionId);
+    }
+
+    /** Returns string representation of ConnectionInfo. */
+    @Override
+    public String toString() {
+        return "ConnectionInfo[" + "connectionId= " + mConnectionId + "]";
+    }
+
+    /** Returns true if this ConnectionInfo object is equal to the other. */
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof ConnectionInfo)) {
+            return false;
+        }
+
+        ConnectionInfo other = (ConnectionInfo) o;
+        return mConnectionId == other.getConnectionId();
+    }
+
+    /** Returns the hashcode of this object */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mConnectionId);
+    }
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/connectivity/ConnectivityManager.java b/remoteauth/service/java/com/android/server/remoteauth/connectivity/ConnectivityManager.java
new file mode 100644
index 0000000..bc0d77e
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/connectivity/ConnectivityManager.java
@@ -0,0 +1,115 @@
+/*
+ * 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 com.android.server.remoteauth.connectivity;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Performs discovery and triggers a connection to an associated device.
+ */
+public interface ConnectivityManager {
+    /**
+     * Starts device discovery.
+     *
+     * <p>Discovery continues until stopped using {@link stopDiscovery} or times out.
+     *
+     * @param discoveryFilter to filter for devices during discovery.
+     * @param discoveredDeviceReceiver callback to run when device is found or lost.
+     */
+    void startDiscovery(
+            @NonNull DiscoveryFilter discoveryFilter,
+            @NonNull DiscoveredDeviceReceiver discoveredDeviceReceiver);
+
+    /**
+     * Stops device discovery.
+     *
+     * @param discoveryFilter filter used to start discovery.
+     * @param discoveredDeviceReceiver callback passed with startDiscovery.
+     */
+    void stopDiscovery(
+            @NonNull DiscoveryFilter discoveryFilter,
+            @NonNull DiscoveredDeviceReceiver discoveredDeviceReceiver);
+
+    /** Unknown reason for connection failure. */
+    int ERROR_REASON_UNKNOWN = 0;
+
+    /** Indicates that the connection request timed out. */
+    int ERROR_CONNECTION_TIMED_OUT = 1;
+
+    /** Indicates that the connection request was refused by the peer. */
+    int ERROR_CONNECTION_REFUSED = 2;
+
+    /** Indicates that the peer device was unavailable. */
+    int ERROR_DEVICE_UNAVAILABLE = 3;
+
+    /** Reason codes for connect failure. */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ERROR_REASON_UNKNOWN, ERROR_CONNECTION_TIMED_OUT, ERROR_CONNECTION_REFUSED,
+            ERROR_DEVICE_UNAVAILABLE})
+    @interface ReasonCode {}
+
+    /**
+     * Initiates a connection with the peer device.
+     *
+     * @param connectionInfo of the device discovered using {@link startDiscovery}.
+     * @param eventListener to listen for events from the underlying transport.
+     * @return {@link Connection} object or null connection is not established.
+     * @throws ConnectionException in case connection cannot be established.
+     */
+    @Nullable
+    Connection connect(
+            @NonNull ConnectionInfo connectionInfo, @NonNull EventListener eventListener);
+
+    /**
+     * Message received callback.
+     *
+     * <p>Clients should implement this callback to receive messages from the peer device.
+     */
+    abstract class MessageReceiver {
+        /**
+         * Receive message from the peer device.
+         *
+         * <p>Clients can set empty buffer as an ACK to the request.
+         *
+         * @param messageIn message from peer device.
+         * @param responseCallback {@link ResponseCallback} callback to send the response back.
+         */
+        public void onMessageReceived(byte[] messageIn, ResponseCallback responseCallback) {}
+    }
+
+    /**
+     * Starts listening for incoming messages.
+     *
+     * <p>Runs MessageReceiver callback when a message is received.
+     *
+     * @param messageReceiver to receive messages.
+     * @throws AssertionError if a listener is already configured.
+     */
+    void startListening(MessageReceiver messageReceiver);
+
+    /**
+     * Stops listening to incoming messages.
+     *
+     * @param messageReceiver to receive messages.
+     */
+    void stopListening(MessageReceiver messageReceiver);
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/connectivity/DiscoveredDevice.java b/remoteauth/service/java/com/android/server/remoteauth/connectivity/DiscoveredDevice.java
new file mode 100644
index 0000000..a3e1e58
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/connectivity/DiscoveredDevice.java
@@ -0,0 +1,79 @@
+/*
+ * 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 com.android.server.remoteauth.connectivity;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.Objects;
+
+/** Device discovered on a network interface like Bluetooth. */
+public final class DiscoveredDevice {
+    private @NonNull ConnectionInfo mConnectionInfo;
+    private @Nullable String mDisplayName;
+
+    public DiscoveredDevice(
+            @NonNull ConnectionInfo connectionInfo, @Nullable String displayName) {
+        this.mConnectionInfo = connectionInfo;
+        this.mDisplayName = displayName;
+    }
+
+    /**
+     * Returns connection information.
+     *
+     * @return connection information.
+     */
+    @NonNull
+    public ConnectionInfo getConnectionInfo() {
+        return this.mConnectionInfo;
+    }
+
+    /**
+     * Returns display name of the device.
+     *
+     * @return display name string.
+     */
+    @Nullable
+    public String getDisplayName() {
+        return this.mDisplayName;
+    }
+
+    /**
+     * Checks for equality between this and other object.
+     *
+     * @return true if equal, false otherwise.
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof DiscoveredDevice)) {
+            return false;
+        }
+
+        DiscoveredDevice other = (DiscoveredDevice) o;
+        return mConnectionInfo.equals(other.getConnectionInfo())
+                && mDisplayName.equals(other.getDisplayName());
+    }
+
+    /**
+     * Returns hash code of the object.
+     *
+     * @return hash code.
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDisplayName, mConnectionInfo.getConnectionId());
+    }
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/connectivity/DiscoveredDeviceReceiver.java b/remoteauth/service/java/com/android/server/remoteauth/connectivity/DiscoveredDeviceReceiver.java
new file mode 100644
index 0000000..90a3e30
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/connectivity/DiscoveredDeviceReceiver.java
@@ -0,0 +1,34 @@
+/*
+ * 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 com.android.server.remoteauth.connectivity;
+
+/** Callbacks triggered on discovery. */
+public abstract class DiscoveredDeviceReceiver {
+    /**
+     * Callback called when a device matching the discovery filter is found.
+     *
+     * @param discoveredDevice the discovered device.
+     */
+    public void onDiscovered(DiscoveredDevice discoveredDevice) {}
+
+    /**
+     * Callback called when a previously discovered device using {@link
+     * ConnectivityManager#startDiscovery} is lost.
+     *
+     * @param discoveredDevice the lost device
+     */
+    public void onLost(DiscoveredDevice discoveredDevice) {}
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/connectivity/DiscoveryFilter.java b/remoteauth/service/java/com/android/server/remoteauth/connectivity/DiscoveryFilter.java
new file mode 100644
index 0000000..36c4b60
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/connectivity/DiscoveryFilter.java
@@ -0,0 +1,132 @@
+/*
+ * 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 com.android.server.remoteauth.connectivity;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Filter for device discovery.
+ *
+ * <p>Callers can use this class to provide a discovery filter to the {@link
+ * ConnectivityManager.startDiscovery} method. A device is discovered if it matches at least one of
+ * the filter criteria (device type, name or peer address).
+ */
+public final class DiscoveryFilter {
+
+    /** Device type WATCH. */
+    public static final int WATCH = 0;
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({WATCH})
+    public @interface DeviceType {}
+
+    private @DeviceType int mDeviceType;
+    private final @Nullable String mDeviceName;
+    private final @Nullable String mPeerAddress;
+
+    public DiscoveryFilter(
+            @DeviceType int deviceType, @Nullable String deviceName, @Nullable String peerAddress) {
+        this.mDeviceType = deviceType;
+        this.mDeviceName = deviceName;
+        this.mPeerAddress = peerAddress;
+    }
+
+    /**
+     * Returns device type.
+     *
+     * @return device type.
+     */
+    public @DeviceType int getDeviceType() {
+        return this.mDeviceType;
+    }
+
+    /**
+     * Returns device name.
+     *
+     * @return device name.
+     */
+    public @Nullable String getDeviceName() {
+        return this.mDeviceName;
+    }
+
+    /**
+     * Returns mac address of the peer device .
+     *
+     * @return mac address string.
+     */
+    public @Nullable String getPeerAddress() {
+        return this.mPeerAddress;
+    }
+
+    /** Builder for {@link DiscoverFilter} */
+    public static class Builder {
+        private @DeviceType int mDeviceType;
+        private @Nullable String mDeviceName;
+        private @Nullable String mPeerAddress;
+
+        private Builder() {}
+
+        /** Static method to create a new builder */
+        public static Builder newInstance() {
+            return new Builder();
+        }
+
+        /**
+         * Sets the device type of the DiscoveryFilter.
+         *
+         * @param deviceType of the peer device.
+         */
+        @NonNull
+        public Builder setDeviceType(@DeviceType int deviceType) {
+            mDeviceType = deviceType;
+            return this;
+        }
+
+        /**
+         * Sets the device name.
+         *
+         * @param deviceName May be null.
+         */
+        @NonNull
+        public Builder setDeviceName(String deviceName) {
+            mDeviceName = deviceName;
+            return this;
+        }
+
+        /**
+         * Sets the peer address.
+         *
+         * @param peerAddress Mac address of the peer device.
+         */
+        @NonNull
+        public Builder setPeerAddress(String peerAddress) {
+            mPeerAddress = peerAddress;
+            return this;
+        }
+
+        /** Builds the DiscoveryFilter object. */
+        @NonNull
+        public DiscoveryFilter build() {
+            return new DiscoveryFilter(this.mDeviceType, this.mDeviceName, this.mPeerAddress);
+        }
+    }
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/connectivity/EventListener.java b/remoteauth/service/java/com/android/server/remoteauth/connectivity/EventListener.java
new file mode 100644
index 0000000..d07adb1
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/connectivity/EventListener.java
@@ -0,0 +1,27 @@
+/*
+ * 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 com.android.server.remoteauth.connectivity;
+
+import android.annotation.NonNull;
+
+/**
+ * Listens to the events from underlying transport.
+ */
+interface EventListener {
+    /** Called when remote device is disconnected from the underlying transport. */
+    void onDisconnect(@NonNull ConnectionInfo connectionInfo);
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/connectivity/ResponseCallback.java b/remoteauth/service/java/com/android/server/remoteauth/connectivity/ResponseCallback.java
new file mode 100644
index 0000000..8a09ab3
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/connectivity/ResponseCallback.java
@@ -0,0 +1,63 @@
+/*
+ * 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 com.android.server.remoteauth.connectivity;
+
+import android.annotation.NonNull;
+
+/**
+ * Abstract class to expose a callback for clients to send a response to the peer device.
+ *
+ * <p>When a device receives a message on a connection, this object is constructed using the
+ * connection information of the connection and the message id from the incoming message. This
+ * object is forwarded to the clients of the connection to allow them to send a response to the peer
+ * device.
+ */
+public abstract class ResponseCallback {
+    private final long mMessageId;
+    private final ConnectionInfo mConnectionInfo;
+
+    public ResponseCallback(long messageId, @NonNull ConnectionInfo connectionInfo) {
+        mMessageId = messageId;
+        mConnectionInfo = connectionInfo;
+    }
+
+    /**
+     * Returns message id identifying the message.
+     *
+     * @return message id of this message.
+     */
+    public long getMessageId() {
+        return mMessageId;
+    }
+
+    /**
+     * Returns connection info from the response.
+     *
+     * @return connection info.
+     */
+    @NonNull
+    public ConnectionInfo getConnectionInfo() {
+        return mConnectionInfo;
+    }
+
+    /**
+     * Callback to send a response to the peer device.
+     *
+     * @param response buffer to send to the peer device.
+     */
+    public void onResponse(@NonNull byte[] response) {}
+}
diff --git a/service-t/jni/com_android_server_net_NetworkStatsService.cpp b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
index 4a6e733..bdbb655 100644
--- a/service-t/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/service-t/jni/com_android_server_net_NetworkStatsService.cpp
@@ -47,8 +47,6 @@
     RX_PACKETS = 1,
     TX_BYTES = 2,
     TX_PACKETS = 3,
-    TCP_RX_PACKETS = 4,  // not supported, always returns UNKNOWN (-1)
-    TCP_TX_PACKETS = 5,  // not supported, always returns UNKNOWN (-1)
 };
 
 static uint64_t getStatsType(StatsValue* stats, StatsType type) {
@@ -61,8 +59,6 @@
             return stats->txBytes;
         case TX_PACKETS:
             return stats->txPackets;
-        case TCP_RX_PACKETS:
-        case TCP_TX_PACKETS:
         default:
             return UNKNOWN;
     }
diff --git a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
index c3b5086..052019f 100644
--- a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
+++ b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
@@ -206,4 +206,19 @@
         builder.setEventDurationMillisec(durationMs);
         mDependencies.statsWrite(builder.build());
     }
+
+    /**
+     * Report service resolution stop metric data.
+     *
+     * @param transactionId The transaction id of service resolution.
+     * @param durationMs The duration before stop resolving the service.
+     */
+    public void reportServiceResolutionStop(int transactionId, long durationMs) {
+        final Builder builder = makeReportedBuilder();
+        builder.setTransactionId(transactionId);
+        builder.setType(NsdEventType.NET_RESOLVE);
+        builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_RESOLUTION_STOP);
+        builder.setEventDurationMillisec(durationMs);
+        mDependencies.statsWrite(builder.build());
+    }
 }
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index b975a1b..eb73ee5 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -144,7 +144,7 @@
      * "mdns_advertiser_allowlist_othertype_version"
      * would be used to toggle MdnsDiscoveryManager / MdnsAdvertiser for each type. The flags will
      * be read with
-     * {@link DeviceConfigUtils#isFeatureEnabled(Context, String, String, String, boolean)}.
+     * {@link DeviceConfigUtils#isTetheringFeatureEnabled}
      *
      * @see #MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX
      * @see #MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX
@@ -1062,12 +1062,12 @@
                         if (request instanceof DiscoveryManagerRequest) {
                             stopDiscoveryManagerRequest(
                                     request, clientRequestId, transactionId, clientInfo);
-                            clientInfo.onStopResolutionSucceeded(clientRequestId);
+                            clientInfo.onStopResolutionSucceeded(clientRequestId, request);
                             clientInfo.log("Unregister the ResolutionListener " + transactionId);
                         } else {
                             removeRequestMap(clientRequestId, transactionId, clientInfo);
                             if (stopResolveService(transactionId)) {
-                                clientInfo.onStopResolutionSucceeded(clientRequestId);
+                                clientInfo.onStopResolutionSucceeded(clientRequestId, request);
                             } else {
                                 clientInfo.onStopResolutionFailed(
                                         clientRequestId, NsdManager.FAILURE_OPERATION_NOT_RUNNING);
@@ -1657,9 +1657,9 @@
          * @return true if the MdnsDiscoveryManager feature is enabled.
          */
         public boolean isMdnsDiscoveryManagerEnabled(Context context) {
-            return isAtLeastU() || DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_TETHERING,
-                    MDNS_DISCOVERY_MANAGER_VERSION, DeviceConfigUtils.TETHERING_MODULE_NAME,
-                    false /* defaultEnabled */);
+            return isAtLeastU() || DeviceConfigUtils.isTetheringFeatureEnabled(context,
+                    NAMESPACE_TETHERING, MDNS_DISCOVERY_MANAGER_VERSION,
+                    DeviceConfigUtils.TETHERING_MODULE_NAME, false /* defaultEnabled */);
         }
 
         /**
@@ -1669,9 +1669,9 @@
          * @return true if the MdnsAdvertiser feature is enabled.
          */
         public boolean isMdnsAdvertiserEnabled(Context context) {
-            return isAtLeastU() || DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_TETHERING,
-                    MDNS_ADVERTISER_VERSION, DeviceConfigUtils.TETHERING_MODULE_NAME,
-                    false /* defaultEnabled */);
+            return isAtLeastU() || DeviceConfigUtils.isTetheringFeatureEnabled(context,
+                    NAMESPACE_TETHERING, MDNS_ADVERTISER_VERSION,
+                    DeviceConfigUtils.TETHERING_MODULE_NAME, false /* defaultEnabled */);
         }
 
         /**
@@ -1685,10 +1685,10 @@
         }
 
         /**
-         * @see DeviceConfigUtils#isFeatureEnabled(Context, String, String, String, boolean)
+         * @see DeviceConfigUtils#isTetheringFeatureEnabled
          */
         public boolean isFeatureEnabled(Context context, String feature) {
-            return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_TETHERING,
+            return DeviceConfigUtils.isTetheringFeatureEnabled(context, NAMESPACE_TETHERING,
                     feature, DeviceConfigUtils.TETHERING_MODULE_NAME, false /* defaultEnabled */);
         }
 
@@ -2393,6 +2393,9 @@
                                 request.getFoundServiceCount(),
                                 request.getLostServiceCount(),
                                 request.getServicesCount());
+                    } else if (listener instanceof ResolutionListener) {
+                        mMetrics.reportServiceResolutionStop(transactionId,
+                                request.calculateRequestDurationMs(mClock.elapsedRealtime()));
                     }
                     continue;
                 }
@@ -2419,6 +2422,8 @@
                         break;
                     case NsdManager.RESOLVE_SERVICE:
                         stopResolveService(transactionId);
+                        mMetrics.reportServiceResolutionStop(transactionId,
+                                request.calculateRequestDurationMs(mClock.elapsedRealtime()));
                         break;
                     case NsdManager.REGISTER_SERVICE:
                         unregisterService(transactionId);
@@ -2605,7 +2610,10 @@
             }
         }
 
-        void onStopResolutionSucceeded(int listenerKey) {
+        void onStopResolutionSucceeded(int listenerKey, ClientRequest request) {
+            mMetrics.reportServiceResolutionStop(
+                    request.mTransactionId,
+                    request.calculateRequestDurationMs(mClock.elapsedRealtime()));
             try {
                 mCb.onStopResolutionSucceeded(listenerKey);
             } catch (RemoteException e) {
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 8141350..48e86d8 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -325,7 +325,7 @@
     protected void unicastInterfaceStateChange(@NonNull IEthernetServiceListener listener,
             @NonNull String iface) {
         ensureRunningOnEthernetServiceThread();
-        final int state = mFactory.getInterfaceState(iface);
+        final int state = getInterfaceState(iface);
         final int role = getInterfaceRole(iface);
         final IpConfiguration config = getIpConfigurationForCallback(iface, state);
         try {
diff --git a/service/native/TrafficController.cpp b/service/native/TrafficController.cpp
index 8f6df21..3828389 100644
--- a/service/native/TrafficController.cpp
+++ b/service/native/TrafficController.cpp
@@ -576,53 +576,12 @@
     }
 }
 
-std::string getMapStatus(const base::unique_fd& map_fd, const char* path) {
-    if (map_fd.get() < 0) {
-        return StringPrintf("map fd lost");
-    }
-    if (access(path, F_OK) != 0) {
-        return StringPrintf("map not pinned to location: %s", path);
-    }
-    return StringPrintf("OK");
-}
-
-// NOLINTNEXTLINE(google-runtime-references): grandfathered pass by non-const reference
-void dumpBpfMap(const std::string& mapName, DumpWriter& dw, const std::string& header) {
-    dw.blankline();
-    dw.println("%s:", mapName.c_str());
-    if (!header.empty()) {
-        dw.println(header);
-    }
-}
-
 void TrafficController::dump(int fd, bool verbose __unused) {
     std::lock_guard guard(mMutex);
     DumpWriter dw(fd);
 
     ScopedIndent indentTop(dw);
     dw.println("TrafficController");
-
-    ScopedIndent indentPreBpfModule(dw);
-
-    dw.blankline();
-    dw.println("mCookieTagMap status: %s",
-               getMapStatus(mCookieTagMap.getMap(), COOKIE_TAG_MAP_PATH).c_str());
-    dw.println("mUidCounterSetMap status: %s",
-               getMapStatus(mUidCounterSetMap.getMap(), UID_COUNTERSET_MAP_PATH).c_str());
-    dw.println("mAppUidStatsMap status: %s",
-               getMapStatus(mAppUidStatsMap.getMap(), APP_UID_STATS_MAP_PATH).c_str());
-    dw.println("mStatsMapA status: %s",
-               getMapStatus(mStatsMapA.getMap(), STATS_MAP_A_PATH).c_str());
-    dw.println("mStatsMapB status: %s",
-               getMapStatus(mStatsMapB.getMap(), STATS_MAP_B_PATH).c_str());
-    dw.println("mIfaceIndexNameMap status: %s",
-               getMapStatus(mIfaceIndexNameMap.getMap(), IFACE_INDEX_NAME_MAP_PATH).c_str());
-    dw.println("mIfaceStatsMap status: %s",
-               getMapStatus(mIfaceStatsMap.getMap(), IFACE_STATS_MAP_PATH).c_str());
-    dw.println("mConfigurationMap status: %s",
-               getMapStatus(mConfigurationMap.getMap(), CONFIGURATION_MAP_PATH).c_str());
-    dw.println("mUidOwnerMap status: %s",
-               getMapStatus(mUidOwnerMap.getMap(), UID_OWNER_MAP_PATH).c_str());
 }
 
 }  // namespace net
diff --git a/service/native/TrafficControllerTest.cpp b/service/native/TrafficControllerTest.cpp
index 57f32af..99e9831 100644
--- a/service/native/TrafficControllerTest.cpp
+++ b/service/native/TrafficControllerTest.cpp
@@ -269,109 +269,6 @@
         }
         return ret;
     }
-
-    Status dump(bool verbose, std::vector<std::string>& outputLines) {
-      if (!outputLines.empty()) return statusFromErrno(EUCLEAN, "Output buffer is not empty");
-
-      android::base::unique_fd localFd, remoteFd;
-      if (!Pipe(&localFd, &remoteFd)) return statusFromErrno(errno, "Failed on pipe");
-
-      // dump() blocks until another thread has consumed all its output.
-      std::thread dumpThread =
-          std::thread([this, remoteFd{std::move(remoteFd)}, verbose]() {
-            mTc.dump(remoteFd, verbose);
-          });
-
-      std::string dumpContent;
-      if (!android::base::ReadFdToString(localFd.get(), &dumpContent)) {
-        return statusFromErrno(errno, "Failed to read dump results from fd");
-      }
-      dumpThread.join();
-
-      std::stringstream dumpStream(std::move(dumpContent));
-      std::string line;
-      while (std::getline(dumpStream, line)) {
-        outputLines.push_back(line);
-      }
-
-      return netdutils::status::ok;
-    }
-
-    // Strings in the |expect| must exist in dump results in order. But no need to be consecutive.
-    bool expectDumpsysContains(std::vector<std::string>& expect) {
-        if (expect.empty()) return false;
-
-        std::vector<std::string> output;
-        Status result = dump(true, output);
-        if (!isOk(result)) {
-            GTEST_LOG_(ERROR) << "TrafficController dump failed: " << netdutils::toString(result);
-            return false;
-        }
-
-        int matched = 0;
-        auto it = expect.begin();
-        for (const auto& line : output) {
-            if (it == expect.end()) break;
-            if (std::string::npos != line.find(*it)) {
-                matched++;
-                ++it;
-            }
-        }
-
-        if (matched != expect.size()) {
-            // dump results for debugging
-            for (const auto& o : output) LOG(INFO) << "output: " << o;
-            for (const auto& e : expect) LOG(INFO) << "expect: " << e;
-            return false;
-        }
-        return true;
-    }
-
-    // Once called, the maps of TrafficController can't recover to valid maps which initialized
-    // in SetUp().
-    void makeTrafficControllerMapsInvalid() {
-        constexpr char INVALID_PATH[] = "invalid";
-
-        mFakeCookieTagMap.init(INVALID_PATH);
-        mTc.mCookieTagMap = mFakeCookieTagMap;
-        ASSERT_INVALID(mTc.mCookieTagMap);
-
-        mFakeAppUidStatsMap.init(INVALID_PATH);
-        mTc.mAppUidStatsMap = mFakeAppUidStatsMap;
-        ASSERT_INVALID(mTc.mAppUidStatsMap);
-
-        mFakeStatsMapA.init(INVALID_PATH);
-        mTc.mStatsMapA = mFakeStatsMapA;
-        ASSERT_INVALID(mTc.mStatsMapA);
-
-        mFakeStatsMapB.init(INVALID_PATH);
-        mTc.mStatsMapB = mFakeStatsMapB;
-        ASSERT_INVALID(mTc.mStatsMapB);
-
-        mFakeIfaceStatsMap.init(INVALID_PATH);
-        mTc.mIfaceStatsMap = mFakeIfaceStatsMap;
-        ASSERT_INVALID(mTc.mIfaceStatsMap);
-
-        mFakeConfigurationMap.init(INVALID_PATH);
-        mTc.mConfigurationMap = mFakeConfigurationMap;
-        ASSERT_INVALID(mTc.mConfigurationMap);
-
-        mFakeUidOwnerMap.init(INVALID_PATH);
-        mTc.mUidOwnerMap = mFakeUidOwnerMap;
-        ASSERT_INVALID(mTc.mUidOwnerMap);
-
-        mFakeUidPermissionMap.init(INVALID_PATH);
-        mTc.mUidPermissionMap = mFakeUidPermissionMap;
-        ASSERT_INVALID(mTc.mUidPermissionMap);
-
-        mFakeUidCounterSetMap.init(INVALID_PATH);
-        mTc.mUidCounterSetMap = mFakeUidCounterSetMap;
-        ASSERT_INVALID(mTc.mUidCounterSetMap);
-
-        mFakeIfaceIndexNameMap.init(INVALID_PATH);
-        mTc.mIfaceIndexNameMap = mFakeIfaceIndexNameMap;
-        ASSERT_INVALID(mTc.mIfaceIndexNameMap);
-    }
 };
 
 TEST_F(TrafficControllerTest, TestUpdateOwnerMapEntry) {
diff --git a/service/native/include/TrafficController.h b/service/native/include/TrafficController.h
index cb6c836..d610d25 100644
--- a/service/native/include/TrafficController.h
+++ b/service/native/include/TrafficController.h
@@ -33,8 +33,6 @@
 
 class TrafficController {
   public:
-    static constexpr char DUMP_KEYWORD[] = "trafficcontroller";
-
     /*
      * Initialize the whole controller
      */
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index a29f47f..ab5024c 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -1413,10 +1413,10 @@
         }
 
         /**
-         * @see DeviceConfigUtils#isFeatureEnabled
+         * @see DeviceConfigUtils#isTetheringFeatureEnabled
          */
         public boolean isFeatureEnabled(Context context, String name) {
-            return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_TETHERING, name,
+            return DeviceConfigUtils.isTetheringFeatureEnabled(context, NAMESPACE_TETHERING, name,
                     TETHERING_MODULE_NAME, false /* defaultValue */);
         }
 
diff --git a/tests/common/java/android/net/KeepalivePacketDataTest.kt b/tests/common/java/android/net/KeepalivePacketDataTest.kt
index f464ec6..92ffc5c 100644
--- a/tests/common/java/android/net/KeepalivePacketDataTest.kt
+++ b/tests/common/java/android/net/KeepalivePacketDataTest.kt
@@ -50,9 +50,9 @@
 
     // 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)
@@ -63,20 +63,6 @@
         var data: TestKeepalivePacketData
 
         try {
-            data = TestKeepalivePacketData(srcAddress = null)
-            fail("Null src address should cause exception")
-        } catch (e: InvalidPacketException) {
-            assertEquals(e.error, ERROR_INVALID_IP_ADDRESS)
-        }
-
-        try {
-            data = TestKeepalivePacketData(dstAddress = null)
-            fail("Null dst address should cause exception")
-        } catch (e: InvalidPacketException) {
-            assertEquals(e.error, ERROR_INVALID_IP_ADDRESS)
-        }
-
-        try {
             data = TestKeepalivePacketData(dstAddress = TEST_ADDRV6)
             fail("Ip family mismatched should cause exception")
         } catch (e: InvalidPacketException) {
@@ -117,4 +103,4 @@
     @Test
     @IgnoreUpTo(Build.VERSION_CODES.Q)
     fun testPacket() = assertTrue(Arrays.equals(TESTBYTES, TestKeepalivePacketData().packet))
-}
\ No newline at end of file
+}
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index 09f5d6e..d2e7c99 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -1260,7 +1260,7 @@
         assertFalse(lp.hasIpv4UnreachableDefaultRoute());
     }
 
-    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
     @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
     @EnableCompatChanges({ConnectivityCompatChanges.EXCLUDED_ROUTES})
     public void testHasExcludeRoute() {
@@ -1273,7 +1273,7 @@
         assertTrue(lp.hasExcludeRoute());
     }
 
-    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
     @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
     @EnableCompatChanges({ConnectivityCompatChanges.EXCLUDED_ROUTES})
     public void testRouteAddWithSameKey() throws Exception {
@@ -1347,14 +1347,14 @@
         assertExcludeRoutesVisible();
     }
 
-    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
     @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
     @EnableCompatChanges({ConnectivityCompatChanges.EXCLUDED_ROUTES})
     public void testExcludedRoutesEnabledByCompatChange() {
         assertExcludeRoutesVisible();
     }
 
-    @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+    @Test @IgnoreUpTo(Build.VERSION_CODES.S_V2)
     @CtsNetTestCasesMaxTargetSdk31(reason = "Compat change cannot be overridden when targeting T+")
     @DisableCompatChanges({ConnectivityCompatChanges.EXCLUDED_ROUTES})
     public void testExcludedRoutesDisabledByCompatChange() {
diff --git a/tests/common/java/android/net/NetworkProviderTest.kt b/tests/common/java/android/net/NetworkProviderTest.kt
index fcbb0dd..c6a7346 100644
--- a/tests/common/java/android/net/NetworkProviderTest.kt
+++ b/tests/common/java/android/net/NetworkProviderTest.kt
@@ -67,7 +67,7 @@
 class NetworkProviderTest {
     @Rule @JvmField
     val mIgnoreRule = DevSdkIgnoreRule()
-    private val mCm = context.getSystemService(ConnectivityManager::class.java)
+    private val mCm = context.getSystemService(ConnectivityManager::class.java)!!
     private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread")
 
     @Before
diff --git a/tests/cts/OWNERS b/tests/cts/OWNERS
index 286f9c8..cb4ca59 100644
--- a/tests/cts/OWNERS
+++ b/tests/cts/OWNERS
@@ -5,3 +5,8 @@
 
 # IPsec
 per-file **IpSec* = benedictwong@google.com, nharold@google.com
+
+# For incremental changes on EthernetManagerTest to increase coverage for existing behavior and for
+# testing bug fixes.
+per-file net/src/android/net/cts/EthernetManagerTest.kt = prohr@google.com #{LAST_RESORT_SUGGESTION}
+
diff --git a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
index 1d79806..99222dd 100644
--- a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
+++ b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
@@ -95,7 +95,7 @@
 @RunWith(AndroidJUnit4::class)
 class CaptivePortalTest {
     private val context: android.content.Context by lazy { getInstrumentation().context }
-    private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
+    private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! }
     private val pm by lazy { context.packageManager }
     private val utils by lazy { CtsNetUtils(context) }
 
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index db13c49..f73134a 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -125,7 +125,7 @@
     private val TEST_TARGET_MAC_ADDR = MacAddress.fromString("12:34:56:78:9a:bc")
 
     private val realContext = InstrumentationRegistry.getContext()
-    private val cm = realContext.getSystemService(ConnectivityManager::class.java)
+    private val cm = realContext.getSystemService(ConnectivityManager::class.java)!!
 
     private val agentsToCleanUp = mutableListOf<NetworkAgent>()
     private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>()
@@ -160,7 +160,7 @@
         assumeTrue(kernelIsAtLeast(5, 15))
 
         runAsShell(MANAGE_TEST_NETWORKS) {
-            val tnm = realContext.getSystemService(TestNetworkManager::class.java)
+            val tnm = realContext.getSystemService(TestNetworkManager::class.java)!!
 
             // Only statically configure the IPv4 address; for IPv6, use the SLAAC generated
             // address.
@@ -306,7 +306,7 @@
 
         val socket = Os.socket(if (sendV6) AF_INET6 else AF_INET, SOCK_DGRAM or SOCK_NONBLOCK,
                 IPPROTO_UDP)
-        agent.network.bindSocket(socket)
+        checkNotNull(agent.network).bindSocket(socket)
 
         val originalPacket = testPacket.readAsArray()
         Os.sendto(socket, originalPacket, 0 /* bytesOffset */, originalPacket.size, 0 /* flags */,
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index 732a42b..7fe9299 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -135,8 +135,8 @@
 class EthernetManagerTest {
 
     private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
-    private val em by lazy { context.getSystemService(EthernetManager::class.java) }
-    private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
+    private val em by lazy { context.getSystemService(EthernetManager::class.java)!! }
+    private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! }
     private val handler by lazy { Handler(Looper.getMainLooper()) }
 
     private val ifaceListener = EthernetStateListener()
@@ -160,7 +160,7 @@
 
         init {
             tnm = runAsShell(MANAGE_TEST_NETWORKS) {
-                context.getSystemService(TestNetworkManager::class.java)
+                context.getSystemService(TestNetworkManager::class.java)!!
             }
             tapInterface = runAsShell(MANAGE_TEST_NETWORKS) {
                 // Configuring a tun/tap interface always enables the carrier. If hasCarrier is
@@ -275,16 +275,17 @@
             return events.poll(TIMEOUT_MS) ?: fail("Did not receive callback after ${TIMEOUT_MS}ms")
         }
 
-        fun eventuallyExpect(expected: CallbackEntry) = events.poll(TIMEOUT_MS) { it == expected }
+        fun eventuallyExpect(expected: CallbackEntry) {
+            val cb = events.poll(TIMEOUT_MS) { it == expected }
+            assertNotNull(cb, "Never received expected $expected. Received: ${events.backtrace()}")
+        }
 
         fun eventuallyExpect(iface: EthernetTestInterface, state: Int, role: Int) {
-            val event = createChangeEvent(iface.name, state, role)
-            assertNotNull(eventuallyExpect(event), "Never received expected $event")
+            eventuallyExpect(createChangeEvent(iface.name, state, role))
         }
 
         fun eventuallyExpect(state: Int) {
-            val event = EthernetStateChanged(state)
-            assertNotNull(eventuallyExpect(event), "Never received expected $event")
+            eventuallyExpect(EthernetStateChanged(state))
         }
 
         fun assertNoCallback() {
@@ -652,9 +653,8 @@
 
         val listener = EthernetStateListener()
         addInterfaceStateListener(listener)
-        // Note: using eventuallyExpect as there may be other interfaces present.
-        listener.eventuallyExpect(InterfaceStateChanged(iface.name,
-                STATE_LINK_UP, ROLE_SERVER, /* IpConfiguration */ null))
+        // TODO(b/295146844): do not report IpConfiguration for server mode interfaces.
+        listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_SERVER)
 
         releaseTetheredInterface()
         listener.eventuallyExpect(iface, STATE_LINK_UP, ROLE_CLIENT)
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 98ea224..5937655 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -257,7 +257,7 @@
         callback: TestableNetworkCallback,
         handler: Handler
     ) {
-        mCM!!.registerBestMatchingNetworkCallback(request, callback, handler)
+        mCM.registerBestMatchingNetworkCallback(request, callback, handler)
         callbacksToCleanUp.add(callback)
     }
 
@@ -394,8 +394,8 @@
                 .setLegacyExtraInfo(legacyExtraInfo).build()
         val (agent, callback) = createConnectedNetworkAgent(initialConfig = config)
         val networkInfo = mCM.getNetworkInfo(agent.network)
-        assertEquals(subtypeLTE, networkInfo.getSubtype())
-        assertEquals(subtypeNameLTE, networkInfo.getSubtypeName())
+        assertEquals(subtypeLTE, networkInfo?.getSubtype())
+        assertEquals(subtypeNameLTE, networkInfo?.getSubtypeName())
         assertEquals(legacyExtraInfo, config.getLegacyExtraInfo())
     }
 
@@ -417,8 +417,8 @@
             val nc = NetworkCapabilities(agent.nc)
             nc.addCapability(NET_CAPABILITY_NOT_METERED)
             agent.sendNetworkCapabilities(nc)
-            callback.expectCaps(agent.network) { it.hasCapability(NET_CAPABILITY_NOT_METERED) }
-            val networkInfo = mCM.getNetworkInfo(agent.network)
+            callback.expectCaps(agent.network!!) { it.hasCapability(NET_CAPABILITY_NOT_METERED) }
+            val networkInfo = mCM.getNetworkInfo(agent.network!!)!!
             assertEquals(subtypeUMTS, networkInfo.getSubtype())
             assertEquals(subtypeNameUMTS, networkInfo.getSubtypeName())
     }
@@ -631,6 +631,7 @@
         val defaultNetwork = mCM.activeNetwork
         assertNotNull(defaultNetwork)
         val defaultNetworkCapabilities = mCM.getNetworkCapabilities(defaultNetwork)
+        assertNotNull(defaultNetworkCapabilities)
         val defaultNetworkTransports = defaultNetworkCapabilities.transportTypes
 
         val agent = createNetworkAgent(initialNc = nc)
@@ -671,7 +672,7 @@
         // This is not very accurate because the test does not control the capabilities of the
         // underlying networks, and because not congested, not roaming, and not suspended are the
         // default anyway. It's still useful as an extra check though.
-        vpnNc = mCM.getNetworkCapabilities(agent.network!!)
+        vpnNc = mCM.getNetworkCapabilities(agent.network!!)!!
         for (cap in listOf(NET_CAPABILITY_NOT_CONGESTED,
                 NET_CAPABILITY_NOT_ROAMING,
                 NET_CAPABILITY_NOT_SUSPENDED)) {
@@ -1041,8 +1042,8 @@
     }
 
     fun QosSocketInfo(agent: NetworkAgent, socket: Closeable) = when (socket) {
-        is Socket -> QosSocketInfo(agent.network, socket)
-        is DatagramSocket -> QosSocketInfo(agent.network, socket)
+        is Socket -> QosSocketInfo(checkNotNull(agent.network), socket)
+        is DatagramSocket -> QosSocketInfo(checkNotNull(agent.network), socket)
         else -> fail("unexpected socket type")
     }
 
@@ -1405,8 +1406,8 @@
         val nc = makeTestNetworkCapabilities(ifName, transports).also {
             if (transports.contains(TRANSPORT_VPN)) {
                 val sessionId = "NetworkAgentTest-${Process.myPid()}"
-                it.transportInfo = VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, sessionId,
-                    /*bypassable=*/ false, /*longLivedTcpConnectionsExpensive=*/ false)
+                it.setTransportInfo(VpnTransportInfo(VpnManager.TYPE_VPN_PLATFORM, sessionId,
+                    /*bypassable=*/ false, /*longLivedTcpConnectionsExpensive=*/ false))
                 it.underlyingNetworks = listOf()
             }
         }
diff --git a/tests/cts/net/src/android/net/cts/NetworkInfoTest.kt b/tests/cts/net/src/android/net/cts/NetworkInfoTest.kt
index d6120f8..d8ec761 100644
--- a/tests/cts/net/src/android/net/cts/NetworkInfoTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkInfoTest.kt
@@ -104,13 +104,6 @@
             NetworkInfo(ConnectivityManager.MAX_NETWORK_TYPE + 1,
                     TelephonyManager.NETWORK_TYPE_LTE, MOBILE_TYPE_NAME, LTE_SUBTYPE_NAME)
         }
-
-        if (SdkLevel.isAtLeastT()) {
-            assertFailsWith<NullPointerException> { NetworkInfo(null) }
-        } else {
-            // Doesn't immediately crash on S-
-            NetworkInfo(null)
-        }
     }
 
     @Test
@@ -133,21 +126,11 @@
         constructor.isAccessible = true
         val incorrectDetailedState = constructor.newInstance("any", 200) as DetailedState
         if (SdkLevel.isAtLeastT()) {
-            assertFailsWith<NullPointerException> {
-                NetworkInfo(null)
-            }
-            assertFailsWith<NullPointerException> {
-                networkInfo.setDetailedState(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(null)
-            networkInfo.setDetailedState(null, "reason", "extraInfo")
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
index 2704dd3..e660b1e 100644
--- a/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkScoreTest.kt
@@ -67,7 +67,7 @@
 @RunWith(DevSdkIgnoreRunner::class)
 class NetworkScoreTest {
     private val TAG = javaClass.simpleName
-    private val mCm = testContext.getSystemService(ConnectivityManager::class.java)
+    private val mCm = testContext.getSystemService(ConnectivityManager::class.java)!!
     private val mHandlerThread = HandlerThread("$TAG handler thread")
     private val mHandler by lazy { Handler(mHandlerThread.looper) }
     private val agentsToCleanUp = Collections.synchronizedList(mutableListOf<NetworkAgent>())
@@ -111,7 +111,7 @@
     // made for ConnectivityServiceTest.
     // TODO : have TestNetworkCallback work for NetworkAgent too and remove this class.
     private class AgentWrapper(val agent: NetworkAgent) : HasNetwork {
-        override val network = agent.network
+        override val network = checkNotNull(agent.network)
         fun sendNetworkScore(s: NetworkScore) = agent.sendNetworkScore(s)
     }
 
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 7be4f78..49a6ef1 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -159,9 +159,9 @@
     val ignoreRule = DevSdkIgnoreRule()
 
     private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
-    private val nsdManager by lazy { context.getSystemService(NsdManager::class.java) }
+    private val nsdManager by lazy { context.getSystemService(NsdManager::class.java)!! }
 
-    private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
+    private val cm by lazy { context.getSystemService(ConnectivityManager::class.java)!! }
     private val serviceName = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
     private val serviceType = "_nmt%09d._tcp".format(Random().nextInt(1_000_000_000))
     private val handlerThread = HandlerThread(NsdManagerTest::class.java.simpleName)
@@ -397,7 +397,7 @@
     }
 
     private fun createTestNetwork(): TestTapNetwork {
-        val tnm = context.getSystemService(TestNetworkManager::class.java)
+        val tnm = context.getSystemService(TestNetworkManager::class.java)!!
         val iface = tnm.createTapInterface()
         val cb = TestableNetworkCallback()
         val testNetworkSpecifier = TestNetworkSpecifier(iface.interfaceName)
diff --git a/tests/cts/net/src/android/net/cts/ProxyTest.kt b/tests/cts/net/src/android/net/cts/ProxyTest.kt
index a661b26..872dbb9 100644
--- a/tests/cts/net/src/android/net/cts/ProxyTest.kt
+++ b/tests/cts/net/src/android/net/cts/ProxyTest.kt
@@ -70,7 +70,7 @@
 
     private fun getDefaultProxy(): ProxyInfo? {
         return InstrumentationRegistry.getInstrumentation().context
-                .getSystemService(ConnectivityManager::class.java)
+                .getSystemService(ConnectivityManager::class.java)!!
                 .getDefaultProxy()
     }
 
@@ -100,4 +100,4 @@
             Proxy.setHttpProxyConfiguration(original)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 67e1296..e264b55 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -56,6 +56,7 @@
 import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
 import com.android.testutils.TestableNetworkCallback
 import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
 import kotlin.test.assertTrue
 import kotlin.test.fail
 import org.junit.After
@@ -291,6 +292,7 @@
         val capportData = testCb.expect<LinkPropertiesChanged>(na, TEST_TIMEOUT_MS) {
             it.lp.captivePortalData != null
         }.lp.captivePortalData
+        assertNotNull(capportData)
         assertTrue(capportData.isCaptive)
         assertEquals(Uri.parse("https://login.capport.android.com"), capportData.userPortalUrl)
         assertEquals(Uri.parse("https://venueinfo.capport.android.com"), capportData.venueInfoUrl)
diff --git a/tests/integration/src/com/android/server/net/integrationtests/HttpResponse.kt b/tests/integration/src/com/android/server/net/integrationtests/HttpResponse.kt
index e206313..467708a 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/HttpResponse.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/HttpResponse.kt
@@ -20,9 +20,9 @@
 import android.os.Parcelable
 
 data class HttpResponse(
-    val requestUrl: String,
+    val requestUrl: String?,
     val responseCode: Int,
-    val content: String = "",
+    val content: String? = "",
     val redirectUrl: String? = null
 ) : Parcelable {
     constructor(p: Parcel): this(p.readString(), p.readInt(), p.readString(), p.readString())
@@ -46,4 +46,4 @@
         override fun createFromParcel(source: Parcel) = HttpResponse(source)
         override fun newArray(size: Int) = arrayOfNulls<HttpResponse?>(size)
     }
-}
\ No newline at end of file
+}
diff --git a/tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt b/tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt
index e807952..104d063 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt
@@ -70,7 +70,7 @@
          * request is seen, the test will fail.
          */
         override fun addHttpResponse(response: HttpResponse) {
-            httpResponses.getValue(response.requestUrl).add(response)
+            httpResponses.getValue(checkNotNull(response.requestUrl)).add(response)
         }
 
         /**
@@ -81,4 +81,4 @@
             return ArrayList(httpRequestUrls)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
index 361c968..7e227c4 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
@@ -69,7 +69,8 @@
         url: URL,
         private val response: HttpResponse
     ) : HttpURLConnection(url) {
-        private val responseBytes = response.content.toByteArray(StandardCharsets.UTF_8)
+        private val responseBytes = checkNotNull(response.content)
+            .toByteArray(StandardCharsets.UTF_8)
         override fun getResponseCode() = response.responseCode
         override fun getContentLengthLong() = responseBytes.size.toLong()
         override fun getHeaderField(field: String): String? {
diff --git a/tests/unit/java/android/net/NetworkTemplateTest.kt b/tests/unit/java/android/net/NetworkTemplateTest.kt
index 2f6c76b..f0da89f 100644
--- a/tests/unit/java/android/net/NetworkTemplateTest.kt
+++ b/tests/unit/java/android/net/NetworkTemplateTest.kt
@@ -218,18 +218,6 @@
         templateNullWifiKey.assertDoesNotMatch(identWifiNullKey)
     }
 
-    @DevSdkIgnoreRule.IgnoreAfter(Build.VERSION_CODES.TIRAMISU)
-    @Test
-    fun testBuildTemplateMobileAll_nullSubscriberId() {
-        val templateMobileAllWithNullImsi = buildTemplateMobileAll(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)
diff --git a/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
index 872326e..97aa575 100644
--- a/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
+++ b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
@@ -201,4 +201,24 @@
             assertEquals(durationMs, it.eventDurationMillisec)
         }
     }
+
+    @Test
+    fun testReportServiceResolutionStop() {
+        val clientId = 99
+        val transactionId = 100
+        val durationMs = 10L
+        val metrics = NetworkNsdReportedMetrics(true /* isLegacy */, clientId, deps)
+        metrics.reportServiceResolutionStop(transactionId, durationMs)
+
+        val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
+        verify(deps).statsWrite(eventCaptor.capture())
+        eventCaptor.value.let {
+            assertTrue(it.isLegacy)
+            assertEquals(clientId, it.clientId)
+            assertEquals(transactionId, it.transactionId)
+            assertEquals(NsdEventType.NET_RESOLVE, it.type)
+            assertEquals(MdnsQueryResult.MQR_SERVICE_RESOLUTION_STOP, it.queryResult)
+            assertEquals(durationMs, it.eventDurationMillisec)
+        }
+    }
 }
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 4e594a3..dbd4e4e 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -717,6 +717,7 @@
                 eq("local.") /* domain */, eq(IFACE_IDX_ANY));
 
         final int resolveId = resolvIdCaptor.getValue();
+        doReturn(TEST_TIME_MS + 10L).when(mClock).elapsedRealtime();
         client.stopServiceResolution(resolveListener);
         waitForIdle();
 
@@ -724,6 +725,7 @@
         verify(resolveListener, timeout(TIMEOUT_MS)).onResolutionStopped(argThat(ns ->
                 request.getServiceName().equals(ns.getServiceName())
                         && request.getServiceType().equals(ns.getServiceType())));
+        verify(mMetrics).reportServiceResolutionStop(resolveId, 10L /* durationMs */);
     }
 
     @Test
@@ -786,6 +788,7 @@
                 eq(IFACE_IDX_ANY));
 
         final int getAddrId = getAddrIdCaptor.getValue();
+        doReturn(TEST_TIME_MS + 10L).when(mClock).elapsedRealtime();
         client.stopServiceResolution(resolveListener);
         waitForIdle();
 
@@ -793,6 +796,7 @@
         verify(resolveListener, timeout(TIMEOUT_MS)).onResolutionStopped(argThat(ns ->
                 request.getServiceName().equals(ns.getServiceName())
                         && request.getServiceType().equals(ns.getServiceType())));
+        verify(mMetrics).reportServiceResolutionStop(getAddrId, 10L /* durationMs */);
     }
 
     private void verifyUpdatedServiceInfo(NsdServiceInfo info, String serviceName,
@@ -1355,8 +1359,8 @@
         final Network network = new Network(999);
         final String serviceType = "_nsd._service._tcp";
         final String constructedServiceType = "_service._tcp.local";
-        final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
-                ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
+        final ArgumentCaptor<MdnsListener> listenerCaptor =
+                ArgumentCaptor.forClass(MdnsListener.class);
         final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, serviceType);
         request.setNetwork(network);
         client.resolveService(request, resolveListener);
@@ -1371,16 +1375,19 @@
         // Subtypes are not used for resolution, only for discovery
         assertEquals(Collections.emptyList(), optionsCaptor.getValue().getSubtypes());
 
+        doReturn(TEST_TIME_MS + 10L).when(mClock).elapsedRealtime();
         client.stopServiceResolution(resolveListener);
         waitForIdle();
 
         // Verify the listener has been unregistered.
+        final MdnsListener listener = listenerCaptor.getValue();
         verify(mDiscoveryManager, timeout(TIMEOUT_MS))
-                .unregisterListener(eq(constructedServiceType), eq(listenerCaptor.getValue()));
+                .unregisterListener(eq(constructedServiceType), eq(listener));
         verify(resolveListener, timeout(TIMEOUT_MS)).onResolutionStopped(argThat(ns ->
                 request.getServiceName().equals(ns.getServiceName())
                         && request.getServiceType().equals(ns.getServiceType())));
         verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
+        verify(mMetrics).reportServiceResolutionStop(listener.mTransactionId, 10L /* durationMs */);
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
index 3520c5b..0a3822a 100644
--- a/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/FullScoreTest.kt
@@ -74,7 +74,7 @@
 
     private val TAG = this::class.simpleName
 
-    private var wtfHandler: Log.TerribleFailureHandler? = null
+    private lateinit var wtfHandler: Log.TerribleFailureHandler
 
     @Before
     fun setUp() {
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt
index f88da1f..b667e5f 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsPacketTest.kt
@@ -59,12 +59,12 @@
         }
 
         assertEquals(InetAddresses.parseNumericAddress("192.0.2.123"),
-                (packet.authorityRecords[0] as MdnsInetAddressRecord).inet4Address)
+                (packet.authorityRecords[0] as MdnsInetAddressRecord).inet4Address!!)
         assertEquals(InetAddresses.parseNumericAddress("2001:db8::123"),
-                (packet.authorityRecords[1] as MdnsInetAddressRecord).inet6Address)
+                (packet.authorityRecords[1] as MdnsInetAddressRecord).inet6Address!!)
         assertEquals(InetAddresses.parseNumericAddress("2001:db8::456"),
-                (packet.authorityRecords[2] as MdnsInetAddressRecord).inet6Address)
+                (packet.authorityRecords[2] as MdnsInetAddressRecord).inet6Address!!)
         assertEquals(InetAddresses.parseNumericAddress("2001:db8::789"),
-                (packet.authorityRecords[3] as MdnsInetAddressRecord).inet6Address)
+                (packet.authorityRecords[3] as MdnsInetAddressRecord).inet6Address!!)
     }
 }