wifidirectplugin 1.3.0 → 1.3.2
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.
|
@@ -4,10 +4,16 @@ import android.content.BroadcastReceiver;
|
|
|
4
4
|
import android.content.Context;
|
|
5
5
|
import android.content.Intent;
|
|
6
6
|
import android.content.IntentFilter;
|
|
7
|
+
import android.net.ConnectivityManager;
|
|
8
|
+
import android.net.Network;
|
|
9
|
+
import android.net.NetworkCapabilities;
|
|
10
|
+
import android.net.NetworkRequest;
|
|
7
11
|
import android.net.wifi.WpsInfo;
|
|
8
12
|
import android.net.wifi.p2p.*;
|
|
9
13
|
import android.net.NetworkInfo;
|
|
10
14
|
import android.os.Build;
|
|
15
|
+
import android.os.Handler;
|
|
16
|
+
import android.os.Looper;
|
|
11
17
|
import android.util.Log;
|
|
12
18
|
|
|
13
19
|
import com.getcapacitor.*;
|
|
@@ -29,6 +35,18 @@ public class WifiDirectPlugin extends Plugin {
|
|
|
29
35
|
|
|
30
36
|
private PluginCall pendingConnectCall = null;
|
|
31
37
|
|
|
38
|
+
// Group 형성 broadcast가 안 올 경우 대비한 timeout과 메인 핸들러
|
|
39
|
+
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
40
|
+
private Runnable pendingConnectTimeout = null;
|
|
41
|
+
private static final long CONNECT_TIMEOUT_MS = 20000L;
|
|
42
|
+
// removeGroup 직후 framework가 안정될 때까지 대기 시간
|
|
43
|
+
private static final long REMOVE_GROUP_SETTLE_MS = 500L;
|
|
44
|
+
|
|
45
|
+
// P2P 네트워크에 앱 프로세스를 바인딩해 HTTP 요청을 P2P 인터페이스로 강제하기 위한 상태
|
|
46
|
+
private ConnectivityManager connectivityManager;
|
|
47
|
+
private ConnectivityManager.NetworkCallback p2pNetworkCallback = null;
|
|
48
|
+
private Network boundP2pNetwork = null;
|
|
49
|
+
|
|
32
50
|
private final BroadcastReceiver wifiP2pReceiver = new BroadcastReceiver() {
|
|
33
51
|
@Override
|
|
34
52
|
public void onReceive(Context context, Intent intent) {
|
|
@@ -39,6 +57,7 @@ public class WifiDirectPlugin extends Plugin {
|
|
|
39
57
|
if (networkInfo != null && networkInfo.isConnected()) {
|
|
40
58
|
Log.d(TAG, "Wi-Fi Direct connected");
|
|
41
59
|
if (pendingConnectCall != null) {
|
|
60
|
+
cancelConnectTimeout();
|
|
42
61
|
manager.requestConnectionInfo(channel, info -> {
|
|
43
62
|
if (info != null && info.groupFormed) {
|
|
44
63
|
Log.d(TAG, "Group formed, resolving connectToDevice");
|
|
@@ -50,6 +69,7 @@ public class WifiDirectPlugin extends Plugin {
|
|
|
50
69
|
} else {
|
|
51
70
|
Log.d(TAG, "Wi-Fi Direct disconnected");
|
|
52
71
|
if (pendingConnectCall != null) {
|
|
72
|
+
cancelConnectTimeout();
|
|
53
73
|
pendingConnectCall.reject("Wi-Fi Direct connection lost before group formed");
|
|
54
74
|
pendingConnectCall = null;
|
|
55
75
|
}
|
|
@@ -68,7 +88,7 @@ public class WifiDirectPlugin extends Plugin {
|
|
|
68
88
|
Log.e(TAG, "P2P Channel disconnected");
|
|
69
89
|
}
|
|
70
90
|
});
|
|
71
|
-
|
|
91
|
+
|
|
72
92
|
IntentFilter intentFilter = new IntentFilter();
|
|
73
93
|
intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
|
|
74
94
|
intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
|
|
@@ -76,6 +96,10 @@ public class WifiDirectPlugin extends Plugin {
|
|
|
76
96
|
intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
|
|
77
97
|
|
|
78
98
|
context.registerReceiver(wifiP2pReceiver, intentFilter);
|
|
99
|
+
|
|
100
|
+
// P2P 네트워크 가용성을 감시해 onAvailable 시점에 자동으로 프로세스를 바인딩
|
|
101
|
+
connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
|
102
|
+
registerP2pNetworkCallback();
|
|
79
103
|
}
|
|
80
104
|
|
|
81
105
|
public void unload() {
|
|
@@ -84,6 +108,7 @@ public class WifiDirectPlugin extends Plugin {
|
|
|
84
108
|
} catch (IllegalArgumentException e) {
|
|
85
109
|
Log.w(TAG, "Receiver not registered or already registered");
|
|
86
110
|
}
|
|
111
|
+
unregisterP2pNetworkCallback();
|
|
87
112
|
}
|
|
88
113
|
@PluginMethod
|
|
89
114
|
public void scanWifiPeers(PluginCall call) {
|
|
@@ -155,21 +180,31 @@ public class WifiDirectPlugin extends Plugin {
|
|
|
155
180
|
|
|
156
181
|
manager.requestConnectionInfo(channel, info -> {
|
|
157
182
|
if(info != null && info.groupFormed) {
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// 그룹 해제 성공 시 연결 계속 진행
|
|
165
|
-
initiateConnect(deviceAddress, pin, call);
|
|
166
|
-
}
|
|
167
|
-
@Override
|
|
168
|
-
public void onFailure(int reason) {
|
|
169
|
-
Log.w(TAG, "removeGroup failed: " + reason + ". Proceed anyway.");
|
|
170
|
-
// 그룹 해제 실패 시에도 연결 시도(혹은 call.reject로 실패 처리 가능)
|
|
171
|
-
initiateConnect(deviceAddress, pin, call);
|
|
183
|
+
// 이미 그룹이 형성된 경우, 대상 기기와의 그룹이면 그대로 사용
|
|
184
|
+
manager.requestGroupInfo(channel, group -> {
|
|
185
|
+
if (group != null && isTargetInGroup(group, deviceAddress)) {
|
|
186
|
+
Log.d(TAG, "Already in group with target device, resolving immediately");
|
|
187
|
+
call.resolve();
|
|
188
|
+
return;
|
|
172
189
|
}
|
|
190
|
+
Log.d(TAG, "Existing Wi-Fi Direct group is not the target. Removing group.");
|
|
191
|
+
manager.removeGroup(channel, new WifiP2pManager.ActionListener() {
|
|
192
|
+
@Override
|
|
193
|
+
public void onSuccess() {
|
|
194
|
+
Log.d(TAG, "removeGroup success, settle " + REMOVE_GROUP_SETTLE_MS + "ms before connect.");
|
|
195
|
+
// P2P framework가 BUSY 상태에서 빠져나올 시간 확보
|
|
196
|
+
mainHandler.postDelayed(
|
|
197
|
+
() -> initiateConnect(deviceAddress, pin, call),
|
|
198
|
+
REMOVE_GROUP_SETTLE_MS);
|
|
199
|
+
}
|
|
200
|
+
@Override
|
|
201
|
+
public void onFailure(int reason) {
|
|
202
|
+
Log.w(TAG, "removeGroup failed: " + reasonName(reason) + ". Proceed anyway after settle.");
|
|
203
|
+
mainHandler.postDelayed(
|
|
204
|
+
() -> initiateConnect(deviceAddress, pin, call),
|
|
205
|
+
REMOVE_GROUP_SETTLE_MS);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
173
208
|
});
|
|
174
209
|
} else {
|
|
175
210
|
Log.d(TAG, "No existing group, proceed to connect directly.");
|
|
@@ -177,6 +212,23 @@ public class WifiDirectPlugin extends Plugin {
|
|
|
177
212
|
}
|
|
178
213
|
});
|
|
179
214
|
}
|
|
215
|
+
|
|
216
|
+
private boolean isTargetInGroup(WifiP2pGroup group, String deviceAddress) {
|
|
217
|
+
if (deviceAddress == null) return false;
|
|
218
|
+
WifiP2pDevice owner = group.getOwner();
|
|
219
|
+
if (owner != null && deviceAddress.equalsIgnoreCase(owner.deviceAddress)) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
if (group.getClientList() != null) {
|
|
223
|
+
for (WifiP2pDevice client : group.getClientList()) {
|
|
224
|
+
if (deviceAddress.equalsIgnoreCase(client.deviceAddress)) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
180
232
|
private void initiateConnect(String deviceAddress, String pin, PluginCall call) {
|
|
181
233
|
WifiP2pConfig config = new WifiP2pConfig();
|
|
182
234
|
config.deviceAddress = deviceAddress;
|
|
@@ -190,30 +242,205 @@ public class WifiDirectPlugin extends Plugin {
|
|
|
190
242
|
Log.w(TAG, "Invalid Pin");
|
|
191
243
|
}
|
|
192
244
|
}
|
|
245
|
+
|
|
246
|
+
// 직전 시도에서 남았을 수 있는 pending negotiation을 명시적으로 정리.
|
|
247
|
+
// 실패해도 (보통 NO_SERVICE_REQUESTS 등) 그냥 무시하고 connect 진행.
|
|
248
|
+
try {
|
|
249
|
+
manager.cancelConnect(channel, new WifiP2pManager.ActionListener() {
|
|
250
|
+
@Override
|
|
251
|
+
public void onSuccess() {
|
|
252
|
+
Log.d(TAG, "cancelConnect success, performing connect");
|
|
253
|
+
performConnect(config, call);
|
|
254
|
+
}
|
|
255
|
+
@Override
|
|
256
|
+
public void onFailure(int reason) {
|
|
257
|
+
Log.d(TAG, "cancelConnect failed (ignored): " + reasonName(reason));
|
|
258
|
+
performConnect(config, call);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
} catch (Exception e) {
|
|
262
|
+
Log.w(TAG, "cancelConnect exception (ignored)", e);
|
|
263
|
+
performConnect(config, call);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private void performConnect(WifiP2pConfig config, PluginCall call) {
|
|
193
268
|
try {
|
|
194
269
|
manager.connect(channel, config, new WifiP2pManager.ActionListener() {
|
|
195
270
|
@Override
|
|
196
271
|
public void onSuccess() {
|
|
197
272
|
// connect request accepted — group formation is async
|
|
198
273
|
// resolve will be called from BroadcastReceiver once group is formed
|
|
199
|
-
Log.d(TAG, "connect onSuccess Called, waiting for group formation");
|
|
274
|
+
Log.d(TAG, "connect onSuccess Called, waiting for group formation (timeout " + CONNECT_TIMEOUT_MS + "ms)");
|
|
200
275
|
pendingConnectCall = call;
|
|
276
|
+
scheduleConnectTimeout();
|
|
201
277
|
}
|
|
202
278
|
|
|
203
279
|
@Override
|
|
204
280
|
public void onFailure(int reason) {
|
|
205
|
-
Log.e(TAG, "connect onFailure
|
|
281
|
+
Log.e(TAG, "connect onFailure: " + reasonName(reason));
|
|
206
282
|
pendingConnectCall = null;
|
|
207
|
-
|
|
283
|
+
cancelConnectTimeout();
|
|
284
|
+
call.reject("Connection failed: " + reasonName(reason));
|
|
208
285
|
}
|
|
209
286
|
});
|
|
210
287
|
} catch (Exception e) {
|
|
211
288
|
Log.e(TAG, "connect exception", e);
|
|
212
289
|
pendingConnectCall = null;
|
|
290
|
+
cancelConnectTimeout();
|
|
213
291
|
call.reject("Connection exception: " + e.getMessage());
|
|
214
292
|
}
|
|
215
293
|
}
|
|
216
294
|
|
|
295
|
+
private void scheduleConnectTimeout() {
|
|
296
|
+
cancelConnectTimeout();
|
|
297
|
+
pendingConnectTimeout = () -> {
|
|
298
|
+
if (pendingConnectCall != null) {
|
|
299
|
+
Log.w(TAG, "Group formation timed out after " + CONNECT_TIMEOUT_MS + "ms");
|
|
300
|
+
pendingConnectCall.reject("Group formation timed out");
|
|
301
|
+
pendingConnectCall = null;
|
|
302
|
+
}
|
|
303
|
+
pendingConnectTimeout = null;
|
|
304
|
+
};
|
|
305
|
+
mainHandler.postDelayed(pendingConnectTimeout, CONNECT_TIMEOUT_MS);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private void cancelConnectTimeout() {
|
|
309
|
+
if (pendingConnectTimeout != null) {
|
|
310
|
+
mainHandler.removeCallbacks(pendingConnectTimeout);
|
|
311
|
+
pendingConnectTimeout = null;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private String reasonName(int reason) {
|
|
316
|
+
switch (reason) {
|
|
317
|
+
case WifiP2pManager.ERROR: return "ERROR(0)";
|
|
318
|
+
case WifiP2pManager.P2P_UNSUPPORTED: return "P2P_UNSUPPORTED(1)";
|
|
319
|
+
case WifiP2pManager.BUSY: return "BUSY(2)";
|
|
320
|
+
case WifiP2pManager.NO_SERVICE_REQUESTS: return "NO_SERVICE_REQUESTS(3)";
|
|
321
|
+
default: return String.valueOf(reason);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ── P2P 네트워크 프로세스 바인딩 ──────────────────────────────────────────
|
|
326
|
+
// ECU 서버(192.168.X.1)는 P2P 인터페이스에서만 도달 가능하지만, Android의
|
|
327
|
+
// default network는 인터넷이 가능한 일반 Wi-Fi/셀룰러로 잡히기 때문에
|
|
328
|
+
// 앱의 HTTP 요청이 P2P가 아닌 곳으로 빠져 IOException이 발생한다.
|
|
329
|
+
// P2P 트랜스포트 네트워크 가용 시점에 명시적으로 프로세스를 바인딩한다.
|
|
330
|
+
private void registerP2pNetworkCallback() {
|
|
331
|
+
if (connectivityManager == null) return;
|
|
332
|
+
if (p2pNetworkCallback != null) return;
|
|
333
|
+
|
|
334
|
+
NetworkRequest req = new NetworkRequest.Builder()
|
|
335
|
+
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_P2P)
|
|
336
|
+
// P2P는 인터넷 capability가 없으므로 매칭을 위해 제거
|
|
337
|
+
.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
|
338
|
+
.build();
|
|
339
|
+
|
|
340
|
+
p2pNetworkCallback = new ConnectivityManager.NetworkCallback() {
|
|
341
|
+
@Override
|
|
342
|
+
public void onAvailable(Network network) {
|
|
343
|
+
Log.d(TAG, "P2P network available, binding process to it");
|
|
344
|
+
boundP2pNetwork = network;
|
|
345
|
+
try {
|
|
346
|
+
connectivityManager.bindProcessToNetwork(network);
|
|
347
|
+
} catch (Exception e) {
|
|
348
|
+
Log.e(TAG, "bindProcessToNetwork failed", e);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
@Override
|
|
353
|
+
public void onLost(Network network) {
|
|
354
|
+
Log.d(TAG, "P2P network lost: " + network);
|
|
355
|
+
if (network.equals(boundP2pNetwork)) {
|
|
356
|
+
try {
|
|
357
|
+
connectivityManager.bindProcessToNetwork(null);
|
|
358
|
+
} catch (Exception e) {
|
|
359
|
+
Log.w(TAG, "bindProcessToNetwork(null) failed", e);
|
|
360
|
+
}
|
|
361
|
+
boundP2pNetwork = null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
try {
|
|
367
|
+
connectivityManager.registerNetworkCallback(req, p2pNetworkCallback);
|
|
368
|
+
Log.d(TAG, "P2P NetworkCallback registered");
|
|
369
|
+
} catch (Exception e) {
|
|
370
|
+
Log.e(TAG, "registerNetworkCallback failed", e);
|
|
371
|
+
p2pNetworkCallback = null;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private void unregisterP2pNetworkCallback() {
|
|
376
|
+
if (connectivityManager == null) return;
|
|
377
|
+
if (p2pNetworkCallback != null) {
|
|
378
|
+
try {
|
|
379
|
+
connectivityManager.unregisterNetworkCallback(p2pNetworkCallback);
|
|
380
|
+
} catch (IllegalArgumentException e) {
|
|
381
|
+
Log.w(TAG, "P2P NetworkCallback already unregistered");
|
|
382
|
+
}
|
|
383
|
+
p2pNetworkCallback = null;
|
|
384
|
+
}
|
|
385
|
+
if (boundP2pNetwork != null) {
|
|
386
|
+
try {
|
|
387
|
+
connectivityManager.bindProcessToNetwork(null);
|
|
388
|
+
} catch (Exception ignored) {}
|
|
389
|
+
boundP2pNetwork = null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// JS에서 강제로 인터넷 사용이 필요한 페이지로 갈 때 호출
|
|
394
|
+
// (예: 백엔드 API 호출 직전). 호출 후 P2P 그룹이 다시 형성되거나
|
|
395
|
+
// bindToP2p() 가 호출되면 다시 바인딩된다.
|
|
396
|
+
@PluginMethod
|
|
397
|
+
public void unbindFromP2p(PluginCall call) {
|
|
398
|
+
if (connectivityManager == null) {
|
|
399
|
+
call.reject("ConnectivityManager not initialized");
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
connectivityManager.bindProcessToNetwork(null);
|
|
404
|
+
boundP2pNetwork = null;
|
|
405
|
+
Log.d(TAG, "Process unbound from P2P network");
|
|
406
|
+
JSObject ret = new JSObject();
|
|
407
|
+
ret.put("unbound", true);
|
|
408
|
+
call.resolve(ret);
|
|
409
|
+
} catch (Exception e) {
|
|
410
|
+
call.reject("unbindFromP2p failed: " + e.getMessage());
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// JS에서 명시적으로 다시 P2P에 바인딩해야 할 때 호출.
|
|
415
|
+
// 일반적으로는 NetworkCallback이 자동 처리하므로 필요 없지만, 콜백이
|
|
416
|
+
// 늦거나 누락된 환경을 위해 제공.
|
|
417
|
+
@PluginMethod
|
|
418
|
+
public void bindToP2p(PluginCall call) {
|
|
419
|
+
if (connectivityManager == null) {
|
|
420
|
+
call.reject("ConnectivityManager not initialized");
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
Network[] networks = connectivityManager.getAllNetworks();
|
|
424
|
+
for (Network n : networks) {
|
|
425
|
+
NetworkCapabilities caps = connectivityManager.getNetworkCapabilities(n);
|
|
426
|
+
if (caps != null && caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_P2P)) {
|
|
427
|
+
try {
|
|
428
|
+
connectivityManager.bindProcessToNetwork(n);
|
|
429
|
+
boundP2pNetwork = n;
|
|
430
|
+
Log.d(TAG, "Process bound to P2P network (manual)");
|
|
431
|
+
JSObject ret = new JSObject();
|
|
432
|
+
ret.put("bound", true);
|
|
433
|
+
call.resolve(ret);
|
|
434
|
+
return;
|
|
435
|
+
} catch (Exception e) {
|
|
436
|
+
call.reject("bindProcessToNetwork failed: " + e.getMessage());
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
call.reject("No P2P network available to bind");
|
|
442
|
+
}
|
|
443
|
+
|
|
217
444
|
@PluginMethod
|
|
218
445
|
public void getGroupOwnerAddress(PluginCall call) {
|
|
219
446
|
manager.requestConnectionInfo(channel, wifiP2pInfo -> {
|