diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2022-01-28 01:44:00 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2022-01-28 01:44:00 +0000 |
commit | a4195e47e41972a8308a66abb8b9995daf6edbcc (patch) | |
tree | fed65680473b8e007e981805b18ea0ea073e02e7 | |
parent | 6ca88d122bd794a1e432f2dc1ae4b785dd4eaa63 (diff) | |
parent | d6f8dbbcfe6fe54c90d9deabe60b1644698f9cd0 (diff) |
Merge "Introduces mechanism for background rfcomm servers" am: 0662f5b93c am: d6f8dbbcfe
Original change: https://android-review.googlesource.com/c/platform/packages/modules/Bluetooth/+/1885648
Change-Id: Iee0e7a37f422586e200c151655c4c3a552afdada
-rw-r--r-- | android/app/src/com/android/bluetooth/btservice/AdapterService.java | 252 | ||||
-rw-r--r-- | framework/api/system-current.txt | 9 | ||||
-rw-r--r-- | framework/java/android/bluetooth/BluetoothAdapter.java | 155 | ||||
-rw-r--r-- | framework/java/android/bluetooth/BluetoothSocket.java | 32 | ||||
-rw-r--r-- | framework/java/android/bluetooth/BluetoothStatusCodes.java | 51 | ||||
-rw-r--r-- | system/binder/Android.bp | 1 | ||||
-rw-r--r-- | system/binder/android/bluetooth/IBluetooth.aidl | 9 | ||||
-rw-r--r-- | system/binder/android/bluetooth/IncomingRfcommSocketInfo.aidl | 38 |
8 files changed, 547 insertions, 0 deletions
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java index c7ce47d651..bdb5b33fb9 100644 --- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java +++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java @@ -48,6 +48,8 @@ import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProtoEnums; +import android.bluetooth.BluetoothServerSocket; +import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothStatusCodes; import android.bluetooth.BluetoothUuid; import android.bluetooth.BufferConstraints; @@ -57,6 +59,7 @@ import android.bluetooth.IBluetoothConnectionCallback; import android.bluetooth.IBluetoothMetadataListener; import android.bluetooth.IBluetoothOobDataCallback; import android.bluetooth.IBluetoothSocketManager; +import android.bluetooth.IncomingRfcommSocketInfo; import android.bluetooth.OobData; import android.bluetooth.UidTraffic; import android.companion.CompanionDeviceManager; @@ -92,6 +95,7 @@ import android.sysprop.BluetoothProperties; import android.text.TextUtils; import android.util.Base64; import android.util.Log; +import android.util.Slog; import android.util.SparseArray; import com.android.bluetooth.BluetoothMetricsProto; @@ -137,13 +141,19 @@ import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; +import java.time.Duration; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executor; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -154,6 +164,7 @@ public class AdapterService extends Service { private static final int MIN_ADVT_INSTANCES_FOR_MA = 5; private static final int MIN_OFFLOADED_FILTERS = 10; private static final int MIN_OFFLOADED_SCAN_STORAGE_BYTES = 1024; + private static final Duration PENDING_SOCKET_HANDOFF_TIMEOUT = Duration.ofMinutes(1); private final Object mEnergyInfoLock = new Object(); private int mStackReportedState; @@ -250,6 +261,7 @@ public class AdapterService extends Service { } } + private BluetoothAdapter mAdapter; private AdapterProperties mAdapterProperties; private AdapterState mAdapterStateMachine; private BondStateMachine mBondStateMachine; @@ -271,6 +283,10 @@ public class AdapterService extends Service { private boolean mQuietmode = false; private HashMap<String, CallerInfo> mBondAttemptCallerInfo = new HashMap<>(); + private final Map<UUID, RfcommListenerData> mBluetoothServerSockets = + Collections.synchronizedMap(new HashMap<>()); + private final Executor mSocketServersExecutor = r -> new Thread(r).start(); + private AlarmManager mAlarmManager; private PendingIntent mPendingAlarm; private BatteryStatsManager mBatteryStatsManager; @@ -460,6 +476,7 @@ public class AdapterService extends Service { mRemoteDevices.init(); clearDiscoveringPackages(); mBinder = new AdapterServiceBinder(this); + mAdapter = BluetoothAdapter.getDefaultAdapter(); mAdapterProperties = new AdapterProperties(this); mAdapterStateMachine = AdapterState.make(this); mJniCallbacks = new JniCallbacks(this, mAdapterProperties); @@ -855,6 +872,8 @@ public class AdapterService extends Service { unregisterReceiver(mAlarmBroadcastReceiver); + stopRfcommServerSockets(); + if (mPendingAlarm != null) { mAlarmManager.cancel(mPendingAlarm); mPendingAlarm = null; @@ -1256,6 +1275,219 @@ public class AdapterService extends Service { mLeAudioService = LeAudioService.getLeAudioService(); } + @BluetoothAdapter.RfcommListenerResult + private int startRfcommListener( + String name, + ParcelUuid uuid, + PendingIntent pendingIntent, + AttributionSource attributionSource) { + if (mBluetoothServerSockets.containsKey(uuid.getUuid())) { + Slog.d(TAG, + String.format( + "Cannot start RFCOMM listener: UUID %s already in use.", + uuid.getUuid())); + return BluetoothStatusCodes.RFCOMM_LISTENER_START_FAILED_UUID_IN_USE; + } + + try { + startRfcommListenerInternal(name, uuid.getUuid(), pendingIntent, attributionSource); + } catch (IOException e) { + return BluetoothStatusCodes.RFCOMM_LISTENER_FAILED_TO_CREATE_SERVER_SOCKET; + } + + return BluetoothStatusCodes.SUCCESS; + } + + @BluetoothAdapter.RfcommListenerResult + private int stopRfcommListener(ParcelUuid uuid, AttributionSource attributionSource) { + RfcommListenerData listenerData = mBluetoothServerSockets.get(uuid.getUuid()); + + if (listenerData == null) { + Slog.d(TAG, + String.format( + "Cannot stop RFCOMM listener: UUID %s is not registered.", + uuid.getUuid())); + return BluetoothStatusCodes.RFCOMM_LISTENER_OPERATION_FAILED_NO_MATCHING_SERVICE_RECORD; + } + + if (attributionSource.getUid() != listenerData.mAttributionSource.getUid()) { + return BluetoothStatusCodes.RFCOMM_LISTENER_OPERATION_FAILED_DIFFERENT_APP; + } + + // Remove the entry so that it does not try and restart the server socket. + mBluetoothServerSockets.remove(uuid.getUuid()); + + return listenerData.closeServerAndPendingSockets(mHandler); + } + + private IncomingRfcommSocketInfo retrievePendingSocketForServiceRecord( + ParcelUuid uuid, AttributionSource attributionSource) { + IncomingRfcommSocketInfo socketInfo = new IncomingRfcommSocketInfo(); + + RfcommListenerData listenerData = mBluetoothServerSockets.get(uuid.getUuid()); + + if (listenerData == null) { + socketInfo.status = + BluetoothStatusCodes + .RFCOMM_LISTENER_OPERATION_FAILED_NO_MATCHING_SERVICE_RECORD; + return socketInfo; + } + + if (attributionSource.getUid() != listenerData.mAttributionSource.getUid()) { + socketInfo.status = BluetoothStatusCodes.RFCOMM_LISTENER_OPERATION_FAILED_DIFFERENT_APP; + return socketInfo; + } + + BluetoothSocket socket = listenerData.mPendingSockets.poll(); + + if (socket == null) { + socketInfo.status = BluetoothStatusCodes.RFCOMM_LISTENER_NO_SOCKET_AVAILABLE; + return socketInfo; + } + + mHandler.removeCallbacksAndEqualMessages(socket); + + socketInfo.bluetoothDevice = socket.getRemoteDevice(); + socketInfo.pfd = socket.getParcelFileDescriptor(); + socketInfo.status = BluetoothStatusCodes.SUCCESS; + + return socketInfo; + } + + private void handleIncomingRfcommConnections(UUID uuid) { + RfcommListenerData listenerData = mBluetoothServerSockets.get(uuid); + for (;;) { + BluetoothSocket socket; + try { + socket = listenerData.mServerSocket.accept(); + } catch (IOException e) { + if (mBluetoothServerSockets.containsKey(uuid)) { + // The uuid still being in the map indicates that the accept failure is + // unexpected. Try and restart the listener. + Slog.e(TAG, "Failed to accept socket on " + listenerData.mServerSocket, e); + restartRfcommListener(listenerData, uuid); + } + return; + } + + listenerData.mPendingSockets.add(socket); + try { + listenerData.mPendingIntent.send(); + } catch (PendingIntent.CanceledException e) { + Slog.e(TAG, "PendingIntent for RFCOMM socket notifications cancelled.", e); + // The pending intent was cancelled, close the server as there is no longer any way + // to notify the app that registered the listener. + listenerData.closeServerAndPendingSockets(mHandler); + mBluetoothServerSockets.remove(uuid); + return; + } + mHandler.postDelayed( + () -> pendingSocketTimeoutRunnable(listenerData, socket), + socket, + PENDING_SOCKET_HANDOFF_TIMEOUT.toMillis()); + } + } + + // Tries to restart the rfcomm listener for the given UUID + private void restartRfcommListener(RfcommListenerData listenerData, UUID uuid) { + listenerData.closeServerAndPendingSockets(mHandler); + try { + startRfcommListenerInternal( + listenerData.mName, + uuid, + listenerData.mPendingIntent, + listenerData.mAttributionSource); + } catch (IOException e) { + Slog.e(TAG, "Failed to recreate rfcomm server socket", e); + + mBluetoothServerSockets.remove(uuid); + } + } + + private void pendingSocketTimeoutRunnable( + RfcommListenerData listenerData, BluetoothSocket socket) { + boolean socketFound = listenerData.mPendingSockets.remove(socket); + if (socketFound) { + try { + socket.close(); + } catch (IOException e) { + Slog.e(TAG, "Failed to close bt socket", e); + // We don't care if closing the socket failed, just continue on. + } + } + } + + private void startRfcommListenerInternal( + String name, UUID uuid, PendingIntent intent, AttributionSource attributionSource) + throws IOException { + BluetoothServerSocket bluetoothServerSocket = + mAdapter.listenUsingRfcommWithServiceRecord(name, uuid); + + RfcommListenerData listenerData = + new RfcommListenerData(bluetoothServerSocket, name, intent, attributionSource); + + mBluetoothServerSockets.put(uuid, listenerData); + + mSocketServersExecutor.execute(() -> handleIncomingRfcommConnections(uuid)); + } + + private void stopRfcommServerSockets() { + synchronized (mBluetoothServerSockets) { + mBluetoothServerSockets.forEach((key, value) -> { + mBluetoothServerSockets.remove(key); + value.closeServerAndPendingSockets(mHandler); + }); + } + } + + private static class RfcommListenerData { + final BluetoothServerSocket mServerSocket; + // Service record name + final String mName; + // The Intent which contains the Service info to which the incoming socket connections are + // handed off to. + final PendingIntent mPendingIntent; + // AttributionSource for the requester of the RFCOMM listener + final AttributionSource mAttributionSource; + // Contains the connected sockets which are pending transfer to the app which requested the + // listener. + final ConcurrentLinkedQueue<BluetoothSocket> mPendingSockets = + new ConcurrentLinkedQueue<>(); + + RfcommListenerData( + BluetoothServerSocket serverSocket, + String name, + PendingIntent pendingIntent, + AttributionSource attributionSource) { + mServerSocket = serverSocket; + mName = name; + mPendingIntent = pendingIntent; + mAttributionSource = attributionSource; + } + + int closeServerAndPendingSockets(Handler handler) { + int result = BluetoothStatusCodes.SUCCESS; + try { + mServerSocket.close(); + } catch (IOException e) { + Slog.e(TAG, "Failed to call close on rfcomm server socket", e); + result = BluetoothStatusCodes.RFCOMM_LISTENER_FAILED_TO_CLOSE_SERVER_SOCKET; + } + mPendingSockets.forEach( + pendingSocket -> { + handler.removeCallbacksAndEqualMessages(pendingSocket); + try { + pendingSocket.close(); + } catch (IOException e) { + Slog.e(TAG, "Failed to close socket", e); + } + }); + mPendingSockets.clear(); + + return result; + } + } + private boolean isAvailable() { return !mCleaningUp; } @@ -2674,6 +2906,26 @@ public class AdapterService extends Service { enforceBluetoothPrivilegedPermission(service); return service.allowLowLatencyAudio(allowed, device); } + + @Override + public int startRfcommListener( + String name, + ParcelUuid uuid, + PendingIntent pendingIntent, + AttributionSource attributionSource) { + return mService.startRfcommListener(name, uuid, pendingIntent, attributionSource); + } + + @Override + public int stopRfcommListener(ParcelUuid uuid, AttributionSource attributionSource) { + return mService.stopRfcommListener(uuid, attributionSource); + } + + @Override + public IncomingRfcommSocketInfo retrievePendingSocketForServiceRecord( + ParcelUuid uuid, AttributionSource attributionSource) { + return mService.retrievePendingSocketForServiceRecord(uuid, attributionSource); + } } // ----API Methods-------- diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 1f928c1f55..62002c0b8a 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -44,6 +44,7 @@ package android.bluetooth { public final class BluetoothAdapter { method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean addOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int closeRfcommServer(@NonNull java.util.UUID); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean disable(boolean); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disableBLE(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enableBLE(); @@ -56,7 +57,9 @@ package android.bluetooth { method public boolean isLeEnabled(); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean removeActiveDevice(int); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean removeOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener); + method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public android.bluetooth.BluetoothSocket retrieveConnectedRfcommSocket(@NonNull java.util.UUID); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setActiveDevice(@NonNull android.bluetooth.BluetoothDevice, int); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int startRfcommServer(@NonNull String, @NonNull java.util.UUID, @NonNull android.app.PendingIntent); field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED"; field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE"; field public static final int ACTIVE_DEVICE_ALL = 2; // 0x2 @@ -260,6 +263,12 @@ package android.bluetooth { field public static final int ERROR_ANOTHER_ACTIVE_OOB_REQUEST = 1000; // 0x3e8 field public static final int ERROR_TIMEOUT = 15; // 0xf field public static final int NOT_ALLOWED = 401; // 0x191 + field public static final int RFCOMM_LISTENER_FAILED_TO_CLOSE_SERVER_SOCKET = 2004; // 0x7d4 + field public static final int RFCOMM_LISTENER_FAILED_TO_CREATE_SERVER_SOCKET = 2003; // 0x7d3 + field public static final int RFCOMM_LISTENER_NO_SOCKET_AVAILABLE = 2005; // 0x7d5 + field public static final int RFCOMM_LISTENER_OPERATION_FAILED_DIFFERENT_APP = 2002; // 0x7d2 + field public static final int RFCOMM_LISTENER_OPERATION_FAILED_NO_MATCHING_SERVICE_RECORD = 2001; // 0x7d1 + field public static final int RFCOMM_LISTENER_START_FAILED_UUID_IN_USE = 2000; // 0x7d0 } public final class BluetoothUuid { diff --git a/framework/java/android/bluetooth/BluetoothAdapter.java b/framework/java/android/bluetooth/BluetoothAdapter.java index f420b94db7..ff22c7614a 100644 --- a/framework/java/android/bluetooth/BluetoothAdapter.java +++ b/framework/java/android/bluetooth/BluetoothAdapter.java @@ -29,6 +29,7 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SuppressLint; import android.annotation.SystemApi; //import android.app.PropertyInvalidatedCache; +import android.app.PendingIntent; import android.bluetooth.BluetoothDevice.Transport; import android.bluetooth.BluetoothProfile.ConnectionPolicy; import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission; @@ -234,6 +235,31 @@ public final class BluetoothAdapter { UUID.fromString("2d410339-82b6-42aa-b34e-e2e01df8cc1a"); /** + * Used as an optional extra field for the {@link PendingIntent} provided to {@link + * #startRfcommServer(String, UUID, PendingIntent)}. This is useful for when an + * application registers multiple RFCOMM listeners, and needs a way to determine which service + * record the incoming {@link BluetoothSocket} is using. + * + * @hide + */ + public static final String EXTRA_RFCOMM_LISTENER_ID = + "android.bluetooth.adapter.extra.RFCOMM_LISTENER_ID"; + + /** @hide */ + @IntDef(value = { + BluetoothStatusCodes.SUCCESS, + BluetoothStatusCodes.ERROR_TIMEOUT, + BluetoothStatusCodes.RFCOMM_LISTENER_START_FAILED_UUID_IN_USE, + BluetoothStatusCodes.RFCOMM_LISTENER_OPERATION_FAILED_NO_MATCHING_SERVICE_RECORD, + BluetoothStatusCodes.RFCOMM_LISTENER_OPERATION_FAILED_DIFFERENT_APP, + BluetoothStatusCodes.RFCOMM_LISTENER_FAILED_TO_CREATE_SERVER_SOCKET, + BluetoothStatusCodes.RFCOMM_LISTENER_FAILED_TO_CLOSE_SERVER_SOCKET, + BluetoothStatusCodes.RFCOMM_LISTENER_NO_SOCKET_AVAILABLE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RfcommListenerResult {} + + /** * Human-readable string helper for AdapterState * * @hide @@ -2845,6 +2871,135 @@ public final class BluetoothAdapter { } /** + * Requests the framework to start an RFCOMM socket server which listens based on the provided + * {@code name} and {@code uuid}. + * <p> + * Incoming connections will cause the system to start the component described in the {@link + * PendingIntent}, {@code pendingIntent}. After the component is started, it should obtain a + * {@link BluetoothAdapter} and retrieve the {@link BluetoothSocket} via {@link + * #retrieveConnectedRfcommSocket(UUID)}. + * <p> + * An application may register multiple RFCOMM listeners. It is recommended to set the extra + * field {@link #EXTRA_RFCOMM_LISTENER_ID} to help determine which service record the incoming + * {@link BluetoothSocket} is using. + * <p> + * The provided {@link PendingIntent} must be created with the {@link + * PendingIntent#FLAG_IMMUTABLE} flag. + * + * @param name service name for SDP record + * @param uuid uuid for SDP record + * @param pendingIntent component which is called when a new RFCOMM connection is available + * @return a status code from {@link BluetoothStatusCodes} + * @throws IllegalArgumentException if {@code pendingIntent} is not created with the {@link + * PendingIntent#FLAG_IMMUTABLE} flag. + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED + }) + @RfcommListenerResult + public int startRfcommServer(@NonNull String name, @NonNull UUID uuid, + @NonNull PendingIntent pendingIntent) { + if (!pendingIntent.isImmutable()) { + throw new IllegalArgumentException("The provided PendingIntent is not immutable"); + } + try { + return mService.startRfcommListener( + name, new ParcelUuid(uuid), pendingIntent, mAttributionSource); + } catch (RemoteException e) { + Log.e(TAG, "Failed to transact RFCOMM listener start request", e); + return BluetoothStatusCodes.ERROR_TIMEOUT; + } + } + + /** + * Closes the RFCOMM socket server listening on the given SDP record name and UUID. This can be + * called by applications after calling {@link #startRfcommServer(String, UUID, + * PendingIntent)} to stop listening for incoming RFCOMM connections. + * + * @param uuid uuid for SDP record + * @return a status code from {@link BluetoothStatusCodes} + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + @RfcommListenerResult + public int closeRfcommServer(@NonNull UUID uuid) { + try { + return mService.stopRfcommListener(new ParcelUuid(uuid), mAttributionSource); + } catch (RemoteException e) { + Log.e(TAG, "Failed to transact RFCOMM listener stop request", e); + return BluetoothStatusCodes.ERROR_TIMEOUT; + } + } + + /** + * Retrieves a connected {@link BluetoothSocket} for the given service record from a RFCOMM + * listener which was registered with {@link #startRfcommServer(String, UUID, PendingIntent)}. + * <p> + * This method should be called by the component started by the {@link PendingIntent} which was + * registered during the call to {@link #startRfcommServer(String, UUID, PendingIntent)} in + * order to retrieve the socket. + * + * @param uuid the same UUID used to register the listener previously + * @return a connected {@link BluetoothSocket} or {@code null} if no socket is available + * @throws IllegalStateException if the socket could not be retrieved because the application is + * trying to obtain a socket for a listener it did not register (incorrect {@code + * uuid}). + * @hide + */ + @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public @NonNull BluetoothSocket retrieveConnectedRfcommSocket(@NonNull UUID uuid) { + IncomingRfcommSocketInfo socketInfo; + + try { + socketInfo = + mService.retrievePendingSocketForServiceRecord( + new ParcelUuid(uuid), mAttributionSource); + } catch (RemoteException e) { + return null; + } + + switch (socketInfo.status) { + case BluetoothStatusCodes.SUCCESS: + try { + return BluetoothSocket.createSocketFromOpenFd( + socketInfo.pfd, + socketInfo.bluetoothDevice, + new ParcelUuid(uuid)); + } catch (IOException e) { + return null; + } + case BluetoothStatusCodes.RFCOMM_LISTENER_OPERATION_FAILED_DIFFERENT_APP: + throw new IllegalStateException( + String.format( + "RFCOMM listener for UUID %s was not registered by this app", + uuid)); + case BluetoothStatusCodes.RFCOMM_LISTENER_NO_SOCKET_AVAILABLE: + return null; + default: + Log.e(TAG, + String.format( + "Unexpected result: (%d), from the adapter service while retrieving" + + " an rfcomm socket", + socketInfo.status)); + return null; + } + } + + /** * Create a listening, insecure RFCOMM Bluetooth socket with Service Record. * <p>The link key is not required to be authenticated, i.e the communication may be * vulnerable to Person In the Middle attacks. For Bluetooth 2.1 devices, diff --git a/framework/java/android/bluetooth/BluetoothSocket.java b/framework/java/android/bluetooth/BluetoothSocket.java index 9d58447607..032f9df4f7 100644 --- a/framework/java/android/bluetooth/BluetoothSocket.java +++ b/framework/java/android/bluetooth/BluetoothSocket.java @@ -236,6 +236,33 @@ public final class BluetoothSocket implements Closeable { mOutputStream = new BluetoothOutputStream(this); } + /** + * Creates a BluetoothSocket from a {@link ParcelFileDescriptor}. This is used for when the + * underlying mPfd is transferred to a separate process (e.g. over a binder), and the socket + * must be reconstructed. + * <p> + * The socket should already be connected in this case, so {@link #connect()} should not be + * called. + * + * @param pfd is the {@link ParcelFileDescriptor} for an already connected BluetoothSocket + * @param device is the remote {@link BluetoothDevice} that this socket is connected to + * @param uuid is the service ID that this RFCOMM connection is using + * @throws IOException if socket creation fails. + */ + /*package*/ static BluetoothSocket createSocketFromOpenFd( + ParcelFileDescriptor pfd, BluetoothDevice device, ParcelUuid uuid) throws IOException { + BluetoothSocket bluetoothSocket = + new BluetoothSocket(TYPE_RFCOMM, pfd.getFd(), true, true, device, -1, uuid); + + bluetoothSocket.mPfd = pfd; + bluetoothSocket.mSocket = new LocalSocket(pfd.getFileDescriptor()); + bluetoothSocket.mSocketIS = bluetoothSocket.mSocket.getInputStream(); + bluetoothSocket.mSocketOS = bluetoothSocket.mSocket.getOutputStream(); + bluetoothSocket.mSocketState = SocketState.CONNECTED; + + return bluetoothSocket; + } + private BluetoothSocket(BluetoothSocket s) { if (VDBG) Log.d(TAG, "Creating new Private BluetoothSocket of type: " + s.mType); mUuid = s.mUuid; @@ -718,6 +745,11 @@ public final class BluetoothSocket implements Closeable { } } + /** @hide */ + public ParcelFileDescriptor getParcelFileDescriptor() { + return mPfd; + } + private String convertAddr(final byte[] addr) { return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); diff --git a/framework/java/android/bluetooth/BluetoothStatusCodes.java b/framework/java/android/bluetooth/BluetoothStatusCodes.java index c6f6cde44e..0425220c8d 100644 --- a/framework/java/android/bluetooth/BluetoothStatusCodes.java +++ b/framework/java/android/bluetooth/BluetoothStatusCodes.java @@ -310,6 +310,57 @@ public final class BluetoothStatusCodes { public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_DISABLE_ENCRYPTION_FAILED = 1115; /** + * Indicates that the RFCOMM listener could not be started due to the requested UUID already + * being in use. + * + * @hide + */ + @SystemApi + public static final int RFCOMM_LISTENER_START_FAILED_UUID_IN_USE = 2000; + + /** + * Indicates that the operation could not be competed because the service record on which the + * operation was requested on does not exist. + * + * @hide + */ + @SystemApi + public static final int RFCOMM_LISTENER_OPERATION_FAILED_NO_MATCHING_SERVICE_RECORD = 2001; + + /** + * Indicates that the operation could not be completed because the application requesting the + * operation on the RFCOMM listener was not the one which registered it. + * + * @hide + */ + @SystemApi + public static final int RFCOMM_LISTENER_OPERATION_FAILED_DIFFERENT_APP = 2002; + + /** + * Indicates that the creation of the underlying BluetoothServerSocket failed. + * + * @hide + */ + @SystemApi + public static final int RFCOMM_LISTENER_FAILED_TO_CREATE_SERVER_SOCKET = 2003; + + /** + * Indicates that closing the underlying BluetoothServerSocket failed. + * + * @hide + */ + @SystemApi + public static final int RFCOMM_LISTENER_FAILED_TO_CLOSE_SERVER_SOCKET = 2004; + + /** + * Indicates that there is no socket available to retrieve from the given listener. + * + * @hide + */ + @SystemApi + public static final int RFCOMM_LISTENER_NO_SOCKET_AVAILABLE = 2005; + + /** * Indicates that an unknown error has occurred has occurred. */ public static final int ERROR_UNKNOWN = Integer.MAX_VALUE; diff --git a/system/binder/Android.bp b/system/binder/Android.bp index 5baca984be..54b7f01be7 100644 --- a/system/binder/Android.bp +++ b/system/binder/Android.bp @@ -50,6 +50,7 @@ filegroup { "android/bluetooth/IBluetoothOobDataCallback.aidl", "android/bluetooth/IBluetoothLeCallControl.aidl", "android/bluetooth/IBluetoothLeCallControlCallback.aidl", + "android/bluetooth/IncomingRfcommSocketInfo.aidl", "android/bluetooth/le/IAdvertisingSetCallback.aidl", "android/bluetooth/le/IPeriodicAdvertisingCallback.aidl", "android/bluetooth/le/IScannerCallback.aidl", diff --git a/system/binder/android/bluetooth/IBluetooth.aidl b/system/binder/android/bluetooth/IBluetooth.aidl index 9a5b6b9335..cf3b679dfd 100644 --- a/system/binder/android/bluetooth/IBluetooth.aidl +++ b/system/binder/android/bluetooth/IBluetooth.aidl @@ -16,6 +16,7 @@ package android.bluetooth; +import android.app.PendingIntent; import android.bluetooth.IBluetoothCallback; import android.bluetooth.IBluetoothConnectionCallback; import android.bluetooth.IBluetoothMetadataListener; @@ -25,6 +26,7 @@ import android.bluetooth.IBluetoothStateChangeCallback; import android.bluetooth.BluetoothActivityEnergyInfo; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; +import android.bluetooth.IncomingRfcommSocketInfo; import android.bluetooth.OobData; import android.content.AttributionSource; import android.os.ParcelUuid; @@ -258,4 +260,11 @@ interface IBluetooth @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") boolean allowLowLatencyAudio(in boolean allowed, in BluetoothDevice device); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") + int startRfcommListener(String name, in ParcelUuid uuid, in PendingIntent intent, in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") + int stopRfcommListener(in ParcelUuid uuid, in AttributionSource attributionSource); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") + IncomingRfcommSocketInfo retrievePendingSocketForServiceRecord(in ParcelUuid uuid, in AttributionSource attributionSource); } diff --git a/system/binder/android/bluetooth/IncomingRfcommSocketInfo.aidl b/system/binder/android/bluetooth/IncomingRfcommSocketInfo.aidl new file mode 100644 index 0000000000..38ffe6f699 --- /dev/null +++ b/system/binder/android/bluetooth/IncomingRfcommSocketInfo.aidl @@ -0,0 +1,38 @@ +/* + * Copyright 2022, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.os.ParcelFileDescriptor; + +/** + * Output parameters for IBluetooth.retrievePendingSocketForServiceRecord + * + * @hide + */ +parcelable IncomingRfcommSocketInfo { + /** The underlying file descriptor for the socket. */ + ParcelFileDescriptor pfd; + /** The bluetooth device info for the remote device that this socket is connected to. */ + BluetoothDevice bluetoothDevice; + /** + * Status info on whether or not the socket was retrieved successfully. See + * BluetoothAdapter.RfcommListenerResult for possible values. + */ + int status; +} + |