@nitra/zebra 6.2.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/NitraZebra.podspec +16 -13
  2. package/Package.swift +28 -0
  3. package/README.md +72 -0
  4. package/android/build.gradle +16 -14
  5. package/android/src/main/AndroidManifest.xml +2 -22
  6. package/android/src/main/java/dev/nitra/zebra/Zebra.java +11 -0
  7. package/android/src/main/java/dev/nitra/zebra/ZebraPlugin.java +22 -0
  8. package/dist/plugin.js +842 -1296
  9. package/dist/plugin.js.map +1 -1
  10. package/ios/Sources/ZebraPlugin/Zebra.swift +8 -0
  11. package/ios/Sources/ZebraPlugin/ZebraPlugin.swift +23 -0
  12. package/ios/Tests/ZebraPluginTests/ZebraTests.swift +15 -0
  13. package/package.json +46 -54
  14. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  15. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  16. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  17. package/android/.gradle/8.9/gc.properties +0 -0
  18. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  19. package/android/.gradle/buildOutputCleanup/cache.properties +0 -2
  20. package/android/.gradle/vcs-1/gc.properties +0 -0
  21. package/android/.project +0 -28
  22. package/android/capacitor.settings.gradle +0 -3
  23. package/android/gradle.properties +0 -19
  24. package/android/proguard-rules.pro +0 -21
  25. package/android/src/main/java/com/nitra/zebra_printer_plugin/ZebraPrinter.java +0 -1418
  26. package/android/src/main/res/xml/capacitor_plugins.xml +0 -4
  27. package/android/variables.gradle +0 -14
  28. package/ios/Info.plist +0 -36
  29. package/ios/Plugin/ZebraPrinterPlugin.m +0 -16
  30. package/ios/Plugin/ZebraPrinterPlugin.swift +0 -465
  31. /package/android/{.gradle/8.9/dependencies-accessors/gc.properties → src/main/res/.gitkeep} +0 -0
@@ -1,1418 +0,0 @@
1
- package com.nitra.zebra_printer_plugin;
2
-
3
- import android.Manifest;
4
- import android.bluetooth.BluetoothAdapter;
5
- import android.bluetooth.BluetoothDevice;
6
- import android.bluetooth.BluetoothGatt;
7
- import android.bluetooth.BluetoothGattCallback;
8
- import android.bluetooth.BluetoothGattCharacteristic;
9
- import android.bluetooth.BluetoothGattService;
10
- import android.bluetooth.BluetoothManager;
11
- import android.bluetooth.BluetoothProfile;
12
- import android.bluetooth.BluetoothSocket;
13
- import android.bluetooth.le.BluetoothLeScanner;
14
- import android.bluetooth.le.ScanCallback;
15
- import android.bluetooth.le.ScanResult;
16
- import android.content.BroadcastReceiver;
17
- import android.content.Context;
18
- import android.content.Intent;
19
- import android.content.IntentFilter;
20
- import android.content.pm.PackageManager;
21
- import android.os.Build;
22
- import android.os.Handler;
23
- import android.os.Looper;
24
- import android.util.Log;
25
-
26
- import androidx.core.app.ActivityCompat;
27
- import androidx.core.content.ContextCompat;
28
-
29
- import com.getcapacitor.JSObject;
30
- import com.getcapacitor.Plugin;
31
- import com.getcapacitor.PluginCall;
32
- import com.getcapacitor.PluginMethod;
33
- import com.getcapacitor.annotation.CapacitorPlugin;
34
- import com.getcapacitor.annotation.Permission;
35
-
36
- import java.io.IOException;
37
- import java.io.OutputStream;
38
- import java.lang.reflect.Method;
39
- import java.util.ArrayList;
40
- import java.util.List;
41
- import java.util.UUID;
42
- import java.util.concurrent.ExecutorService;
43
- import java.util.concurrent.Executors;
44
- import java.util.concurrent.Future;
45
- import java.util.concurrent.TimeUnit;
46
- import java.util.concurrent.TimeoutException;
47
- import android.net.ConnectivityManager;
48
- import android.net.Network;
49
- import android.net.NetworkCapabilities;
50
- import android.net.wifi.WifiInfo;
51
- import android.net.wifi.WifiManager;
52
-
53
- @CapacitorPlugin(name = "ZebraPrinter", permissions = {
54
- @Permission(alias = "bluetooth", strings = {
55
- Manifest.permission.BLUETOOTH,
56
- Manifest.permission.BLUETOOTH_ADMIN,
57
- Manifest.permission.BLUETOOTH_CONNECT,
58
- Manifest.permission.BLUETOOTH_SCAN,
59
- Manifest.permission.ACCESS_FINE_LOCATION,
60
- Manifest.permission.ACCESS_COARSE_LOCATION,
61
- Manifest.permission.NEARBY_WIFI_DEVICES
62
- })
63
- })
64
- public class ZebraPrinter extends Plugin {
65
-
66
- private static final String TAG = "ZebraPrinterPlugin";
67
-
68
- // SPP UUID for classic Bluetooth
69
- private static final UUID SPP_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
70
-
71
- // BLE UUIDs for Zebra printers
72
- private static final UUID ZEBRA_SERVICE_UUID = UUID.fromString("0000FF00-0000-1000-8000-00805F9B34FB");
73
- private static final UUID ZEBRA_CHARACTERISTIC_UUID = UUID.fromString("0000FF01-0000-1000-8000-00805F9B34FB");
74
-
75
- private BluetoothManager bluetoothManager;
76
- private BluetoothAdapter bluetoothAdapter;
77
- private BluetoothLeScanner bluetoothLeScanner;
78
- private BluetoothGatt bluetoothGatt;
79
- private BluetoothGattCharacteristic printerCharacteristic;
80
-
81
- private List<BluetoothDevice> discoveredDevices = new ArrayList<>();
82
- private boolean isScanning = false;
83
- private boolean isConnected = false;
84
- private String connectedPrinterAddress;
85
-
86
- // MTU size for BLE
87
- private static final int BLE_MTU_SIZE = 20;
88
-
89
- // CRITICAL FIX: Async chunk sending system
90
- private boolean isWriting = false;
91
- private byte[] currentData;
92
- private int currentOffset = 0;
93
- private PluginCall currentPrintCall;
94
-
95
- // SPP support for fallback
96
- private BluetoothSocket sppSocket;
97
- private OutputStream sppOutputStream;
98
- private boolean useSPP = false;
99
-
100
- @Override
101
- public void load() {
102
- super.load();
103
- Log.d(TAG, "🔧 Loading NitraZebraPlugin...");
104
-
105
- bluetoothManager = (BluetoothManager) getContext().getSystemService(Context.BLUETOOTH_SERVICE);
106
- bluetoothAdapter = bluetoothManager.getAdapter();
107
-
108
- if (bluetoothAdapter != null) {
109
- bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
110
- Log.d(TAG, "✅ Bluetooth adapter initialized");
111
-
112
- // Register Classic Bluetooth discovery receiver
113
- IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
114
- filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
115
- getContext().registerReceiver(bluetoothReceiver, filter);
116
- Log.d(TAG, "✅ Classic Bluetooth discovery and bonding receiver registered");
117
- } else {
118
- Log.e(TAG, "❌ Bluetooth not supported");
119
- }
120
- }
121
-
122
- @PluginMethod
123
- public void checkPermissions(PluginCall call) {
124
- Log.d(TAG, "🔍 Checking permissions...");
125
-
126
- boolean hasAllPermissions = true;
127
- List<String> missingPermissions = new ArrayList<>();
128
-
129
- // Check basic Bluetooth permissions
130
- if (ContextCompat.checkSelfPermission(getContext(),
131
- Manifest.permission.BLUETOOTH) != PackageManager.PERMISSION_GRANTED) {
132
- hasAllPermissions = false;
133
- missingPermissions.add(Manifest.permission.BLUETOOTH);
134
- }
135
-
136
- if (ContextCompat.checkSelfPermission(getContext(),
137
- Manifest.permission.BLUETOOTH_ADMIN) != PackageManager.PERMISSION_GRANTED) {
138
- hasAllPermissions = false;
139
- missingPermissions.add(Manifest.permission.BLUETOOTH_ADMIN);
140
- }
141
-
142
- // Check location permissions (required for BLE scanning)
143
- if (ContextCompat.checkSelfPermission(getContext(),
144
- Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
145
- hasAllPermissions = false;
146
- missingPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
147
- }
148
-
149
- // Check Android 12+ Bluetooth permissions
150
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
151
- if (ContextCompat.checkSelfPermission(getContext(),
152
- Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
153
- hasAllPermissions = false;
154
- missingPermissions.add(Manifest.permission.BLUETOOTH_CONNECT);
155
- }
156
-
157
- if (ContextCompat.checkSelfPermission(getContext(),
158
- Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
159
- hasAllPermissions = false;
160
- missingPermissions.add(Manifest.permission.BLUETOOTH_SCAN);
161
- }
162
- }
163
-
164
- JSObject result = new JSObject();
165
- result.put("hasPermissions", hasAllPermissions);
166
- result.put("missingPermissions", missingPermissions.toArray(new String[0]));
167
- result.put("bluetoothSupported", bluetoothAdapter != null);
168
- result.put("bleSupported",
169
- getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE));
170
-
171
- call.resolve(result);
172
- }
173
-
174
- @PluginMethod
175
- public void requestPermissions(PluginCall call) {
176
- Log.d(TAG, "🔐 Requesting permissions...");
177
-
178
- List<String> permissionsToRequest = new ArrayList<>();
179
-
180
- // Add basic Bluetooth permissions
181
- permissionsToRequest.add(Manifest.permission.BLUETOOTH);
182
- permissionsToRequest.add(Manifest.permission.BLUETOOTH_ADMIN);
183
- permissionsToRequest.add(Manifest.permission.ACCESS_FINE_LOCATION);
184
- permissionsToRequest.add(Manifest.permission.ACCESS_COARSE_LOCATION);
185
- permissionsToRequest.add(Manifest.permission.NEARBY_WIFI_DEVICES);
186
-
187
- // Add Android 12+ Bluetooth permissions
188
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
189
- permissionsToRequest.add(Manifest.permission.BLUETOOTH_CONNECT);
190
- permissionsToRequest.add(Manifest.permission.BLUETOOTH_SCAN);
191
- }
192
-
193
- requestPermissionForAliases(permissionsToRequest.toArray(new String[0]), call, "permissionsCallback");
194
- }
195
-
196
- @PluginMethod
197
- public void echo(PluginCall call) {
198
- String value = call.getString("value");
199
- if (value == null)
200
- value = "";
201
- JSObject result = new JSObject();
202
- result.put("value", value);
203
- call.resolve(result);
204
- }
205
-
206
- @PluginMethod
207
- public void initialize(PluginCall call) {
208
- Log.d(TAG, "🔧 Initializing Zebra printer plugin...");
209
-
210
- // Initialize Bluetooth adapter if not already initialized
211
- if (bluetoothAdapter == null) {
212
- bluetoothManager = (BluetoothManager) getContext().getSystemService(Context.BLUETOOTH_SERVICE);
213
- bluetoothAdapter = bluetoothManager.getAdapter();
214
-
215
- if (bluetoothAdapter != null) {
216
- bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
217
- Log.d(TAG, "✅ Bluetooth adapter initialized");
218
- } else {
219
- Log.e(TAG, "❌ Bluetooth not supported");
220
- call.reject("Initialization failed", "Bluetooth not supported");
221
- return;
222
- }
223
- }
224
-
225
- JSObject result = new JSObject();
226
- result.put("success", true);
227
- result.put("message", "Zebra printer plugin initialized");
228
- result.put("bluetoothSupported", bluetoothAdapter != null);
229
- result.put("bluetoothEnabled", bluetoothAdapter != null && bluetoothAdapter.isEnabled());
230
- call.resolve(result);
231
- }
232
-
233
- @PluginMethod
234
- public void sendRawCommand(PluginCall call) {
235
- String command = call.getString("command");
236
- if (command == null || command.isEmpty()) {
237
- call.reject("Invalid command", "Command cannot be empty");
238
- return;
239
- }
240
-
241
- Log.d(TAG, "📤 Sending RAW command: " + command);
242
-
243
- // Check printer status first
244
- if (!isConnected) {
245
- Log.e(TAG, "❌ Not connected to printer");
246
- call.reject("Send failed", "Not connected to printer");
247
- return;
248
- }
249
-
250
- // Check if we have either BLE characteristic or SPP connection
251
- if (!useSPP && printerCharacteristic == null) {
252
- Log.e(TAG, "❌ No BLE characteristic available");
253
- call.reject("Send failed", "No BLE characteristic available");
254
- return;
255
- }
256
-
257
- // CRITICAL FIX: Store call reference for async completion
258
- currentPrintCall = call;
259
-
260
- // Send raw command directly to printer using async chunking
261
- if (sendDataToPrinter(command)) {
262
- Log.d(TAG, "✅ Started sending RAW command (async)");
263
- // Don't resolve here - wait for sendNextChunkAsync to complete
264
- } else {
265
- Log.e(TAG, "❌ Failed to start sending RAW command");
266
- currentPrintCall = null;
267
- call.reject("Send failed", "Failed to start sending RAW command to printer");
268
- }
269
- }
270
-
271
- @PluginMethod
272
- public void connectToPrinter(PluginCall call) {
273
- Log.d(TAG, "🔌 ConnectToPrinter method called");
274
-
275
- String name = call.getString("name");
276
- String address = call.getString("address");
277
- String type = call.getString("type", "bluetooth");
278
-
279
- Log.d(TAG, "📱 Connection request - Name: " + (name != null ? name : "none") +
280
- ", Address: " + (address != null ? address : "none") +
281
- ", Type: " + type);
282
-
283
- // For Android, we only support Bluetooth connections
284
- if (!"bluetooth".equals(type)) {
285
- call.reject("Unsupported connection type", "Android only supports Bluetooth connections");
286
- return;
287
- }
288
-
289
- // If address is provided, try to connect to specific device
290
- if (address != null && !address.isEmpty()) {
291
- // Find device with matching address
292
- BluetoothDevice targetDevice = null;
293
- for (BluetoothDevice device : discoveredDevices) {
294
- if (address.equals(device.getAddress())) {
295
- targetDevice = device;
296
- break;
297
- }
298
- }
299
-
300
- // If not found in discovered devices, try to get from paired devices
301
- if (targetDevice == null && bluetoothAdapter != null) {
302
- if (ActivityCompat.checkSelfPermission(getContext(),
303
- Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) {
304
- for (BluetoothDevice device : bluetoothAdapter.getBondedDevices()) {
305
- if (address.equals(device.getAddress())) {
306
- targetDevice = device;
307
- discoveredDevices.add(device);
308
- break;
309
- }
310
- }
311
- }
312
- }
313
-
314
- if (targetDevice != null) {
315
- Log.d(TAG, "🔌 Connecting to specific printer: " + targetDevice.getName());
316
- connectToDevice(targetDevice);
317
- JSObject result = new JSObject();
318
- result.put("success", true);
319
- result.put("connected", false);
320
- result.put("name", targetDevice.getName());
321
- result.put("address", address);
322
- result.put("type", "bluetooth");
323
- result.put("message", "Connecting to printer...");
324
- call.resolve(result);
325
- return;
326
- } else {
327
- call.reject("Printer not found", "Printer with address " + address + " not found");
328
- return;
329
- }
330
- }
331
-
332
- // If no address provided, try to connect to first available printer
333
- if (!discoveredDevices.isEmpty()) {
334
- BluetoothDevice firstPrinter = discoveredDevices.get(0);
335
- Log.d(TAG, "🔌 Connecting to first available printer: " + firstPrinter.getName());
336
- connectToDevice(firstPrinter);
337
- JSObject result = new JSObject();
338
- result.put("success", true);
339
- result.put("connected", false);
340
- result.put("name", firstPrinter.getName());
341
- result.put("address", firstPrinter.getAddress());
342
- result.put("type", "bluetooth");
343
- result.put("message", "Connecting to printer...");
344
- call.resolve(result);
345
- } else {
346
- Log.e(TAG, "❌ No printers discovered");
347
- call.reject("No printers found", "No Zebra printers discovered. Please scan for printers first.");
348
- }
349
- }
350
-
351
- @PluginMethod
352
- public void printText(PluginCall call) {
353
- String text = call.getString("text");
354
- if (text == null)
355
- text = "";
356
- Integer fontSize = call.getInt("fontSize");
357
- Boolean bold = call.getBoolean("bold");
358
- String alignment = call.getString("alignment");
359
-
360
- Log.d(TAG, "🖨️ Printing text: " + text);
361
-
362
- // CRITICAL FIX: Check connection status and verify socket is still connected
363
- if (!isConnected) {
364
- Log.e(TAG, "❌ Not connected to printer");
365
- call.reject("Print failed", "Not connected to printer");
366
- return;
367
- }
368
-
369
- // CRITICAL FIX: Verify SPP socket is still connected
370
- // But don't disconnect immediately - try to reconnect first
371
- if (useSPP && sppSocket != null) {
372
- try {
373
- if (!sppSocket.isConnected()) {
374
- Log.w(TAG, "⚠️ SPP socket is not connected, will try to reconnect");
375
- // CRITICAL FIX: Don't disconnect immediately - try to reconnect first
376
- // Keep connection status until reconnection attempt completes
377
-
378
- // Try to reconnect if we have device address
379
- if (connectedPrinterAddress != null && bluetoothAdapter != null) {
380
- Log.d(TAG, "🔄 Attempting to reconnect to: " + connectedPrinterAddress);
381
- try {
382
- BluetoothDevice device = bluetoothAdapter.getRemoteDevice(connectedPrinterAddress);
383
- if (device != null) {
384
- // CRITICAL FIX: Reconnect synchronously to ensure it completes before continuing
385
- // Don't start background thread - reconnect directly
386
- boolean reconnected = connectSPP(device);
387
-
388
- if (reconnected && sppSocket != null && sppSocket.isConnected()) {
389
- Log.d(TAG, "✅ Socket reconnected, continuing with print");
390
- // CRITICAL FIX: Ensure isConnected is set after successful reconnection
391
- if (!isConnected) {
392
- isConnected = true;
393
- useSPP = true;
394
- Log.d(TAG, "✅ Connection status updated after reconnection");
395
- }
396
- // Continue with print - don't reject
397
- } else {
398
- Log.w(TAG, "⚠️ Reconnection failed or socket still not connected");
399
- // CRITICAL FIX: Only disconnect if reconnection definitively failed
400
- // Check if socket is actually invalid before disconnecting
401
- boolean socketInvalid = true;
402
- if (sppSocket != null) {
403
- try {
404
- if (sppSocket.isConnected()) {
405
- // Socket is connected, keep connection alive
406
- socketInvalid = false;
407
- if (!isConnected) {
408
- isConnected = true;
409
- useSPP = true;
410
- Log.d(TAG, "✅ Connection status updated - socket is valid");
411
- }
412
- } else {
413
- // Try to verify socket is actually invalid
414
- try {
415
- sppSocket.getRemoteDevice();
416
- // If we can get remote device, socket might still be valid
417
- socketInvalid = false;
418
- if (!isConnected) {
419
- isConnected = true;
420
- useSPP = true;
421
- Log.d(TAG, "✅ Connection status updated - socket might be valid");
422
- }
423
- } catch (Exception e) {
424
- // Socket is definitely invalid
425
- socketInvalid = true;
426
- }
427
- }
428
- } catch (Exception e) {
429
- // Error checking socket status, assume invalid
430
- Log.e(TAG, "❌ Error checking socket status: " + e.getMessage());
431
- socketInvalid = true;
432
- }
433
- }
434
-
435
- if (socketInvalid) {
436
- // Close invalid socket only if reconnection didn't work
437
- if (sppSocket != null) {
438
- try {
439
- sppSocket.close();
440
- } catch (IOException closeException) {
441
- Log.e(TAG, "❌ Error closing invalid socket", closeException);
442
- }
443
- sppSocket = null;
444
- }
445
-
446
- useSPP = false;
447
- isConnected = false;
448
-
449
- call.reject("Print failed",
450
- "SPP socket is not connected. Reconnection failed.");
451
- return;
452
- } else {
453
- // Socket is valid, continue with print
454
- Log.d(TAG, "✅ Socket is valid, continuing with print");
455
- }
456
- }
457
- } else {
458
- // No device found, disconnect
459
- isConnected = false;
460
- useSPP = false;
461
- if (sppSocket != null) {
462
- try {
463
- sppSocket.close();
464
- } catch (IOException closeException) {
465
- Log.e(TAG, "❌ Error closing invalid socket", closeException);
466
- }
467
- sppSocket = null;
468
- sppOutputStream = null;
469
- }
470
- call.reject("Print failed", "SPP socket is not connected and device not found");
471
- return;
472
- }
473
- } catch (Exception reconnectException) {
474
- Log.e(TAG, "❌ Error during reconnection attempt: " + reconnectException.getMessage());
475
- // Disconnect only if reconnection definitively failed
476
- isConnected = false;
477
- useSPP = false;
478
- if (sppSocket != null) {
479
- try {
480
- sppSocket.close();
481
- } catch (IOException closeException) {
482
- Log.e(TAG, "❌ Error closing invalid socket", closeException);
483
- }
484
- sppSocket = null;
485
- sppOutputStream = null;
486
- }
487
- call.reject("Print failed", "SPP socket is not connected. Reconnection failed.");
488
- return;
489
- }
490
- } else {
491
- // No address or adapter, disconnect
492
- isConnected = false;
493
- useSPP = false;
494
- if (sppSocket != null) {
495
- try {
496
- sppSocket.close();
497
- } catch (IOException closeException) {
498
- Log.e(TAG, "❌ Error closing invalid socket", closeException);
499
- }
500
- sppSocket = null;
501
- sppOutputStream = null;
502
- }
503
- call.reject("Print failed", "SPP socket is not connected and no device address available");
504
- return;
505
- }
506
- }
507
- } catch (Exception e) {
508
- Log.e(TAG, "❌ Error checking SPP socket connection: " + e.getMessage());
509
- // Don't disconnect on check error - socket might still be valid
510
- // Only disconnect if we're sure socket is invalid
511
- Log.w(TAG, "⚠️ Socket check failed, but keeping connection alive");
512
- }
513
- }
514
-
515
- // Check if we have either BLE characteristic or SPP connection
516
- if (!useSPP && printerCharacteristic == null) {
517
- Log.e(TAG, "❌ No BLE characteristic available");
518
- call.reject("Print failed", "No BLE characteristic available");
519
- return;
520
- }
521
-
522
- // CRITICAL FIX: Check if it's already a ZPL command
523
- String dataToSend;
524
- if (text.startsWith("^XA") || text.startsWith("~") || text.startsWith("!")) {
525
- // It's already a ZPL/CPCL command - send it RAW
526
- Log.d(TAG, "📤 Sending RAW ZPL/CPCL command...");
527
- dataToSend = text;
528
- } else {
529
- // It's plain text - create ZPL wrapper with proper formatting
530
- Log.d(TAG, "📤 Converting plain text to ZPL...");
531
- int x = "center".equals(alignment) ? 200 : ("right".equals(alignment) ? 350 : 50);
532
- String fontStyle = (bold != null && bold) ? "B" : "N";
533
- int size = (fontSize != null) ? fontSize : 12;
534
- // Improved ZPL command with proper line breaks and formatting
535
- dataToSend = "^XA\n^FO" + x + ",50^A0" + fontStyle + "," + size + "," + size + "^FD" + text + "^FS\n^XZ";
536
- }
537
-
538
- Log.d(TAG, "📦 Final data to send: " + dataToSend);
539
-
540
- // CRITICAL FIX: Store call reference for async completion
541
- currentPrintCall = call;
542
-
543
- // Send to printer using async chunking system
544
- if (sendDataToPrinter(dataToSend)) {
545
- Log.d(TAG, "✅ Started sending data (async)");
546
- // Don't resolve here - wait for sendNextChunkAsync to complete
547
- } else {
548
- Log.e(TAG, "❌ Failed to start sending data");
549
- currentPrintCall = null;
550
- call.reject("Print failed", "Failed to start sending data to printer");
551
- }
552
- }
553
-
554
- // ASYNC: Async sendDataToPrinter with proper callback handling
555
- private boolean sendDataToPrinter(String data) {
556
- if (!isConnected) {
557
- Log.e(TAG, "❌ Not connected to printer");
558
- return false;
559
- }
560
-
561
- // Use SPP if available (more reliable for printing)
562
- if (useSPP && sppOutputStream != null) {
563
- Log.d(TAG, "📤 Using SPP for data transmission");
564
- return sendDataToPrinterSPP(data);
565
- }
566
-
567
- // Fallback to BLE with async chunking
568
- if (printerCharacteristic == null) {
569
- Log.e(TAG, "❌ No BLE characteristic available");
570
- return false;
571
- }
572
-
573
- try {
574
- currentData = data.getBytes("UTF-8");
575
- currentOffset = 0;
576
- isWriting = false;
577
-
578
- Log.d(TAG, "📏 Total data size: " + currentData.length + " bytes");
579
- Log.d(TAG, "📏 MTU size: " + BLE_MTU_SIZE + " bytes");
580
-
581
- // Start async chunking
582
- sendNextChunkAsync();
583
- return true;
584
-
585
- } catch (Exception e) {
586
- Log.e(TAG, "❌ Error preparing data for printer", e);
587
- return false;
588
- }
589
- }
590
-
591
- // ASYNC: Send next chunk with proper async handling
592
- private void sendNextChunkAsync() {
593
- if (currentOffset >= currentData.length) {
594
- // All chunks sent successfully
595
- Log.d(TAG, "✅ All data sent successfully");
596
- Log.d(TAG, "📋 Data length: " + currentData.length + " bytes");
597
-
598
- // Resolve the call
599
- if (currentPrintCall != null) {
600
- JSObject result = new JSObject();
601
- result.put("success", true);
602
- result.put("message", "Data sent to printer successfully");
603
- result.put("bytes", currentData.length);
604
- result.put("processed", true);
605
- currentPrintCall.resolve(result);
606
- currentPrintCall = null;
607
- }
608
- return;
609
- }
610
-
611
- if (isWriting) {
612
- Log.d(TAG, "⏳ Still writing previous chunk, waiting...");
613
- // Retry after delay
614
- new Handler(Looper.getMainLooper()).postDelayed(() -> {
615
- sendNextChunkAsync();
616
- }, 50);
617
- return;
618
- }
619
-
620
- int chunkSize = Math.min(BLE_MTU_SIZE, currentData.length - currentOffset);
621
- byte[] chunk = new byte[chunkSize];
622
- System.arraycopy(currentData, currentOffset, chunk, 0, chunkSize);
623
-
624
- Log.d(TAG, "📤 Sending chunk at offset " + currentOffset + ": " + chunkSize + " bytes");
625
-
626
- printerCharacteristic.setValue(chunk);
627
- printerCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
628
-
629
- isWriting = true;
630
- boolean writeResult = bluetoothGatt.writeCharacteristic(printerCharacteristic);
631
- Log.d(TAG, "📤 Write result: " + writeResult);
632
-
633
- if (!writeResult) {
634
- Log.e(TAG, "❌ Failed to write characteristic at offset " + currentOffset);
635
- isWriting = false;
636
-
637
- // Retry after delay
638
- new Handler(Looper.getMainLooper()).postDelayed(() -> {
639
- Log.d(TAG, "🔄 Retrying chunk at offset " + currentOffset);
640
- sendNextChunkAsync();
641
- }, 100);
642
- }
643
- // If writeResult is true, wait for onCharacteristicWrite callback
644
- }
645
-
646
- @PluginMethod
647
- public void getStatus(PluginCall call) {
648
- JSObject result = new JSObject();
649
- result.put("connected", isConnected);
650
- result.put("status", isConnected ? "connected" : "disconnected");
651
- if (isConnected) {
652
- result.put("printerAddress", connectedPrinterAddress);
653
- result.put("printerType", useSPP ? "spp" : "ble");
654
- result.put("connectionMethod", useSPP ? "SPP (Classic Bluetooth)" : "BLE (Bluetooth Low Energy)");
655
- }
656
- call.resolve(result);
657
- }
658
-
659
- // NEW: Check printer status with ZPL command
660
- @PluginMethod
661
- public void checkPrinterStatus(PluginCall call) {
662
- if (!isConnected) {
663
- call.reject("Status check failed", "Not connected to printer");
664
- return;
665
- }
666
-
667
- // Check if we have either BLE characteristic or SPP connection
668
- if (!useSPP && printerCharacteristic == null) {
669
- call.reject("Status check failed", "No BLE characteristic available");
670
- return;
671
- }
672
-
673
- // Send printer status command
674
- String statusCommand = "~HS"; // Zebra printer status command
675
- Log.d(TAG, "🔍 Sending status command: " + statusCommand);
676
-
677
- if (sendDataToPrinter(statusCommand)) {
678
- JSObject result = new JSObject();
679
- result.put("success", true);
680
- result.put("message", "Status command sent");
681
- result.put("command", statusCommand);
682
- result.put("connectionType", useSPP ? "SPP" : "BLE");
683
- call.resolve(result);
684
- } else {
685
- call.reject("Status check failed", "Failed to send status command");
686
- }
687
- }
688
-
689
- @PluginMethod
690
- public void scanForPrinters(PluginCall call) {
691
- Log.d(TAG, "🔍 Starting printer scan...");
692
-
693
- // Check permissions first
694
- if (!checkBluetoothPermissions()) {
695
- Log.e(TAG, "❌ Missing Bluetooth permissions");
696
- JSObject result = new JSObject();
697
- result.put("success", false);
698
- result.put("error", "Missing Bluetooth permissions. Please request permissions first.");
699
- call.resolve(result);
700
- return;
701
- }
702
-
703
- if (bluetoothAdapter == null) {
704
- Log.e(TAG, "❌ Bluetooth not supported");
705
- JSObject result = new JSObject();
706
- result.put("success", false);
707
- result.put("error", "Bluetooth not supported");
708
- call.resolve(result);
709
- return;
710
- }
711
-
712
- if (!bluetoothAdapter.isEnabled()) {
713
- Log.e(TAG, "❌ Bluetooth is disabled");
714
- JSObject result = new JSObject();
715
- result.put("success", false);
716
- result.put("error", "Bluetooth is disabled");
717
- call.resolve(result);
718
- return;
719
- }
720
-
721
- // Start scanning (Classic Bluetooth for Zebra printers)
722
- startScanning();
723
-
724
- // Wait a bit for scanning to find devices, then return results
725
- new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
726
- @Override
727
- public void run() {
728
- List<JSObject> printers = new ArrayList<>();
729
- for (BluetoothDevice device : discoveredDevices) {
730
- String deviceName = device.getName();
731
- if (deviceName != null && (deviceName.toLowerCase().contains("zebra") ||
732
- deviceName.toLowerCase().contains("zt") ||
733
- deviceName.toLowerCase().contains("xxzhn") ||
734
- deviceName.toLowerCase().contains("printer") ||
735
- deviceName.toLowerCase().contains("label"))) {
736
- JSObject printer = new JSObject();
737
- printer.put("name", deviceName);
738
- printer.put("address", device.getAddress());
739
- printer.put("type", "bluetooth");
740
- printer.put("paired", device.getBondState() == BluetoothDevice.BOND_BONDED);
741
- printers.add(printer);
742
- }
743
- }
744
-
745
- JSObject result = new JSObject();
746
- result.put("success", true);
747
- result.put("printers", printers.toArray(new JSObject[0]));
748
- result.put("count", printers.size());
749
- result.put("message", "Found " + printers.size() + " printer(s)");
750
- call.resolve(result);
751
- }
752
- }, 3000); // Wait 3 seconds for scanning
753
- }
754
-
755
- private void startScanning() {
756
- if (isScanning) {
757
- Log.d(TAG, "🔍 Already scanning...");
758
- return;
759
- }
760
-
761
- Log.d(TAG, "🔍 Starting Classic Bluetooth scan (for Zebra printers)...");
762
- isScanning = true;
763
-
764
- // Clear previous discoveries
765
- discoveredDevices.clear();
766
-
767
- // Start Classic Bluetooth scan (Zebra printers use Classic Bluetooth SPP)
768
- if (bluetoothAdapter.isDiscovering()) {
769
- bluetoothAdapter.cancelDiscovery();
770
- }
771
-
772
- bluetoothAdapter.startDiscovery();
773
- Log.d(TAG, "🔍 Classic Bluetooth scan started for Zebra printers");
774
-
775
- // Also try BLE scan as fallback (but don't auto-connect)
776
- if (bluetoothLeScanner != null) {
777
- bluetoothLeScanner.startScan(new ScanCallback() {
778
- @Override
779
- public void onScanResult(int callbackType, ScanResult result) {
780
- BluetoothDevice device = result.getDevice();
781
- String deviceName = device.getName();
782
-
783
- Log.d(TAG, "📱 Found BLE device: " + (deviceName != null ? deviceName : "Unknown"));
784
-
785
- // Save BLE Zebra printer but don't auto-connect
786
- if (deviceName != null && (deviceName.toLowerCase().contains("zebra") ||
787
- deviceName.toLowerCase().contains("zt") ||
788
- deviceName.toLowerCase().contains("xxzhn"))) {
789
- Log.d(TAG, "🖨️ Found Zebra printer via BLE: " + deviceName);
790
-
791
- // Save device if not already discovered
792
- if (!discoveredDevices.contains(device)) {
793
- discoveredDevices.add(device);
794
- }
795
- }
796
- }
797
-
798
- @Override
799
- public void onScanFailed(int errorCode) {
800
- Log.e(TAG, "❌ BLE scan failed with error: " + errorCode);
801
- }
802
- });
803
- }
804
-
805
- // Stop scanning after 15 seconds (Classic Bluetooth needs more time)
806
- new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
807
-
808
- @Override
809
- public void run() {
810
- if (isScanning) {
811
- Log.d(TAG, "⏹️ Stopping scan after timeout");
812
- if (bluetoothLeScanner != null) {
813
- bluetoothLeScanner.stopScan(new ScanCallback() {
814
- });
815
- }
816
- if (bluetoothAdapter.isDiscovering()) {
817
- bluetoothAdapter.cancelDiscovery();
818
- }
819
- isScanning = false;
820
- }
821
- }},15000);}
822
-
823
- // Classic Bluetooth discovery callback
824
- private final BroadcastReceiver bluetoothReceiver=new BroadcastReceiver(){@Override public void onReceive(Context context,Intent intent){String action=intent.getAction();Log.d(TAG,"📡 Bluetooth receiver action: "+action);
825
-
826
- if(BluetoothDevice.ACTION_FOUND.equals(action)){BluetoothDevice device=intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);String deviceName=device.getName();
827
-
828
- Log.d(TAG,"📱 Found Classic Bluetooth device: "+(deviceName!=null?deviceName:"Unknown")+" ("+device.getAddress()+")");Log.d(TAG,"📱 Device bond state: "+device.getBondState());Log.d(TAG,"📱 Device type: "+device.getType());
829
-
830
- // Check if it's a Zebra printer (expanded filter)
831
- if(deviceName!=null&&(deviceName.toLowerCase().contains("zebra")||deviceName.toLowerCase().contains("zt")||deviceName.toLowerCase().contains("xxzhn")||deviceName.toLowerCase().contains("printer")||deviceName.toLowerCase().contains("label")||deviceName.toLowerCase().contains("zebraprinter")||deviceName.toLowerCase().contains("zebra_printer"))){Log.d(TAG,"🖨️ Found Zebra printer via Classic Bluetooth: "+deviceName);
832
-
833
- // Save device if not already discovered
834
- if(!discoveredDevices.contains(device)){discoveredDevices.add(device);}
835
-
836
- // Auto-connect if not already connected
837
- // CRITICAL FIX: Always try to connect, but connectToDevice will check if
838
- // already connected
839
- Log.d(TAG,"🔌 Auto-connecting to printer via Classic Bluetooth...");connectToDevice(device);}}else if(BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)){BluetoothDevice device=intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);int bondState=intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,BluetoothDevice.BOND_NONE);Log.d(TAG,"🔗 Bond state changed for "+device.getName()+": "+bondState);
840
-
841
- if(bondState==BluetoothDevice.BOND_BONDED){Log.d(TAG,"✅ Device bonded successfully: "+device.getName());
842
- // Try to connect via SPP after bonding
843
- // CRITICAL FIX: Check if already connected to this device
844
- if((!isConnected||connectedPrinterAddress==null||!connectedPrinterAddress.equals(device.getAddress()))&&discoveredDevices.contains(device)){Log.d(TAG,"🔌 Attempting SPP connection after bonding...");new Thread(()->{if(connectSPP(device)){Log.d(TAG,"✅ Connected via SPP after bonding");
845
- // Ensure isConnected is set (it should be set in connectSPP, but double-check)
846
- if(!isConnected){isConnected=true;connectedPrinterAddress=device.getAddress();useSPP=true;Log.d(TAG,"✅ Connection status updated after bonding");}}else{Log.e(TAG,"❌ Failed to connect via SPP after bonding");}}).start();}else{Log.d(TAG,"✅ Already connected to this printer, skipping connection after bonding");}}else if(bondState==BluetoothDevice.BOND_NONE){Log.d(TAG,"❌ Device bonding failed: "+device.getName());}}}};
847
-
848
- @PluginMethod
849
- public void connect(PluginCall call) {
850
- Log.d(TAG, "🔌 Connect method called");
851
-
852
- String address = call.getString("address");
853
- String type = call.getString("type", "bluetooth");
854
-
855
- Log.d(TAG, "📱 Connection request - Address: " + (address != null ? address : "none") + ", Type: " + type);
856
-
857
- // For Android, we only support Bluetooth connections
858
- if (!"bluetooth".equals(type)) {
859
- call.reject("Unsupported connection type", "Android only supports Bluetooth connections");
860
- return;
861
- }
862
-
863
- // If address is provided, try to connect to specific device
864
- if (address != null && !address.isEmpty()) {
865
- // Find device with matching address
866
- BluetoothDevice targetDevice = null;
867
- for (BluetoothDevice device : discoveredDevices) {
868
- if (address.equals(device.getAddress())) {
869
- targetDevice = device;
870
- break;
871
- }
872
- }
873
-
874
- if (targetDevice != null) {
875
- Log.d(TAG, "🔌 Connecting to specific printer: " + targetDevice.getName());
876
- connectToDevice(targetDevice);
877
- JSObject result = new JSObject();
878
- result.put("success", true);
879
- result.put("connected", false);
880
- result.put("address", address);
881
- result.put("type", "bluetooth");
882
- result.put("message", "Connecting to printer...");
883
- call.resolve(result);
884
- return;
885
- } else {
886
- call.reject("Printer not found", "Printer with address " + address + " not found");
887
- return;
888
- }
889
- }
890
-
891
- // If no address provided, try to connect to first available printer
892
- if (!discoveredDevices.isEmpty()) {
893
- BluetoothDevice firstPrinter = discoveredDevices.get(0);
894
- Log.d(TAG, "🔌 Connecting to first available printer: " + firstPrinter.getName());
895
- connectToDevice(firstPrinter);
896
- JSObject result = new JSObject();
897
- result.put("success", true);
898
- result.put("connected", false);
899
- result.put("address", firstPrinter.getAddress());
900
- result.put("type", "bluetooth");
901
- result.put("message", "Connecting to printer...");
902
- call.resolve(result);
903
- } else {
904
- Log.e(TAG, "❌ No printers discovered");
905
- call.reject("No printers found", "No Zebra printers discovered. Please scan for printers first.");
906
- }
907
- }
908
-
909
- private boolean checkBluetoothPermissions() {
910
- // Check basic Bluetooth permissions
911
- if (ContextCompat.checkSelfPermission(getContext(),
912
- Manifest.permission.BLUETOOTH) != PackageManager.PERMISSION_GRANTED) {
913
- return false;
914
- }
915
-
916
- if (ContextCompat.checkSelfPermission(getContext(),
917
- Manifest.permission.BLUETOOTH_ADMIN) != PackageManager.PERMISSION_GRANTED) {
918
- return false;
919
- }
920
-
921
- // Check location permissions (required for BLE scanning)
922
- if (ContextCompat.checkSelfPermission(getContext(),
923
- Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
924
- return false;
925
- }
926
-
927
- // Check Android 12+ Bluetooth permissions
928
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
929
- if (ContextCompat.checkSelfPermission(getContext(),
930
- Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
931
- return false;
932
- }
933
-
934
- if (ContextCompat.checkSelfPermission(getContext(),
935
- Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
936
- return false;
937
- }
938
- }
939
-
940
- return true;
941
- }
942
-
943
- private void connectToDevice(BluetoothDevice device) {
944
- Log.d(TAG, "Connecting to device: " + device.getName());
945
-
946
- // CRITICAL FIX: Check if already connected to this device
947
- // But don't skip if connection is not valid - always verify socket is actually
948
- // connected
949
- if (isConnected && useSPP && sppSocket != null && connectedPrinterAddress != null) {
950
- if (connectedPrinterAddress.equals(device.getAddress())) {
951
- try {
952
- // CRITICAL FIX: Always verify socket is actually connected, not just check
953
- // isConnected flag
954
- boolean socketActuallyConnected = sppSocket.isConnected();
955
- if (socketActuallyConnected && sppOutputStream != null) {
956
- // Double-check by trying to get remote device
957
- try {
958
- sppSocket.getRemoteDevice();
959
- Log.d(TAG, "✅ Already connected to this device, socket is valid");
960
- // Verify connection is still valid by checking socket
961
- return; // Already connected to this device, no need to reconnect
962
- } catch (Exception e) {
963
- Log.w(TAG, "⚠️ Socket check failed, will reconnect: " + e.getMessage());
964
- // Socket check failed, need to reconnect
965
- isConnected = false;
966
- useSPP = false;
967
- if (sppSocket != null) {
968
- try {
969
- sppSocket.close();
970
- } catch (IOException closeException) {
971
- Log.e(TAG, "Error closing invalid socket", closeException);
972
- }
973
- }
974
- sppSocket = null;
975
- sppOutputStream = null;
976
- }
977
- } else {
978
- Log.w(TAG, "⚠️ Socket exists but not connected (isConnected=" + socketActuallyConnected
979
- + "), will reconnect");
980
- // Socket exists but not connected, need to reconnect
981
- isConnected = false;
982
- useSPP = false;
983
- if (sppSocket != null) {
984
- try {
985
- sppSocket.close();
986
- } catch (IOException e) {
987
- Log.e(TAG, "❌ Error closing invalid socket", e);
988
- }
989
- }
990
- sppSocket = null;
991
- sppOutputStream = null;
992
- }
993
- } catch (Exception e) {
994
- Log.w(TAG, "⚠️ Error checking existing connection: " + e.getMessage());
995
- // Connection check failed, need to reconnect
996
- isConnected = false;
997
- useSPP = false;
998
- if (sppSocket != null) {
999
- try {
1000
- sppSocket.close();
1001
- } catch (IOException closeException) {
1002
- Log.e(TAG, "❌ Error closing invalid socket", closeException);
1003
- }
1004
- }
1005
- sppSocket = null;
1006
- sppOutputStream = null;
1007
- }
1008
- } else {
1009
- Log.d(TAG, "🔌 Connected to different device, will reconnect to new device");
1010
- // Connected to different device, need to disconnect first
1011
- }
1012
- } else {
1013
- // CRITICAL FIX: If isConnected is true but socket is null, reset isConnected
1014
- if (isConnected && (sppSocket == null || !useSPP)) {
1015
- Log.w(TAG, "⚠️ isConnected is true but socket is null or useSPP is false, resetting connection status");
1016
- isConnected = false;
1017
- useSPP = false;
1018
- connectedPrinterAddress = null;
1019
- }
1020
- }
1021
-
1022
- // Disconnect from current device if connected to different device
1023
- if (bluetoothGatt != null) {
1024
- bluetoothGatt.disconnect();
1025
- bluetoothGatt.close();
1026
- }
1027
-
1028
- // Close SPP connection if exists and connected to different device
1029
- if (sppSocket != null
1030
- && (connectedPrinterAddress == null || !connectedPrinterAddress.equals(device.getAddress()))) {
1031
- try {
1032
- sppSocket.close();
1033
- } catch (IOException e) {
1034
- Log.e(TAG, "❌ Error closing SPP socket", e);
1035
- }
1036
- sppSocket = null;
1037
- sppOutputStream = null;
1038
- }
1039
-
1040
- // Try SPP first (Bluetooth Classic - more reliable for printing)
1041
- Log.d(TAG, "🔌 Attempting Bluetooth Classic (SPP) connection first...");
1042
- if (connectSPP(device)) {
1043
- Log.d(TAG, "✅ Connected via Bluetooth Classic (SPP)");
1044
- // CRITICAL FIX: Ensure isConnected is set after successful connection
1045
- if (!isConnected) {
1046
- Log.w(TAG, "⚠️ Connection successful but isConnected not set, setting now...");
1047
- isConnected = true;
1048
- connectedPrinterAddress = device.getAddress();
1049
- useSPP = true;
1050
- }
1051
- Log.d(TAG, "✅ Final connection status in connectToDevice: isConnected=" + isConnected + ", address="
1052
- + connectedPrinterAddress);
1053
- return;
1054
- }
1055
-
1056
- // CRITICAL: Don't use BLE fallback - only use Classic Bluetooth (SPP)
1057
- // If SPP fails, check if bonding is in progress
1058
- Log.d(TAG, "⏳ Bluetooth Classic (SPP) connection pending...");
1059
- Log.d(TAG, "⚠️ NOT using BLE fallback - Classic Bluetooth (SPP) is required for reliable printing");
1060
-
1061
- // Check if device is bonding - if so, wait for bonding to complete
1062
- if (device.getBondState() == BluetoothDevice.BOND_BONDING) {
1063
- Log.d(TAG, "⏳ Device is bonding, will connect automatically after bonding completes...");
1064
- // Connection will happen automatically after bonding completes via
1065
- // BroadcastReceiver
1066
- return;
1067
- }
1068
-
1069
- // If device is not bonded and not bonding, try to bond first
1070
- if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
1071
- Log.d(TAG, "🔗 Device not bonded, attempting to pair...");
1072
- if (ActivityCompat.checkSelfPermission(getContext(),
1073
- Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) {
1074
- boolean bondResult = device.createBond();
1075
- Log.d(TAG, "🔗 Bonding initiated: " + bondResult);
1076
- // Connection will happen automatically after bonding completes via
1077
- // BroadcastReceiver
1078
- return;
1079
- } else {
1080
- Log.e(TAG, "❌ Missing BLUETOOTH_CONNECT permission for bonding");
1081
- return;
1082
- }
1083
- }
1084
-
1085
- // If we get here, device is bonded but SPP connection failed
1086
- // Try to connect again after a short delay
1087
- Log.d(TAG, "🔄 Device is bonded but SPP connection failed, will retry...");
1088
- new Thread(() -> {
1089
- try {
1090
- Thread.sleep(1000); // Wait 1 second (reduced from 2 for faster connection)
1091
- if (connectSPP(device)) {
1092
- Log.d(TAG, "✅ Connected via SPP after retry");
1093
- // Ensure isConnected is set (it should be set in connectSPP, but double-check)
1094
- if (!isConnected) {
1095
- isConnected = true;
1096
- connectedPrinterAddress = device.getAddress();
1097
- useSPP = true;
1098
- Log.d(TAG, "✅ Connection status updated after retry");
1099
- }
1100
- } else {
1101
- Log.e(TAG, "❌ SPP connection failed after retry");
1102
- }
1103
- } catch (InterruptedException e) {
1104
- Log.e(TAG, "❌ Retry thread interrupted", e);
1105
- }
1106
- }).start();
1107
- }
1108
-
1109
- // SPP connection method for Bluetooth Classic - FIXED for ZQ320 (no ANR, stable)
1110
- private boolean connectSPP(BluetoothDevice device) {
1111
- // 1. Bonding
1112
- if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
1113
- Log.d(TAG, "Not bonded → creating bond (PIN usually 0000)");
1114
- if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.BLUETOOTH_CONNECT)
1115
- == PackageManager.PERMISSION_GRANTED) {
1116
- device.createBond();
1117
- }
1118
- return false; // Wait for BOND_BONDED via receiver
1119
- }
1120
-
1121
- // 2. Clean old socket
1122
- if (sppSocket != null) {
1123
- try {
1124
- sppSocket.close();
1125
- } catch (Exception ignored) {
1126
- }
1127
- sppSocket = null;
1128
- sppOutputStream = null;
1129
- }
1130
-
1131
- // 3. Connect with timeout (runs in background, keeps main thread free)
1132
- ExecutorService executor = Executors.newSingleThreadExecutor();
1133
- Future<Boolean> future = executor.submit(() -> {
1134
- try {
1135
- sppSocket = device.createRfcommSocketToServiceRecord(SPP_UUID);
1136
- sppSocket.connect();
1137
- sppOutputStream = sppSocket.getOutputStream();
1138
- return true;
1139
- } catch (Exception e) {
1140
- Log.e(TAG, "SPP connect failed: " + e.getMessage());
1141
- return false;
1142
- }
1143
- });
1144
-
1145
- boolean success = false;
1146
- try {
1147
- success = future.get(10, TimeUnit.SECONDS); // 10s timeout
1148
- if (success && sppSocket != null && sppSocket.isConnected()) {
1149
- isConnected = true;
1150
- connectedPrinterAddress = device.getAddress();
1151
- useSPP = true;
1152
- Log.d(TAG, "ZQ320 CONNECTED VIA SPP! ✅");
1153
- return true;
1154
- }
1155
- } catch (TimeoutException e) {
1156
- Log.e(TAG, "SPP timeout while connecting: " + e.getMessage());
1157
- future.cancel(true); // interrupt background connect to avoid races
1158
- } catch (Exception e) {
1159
- Log.e(TAG, "SPP connect error: " + e.getMessage());
1160
- } finally {
1161
- executor.shutdownNow(); // ensure the task stops before fallback
1162
- try {
1163
- executor.awaitTermination(2, TimeUnit.SECONDS);
1164
- } catch (InterruptedException ie) {
1165
- Thread.currentThread().interrupt();
1166
- }
1167
- }
1168
-
1169
- // Clean any half-open socket before trying fallback to avoid races
1170
- if (sppSocket != null) {
1171
- try {
1172
- sppSocket.close();
1173
- } catch (Exception ignored) {
1174
- }
1175
- sppSocket = null;
1176
- sppOutputStream = null;
1177
- }
1178
-
1179
- // If connection failed, try fallback UUID (rarely needed)
1180
- try {
1181
- Method m = device.getClass().getMethod("createRfcommSocket", new Class[] { int.class });
1182
- sppSocket = (BluetoothSocket) m.invoke(device, 1);
1183
- sppSocket.connect();
1184
- sppOutputStream = sppSocket.getOutputStream();
1185
- isConnected = true;
1186
- useSPP = true;
1187
- connectedPrinterAddress = device.getAddress();
1188
- Log.d(TAG, "ZQ320 connected via fallback socket! ✅");
1189
- return true;
1190
- } catch (Exception e) {
1191
- Log.e(TAG, "Fallback also failed: " + e.getMessage());
1192
- }
1193
-
1194
- return false;
1195
- }
1196
-
1197
- // SPP data sending method for Bluetooth Classic
1198
- private boolean sendDataToPrinterSPP(String data) {
1199
- // CRITICAL FIX: Check connection status and socket validity
1200
- if (!isConnected || sppOutputStream == null || sppSocket == null) {
1201
- Log.e(TAG, "❌ SPP (Bluetooth Classic) not connected");
1202
- if (currentPrintCall != null) {
1203
- currentPrintCall.reject("Send failed", "SPP (Bluetooth Classic) not connected");
1204
- currentPrintCall = null;
1205
- }
1206
- return false;
1207
- }
1208
-
1209
- // Socket might be temporarily disconnected but still valid
1210
- boolean socketConnected = false;
1211
- try {
1212
- socketConnected = sppSocket.isConnected();
1213
- if (!socketConnected) {
1214
- Log.w(TAG, "⚠️ SPP socket is not connected, but will try to send anyway (socket might reconnect)");
1215
- // Don't disconnect immediately - socket might reconnect
1216
- // Only mark as disconnected if send actually fails
1217
- }
1218
- } catch (Exception e) {
1219
- Log.w(TAG, "⚠️ Error checking socket connection: " + e.getMessage() + ", will try to send anyway");
1220
- // Don't disconnect on check error - socket might still be valid
1221
- // Only disconnect if send actually fails
1222
- }
1223
-
1224
- try {
1225
- byte[] bytes = data.getBytes("UTF-8");
1226
- Log.d(TAG, "📤 Sending via SPP (Bluetooth Classic): " + bytes.length + " bytes");
1227
- Log.d(TAG, "📤 Data preview: " + data.substring(0, Math.min(50, data.length())) + "...");
1228
-
1229
- sppOutputStream.write(bytes);
1230
- sppOutputStream.flush();
1231
-
1232
- Log.d(TAG, "✅ SPP (Bluetooth Classic) data sent successfully");
1233
-
1234
- // CRITICAL FIX: Resolve the call after successful send
1235
- if (currentPrintCall != null) {
1236
- JSObject result = new JSObject();
1237
- result.put("success", true);
1238
- result.put("message", "Data sent to printer successfully");
1239
- result.put("bytes", bytes.length);
1240
- result.put("connectionType", "SPP (Classic Bluetooth)");
1241
- currentPrintCall.resolve(result);
1242
- currentPrintCall = null;
1243
- }
1244
- return true;
1245
- } catch (IOException e) {
1246
- Log.e(TAG, "❌ SPP (Bluetooth Classic) send failed: " + e.getMessage());
1247
- Log.e(TAG, "❌ Error details: " + e.getClass().getSimpleName());
1248
-
1249
- // CRITICAL FIX: Don't immediately disconnect - check if socket is actually
1250
- // closed
1251
- // IOException can occur even if socket is still valid (e.g., temporary network
1252
- // issues)
1253
- boolean socketClosed = false;
1254
- if (sppSocket != null) {
1255
- try {
1256
- if (sppSocket.isConnected()) {
1257
- Log.d(TAG, "🔍 Socket still connected despite IOException, keeping connection alive");
1258
- // Socket is still connected, might be temporary issue
1259
- // Don't disconnect - connection is still valid
1260
- socketClosed = false;
1261
- } else {
1262
- Log.w(TAG, "⚠️ Socket not connected after IOException");
1263
- // Check if socket is actually closed by trying to read its state
1264
- try {
1265
- // Try to check socket state more thoroughly
1266
- sppSocket.getRemoteDevice();
1267
- Log.d(TAG, "🔍 Socket might still be valid, keeping connection alive");
1268
- socketClosed = false;
1269
- } catch (Exception socketCheckException) {
1270
- Log.w(TAG, "⚠️ Socket appears to be closed: " + socketCheckException.getMessage());
1271
- socketClosed = true;
1272
- }
1273
- }
1274
- } catch (Exception checkException) {
1275
- Log.w(TAG, "⚠️ Error checking socket status: " + checkException.getMessage());
1276
- // Don't assume socket is closed on check error
1277
- // Only mark as closed if we're certain
1278
- try {
1279
- sppSocket.getRemoteDevice();
1280
- socketClosed = false;
1281
- } catch (Exception e2) {
1282
- socketClosed = true;
1283
- }
1284
- }
1285
- } else {
1286
- socketClosed = true;
1287
- }
1288
-
1289
- // Only disconnect if socket is definitively closed
1290
- if (socketClosed) {
1291
- Log.w(TAG, "⚠️ Marking as disconnected due to confirmed socket closure");
1292
- isConnected = false;
1293
- useSPP = false;
1294
-
1295
- // Clean up socket
1296
- if (sppSocket != null) {
1297
- try {
1298
- sppSocket.close();
1299
- } catch (IOException closeException) {
1300
- Log.e(TAG, "❌ Error closing socket", closeException);
1301
- }
1302
- sppSocket = null;
1303
- sppOutputStream = null;
1304
- }
1305
- } else {
1306
- Log.d(TAG, "✅ Keeping connection alive - socket still valid despite IOException");
1307
- }
1308
-
1309
- // CRITICAL FIX: Reject the call on failure
1310
- if (currentPrintCall != null) {
1311
- currentPrintCall.reject("Send failed", "SPP (Bluetooth Classic) send failed: " + e.getMessage());
1312
- currentPrintCall = null;
1313
- }
1314
- return false;
1315
- }
1316
- }
1317
-
1318
- @PluginMethod
1319
- public void disconnect(PluginCall call) {
1320
- Log.d(TAG, "🔌 Disconnect method called");
1321
-
1322
- if (bluetoothGatt != null) {
1323
- bluetoothGatt.disconnect();
1324
- bluetoothGatt.close();
1325
- bluetoothGatt = null;
1326
- }
1327
-
1328
- // Disconnect SPP
1329
- if (sppSocket != null) {
1330
- try {
1331
- sppSocket.close();
1332
- } catch (IOException e) {
1333
- Log.e(TAG, "❌ Error closing SPP socket", e);
1334
- }
1335
- sppSocket = null;
1336
- sppOutputStream = null;
1337
- }
1338
-
1339
- isConnected = false;
1340
- connectedPrinterAddress = null;
1341
- printerCharacteristic = null;
1342
- useSPP = false;
1343
- isWriting = false;
1344
-
1345
- Log.d(TAG, "✅ Disconnected from printer");
1346
-
1347
- JSObject result = new JSObject();
1348
- result.put("success", true);
1349
- result.put("connected", false);
1350
- call.resolve(result);
1351
- }
1352
-
1353
- @PluginMethod
1354
- public void getNetworkInfo(PluginCall call) {
1355
- JSObject result = new JSObject();
1356
- result.put("supported", true);
1357
-
1358
- String type = "unknown";
1359
- String ip = null;
1360
- String ssid = null;
1361
-
1362
- try {
1363
- WifiManager wifiManager = (WifiManager) getContext().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
1364
- ConnectivityManager connectivityManager = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
1365
-
1366
- if (wifiManager != null) {
1367
- WifiInfo info = wifiManager.getConnectionInfo();
1368
- if (info != null && info.getNetworkId() != -1) {
1369
- int ipInt = info.getIpAddress();
1370
- if (ipInt != 0) {
1371
- ip = intToIp(ipInt);
1372
- }
1373
- ssid = info.getSSID();
1374
- if (ssid != null) {
1375
- ssid = ssid.replace("\"", "");
1376
- if ("<unknown ssid>".equalsIgnoreCase(ssid)) {
1377
- ssid = null;
1378
- }
1379
- }
1380
- type = "wifi";
1381
- }
1382
- }
1383
-
1384
- if ("unknown".equals(type) && connectivityManager != null) {
1385
- Network activeNetwork = connectivityManager.getActiveNetwork();
1386
- if (activeNetwork != null) {
1387
- NetworkCapabilities caps = connectivityManager.getNetworkCapabilities(activeNetwork);
1388
- if (caps != null && caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
1389
- type = "cell";
1390
- }
1391
- }
1392
- }
1393
- } catch (Exception e) {
1394
- Log.w(TAG, "⚠️ getNetworkInfo failed: " + e.getMessage());
1395
- result.put("error", e.getMessage());
1396
- }
1397
-
1398
- result.put("type", type);
1399
- if (ip != null) {
1400
- result.put("ip", ip);
1401
- }
1402
- if (ssid != null) {
1403
- result.put("ssid", ssid);
1404
- }
1405
-
1406
- call.resolve(result);
1407
- }
1408
-
1409
- private String intToIp(int ip) {
1410
- return String.format(
1411
- "%d.%d.%d.%d",
1412
- (ip & 0xff),
1413
- (ip >> 8 & 0xff),
1414
- (ip >> 16 & 0xff),
1415
- (ip >> 24 & 0xff)
1416
- );
1417
- }
1418
- }