package com.pozo.printer; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.usb.*; import android.os.Build; import android.util.Log; import androidx.core.content.ContextCompat; import com.dantsu.escposprinter.EscPosPrinter; import com.dantsu.escposprinter.connection.usb.UsbConnection; import com.getcapacitor.JSObject; import com.pozo.printer.helper.ReceiptBuilder; import com.pozo.printer.model.TokenItem; import com.pozo.printer.utils.EscPosUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class UsbPrinter { private static final String ACTION_USB_PERMISSION = "com.pozo.printer.USB_PERMISSION"; public static UsbDevice connectedUsbDevice; private static ConnectCallback pendingConnectCallback; private static final BroadcastReceiver usbReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (!ACTION_USB_PERMISSION.equals(intent.getAction())) { return; } if (pendingConnectCallback == null) { return; } boolean granted = intent.getBooleanExtra( UsbManager.EXTRA_PERMISSION_GRANTED, false); UsbDevice device; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { device = intent.getParcelableExtra( UsbManager.EXTRA_DEVICE, UsbDevice.class); } else { device = intent.getParcelableExtra( UsbManager.EXTRA_DEVICE); } if (granted && device != null) { connectedUsbDevice = device; JSObject obj = new JSObject(); obj.put( "name", device.getProductName() != null ? device.getProductName() : "USB Printer"); obj.put( "address", String.valueOf(device.getDeviceId())); obj.put("connected", true); pendingConnectCallback.onConnected(obj); } else { pendingConnectCallback.onError( "USB Permission Denied"); } pendingConnectCallback = null; } }; public static void initialize(Context context) { IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { context.registerReceiver( usbReceiver, filter, Context.RECEIVER_NOT_EXPORTED); } else { context.registerReceiver( usbReceiver, filter); } } public static UsbDevice findUsbPrinter(Context context) { UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE); if (manager == null) return null; for (UsbDevice device : manager.getDeviceList().values()) { for (int i = 0; i < device.getInterfaceCount(); i++) { if (device.getInterface(i).getInterfaceClass() == UsbConstants.USB_CLASS_PRINTER) { return device; } } } return null; } // ─── Callback interface ───────────────────────────────────────── public interface ConnectCallback { void onConnected(JSObject printer); void onError(String message); } public interface PrintCallback { void onSuccess(); void onError(String message); } // ─── Scan ─────────────────────────────────────────────────────── public static List scanDevices(Context context) { List result = new ArrayList<>(); UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE); if (manager == null) return result; for (UsbDevice device : manager.getDeviceList().values()) { boolean isPrinter = false; for (int i = 0; i < device.getInterfaceCount(); i++) { UsbInterface usbInterface = device.getInterface(i); if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_PRINTER) { isPrinter = true; break; } } if (isPrinter) { JSObject obj = new JSObject(); obj.put("name", device.getProductName() != null ? device.getProductName() : "USB Printer"); obj.put("address", String.valueOf(device.getDeviceId())); result.add(obj); } } return result; } private static final String TAG = "USB_CONNECT"; public static void connectUsb( Context context, ConnectCallback callback) throws Exception { Log.d(TAG, "===== connectUsb() called ====="); UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE); Log.d(TAG, "UsbManager: " + manager); if (manager == null) { Log.e(TAG, "UsbManager unavailable"); throw new Exception("UsbManager unavailable"); } Log.d(TAG, "Searching for USB printer..."); UsbDevice printer = findUsbPrinter(context); Log.d(TAG, "Printer found: " + printer); if (printer == null) { Log.e(TAG, "No USB printer found"); throw new Exception("No USB printer found"); } Log.d(TAG, "Device Name: " + printer.getDeviceName()); Log.d(TAG, "Product Name: " + printer.getProductName()); Log.d(TAG, "Vendor ID: " + printer.getVendorId()); Log.d(TAG, "Product ID: " + printer.getProductId()); Log.d(TAG, "Device ID: " + printer.getDeviceId()); boolean hasPermission = manager.hasPermission(printer); Log.d(TAG, "Has USB Permission: " + hasPermission); // Already has permission if (hasPermission) { Log.d(TAG, "Permission already granted."); connectedUsbDevice = printer; JSObject obj = new JSObject(); obj.put( "name", printer.getProductName() != null ? printer.getProductName() : "USB Printer"); obj.put( "address", String.valueOf(printer.getDeviceId())); obj.put("connected", true); Log.d(TAG, "Calling callback.onConnected()"); callback.onConnected(obj); Log.d(TAG, "connectUsb() completed successfully."); return; } Log.d(TAG, "Permission NOT granted. Requesting permission..."); // Save callback until permission result arrives pendingConnectCallback = callback; int flags; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { flags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE; Log.d(TAG, "Using FLAG_MUTABLE"); } else { flags = PendingIntent.FLAG_UPDATE_CURRENT; Log.d(TAG, "Using FLAG_UPDATE_CURRENT"); } PendingIntent permissionIntent = PendingIntent.getBroadcast( context, 0, new Intent(ACTION_USB_PERMISSION), flags); Log.d(TAG, "PendingIntent created."); manager.requestPermission( printer, permissionIntent); Log.d(TAG, "requestPermission() called. Waiting for BroadcastReceiver..."); } // ─── Find device ──────────────────────────────────────────────── public static UsbDevice findDevice(UsbManager manager, String deviceIdStr) { HashMap devices = manager.getDeviceList(); if (deviceIdStr != null) { int id = Integer.parseInt(deviceIdStr); for (UsbDevice d : devices.values()) { if (d.getDeviceId() == id) return d; } return null; } return devices.isEmpty() ? null : devices.values().iterator().next(); } // ─── Print with auto permission ───────────────────────────────── public static void printWithPermission( Context context, byte[] data, String deviceIdStr, PrintCallback callback ) { UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE); if (manager == null) { callback.onError("USB service not available"); return; } UsbDevice target = findDevice(manager, deviceIdStr); if (target == null) { callback.onError("USB printer not found"); return; } if (manager.hasPermission(target)) { // ── Already permitted → print immediately ── new Thread(() -> { try { sendData(manager, target, data); callback.onSuccess(); } catch (Exception e) { callback.onError(e.getMessage()); } }).start(); } else { // ── No permission → show dialog, then print on grant ── PendingIntent pi = PendingIntent.getBroadcast( context, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context ctx, Intent intent) { if (!ACTION_USB_PERMISSION.equals(intent.getAction())) return; context.unregisterReceiver(this); boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); if (granted) { new Thread(() -> { try { sendData(manager, target, data); callback.onSuccess(); } catch (Exception e) { callback.onError(e.getMessage()); } }).start(); } else { callback.onError("USB permission denied by user"); } } }; context.registerReceiver(receiver, new IntentFilter(ACTION_USB_PERMISSION)); manager.requestPermission(target, pi); } } // ─── Raw data transfer ────────────────────────────────────────── private static void sendData(UsbManager manager, UsbDevice device, byte[] data) throws Exception { Log.d("Gokullog", "sendData: " + device.getProductName()); UsbInterface intf = device.getInterface(0); UsbEndpoint endpoint = null; for (int i = 0; i < intf.getEndpointCount(); i++) { UsbEndpoint ep = intf.getEndpoint(i); if (ep.getDirection() == UsbConstants.USB_DIR_OUT) { endpoint = ep; break; } } if (endpoint == null) throw new Exception("No OUT endpoint on USB device"); UsbDeviceConnection connection = manager.openDevice(device); if (connection == null) throw new Exception("Cannot open USB device"); connection.claimInterface(intf, true); try { int chunkSize = endpoint.getMaxPacketSize(); int offset = 0; while (offset < data.length) { int size = Math.min(chunkSize, data.length - offset); byte[] chunk = new byte[size]; System.arraycopy(data, offset, chunk, 0, size); int transferred = connection.bulkTransfer(endpoint, chunk, size, 5000); if (transferred < 0) throw new Exception("Transfer failed at offset " + offset); offset += size; } } finally { connection.releaseInterface(intf); connection.close(); } } public static void printUsb( Context context, String usbDeviceId, String receiptText, String estimateText, String logoImage, String totalAmount, String estimateAmount, String footerPrint, String upiId, List> salesTokens, List> estimateTokens, String orderId, String estimateOrderId, String salesId, String salesIdEst) throws Exception { UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); HashMap deviceList = usbManager.getDeviceList(); UsbDevice selectedDevice = null; for (UsbDevice device : deviceList.values()) { if (String.valueOf(device.getDeviceId()).equals(usbDeviceId)) { selectedDevice = device; break; } } if (selectedDevice == null) { throw new Exception("USB printer not found"); } if (!usbManager.hasPermission(selectedDevice)) { PendingIntent permissionIntent = PendingIntent.getBroadcast( context, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE); BroadcastReceiver usbReceiver = new BroadcastReceiver() { @Override public void onReceive( Context ctx, Intent intent) { if (!ACTION_USB_PERMISSION.equals( intent.getAction())) { return; } try { ctx.unregisterReceiver(this); } catch (Exception ignored) { } UsbDevice device; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { device = intent.getParcelableExtra( UsbManager.EXTRA_DEVICE, UsbDevice.class); } else { device = intent.getParcelableExtra( UsbManager.EXTRA_DEVICE); } boolean granted = intent.getBooleanExtra( UsbManager.EXTRA_PERMISSION_GRANTED, false); if (!granted || device == null) { Log.e("USB", "Permission denied"); return; } try { printUsbInternal( usbManager, device, receiptText, estimateText, logoImage, totalAmount, estimateAmount, footerPrint, upiId, salesTokens, estimateTokens, orderId, estimateOrderId, salesId, salesIdEst); } catch (Exception e) { Log.e("USB", "Print failed", e); } } }; ContextCompat.registerReceiver( context, usbReceiver, new IntentFilter(ACTION_USB_PERMISSION), ContextCompat.RECEIVER_NOT_EXPORTED); usbManager.requestPermission( selectedDevice, permissionIntent); return; } printUsbInternal( usbManager, selectedDevice, receiptText, estimateText, logoImage, totalAmount, estimateAmount, footerPrint, upiId, salesTokens, estimateTokens, orderId, estimateOrderId, salesId, salesIdEst); } private static void printUsbInternal( UsbManager usbManager, UsbDevice selectedDevice, String receiptText, String estimateText, String logoImage, String totalAmount, String estimateAmount, String footerPrint, String upiId, List> salesTokens, List> estimateTokens, String orderId, String estimateOrderId, String salesId, String salesIdEst) throws Exception { UsbConnection connection = new UsbConnection( usbManager, selectedDevice); long t = System.currentTimeMillis(); EscPosPrinter printer = new EscPosPrinter( connection, 203, 80f, 45); Log.d("USB_TIME", "Printer = " + (System.currentTimeMillis() - t)); if (receiptText != null && !receiptText.isEmpty()) { t = System.currentTimeMillis(); String receipt = ReceiptBuilder.buildReceipt( printer, receiptText, logoImage, upiId, totalAmount, footerPrint); Log.d("USB_TIME", "Receipt = " + (System.currentTimeMillis() - t)); t = System.currentTimeMillis(); printer.printFormattedTextAndCut(receipt); Log.d("USB_TIME", "Print = " + (System.currentTimeMillis() - t)); } if (estimateText != null && !estimateText.isEmpty()) { String estimate = ReceiptBuilder.buildReceipt( printer, estimateText, logoImage, upiId, estimateAmount, footerPrint); printer.printFormattedTextAndCut(estimate); } if (salesTokens != null && !salesTokens.isEmpty()) { EscPosUtils.printUsbOrNetworkTokens( printer, salesTokens, orderId, salesId, "Sales"); } if (estimateTokens != null && !estimateTokens.isEmpty()) { EscPosUtils.printUsbOrNetworkTokens( printer, estimateTokens, estimateOrderId, salesIdEst, "Estimate"); } printer.disconnectPrinter(); Log.d("USB", "Print Success"); } }