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