Introduces mechanism for background rfcomm servers

This change adds a mechanism in the AdapterService which allows it to
register RFCOMM listeners requested by a BluetoothAdapter.
This is so that applications can request the framework to listen for
incoming RFCOMM connections in the background.

Apps can request that the bluetooth manager, via a BluetoothAdapter,
listen on a specific service record, and then transact incoming socket
connections after receiving a notification of availability via a
PendingIntent.

Tag: #feature
Test: Run the CTS Bluetooth Rfcomm Handoff service Test. This requires
two devices.
Bug: 186494155
Ignore-AOSP-First: Need to commit to downstream first in order to
resolve a merge conflict.

Change-Id: Ia0c71969e691e6353f22fe3b7dae4a7500230e03
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
index 7490cc3..2887246 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.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.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.text.TextUtils;
 import android.util.Base64;
 import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.bluetooth.BluetoothMetricsProto;
@@ -136,13 +140,19 @@
 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;
 
@@ -153,6 +163,7 @@
     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;
@@ -249,6 +260,7 @@
         }
     }
 
+    private BluetoothAdapter mAdapter;
     private AdapterProperties mAdapterProperties;
     private AdapterState mAdapterStateMachine;
     private BondStateMachine mBondStateMachine;
@@ -270,6 +282,10 @@
     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;
@@ -459,6 +475,7 @@
         mRemoteDevices.init();
         clearDiscoveringPackages();
         mBinder = new AdapterServiceBinder(this);
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
         mAdapterProperties = new AdapterProperties(this);
         mAdapterStateMachine = AdapterState.make(this);
         mJniCallbacks = new JniCallbacks(this, mAdapterProperties);
@@ -854,6 +871,8 @@
 
         unregisterReceiver(mAlarmBroadcastReceiver);
 
+        stopRfcommServerSockets();
+
         if (mPendingAlarm != null) {
             mAlarmManager.cancel(mPendingAlarm);
             mPendingAlarm = null;
@@ -1255,6 +1274,219 @@
         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;
     }
@@ -2676,6 +2908,26 @@
             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 8c1c896..7cfc442 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -44,6 +44,7 @@
 
   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,9 +57,11 @@
     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_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int setDiscoverableTimeout(@NonNull java.time.Duration);
     method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int setScanMode(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
@@ -279,6 +282,12 @@
     field public static final int ERROR_PROFILE_NOT_CONNECTED = 14; // 0xe
     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 7b4cba2..1b085bd 100644
--- a/framework/java/android/bluetooth/BluetoothAdapter.java
+++ b/framework/java/android/bluetooth/BluetoothAdapter.java
@@ -29,6 +29,7 @@
 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.BluetoothFrameworkInitializer;
 import android.bluetooth.BluetoothProfile.ConnectionPolicy;
@@ -235,6 +236,31 @@
             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
@@ -2825,6 +2851,135 @@
     }
 
     /**
+     * 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 9d58447..032f9df 100644
--- a/framework/java/android/bluetooth/BluetoothSocket.java
+++ b/framework/java/android/bluetooth/BluetoothSocket.java
@@ -236,6 +236,33 @@
         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 @@
         }
     }
 
+    /** @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 b1aa27d..5017198 100644
--- a/framework/java/android/bluetooth/BluetoothStatusCodes.java
+++ b/framework/java/android/bluetooth/BluetoothStatusCodes.java
@@ -371,6 +371,57 @@
     public static final int ERROR_CALL_ACTIVE = 1119;
 
     /**
+     * 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 5baca98..54b7f01 100644
--- a/system/binder/Android.bp
+++ b/system/binder/Android.bp
@@ -50,6 +50,7 @@
         "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 95ee1cb..c472873 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.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 @@
 
     @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 0000000..38ffe6f
--- /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;
+}
+