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