Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0662f5b9 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Introduces mechanism for background rfcomm servers"

parents 46b50d22 e671840b
Loading
Loading
Loading
Loading
+252 −0
Original line number Diff line number Diff line
@@ -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--------
+9 −0
Original line number Diff line number Diff line
@@ -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 {
+155 −0
Original line number Diff line number Diff line
@@ -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;
@@ -233,6 +234,31 @@ public final class BluetoothAdapter {
    public static final UUID LE_PSM_CHARACTERISTIC_UUID =
            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
     *
@@ -2844,6 +2870,135 @@ public final class BluetoothAdapter {
        return createNewRfcommSocketAndRecord(name, uuid, true, true);
    }

    /**
     * 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
+32 −0

File changed.

Preview size limit exceeded, changes collapsed.

+51 −0

File changed.

Preview size limit exceeded, changes collapsed.

Loading