Allocate a BluetoothServerSocket for the ReservedServerOffer

This change allocates a BluetoothServerSocket as a response to the
reservation. The resulting PSM is used to create the reserved
capabilities stored in ReservedServerOffer.

Test: TH
Change-Id: I3e789541474a492bb1c015d6316db12a3c995995
diff --git a/service/src/com/android/server/L2capNetworkProvider.java b/service/src/com/android/server/L2capNetworkProvider.java
index 7440c0a..5816e8b 100644
--- a/service/src/com/android/server/L2capNetworkProvider.java
+++ b/service/src/com/android/server/L2capNetworkProvider.java
@@ -32,6 +32,9 @@
 import static android.content.pm.PackageManager.FEATURE_BLUETOOTH_LE;
 
 import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothServerSocket;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
@@ -50,6 +53,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.IOException;
 import java.util.Map;
 
 
@@ -76,6 +80,9 @@
     private final NetworkProvider mProvider;
     private final BlanketReservationOffer mBlanketOffer;
     private final Map<Integer, ReservedServerOffer> mReservedServerOffers = new ArrayMap<>();
+    // mBluetoothManager guaranteed non-null when read on handler thread after start() is called
+    @Nullable
+    private BluetoothManager mBluetoothManager;
 
     /**
      * The blanket reservation offer is used to create an L2CAP server network, i.e. a network
@@ -135,51 +142,78 @@
                 return;
             }
 
-            final NetworkCapabilities reservationCaps = request.networkCapabilities;
-            final ReservedServerOffer reservedOffer = new ReservedServerOffer(reservationCaps);
+            final ReservedServerOffer reservedOffer = createReservedServerOffer(request);
+            if (reservedOffer == null) {
+                // Something went wrong when creating the offer. Send onUnavailable() to the app.
+                Log.e(TAG, "Failed to create L2cap server offer");
+                mProvider.declareNetworkRequestUnfulfillable(request);
+                return;
+            }
 
             final NetworkCapabilities reservedCaps = reservedOffer.getReservedCapabilities();
             mProvider.registerNetworkOffer(SCORE, reservedCaps, mHandler::post, reservedOffer);
             mReservedServerOffers.put(request.requestId, reservedOffer);
         }
 
+        @Nullable
+        private ReservedServerOffer createReservedServerOffer(NetworkRequest reservation) {
+            final BluetoothAdapter bluetoothAdapter = mBluetoothManager.getAdapter();
+            if (bluetoothAdapter == null) {
+                Log.w(TAG, "Failed to get BluetoothAdapter");
+                return null;
+            }
+            final BluetoothServerSocket serverSocket;
+            try {
+                serverSocket = bluetoothAdapter.listenUsingInsecureL2capChannel();
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to open BluetoothServerSocket");
+                return null;
+            }
+
+            // Create the reserved capabilities partially from the reservation itself (non-reserved
+            // parts of the L2capNetworkSpecifier), the COMMON_CAPABILITIES, and the reserved data
+            // (BLE L2CAP PSM from the BluetoothServerSocket).
+            final NetworkCapabilities reservationNc = reservation.networkCapabilities;
+            final L2capNetworkSpecifier reservationSpec =
+                    (L2capNetworkSpecifier) reservationNc.getNetworkSpecifier();
+            // Note: the RemoteAddress is unspecified for server networks.
+            final L2capNetworkSpecifier reservedSpec = new L2capNetworkSpecifier.Builder()
+                    .setRole(ROLE_SERVER)
+                    .setHeaderCompression(reservationSpec.getHeaderCompression())
+                    .setPsm(serverSocket.getPsm())
+                    .build();
+            NetworkCapabilities reservedNc =
+                    new NetworkCapabilities.Builder(COMMON_CAPABILITIES)
+                            .setNetworkSpecifier(reservedSpec)
+                            .build();
+            reservedNc.setReservationId(reservationNc.getReservationId());
+            return new ReservedServerOffer(reservedNc, serverSocket);
+        }
+
         @Override
         public void onNetworkUnneeded(NetworkRequest request) {
             if (!mReservedServerOffers.containsKey(request.requestId)) {
                 return;
             }
 
-            final ReservedServerOffer reservedOffer = mReservedServerOffers.get(request.requestId);
+            final ReservedServerOffer reservedOffer = mReservedServerOffers.remove(request.requestId);
             // Note that the reserved offer gets torn down when the reservation goes away, even if
             // there are lingering requests.
             reservedOffer.tearDown();
             mProvider.unregisterNetworkOffer(reservedOffer);
         }
+
     }
 
     private class ReservedServerOffer implements NetworkOfferCallback {
-        private final boolean mUseHeaderCompression;
-        private final int mPsm;
         private final NetworkCapabilities mReservedCapabilities;
+        private final BluetoothServerSocket mServerSocket;
 
-        public ReservedServerOffer(NetworkCapabilities reservationCaps) {
-            // getNetworkSpecifier() is guaranteed to return a non-null L2capNetworkSpecifier.
-            final L2capNetworkSpecifier reservationSpec =
-                    (L2capNetworkSpecifier) reservationCaps.getNetworkSpecifier();
-            mUseHeaderCompression =
-                    reservationSpec.getHeaderCompression() == HEADER_COMPRESSION_6LOWPAN;
-
-            // TODO: open BluetoothServerSocket and allocate a PSM.
-            mPsm = 0x80;
-
-            final L2capNetworkSpecifier reservedSpec = new L2capNetworkSpecifier.Builder()
-                    .setRole(ROLE_SERVER)
-                    .setHeaderCompression(reservationSpec.getHeaderCompression())
-                    .setPsm(mPsm)
-                    .build();
-            mReservedCapabilities = new NetworkCapabilities.Builder(reservationCaps)
-                    .setNetworkSpecifier(reservedSpec)
-                    .build();
+        public ReservedServerOffer(NetworkCapabilities reservedCapabilities,
+                BluetoothServerSocket serverSocket) {
+            mReservedCapabilities = reservedCapabilities;
+            // TODO: ServerSocket will be managed by an AcceptThread.
+            mServerSocket = serverSocket;
         }
 
         public NetworkCapabilities getReservedCapabilities() {
@@ -202,8 +236,11 @@
          * This method can be called multiple times.
          */
         public void tearDown() {
-            // TODO: implement.
-            // This method can be called multiple times.
+            try {
+                mServerSocket.close();
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to close BluetoothServerSocket", e);
+            }
         }
     }
 
@@ -244,11 +281,18 @@
     public void start() {
         mHandler.post(() -> {
             final PackageManager pm = mContext.getPackageManager();
-            if (pm.hasSystemFeature(FEATURE_BLUETOOTH_LE)) {
-                mContext.getSystemService(ConnectivityManager.class).registerNetworkProvider(mProvider);
-                mProvider.registerNetworkOffer(BlanketReservationOffer.SCORE,
-                        BlanketReservationOffer.CAPABILITIES, mHandler::post, mBlanketOffer);
+            if (!pm.hasSystemFeature(FEATURE_BLUETOOTH_LE)) {
+                return;
             }
+            mBluetoothManager = mContext.getSystemService(BluetoothManager.class);
+            if (mBluetoothManager == null) {
+                // Can this ever happen?
+                Log.wtf(TAG, "BluetoothManager not found");
+                return;
+            }
+            mContext.getSystemService(ConnectivityManager.class).registerNetworkProvider(mProvider);
+            mProvider.registerNetworkOffer(BlanketReservationOffer.SCORE,
+                    BlanketReservationOffer.CAPABILITIES, mHandler::post, mBlanketOffer);
         });
     }
 }
diff --git a/tests/unit/java/com/android/server/L2capNetworkProviderTest.kt b/tests/unit/java/com/android/server/L2capNetworkProviderTest.kt
index 2fd3d4f..49eb476 100644
--- a/tests/unit/java/com/android/server/L2capNetworkProviderTest.kt
+++ b/tests/unit/java/com/android/server/L2capNetworkProviderTest.kt
@@ -16,6 +16,9 @@
 
 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
@@ -75,6 +78,9 @@
     @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)
@@ -87,6 +93,11 @@
         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