@capgo/capacitor-wifi 8.4.0 → 8.5.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.
@@ -12,6 +12,7 @@ import android.net.Network;
12
12
  import android.net.NetworkCapabilities;
13
13
  import android.net.NetworkRequest;
14
14
  import android.net.NetworkSpecifier;
15
+ import android.net.TransportInfo;
15
16
  import android.net.Uri;
16
17
  import android.net.wifi.ScanResult;
17
18
  import android.net.wifi.WifiConfiguration;
@@ -20,6 +21,8 @@ import android.net.wifi.WifiManager;
20
21
  import android.net.wifi.WifiNetworkSpecifier;
21
22
  import android.net.wifi.WifiNetworkSuggestion;
22
23
  import android.os.Build;
24
+ import android.os.Handler;
25
+ import android.os.Looper;
23
26
  import android.provider.Settings;
24
27
  import androidx.activity.result.ActivityResult;
25
28
  import androidx.annotation.NonNull;
@@ -41,6 +44,9 @@ import java.net.NetworkInterface;
41
44
  import java.util.ArrayList;
42
45
  import java.util.Collections;
43
46
  import java.util.List;
47
+ import java.util.concurrent.Executor;
48
+ import java.util.concurrent.atomic.AtomicBoolean;
49
+ import java.util.concurrent.atomic.AtomicInteger;
44
50
 
45
51
  @CapacitorPlugin(
46
52
  name = "CapacitorWifi",
@@ -50,7 +56,7 @@ import java.util.List;
50
56
  )
51
57
  public class CapacitorWifiPlugin extends Plugin {
52
58
 
53
- private final String pluginVersion = "8.4.0";
59
+ private final String pluginVersion = "8.5.0";
54
60
 
55
61
  private WifiManager wifiManager;
56
62
  private ConnectivityManager connectivityManager;
@@ -63,6 +69,19 @@ public class CapacitorWifiPlugin extends Plugin {
63
69
  private static final String EXTRA_WIFI_SSID = "wifi_ssid";
64
70
  private static final String EXTRA_WIFI_PRE_SHARED_KEY = "wifi_psk";
65
71
  private static final int WIFI_SECURITY_WPA_PSK = 2;
72
+ private static final int DEFAULT_CONNECT_TIMEOUT_MS = 30000;
73
+ private static final long SSID_VERIFY_POLL_MS = 500;
74
+
75
+ private final Object connectLock = new Object();
76
+ private final AtomicInteger connectGeneration = new AtomicInteger(0);
77
+ private final Handler connectHandler = new Handler(Looper.getMainLooper());
78
+ private PluginCall activeConnectCall;
79
+ private AtomicBoolean connectCompleted;
80
+ private Runnable connectTimeoutRunnable;
81
+ private Runnable ssidVerifyRunnable;
82
+ private WifiManager.LocalOnlyConnectionFailureListener localOnlyFailureListener;
83
+ private String pendingConnectSsid;
84
+ private Boolean pendingAutoRouteTraffic;
66
85
 
67
86
  // Lock for thread-safe access to boundNetwork
68
87
 
@@ -236,6 +255,23 @@ public class CapacitorWifiPlugin extends Plugin {
236
255
 
237
256
  @PluginMethod
238
257
  public void connect(PluginCall call) {
258
+ synchronized (connectLock) {
259
+ if (activeConnectCall != null && connectCompleted != null && !connectCompleted.get()) {
260
+ rejectConnect(
261
+ call,
262
+ "CONNECTION_IN_PROGRESS",
263
+ "Another Wi-Fi connection attempt is already in progress.",
264
+ "request",
265
+ null,
266
+ null
267
+ );
268
+ return;
269
+ }
270
+ // Reserve the slot early so permission prompts cannot overlap with another connect().
271
+ activeConnectCall = call;
272
+ connectCompleted = new AtomicBoolean(false);
273
+ }
274
+
239
275
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
240
276
  connectModern(call);
241
277
  } else {
@@ -247,7 +283,18 @@ public class CapacitorWifiPlugin extends Plugin {
247
283
  private void connectModern(PluginCall call) {
248
284
  String ssid = call.getString("ssid");
249
285
  if (ssid == null || ssid.isEmpty()) {
250
- call.reject("SSID is required");
286
+ rejectConnectAndClear(call, "INVALID_CONFIGURATION", "SSID is required.", "validation");
287
+ return;
288
+ }
289
+
290
+ Integer timeoutMs = resolveTimeoutMs(call);
291
+ if (timeoutMs == null) {
292
+ rejectConnectAndClear(call, "INVALID_CONFIGURATION", "timeoutMs must be a positive number.", "validation");
293
+ return;
294
+ }
295
+
296
+ if (!wifiManager.isWifiEnabled()) {
297
+ rejectConnectAndClear(call, "WIFI_DISABLED", "Wi-Fi is disabled.", "request");
251
298
  return;
252
299
  }
253
300
 
@@ -255,7 +302,24 @@ public class CapacitorWifiPlugin extends Plugin {
255
302
  Boolean isHiddenSsid = call.getBoolean("isHiddenSsid", false);
256
303
  Boolean autoRouteTraffic = call.getBoolean("autoRouteTraffic", false);
257
304
 
305
+ if (password != null && !password.isEmpty() && !isValidWpaPassphrase(password)) {
306
+ rejectConnectAndClear(call, "INVALID_CONFIGURATION", "Invalid WPA passphrase format.", "validation");
307
+ return;
308
+ }
309
+
310
+ final int generation = beginConnectAttempt(call, ssid, autoRouteTraffic, timeoutMs);
311
+
258
312
  try {
313
+ // Drop any previous local-only callback so a new request can take over.
314
+ if (networkCallback != null) {
315
+ try {
316
+ connectivityManager.unregisterNetworkCallback(networkCallback);
317
+ } catch (Exception ignored) {
318
+ // already unregistered
319
+ }
320
+ networkCallback = null;
321
+ }
322
+
259
323
  WifiNetworkSpecifier.Builder specifierBuilder = new WifiNetworkSpecifier.Builder().setSsid(ssid);
260
324
 
261
325
  if (isHiddenSsid != null && isHiddenSsid) {
@@ -274,17 +338,26 @@ public class CapacitorWifiPlugin extends Plugin {
274
338
 
275
339
  // Only remove internet capability if autoRouteTraffic is false
276
340
  // If autoRouteTraffic is true, we want Android to consider this network valid for routing
277
- if (autoRouteTraffic == null || !autoRouteTraffic) {
341
+ boolean localOnly = autoRouteTraffic == null || !autoRouteTraffic;
342
+ if (localOnly) {
278
343
  requestBuilder.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
279
344
  }
280
345
 
281
346
  NetworkRequest request = requestBuilder.build();
282
347
 
348
+ if (localOnly && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
349
+ registerLocalOnlyFailureListener(generation, specifier);
350
+ }
351
+
283
352
  networkCallback = new ConnectivityManager.NetworkCallback() {
284
353
  @Override
285
354
  public void onAvailable(@NonNull Network network) {
286
355
  super.onAvailable(network);
287
356
 
357
+ if (!isCurrentConnectAttempt(generation)) {
358
+ return;
359
+ }
360
+
288
361
  // Bind process to network if autoRouteTraffic is enabled
289
362
  if (autoRouteTraffic != null && autoRouteTraffic) {
290
363
  try {
@@ -306,19 +379,60 @@ public class CapacitorWifiPlugin extends Plugin {
306
379
  }
307
380
  }
308
381
 
309
- call.resolve();
382
+ startSsidVerification(generation, network, ssid, true);
310
383
  }
311
384
 
312
385
  @Override
313
386
  public void onUnavailable() {
314
387
  super.onUnavailable();
315
- call.reject("Failed to connect to network");
388
+ // onUnavailable has no reliable detailed reason.
389
+ // On API 34+ local-only connects, prefer LocalOnlyConnectionFailureListener.
390
+ if (localOnly && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
391
+ return;
392
+ }
393
+ completeConnectFailure(
394
+ generation,
395
+ "CONNECTION_FAILED",
396
+ "Failed to connect to network.",
397
+ "association",
398
+ null,
399
+ null,
400
+ true
401
+ );
316
402
  }
317
403
  };
318
404
 
319
405
  connectivityManager.requestNetwork(request, networkCallback);
406
+ } catch (SecurityException e) {
407
+ completeConnectFailure(
408
+ generation,
409
+ "PERMISSION_DENIED",
410
+ "Missing permission to connect to Wi-Fi.",
411
+ "permission",
412
+ null,
413
+ e.getMessage(),
414
+ true
415
+ );
416
+ } catch (IllegalArgumentException e) {
417
+ completeConnectFailure(
418
+ generation,
419
+ "INVALID_CONFIGURATION",
420
+ "Invalid Wi-Fi connection configuration.",
421
+ "validation",
422
+ null,
423
+ e.getMessage(),
424
+ true
425
+ );
320
426
  } catch (Exception e) {
321
- call.reject("Failed to connect: " + e.getMessage(), e);
427
+ completeConnectFailure(
428
+ generation,
429
+ "UNKNOWN",
430
+ "Unexpected error while connecting to Wi-Fi.",
431
+ "request",
432
+ null,
433
+ e.getMessage(),
434
+ true
435
+ );
322
436
  }
323
437
  }
324
438
 
@@ -330,7 +444,18 @@ public class CapacitorWifiPlugin extends Plugin {
330
444
 
331
445
  String ssid = call.getString("ssid");
332
446
  if (ssid == null || ssid.isEmpty()) {
333
- call.reject("SSID is required");
447
+ rejectConnectAndClear(call, "INVALID_CONFIGURATION", "SSID is required.", "validation");
448
+ return;
449
+ }
450
+
451
+ Integer timeoutMs = resolveTimeoutMs(call);
452
+ if (timeoutMs == null) {
453
+ rejectConnectAndClear(call, "INVALID_CONFIGURATION", "timeoutMs must be a positive number.", "validation");
454
+ return;
455
+ }
456
+
457
+ if (!wifiManager.isWifiEnabled()) {
458
+ rejectConnectAndClear(call, "WIFI_DISABLED", "Wi-Fi is disabled.", "request");
334
459
  return;
335
460
  }
336
461
 
@@ -338,6 +463,13 @@ public class CapacitorWifiPlugin extends Plugin {
338
463
  Boolean isHiddenSsid = call.getBoolean("isHiddenSsid", false);
339
464
  Boolean autoRouteTraffic = call.getBoolean("autoRouteTraffic", false);
340
465
 
466
+ if (password != null && !password.isEmpty() && !isValidWpaPassphrase(password)) {
467
+ rejectConnectAndClear(call, "INVALID_CONFIGURATION", "Invalid WPA passphrase format.", "validation");
468
+ return;
469
+ }
470
+
471
+ final int generation = beginConnectAttempt(call, ssid, autoRouteTraffic, timeoutMs);
472
+
341
473
  try {
342
474
  WifiConfiguration wifiConfig = new WifiConfiguration();
343
475
  wifiConfig.SSID = "\"" + ssid + "\"";
@@ -354,66 +486,93 @@ public class CapacitorWifiPlugin extends Plugin {
354
486
 
355
487
  int netId = wifiManager.addNetwork(wifiConfig);
356
488
  if (netId == -1) {
357
- call.reject("Failed to add network configuration");
489
+ completeConnectFailure(
490
+ generation,
491
+ "CONNECTION_FAILED",
492
+ "Failed to add network configuration.",
493
+ "request",
494
+ null,
495
+ null,
496
+ true
497
+ );
358
498
  return;
359
499
  }
360
500
 
361
501
  boolean disconnectResult = wifiManager.disconnect();
362
502
  if (!disconnectResult) {
363
- call.reject("Failed to disconnect from current network");
503
+ completeConnectFailure(
504
+ generation,
505
+ "CONNECTION_FAILED",
506
+ "Failed to disconnect from current network.",
507
+ "request",
508
+ null,
509
+ null,
510
+ true
511
+ );
364
512
  return;
365
513
  }
366
514
 
367
515
  boolean enableResult = wifiManager.enableNetwork(netId, true);
368
516
  if (!enableResult) {
369
- call.reject("Failed to enable network");
517
+ completeConnectFailure(generation, "CONNECTION_FAILED", "Failed to enable network.", "request", null, null, true);
370
518
  return;
371
519
  }
372
520
 
373
521
  boolean reconnectResult = wifiManager.reconnect();
374
522
  if (!reconnectResult) {
375
- call.reject("Failed to reconnect");
523
+ completeConnectFailure(generation, "CONNECTION_FAILED", "Failed to reconnect.", "request", null, null, true);
376
524
  return;
377
525
  }
378
526
 
379
- // Bind process to network if autoRouteTraffic is enabled
380
- if (autoRouteTraffic != null && autoRouteTraffic) {
381
- // Use handler to bind asynchronously after connection is established
382
- new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(
383
- () -> {
384
- try {
385
- synchronized (boundNetworkLock) {
386
- // Unbind from previous network if any
387
- if (boundNetwork != null) {
388
- connectivityManager.bindProcessToNetwork(null);
389
- }
390
-
391
- Network activeNetwork = connectivityManager.getActiveNetwork();
392
- if (activeNetwork != null) {
393
- boolean bound = connectivityManager.bindProcessToNetwork(activeNetwork);
394
- if (bound) {
395
- boundNetwork = activeNetwork;
396
- }
397
- }
398
- }
399
- } catch (Exception e) {
400
- // Log error but don't fail the connection
401
- android.util.Log.e("CapacitorWifi", "Failed to bind process to network: " + e.getMessage());
402
- }
403
- },
404
- 500
405
- );
406
- }
407
-
408
- call.resolve();
527
+ // Do not resolve after reconnect(); wait until the connected SSID matches.
528
+ startSsidVerification(generation, null, ssid, false);
529
+ } catch (SecurityException e) {
530
+ completeConnectFailure(
531
+ generation,
532
+ "PERMISSION_DENIED",
533
+ "Missing permission to connect to Wi-Fi.",
534
+ "permission",
535
+ null,
536
+ e.getMessage(),
537
+ true
538
+ );
539
+ } catch (IllegalArgumentException e) {
540
+ completeConnectFailure(
541
+ generation,
542
+ "INVALID_CONFIGURATION",
543
+ "Invalid Wi-Fi connection configuration.",
544
+ "validation",
545
+ null,
546
+ e.getMessage(),
547
+ true
548
+ );
409
549
  } catch (Exception e) {
410
- call.reject("Failed to connect: " + e.getMessage(), e);
550
+ completeConnectFailure(
551
+ generation,
552
+ "UNKNOWN",
553
+ "Unexpected error while connecting to Wi-Fi.",
554
+ "request",
555
+ null,
556
+ e.getMessage(),
557
+ true
558
+ );
411
559
  }
412
560
  }
413
561
 
414
562
  @PluginMethod
415
563
  public void disconnect(PluginCall call) {
416
564
  try {
565
+ int generation = connectGeneration.get();
566
+ completeConnectFailure(
567
+ generation,
568
+ "CONNECTION_FAILED",
569
+ "Connection canceled because disconnect() was called.",
570
+ "request",
571
+ null,
572
+ null,
573
+ false
574
+ );
575
+
417
576
  // Unbind from network if we were bound
418
577
  synchronized (boundNetworkLock) {
419
578
  if (boundNetwork != null) {
@@ -431,6 +590,7 @@ public class CapacitorWifiPlugin extends Plugin {
431
590
  } else {
432
591
  wifiManager.disconnect();
433
592
  }
593
+ removeLocalOnlyFailureListener();
434
594
  call.resolve();
435
595
  } catch (Exception e) {
436
596
  call.reject("Failed to disconnect: " + e.getMessage(), e);
@@ -856,7 +1016,7 @@ public class CapacitorWifiPlugin extends Plugin {
856
1016
  if (getPermissionState("location") == PermissionState.GRANTED) {
857
1017
  connectLegacy(call);
858
1018
  } else {
859
- call.reject("Location permission is required");
1019
+ rejectConnectAndClear(call, "PERMISSION_DENIED", "Location permission is required.", "permission");
860
1020
  }
861
1021
  }
862
1022
 
@@ -925,4 +1085,335 @@ public class CapacitorWifiPlugin extends Plugin {
925
1085
  networkCallback = null;
926
1086
  }
927
1087
  }
1088
+
1089
+ private void clearConnectReservation() {
1090
+ synchronized (connectLock) {
1091
+ activeConnectCall = null;
1092
+ connectCompleted = null;
1093
+ pendingConnectSsid = null;
1094
+ pendingAutoRouteTraffic = null;
1095
+ }
1096
+ clearConnectTimeout();
1097
+ clearSsidVerification();
1098
+ }
1099
+
1100
+ private void rejectConnectAndClear(PluginCall call, String code, String message, String stage) {
1101
+ clearConnectReservation();
1102
+ rejectConnect(call, code, message, stage, null, null);
1103
+ }
1104
+
1105
+ private Integer resolveTimeoutMs(PluginCall call) {
1106
+ if (!call.hasOption("timeoutMs")) {
1107
+ return DEFAULT_CONNECT_TIMEOUT_MS;
1108
+ }
1109
+ Integer timeoutMs = call.getInt("timeoutMs");
1110
+ if (timeoutMs == null || timeoutMs <= 0) {
1111
+ return null;
1112
+ }
1113
+ return timeoutMs;
1114
+ }
1115
+
1116
+ private boolean isValidWpaPassphrase(String password) {
1117
+ int length = password.length();
1118
+ if (length >= 8 && length <= 63) {
1119
+ return true;
1120
+ }
1121
+ if (length == 64) {
1122
+ for (int i = 0; i < length; i++) {
1123
+ char c = password.charAt(i);
1124
+ boolean hex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
1125
+ if (!hex) {
1126
+ return false;
1127
+ }
1128
+ }
1129
+ return true;
1130
+ }
1131
+ return false;
1132
+ }
1133
+
1134
+ private int beginConnectAttempt(PluginCall call, String ssid, Boolean autoRouteTraffic, int timeoutMs) {
1135
+ int generation = connectGeneration.incrementAndGet();
1136
+ synchronized (connectLock) {
1137
+ activeConnectCall = call;
1138
+ if (connectCompleted == null) {
1139
+ connectCompleted = new AtomicBoolean(false);
1140
+ } else {
1141
+ connectCompleted.set(false);
1142
+ }
1143
+ pendingConnectSsid = ssid;
1144
+ pendingAutoRouteTraffic = autoRouteTraffic;
1145
+ }
1146
+ clearConnectTimeout();
1147
+ clearSsidVerification();
1148
+ removeLocalOnlyFailureListener();
1149
+
1150
+ connectTimeoutRunnable = () ->
1151
+ completeConnectFailure(
1152
+ generation,
1153
+ "CONNECTION_TIMEOUT",
1154
+ "Timed out waiting for Wi-Fi connection confirmation.",
1155
+ "verification",
1156
+ null,
1157
+ null,
1158
+ true
1159
+ );
1160
+ connectHandler.postDelayed(connectTimeoutRunnable, timeoutMs);
1161
+ return generation;
1162
+ }
1163
+
1164
+ private boolean isCurrentConnectAttempt(int generation) {
1165
+ return generation == connectGeneration.get();
1166
+ }
1167
+
1168
+ private void startSsidVerification(int generation, @Nullable Network network, String expectedSsid, boolean keepCallbackOnSuccess) {
1169
+ clearSsidVerification();
1170
+ ssidVerifyRunnable = new Runnable() {
1171
+ @Override
1172
+ public void run() {
1173
+ if (!isCurrentConnectAttempt(generation)) {
1174
+ return;
1175
+ }
1176
+ synchronized (connectLock) {
1177
+ if (connectCompleted == null || connectCompleted.get()) {
1178
+ return;
1179
+ }
1180
+ }
1181
+
1182
+ if (ssidMatches(expectedSsid, network)) {
1183
+ completeConnectSuccess(generation, network, keepCallbackOnSuccess);
1184
+ return;
1185
+ }
1186
+
1187
+ connectHandler.postDelayed(this, SSID_VERIFY_POLL_MS);
1188
+ }
1189
+ };
1190
+ connectHandler.post(ssidVerifyRunnable);
1191
+ }
1192
+
1193
+ private boolean ssidMatches(String expectedSsid, @Nullable Network network) {
1194
+ String current = getConnectedSsid(network);
1195
+ if (current == null) {
1196
+ return false;
1197
+ }
1198
+ return expectedSsid.equals(current);
1199
+ }
1200
+
1201
+ @Nullable
1202
+ private String getConnectedSsid(@Nullable Network network) {
1203
+ try {
1204
+ if (network != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
1205
+ NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
1206
+ if (capabilities != null) {
1207
+ TransportInfo transportInfo = capabilities.getTransportInfo();
1208
+ if (transportInfo instanceof WifiInfo) {
1209
+ return normalizeSsid(((WifiInfo) transportInfo).getSSID());
1210
+ }
1211
+ }
1212
+ }
1213
+
1214
+ WifiInfo wifiInfo = wifiManager.getConnectionInfo();
1215
+ if (wifiInfo != null) {
1216
+ return normalizeSsid(wifiInfo.getSSID());
1217
+ }
1218
+ } catch (Exception e) {
1219
+ android.util.Log.e("CapacitorWifi", "Failed to read connected SSID: " + e.getMessage());
1220
+ }
1221
+ return null;
1222
+ }
1223
+
1224
+ @Nullable
1225
+ private String normalizeSsid(@Nullable String ssid) {
1226
+ if (ssid == null || ssid.isEmpty() || "<unknown ssid>".equalsIgnoreCase(ssid)) {
1227
+ return null;
1228
+ }
1229
+ if (ssid.startsWith("\"") && ssid.endsWith("\"") && ssid.length() >= 2) {
1230
+ return ssid.substring(1, ssid.length() - 1);
1231
+ }
1232
+ return ssid;
1233
+ }
1234
+
1235
+ @RequiresApi(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
1236
+ private void registerLocalOnlyFailureListener(int generation, NetworkSpecifier specifier) {
1237
+ removeLocalOnlyFailureListener();
1238
+ if (!(specifier instanceof WifiNetworkSpecifier)) {
1239
+ return;
1240
+ }
1241
+ localOnlyFailureListener = (failedSpecifier, failureReason) -> {
1242
+ if (!isCurrentConnectAttempt(generation)) {
1243
+ return;
1244
+ }
1245
+ // Only one plugin connect attempt is active at a time; accept the failure for this generation.
1246
+ String code = mapLocalOnlyFailureReason(failureReason);
1247
+ String stage = mapLocalOnlyFailureStage(failureReason);
1248
+ completeConnectFailure(generation, code, "Failed to connect to network.", stage, failureReason, null, true);
1249
+ };
1250
+ Executor executor = (command) -> connectHandler.post(command);
1251
+ wifiManager.addLocalOnlyConnectionFailureListener(executor, localOnlyFailureListener);
1252
+ }
1253
+
1254
+ private String mapLocalOnlyFailureReason(int failureReason) {
1255
+ if (failureReason == WifiManager.STATUS_LOCAL_ONLY_CONNECTION_FAILURE_AUTHENTICATION) {
1256
+ return "AUTHENTICATION_FAILED";
1257
+ }
1258
+ if (failureReason == WifiManager.STATUS_LOCAL_ONLY_CONNECTION_FAILURE_NOT_FOUND) {
1259
+ return "NETWORK_NOT_FOUND";
1260
+ }
1261
+ if (failureReason == WifiManager.STATUS_LOCAL_ONLY_CONNECTION_FAILURE_USER_REJECT) {
1262
+ return "USER_DENIED";
1263
+ }
1264
+ // ASSOCIATION, IP_PROVISIONING, NO_RESPONSE, UNKNOWN -> CONNECTION_FAILED
1265
+ return "CONNECTION_FAILED";
1266
+ }
1267
+
1268
+ private String mapLocalOnlyFailureStage(int failureReason) {
1269
+ if (failureReason == WifiManager.STATUS_LOCAL_ONLY_CONNECTION_FAILURE_AUTHENTICATION) {
1270
+ return "authentication";
1271
+ }
1272
+ if (failureReason == WifiManager.STATUS_LOCAL_ONLY_CONNECTION_FAILURE_IP_PROVISIONING) {
1273
+ return "ipProvisioning";
1274
+ }
1275
+ if (failureReason == WifiManager.STATUS_LOCAL_ONLY_CONNECTION_FAILURE_ASSOCIATION) {
1276
+ return "association";
1277
+ }
1278
+ return "request";
1279
+ }
1280
+
1281
+ private void completeConnectSuccess(int generation, @Nullable Network network, boolean keepCallbackOnSuccess) {
1282
+ if (!isCurrentConnectAttempt(generation)) {
1283
+ return;
1284
+ }
1285
+
1286
+ PluginCall call;
1287
+ Boolean autoRouteTraffic;
1288
+ synchronized (connectLock) {
1289
+ if (connectCompleted == null || !connectCompleted.compareAndSet(false, true)) {
1290
+ return;
1291
+ }
1292
+ call = activeConnectCall;
1293
+ autoRouteTraffic = pendingAutoRouteTraffic;
1294
+ activeConnectCall = null;
1295
+ pendingConnectSsid = null;
1296
+ pendingAutoRouteTraffic = null;
1297
+ }
1298
+
1299
+ clearConnectTimeout();
1300
+ clearSsidVerification();
1301
+ removeLocalOnlyFailureListener();
1302
+
1303
+ // Legacy path: bind after SSID confirmation when requested.
1304
+ if (!keepCallbackOnSuccess && autoRouteTraffic != null && autoRouteTraffic) {
1305
+ try {
1306
+ synchronized (boundNetworkLock) {
1307
+ if (boundNetwork != null) {
1308
+ connectivityManager.bindProcessToNetwork(null);
1309
+ }
1310
+ Network activeNetwork = network != null ? network : connectivityManager.getActiveNetwork();
1311
+ if (activeNetwork != null) {
1312
+ boolean bound = connectivityManager.bindProcessToNetwork(activeNetwork);
1313
+ if (bound) {
1314
+ boundNetwork = activeNetwork;
1315
+ }
1316
+ }
1317
+ }
1318
+ } catch (Exception e) {
1319
+ android.util.Log.e("CapacitorWifi", "Failed to bind process to network: " + e.getMessage());
1320
+ }
1321
+ }
1322
+
1323
+ if (!keepCallbackOnSuccess && networkCallback != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
1324
+ // Legacy connect does not keep a NetworkCallback.
1325
+ }
1326
+
1327
+ if (call != null) {
1328
+ call.resolve();
1329
+ }
1330
+ }
1331
+
1332
+ private void completeConnectFailure(
1333
+ int generation,
1334
+ String code,
1335
+ String message,
1336
+ String stage,
1337
+ @Nullable Integer nativeCode,
1338
+ @Nullable String nativeMessage,
1339
+ boolean cleanupNetworkCallback
1340
+ ) {
1341
+ if (!isCurrentConnectAttempt(generation)) {
1342
+ return;
1343
+ }
1344
+
1345
+ PluginCall call;
1346
+ synchronized (connectLock) {
1347
+ if (connectCompleted == null || !connectCompleted.compareAndSet(false, true)) {
1348
+ return;
1349
+ }
1350
+ call = activeConnectCall;
1351
+ activeConnectCall = null;
1352
+ pendingConnectSsid = null;
1353
+ pendingAutoRouteTraffic = null;
1354
+ }
1355
+
1356
+ clearConnectTimeout();
1357
+ clearSsidVerification();
1358
+ removeLocalOnlyFailureListener();
1359
+
1360
+ if (cleanupNetworkCallback && networkCallback != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
1361
+ try {
1362
+ connectivityManager.unregisterNetworkCallback(networkCallback);
1363
+ } catch (Exception ignored) {
1364
+ // already unregistered
1365
+ }
1366
+ networkCallback = null;
1367
+ }
1368
+
1369
+ if (call != null) {
1370
+ rejectConnect(call, code, message, stage, nativeCode, nativeMessage);
1371
+ }
1372
+ }
1373
+
1374
+ private void rejectConnect(
1375
+ PluginCall call,
1376
+ String code,
1377
+ String message,
1378
+ String stage,
1379
+ @Nullable Integer nativeCode,
1380
+ @Nullable String nativeMessage
1381
+ ) {
1382
+ JSObject data = new JSObject();
1383
+ data.put("platform", "android");
1384
+ data.put("androidApiLevel", Build.VERSION.SDK_INT);
1385
+ data.put("connectionStage", stage);
1386
+ if (nativeCode != null) {
1387
+ data.put("nativeCode", nativeCode);
1388
+ }
1389
+ if (nativeMessage != null) {
1390
+ data.put("nativeMessage", nativeMessage);
1391
+ }
1392
+ call.reject(message, code, data);
1393
+ }
1394
+
1395
+ private void clearConnectTimeout() {
1396
+ if (connectTimeoutRunnable != null) {
1397
+ connectHandler.removeCallbacks(connectTimeoutRunnable);
1398
+ connectTimeoutRunnable = null;
1399
+ }
1400
+ }
1401
+
1402
+ private void clearSsidVerification() {
1403
+ if (ssidVerifyRunnable != null) {
1404
+ connectHandler.removeCallbacks(ssidVerifyRunnable);
1405
+ ssidVerifyRunnable = null;
1406
+ }
1407
+ }
1408
+
1409
+ private void removeLocalOnlyFailureListener() {
1410
+ if (localOnlyFailureListener != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
1411
+ try {
1412
+ wifiManager.removeLocalOnlyConnectionFailureListener(localOnlyFailureListener);
1413
+ } catch (Exception ignored) {
1414
+ // already removed
1415
+ }
1416
+ localOnlyFailureListener = null;
1417
+ }
1418
+ }
928
1419
  }