Issue: Some Printers Not Working in Capacitor Plugin

Description

I’m developing a Capacitor plugin in Java for printing receipts. The same code works perfectly in a native Android app with all printers, but when used in a Capacitor plugin, some printers work while others fail.

Steps to Reproduce

1- Implement printing using Printooth in the Capacitor plugin.
2- Attempt to print with different Bluetooth printers.
3- Some printers fail with the following error:

Connection failed: read failed, socket might be closed or timeout, read ret: -1

Tried using BluetoothSocket directly instead of Printooth.
Encountered this error:

Sending plugin error: {"save":true,"callbackId":"6990095","pluginId":"PrinterBridge","methodName":"print","success":false,"error":{"message":"Bluetooth connect permission not granted"}}

Verified that the required Bluetooth permissions are granted, but the issue persists.

Expected Behavior

All printers should work the same way they do in the native Android app.

Actual Behavior

Some printers fail to connect in the Capacitor plugin, while they work fine in a native Android app.

Additional Notes

The issue seems to be specific to the Capacitor plugin environment.
Could this be related to how Capacitor handles Bluetooth permissions or connections?

@CapacitorPlugin(
        name = "PrinterBridge",
        permissions = {
                @Permission(
                        alias = "bluetooth",
                        strings = {
                                // Basic Bluetooth permissions
                                Manifest.permission.BLUETOOTH,
                                Manifest.permission.BLUETOOTH_ADMIN,
                                // Android 12+ permissions
                                Manifest.permission.BLUETOOTH_CONNECT,
                                Manifest.permission.BLUETOOTH_SCAN,
                                // Android 14+ permission
                                Manifest.permission.NEARBY_WIFI_DEVICES
                        }
                )
        }
)
public class MyPlugin extends Plugin {
    private static final String TAG = "PrinterBridge";

    private BluetoothGatt bluetoothGatt;

    @PluginMethod
    public void print(PluginCall call) {
        Log.d(TAG, "Print method called");

        new Thread(() -> {
            Paper.init(getActivity().getApplicationContext());
            Printooth.INSTANCE.init(getActivity().getApplicationContext());
            Log.d(TAG, "PrinterBridge plugin loaded");

            if (!hasRequiredPermissions()) {
                Log.d(TAG, "Required permissions not granted, requesting permissions");
                requestBluetoothPermissions();
                return;
            }

            String printerName = call.getString("deviceName");
            String deviceId = call.getString("deviceId");

            Printooth.INSTANCE.setPrinter(printerName, deviceId);

            ArrayList<Printable> printables = new ArrayList<>();
            printables.add(new RawPrintable.Builder(new byte[]{0x1B, 0x74, 28}).build());

            try {
                printables.add(new RawPrintable.Builder("test".getBytes("ISO-8859-6"))
                        .setNewLinesAfter(1)
                        .build());
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            Printooth.INSTANCE.printer().print(printables);
            

            Printooth.INSTANCE.printer().setPrintingCallback(new PrintingCallback() {
                @Override
                public void disconnected() {
                    Log.d(TAG, "Connecting with printer...");
                }

                @Override
                public void connectingWithPrinter() {
                    Log.d(TAG, "Connecting with printer...");
                }

                @Override
                public void connectionFailed(String error) {
                    Log.e(TAG, "Connection failed: " + error);
                    getActivity().runOnUiThread(() -> {
                        call.reject("Printer connection failed: " + error);
                    });
                }

                @Override
                public void onError(String error) {
                    Log.e(TAG, "Printing error: " + error);
                    getActivity().runOnUiThread(() -> {
                        call.reject("Printing error: " + error);
                    });
                }

                @Override
                public void onMessage(String message) {
                    Log.d(TAG, "Printer message: " + message);
                }

                @Override
                public void printingOrderSentSuccessfully() {
                    Log.d(TAG, "Printing order sent successfully");
                    getActivity().runOnUiThread(() -> {
                        call.resolve();
                    });
                }
            });
        }).start();
    }

    private void requestBluetoothPermissions() {
        Log.d(TAG, "Explicitly requesting Bluetooth permissions");

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 14+
            ActivityCompat.requestPermissions(getActivity(), new String[] {
                    Manifest.permission.BLUETOOTH_CONNECT,
                    Manifest.permission.BLUETOOTH_SCAN,
                    Manifest.permission.NEARBY_WIFI_DEVICES
            }, BLUETOOTH_PERMISSION_REQUEST);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12+
            ActivityCompat.requestPermissions(getActivity(), new String[] {
                    Manifest.permission.BLUETOOTH_CONNECT,
                    Manifest.permission.BLUETOOTH_SCAN
            }, BLUETOOTH_PERMISSION_REQUEST);
        } else {
            Log.d(TAG, "Bluetooth permissions automatically granted for Android < 12");
        }
    }

    @PluginMethod
    public void checkPermissions() {
        boolean permissionsGranted = hasRequiredPermissions();

        if (permissionsGranted) {
            Toast.makeText(getActivity(), "All required permissions granted", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(getActivity(), "Bluetooth permissions not granted", Toast.LENGTH_SHORT).show();
        }
    }

    @PluginMethod
    public void requestPermissions() {
        Log.d(TAG, "Requesting Bluetooth permissions");

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            boolean hasConnect = ContextCompat.checkSelfPermission(
                    getActivity(), Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED;
            boolean hasScan = ContextCompat.checkSelfPermission(
                    getActivity(), Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED;

            Log.d(TAG, "Current permission status - BLUETOOTH_CONNECT: " +
                    (hasConnect ? "granted" : "denied") +
                    ", BLUETOOTH_SCAN: " + (hasScan ? "granted" : "denied"));

            String[] permissions;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 14+
                permissions = new String[] {
                        Manifest.permission.BLUETOOTH_CONNECT,
                        Manifest.permission.BLUETOOTH_SCAN,
                        Manifest.permission.NEARBY_WIFI_DEVICES
                };
            } else { // Android 12-13
                permissions = new String[] {
                        Manifest.permission.BLUETOOTH_CONNECT,
                        Manifest.permission.BLUETOOTH_SCAN
                };
            }

            ActivityCompat.requestPermissions(getActivity(), permissions, BLUETOOTH_PERMISSION_REQUEST);
        } else {
            Toast.makeText(getActivity(), "No permissions needed for your Android version", Toast.LENGTH_SHORT).show();
        }
    }

    @PermissionCallback
    public void permissionsCallback(PluginCall call) {
        if (call == null) {
            Log.e(TAG, "No stored plugin call for permissions request");
            return;
        }

        JSObject permissionsResultJSON = new JSObject();
        boolean permissionsGranted = hasRequiredPermissions();

        permissionsResultJSON.put("bluetooth", permissionsGranted ? "granted" : "denied");

        Log.d(TAG, "Permissions granted: " + permissionsGranted);

        if (call != null) {
            if (permissionsGranted) {
                Log.d(TAG, "Permissions granted, proceeding with print");
                String printerName = call.getString("deviceName");
                String deviceId = call.getString("deviceId");

               printText(data, printerName, deviceId);

                call = null;
            } else {
                Log.e(TAG, "Permissions denied, cannot proceed with print");
                PluginCall storedCall = call;
                call = null;
                storedCall.reject("Bluetooth permissions not granted");
            }
        }

        call.resolve(permissionsResultJSON);
    }

    public boolean hasRequiredPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 14+
            return ContextCompat.checkSelfPermission(
                    getActivity(), Manifest.permission.BLUETOOTH_CONNECT
            ) == PackageManager.PERMISSION_GRANTED &&
                    ContextCompat.checkSelfPermission(
                            getActivity(), Manifest.permission.BLUETOOTH_SCAN
                    ) == PackageManager.PERMISSION_GRANTED &&
                    ContextCompat.checkSelfPermission(
                            getActivity(), Manifest.permission.NEARBY_WIFI_DEVICES
                    ) == PackageManager.PERMISSION_GRANTED;
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12+
            return ContextCompat.checkSelfPermission(
                    getActivity(), Manifest.permission.BLUETOOTH_CONNECT
            ) == PackageManager.PERMISSION_GRANTED &&
                    ContextCompat.checkSelfPermission(
                            getActivity(), Manifest.permission.BLUETOOTH_SCAN
                    ) == PackageManager.PERMISSION_GRANTED;
        }
        return true; // Permissions automatically granted on Android < 12
    }

    private static final int BLUETOOTH_PERMISSION_REQUEST = 1001;


    @Override
    protected void handleOnDestroy() {
        if (bluetoothGatt != null) {
            if (ActivityCompat.checkSelfPermission(this.getContext(),
                    Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) {
                bluetoothGatt.close();
            }
            bluetoothGatt = null;
        }
        super.handleOnDestroy();
    }
}

Would appreciate any insights or suggestions!