Merge changes I963a1c84,I515d6b60,Iae99f0bc,I99d1240d,I49d078fb into main

* changes:
  Check whether a NetworkRequest is already tracked
  Check whether client NetworkRequest includes a valid specifier
  Add empty ClientOffer
  Delete L2capNetworkProviderTest
  Add support for an L2CAP server network
diff --git a/service/src/com/android/server/L2capNetworkProvider.java b/service/src/com/android/server/L2capNetworkProvider.java
index 15c860b..32ee397 100644
--- a/service/src/com/android/server/L2capNetworkProvider.java
+++ b/service/src/com/android/server/L2capNetworkProvider.java
@@ -19,6 +19,7 @@
 import static android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN;
 import static android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_ANY;
 import static android.net.L2capNetworkSpecifier.PSM_ANY;
+import static android.net.L2capNetworkSpecifier.ROLE_CLIENT;
 import static android.net.L2capNetworkSpecifier.ROLE_SERVER;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
@@ -54,6 +55,7 @@
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.system.Os;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -62,6 +64,9 @@
 import com.android.server.net.L2capNetwork;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 
@@ -88,6 +93,7 @@
     private final NetworkProvider mProvider;
     private final BlanketReservationOffer mBlanketOffer;
     private final Set<ReservedServerOffer> mReservedServerOffers = new ArraySet<>();
+    private final ClientOffer mClientOffer;
     // mBluetoothManager guaranteed non-null when read on handler thread after start() is called
     @Nullable
     private BluetoothManager mBluetoothManager;
@@ -269,16 +275,117 @@
         return new L2capNetwork(mHandler, mContext, mProvider, ifname, socket, tunFd, caps, cb);
     }
 
-
     private class ReservedServerOffer implements NetworkOfferCallback {
         private final NetworkCapabilities mReservedCapabilities;
-        private final BluetoothServerSocket mServerSocket;
+        private final AcceptThread mAcceptThread;
+        // This set should almost always contain at most one network. This is because all L2CAP
+        // server networks created by the same reserved offer are indistinguishable from each other,
+        // so that ConnectivityService will tear down all but the first. However, temporarily, there
+        // can be more than one network.
+        private final Set<L2capNetwork> mL2capNetworks = new ArraySet<>();
+
+        private class AcceptThread extends Thread {
+            private static final int TIMEOUT_MS = 500;
+            private final BluetoothServerSocket mServerSocket;
+            private volatile boolean mIsRunning = true;
+
+            public AcceptThread(BluetoothServerSocket serverSocket) {
+                mServerSocket = serverSocket;
+            }
+
+            private void postDestroyAndUnregisterReservedOffer() {
+                mHandler.post(() -> {
+                    destroyAndUnregisterReservedOffer(ReservedServerOffer.this);
+                });
+            }
+
+            private static void closeBluetoothSocket(BluetoothSocket socket) {
+                try {
+                    socket.close();
+                } catch (IOException e) {
+                    Log.w(TAG, "Failed to close BluetoothSocket", e);
+                }
+            }
+
+            private void postCreateServerNetwork(BluetoothSocket connectedSocket) {
+                mHandler.post(() -> {
+                    final boolean success = createServerNetwork(connectedSocket);
+                    if (!success) closeBluetoothSocket(connectedSocket);
+                });
+            }
+
+            public void run() {
+                while (mIsRunning) {
+                    final BluetoothSocket connectedSocket;
+                    try {
+                        connectedSocket = mServerSocket.accept();
+                    } catch (IOException e) {
+                        // BluetoothServerSocket was closed().
+                        if (!mIsRunning) return;
+
+                        // Else, BluetoothServerSocket encountered exception.
+                        Log.e(TAG, "BluetoothServerSocket#accept failed", e);
+                        postDestroyAndUnregisterReservedOffer();
+                        return; // stop running immediately on error
+                    }
+                    postCreateServerNetwork(connectedSocket);
+                }
+            }
+
+            public void tearDown() {
+                mIsRunning = false;
+                try {
+                    // BluetoothServerSocket.close() is thread-safe.
+                    mServerSocket.close();
+                } catch (IOException e) {
+                    Log.w(TAG, "Failed to close BluetoothServerSocket", e);
+                }
+                try {
+                    join();
+                } catch (InterruptedException e) {
+                    // join() interrupted during tearDown(). Do nothing.
+                }
+            }
+        }
+
+        private boolean createServerNetwork(BluetoothSocket socket) {
+            // It is possible the offer went away.
+            if (!mReservedServerOffers.contains(this)) return false;
+
+            if (!socket.isConnected()) {
+                Log.wtf(TAG, "BluetoothSocket must be connected");
+                return false;
+            }
+
+            final L2capNetwork network = createL2capNetwork(socket, mReservedCapabilities,
+                    new L2capNetwork.ICallback() {
+                @Override
+                public void onError(L2capNetwork network) {
+                    destroyAndUnregisterReservedOffer(ReservedServerOffer.this);
+                }
+                @Override
+                public void onNetworkUnwanted(L2capNetwork network) {
+                    // Leave reservation in place.
+                    final boolean networkExists = mL2capNetworks.remove(network);
+                    if (!networkExists) return; // already torn down.
+                    network.tearDown();
+                }
+            });
+
+            if (network == null) {
+                Log.e(TAG, "Failed to create L2capNetwork");
+                return false;
+            }
+
+            mL2capNetworks.add(network);
+            return true;
+        }
 
         public ReservedServerOffer(NetworkCapabilities reservedCapabilities,
                 BluetoothServerSocket serverSocket) {
             mReservedCapabilities = reservedCapabilities;
-            // TODO: ServerSocket will be managed by an AcceptThread.
-            mServerSocket = serverSocket;
+            mAcceptThread = new AcceptThread(serverSocket);
+            mAcceptThread.start();
         }
 
         public NetworkCapabilities getReservedCapabilities() {
@@ -287,28 +394,137 @@
 
         @Override
         public void onNetworkNeeded(NetworkRequest request) {
-            // TODO: implement
+            // UNUSED: the lifetime of the reserved network is controlled by the blanket offer.
         }
 
         @Override
         public void onNetworkUnneeded(NetworkRequest request) {
-            // TODO: implement
+            // UNUSED: the lifetime of the reserved network is controlled by the blanket offer.
         }
 
-        /**
-         * Called when the reservation goes away and the reserved offer must be torn down.
-         *
-         * This method can be called multiple times.
-         */
+        /** Called when the reservation goes away and the reserved offer must be torn down. */
         public void tearDown() {
-            try {
-                mServerSocket.close();
-            } catch (IOException e) {
-                Log.w(TAG, "Failed to close BluetoothServerSocket", e);
+            mAcceptThread.tearDown();
+            for (L2capNetwork network : mL2capNetworks) {
+                network.tearDown();
             }
         }
     }
 
+    private class ClientOffer implements NetworkOfferCallback {
+        public static final NetworkScore SCORE = new NetworkScore.Builder().build();
+        public static final NetworkCapabilities CAPABILITIES;
+        static {
+            // Below capabilities will match any request with an L2capNetworkSpecifier
+            // that specifies ROLE_CLIENT or without a NetworkSpecifier.
+            final L2capNetworkSpecifier l2capNetworkSpecifier = new L2capNetworkSpecifier.Builder()
+                    .setRole(ROLE_CLIENT)
+                    .build();
+            CAPABILITIES = new NetworkCapabilities.Builder(COMMON_CAPABILITIES)
+                    .setNetworkSpecifier(l2capNetworkSpecifier)
+                    .build();
+        }
+
+        private final Map<L2capNetworkSpecifier, ClientRequestInfo> mClientNetworkRequests =
+                new ArrayMap<>();
+
+        /**
+         * State object to store information for client NetworkRequests.
+         */
+        private static class ClientRequestInfo {
+            public final List<NetworkRequest> requests = new ArrayList<>();
+
+            public ClientRequestInfo(NetworkRequest request) {
+                requests.add(request);
+            }
+        }
+
+
+        private boolean isValidL2capSpecifier(@Nullable NetworkSpecifier spec) {
+            if (spec == null) return false;
+
+            // If not null, guaranteed to be L2capNetworkSepcifier.
+            final L2capNetworkSpecifier l2capSpec = (L2capNetworkSpecifier) spec;
+
+            // The ROLE_CLIENT offer can be satisfied by a ROLE_ANY request.
+            if (l2capSpec.getRole() != ROLE_CLIENT) return false;
+
+            // HEADER_COMPRESSION_ANY is never valid in a request.
+            if (l2capSpec.getHeaderCompression() == HEADER_COMPRESSION_ANY) return false;
+
+            // remoteAddr must not be null for ROLE_CLIENT requests.
+            if (l2capSpec.getRemoteAddress() == null) return false;
+
+            // Client network requests require a PSM to be specified.
+            // Ensure the PSM is within the valid range of dynamic BLE L2CAP values.
+            if (l2capSpec.getPsm() < 0x80) return false;
+            if (l2capSpec.getPsm() > 0xFF) return false;
+
+            return true;
+        }
+
+        @Override
+        public void onNetworkNeeded(NetworkRequest request) {
+            Log.d(TAG, "New client network request: " + request);
+            if (!isValidL2capSpecifier(request.getNetworkSpecifier())) {
+                Log.w(TAG, "Ignoring invalid client request: " + request);
+                return;
+            }
+
+            final L2capNetworkSpecifier requestSpecifier =
+                    (L2capNetworkSpecifier) request.getNetworkSpecifier();
+             // Check whether this exact request is already being tracked.
+            final ClientRequestInfo cri = mClientNetworkRequests.get(requestSpecifier);
+            if (cri != null) {
+                Log.d(TAG, "The request is already being tracked. NetworkRequest: " + request);
+                cri.requests.add(request);
+                return;
+            }
+
+            // Check whether a fuzzy match shows a mismatch in header compression by calling
+            // canBeSatisfiedBy().
+            // TODO: Add a copy constructor to L2capNetworkSpecifier.Builder.
+            final L2capNetworkSpecifier matchAnyHeaderCompressionSpecifier =
+                    new L2capNetworkSpecifier.Builder()
+                            .setRole(requestSpecifier.getRole())
+                            .setRemoteAddress(requestSpecifier.getRemoteAddress())
+                            .setPsm(requestSpecifier.getPsm())
+                            .setHeaderCompression(HEADER_COMPRESSION_ANY)
+                            .build();
+            for (L2capNetworkSpecifier existingSpecifier : mClientNetworkRequests.keySet()) {
+                if (existingSpecifier.canBeSatisfiedBy(matchAnyHeaderCompressionSpecifier)) {
+                    // This requeset can never be serviced as this network already exists with a
+                    // different header compression mechanism.
+                    mProvider.declareNetworkRequestUnfulfillable(request);
+                    return;
+                }
+            }
+
+            // If the code reaches here, this is a new request.
+            mClientNetworkRequests.put(requestSpecifier, new ClientRequestInfo(request));
+
+            // TODO: implement onNetworkNeeded
+        }
+
+        @Override
+        public void onNetworkUnneeded(NetworkRequest request) {
+            final L2capNetworkSpecifier specifier =
+                    (L2capNetworkSpecifier) request.getNetworkSpecifier();
+
+            // Map#get() is safe to call with null key
+            final ClientRequestInfo cri = mClientNetworkRequests.get(specifier);
+            if (cri == null) return;
+
+            cri.requests.remove(request);
+            if (cri.requests.size() > 0) return;
+
+            // If the code reaches here, the network needs to be torn down.
+            mClientNetworkRequests.remove(specifier);
+
+            // TODO: implement onNetworkUnneeded
+        }
+    }
+
     @VisibleForTesting
     public static class Dependencies {
         /** Get NetworkProvider */
@@ -336,6 +552,7 @@
         mHandler = new Handler(mHandlerThread.getLooper());
         mProvider = mDeps.getNetworkProvider(context, mHandlerThread.getLooper());
         mBlanketOffer = new BlanketReservationOffer();
+        mClientOffer = new ClientOffer();
     }
 
     /**
@@ -358,6 +575,8 @@
             mContext.getSystemService(ConnectivityManager.class).registerNetworkProvider(mProvider);
             mProvider.registerNetworkOffer(BlanketReservationOffer.SCORE,
                     BlanketReservationOffer.CAPABILITIES, mHandler::post, mBlanketOffer);
+            mProvider.registerNetworkOffer(ClientOffer.SCORE,
+                    ClientOffer.CAPABILITIES, mHandler::post, mClientOffer);
         });
     }
 }
diff --git a/tests/unit/java/com/android/server/L2capNetworkProviderTest.kt b/tests/unit/java/com/android/server/L2capNetworkProviderTest.kt
deleted file mode 100644
index 49eb476..0000000
--- a/tests/unit/java/com/android/server/L2capNetworkProviderTest.kt
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2024 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
-
-import android.bluetooth.BluetoothAdapter
-import android.bluetooth.BluetoothManager
-import android.bluetooth.BluetoothServerSocket
-import android.content.Context
-import android.content.pm.PackageManager
-import android.content.pm.PackageManager.FEATURE_BLUETOOTH_LE
-import android.net.ConnectivityManager
-import android.net.ConnectivityManager.TYPE_NONE
-import android.net.L2capNetworkSpecifier
-import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN
-import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_NONE
-import android.net.L2capNetworkSpecifier.ROLE_SERVER
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
-import android.net.NetworkProvider
-import android.net.NetworkProvider.NetworkOfferCallback
-import android.net.NetworkRequest
-import android.os.Build
-import android.os.Handler
-import android.os.HandlerThread
-import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.waitForIdle
-import kotlin.test.assertTrue
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.never
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-private const val TAG = "L2capNetworkProviderTest"
-private const val TIMEOUT_MS = 1000
-
-private val RESERVATION_CAPS = NetworkCapabilities.Builder.withoutDefaultCapabilities()
-    .addTransportType(TRANSPORT_BLUETOOTH)
-    .build()
-
-private val RESERVATION = NetworkRequest(
-        NetworkCapabilities(RESERVATION_CAPS),
-        TYPE_NONE,
-        42 /* rId */,
-        NetworkRequest.Type.RESERVATION
-)
-
-@RunWith(DevSdkIgnoreRunner::class)
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-class L2capNetworkProviderTest {
-    @Mock private lateinit var context: Context
-    @Mock private lateinit var deps: L2capNetworkProvider.Dependencies
-    @Mock private lateinit var provider: NetworkProvider
-    @Mock private lateinit var cm: ConnectivityManager
-    @Mock private lateinit var pm: PackageManager
-    @Mock private lateinit var bm: BluetoothManager
-    @Mock private lateinit var adapter: BluetoothAdapter
-    @Mock private lateinit var serverSocket: BluetoothServerSocket
-
-    private val handlerThread = HandlerThread("$TAG handler thread").apply { start() }
-    private val handler = Handler(handlerThread.looper)
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-        doReturn(provider).`when`(deps).getNetworkProvider(any(), any())
-        doReturn(handlerThread).`when`(deps).getHandlerThread()
-        doReturn(cm).`when`(context).getSystemService(eq(ConnectivityManager::class.java))
-        doReturn(pm).`when`(context).getPackageManager()
-        doReturn(true).`when`(pm).hasSystemFeature(FEATURE_BLUETOOTH_LE)
-
-        doReturn(bm).`when`(context).getSystemService(eq(BluetoothManager::class.java))
-        doReturn(adapter).`when`(bm).getAdapter()
-        doReturn(serverSocket).`when`(adapter).listenUsingInsecureL2capChannel()
-        doReturn(0x80).`when`(serverSocket).getPsm()
-    }
-
-    @After
-    fun tearDown() {
-        handlerThread.quitSafely()
-        handlerThread.join()
-    }
-
-    @Test
-    fun testNetworkProvider_registeredWhenSupported() {
-        L2capNetworkProvider(deps, context).start()
-        handlerThread.waitForIdle(TIMEOUT_MS)
-        verify(cm).registerNetworkProvider(eq(provider))
-        verify(provider).registerNetworkOffer(any(), any(), any(), any())
-    }
-
-    @Test
-    fun testNetworkProvider_notRegisteredWhenNotSupported() {
-        doReturn(false).`when`(pm).hasSystemFeature(FEATURE_BLUETOOTH_LE)
-        L2capNetworkProvider(deps, context).start()
-        handlerThread.waitForIdle(TIMEOUT_MS)
-        verify(cm, never()).registerNetworkProvider(eq(provider))
-    }
-
-    fun doTestBlanketOfferIgnoresRequest(request: NetworkRequest) {
-        clearInvocations(provider)
-        L2capNetworkProvider(deps, context).start()
-        handlerThread.waitForIdle(TIMEOUT_MS)
-
-        val blanketOfferCaptor = ArgumentCaptor.forClass(NetworkOfferCallback::class.java)
-        verify(provider).registerNetworkOffer(any(), any(), any(), blanketOfferCaptor.capture())
-
-        blanketOfferCaptor.value.onNetworkNeeded(request)
-        verify(provider).registerNetworkOffer(any(), any(), any(), any())
-    }
-
-    fun doTestBlanketOfferCreatesReservation(
-            request: NetworkRequest,
-            reservation: NetworkCapabilities
-    ) {
-        clearInvocations(provider)
-        L2capNetworkProvider(deps, context).start()
-        handlerThread.waitForIdle(TIMEOUT_MS)
-
-        val blanketOfferCaptor = ArgumentCaptor.forClass(NetworkOfferCallback::class.java)
-        verify(provider).registerNetworkOffer(any(), any(), any(), blanketOfferCaptor.capture())
-
-        blanketOfferCaptor.value.onNetworkNeeded(request)
-
-        val capsCaptor = ArgumentCaptor.forClass(NetworkCapabilities::class.java)
-        verify(provider, times(2)).registerNetworkOffer(any(), capsCaptor.capture(), any(), any())
-
-        assertTrue(reservation.satisfiedByNetworkCapabilities(capsCaptor.value))
-    }
-
-    @Test
-    fun testBlanketOffer_reservationWithoutSpecifier() {
-        val caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
-                .addTransportType(TRANSPORT_BLUETOOTH)
-                .build()
-        val nr = NetworkRequest(caps, TYPE_NONE, 42 /* rId */, NetworkRequest.Type.RESERVATION)
-
-        doTestBlanketOfferIgnoresRequest(nr)
-    }
-
-    @Test
-    fun testBlanketOffer_reservationWithCorrectSpecifier() {
-        var specifier = L2capNetworkSpecifier.Builder()
-                .setRole(ROLE_SERVER)
-                .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN)
-                .build()
-        var caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
-                .addTransportType(TRANSPORT_BLUETOOTH)
-                .setNetworkSpecifier(specifier)
-                .build()
-        var nr = NetworkRequest(caps, TYPE_NONE, 42 /* rId */, NetworkRequest.Type.RESERVATION)
-        doTestBlanketOfferCreatesReservation(nr, caps)
-
-        specifier = L2capNetworkSpecifier.Builder()
-                .setRole(ROLE_SERVER)
-                .setHeaderCompression(HEADER_COMPRESSION_NONE)
-                .build()
-        caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
-                .addTransportType(TRANSPORT_BLUETOOTH)
-                .setNetworkSpecifier(specifier)
-                .build()
-        nr = NetworkRequest(caps, TYPE_NONE, 43 /* rId */, NetworkRequest.Type.RESERVATION)
-        doTestBlanketOfferCreatesReservation(nr, caps)
-    }
-
-    @Test
-    fun testBlanketOffer_reservationWithIncorrectSpecifier() {
-        var specifier = L2capNetworkSpecifier.Builder().build()
-        var caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
-                .addTransportType(TRANSPORT_BLUETOOTH)
-                .setNetworkSpecifier(specifier)
-                .build()
-        var nr = NetworkRequest(caps, TYPE_NONE, 42 /* rId */, NetworkRequest.Type.RESERVATION)
-        doTestBlanketOfferIgnoresRequest(nr)
-
-        specifier = L2capNetworkSpecifier.Builder()
-                .setRole(ROLE_SERVER)
-                .build()
-        caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
-                .addTransportType(TRANSPORT_BLUETOOTH)
-                .setNetworkSpecifier(specifier)
-                .build()
-        nr = NetworkRequest(caps, TYPE_NONE, 44 /* rId */, NetworkRequest.Type.RESERVATION)
-        doTestBlanketOfferIgnoresRequest(nr)
-
-        specifier = L2capNetworkSpecifier.Builder()
-                .setRole(ROLE_SERVER)
-                .setHeaderCompression(HEADER_COMPRESSION_NONE)
-                .setPsm(0x81)
-                .build()
-        caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
-                .addTransportType(TRANSPORT_BLUETOOTH)
-                .setNetworkSpecifier(specifier)
-                .build()
-        nr = NetworkRequest(caps, TYPE_NONE, 45 /* rId */, NetworkRequest.Type.RESERVATION)
-        doTestBlanketOfferIgnoresRequest(nr)
-
-        specifier = L2capNetworkSpecifier.Builder()
-                .setHeaderCompression(HEADER_COMPRESSION_NONE)
-                .build()
-        caps = NetworkCapabilities.Builder.withoutDefaultCapabilities()
-                .addTransportType(TRANSPORT_BLUETOOTH)
-                .setNetworkSpecifier(specifier)
-                .build()
-        nr = NetworkRequest(caps, TYPE_NONE, 47 /* rId */, NetworkRequest.Type.RESERVATION)
-        doTestBlanketOfferIgnoresRequest(nr)
-    }
-}