@capgo/capacitor-inappbrowser 8.15.0 → 8.15.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.
@@ -574,9 +574,7 @@ public class CapgoInAppBrowserPlugin extends Plugin implements WebViewDialog.Per
574
574
  }
575
575
 
576
576
  // Send the result to WebView and clean up
577
- webViewDialog.mFilePathCallback.onReceiveValue(results);
578
- webViewDialog.mFilePathCallback = null;
579
- webViewDialog.tempCameraUri = null;
577
+ webViewDialog.completeLegacyFileChooserResult(results);
580
578
  }
581
579
  }
582
580
 
@@ -0,0 +1,111 @@
1
+ package ee.forgr.capacitor_inappbrowser;
2
+
3
+ import android.content.Intent;
4
+ import android.webkit.MimeTypeMap;
5
+ import java.util.LinkedHashSet;
6
+ import java.util.Locale;
7
+ import java.util.function.Predicate;
8
+
9
+ /**
10
+ * Normalizes WebView file-input accept attributes into Android file-chooser intents.
11
+ */
12
+ final class FileChooserAcceptSupport {
13
+
14
+ private FileChooserAcceptSupport() {}
15
+
16
+ static String acceptEntryToMimeType(String accept) {
17
+ if (accept == null || accept.isEmpty() || accept.equals("undefined")) {
18
+ return null;
19
+ }
20
+ accept = accept.toLowerCase(Locale.ROOT);
21
+ if (accept.contains("/")) {
22
+ return accept;
23
+ }
24
+ if (!accept.startsWith(".")) {
25
+ return null;
26
+ }
27
+ return MimeTypeMap.getSingleton().getMimeTypeFromExtension(accept.substring(1).toLowerCase(Locale.ROOT));
28
+ }
29
+
30
+ static LinkedHashSet<String> normalizeAcceptTypes(String[] acceptTypes) {
31
+ LinkedHashSet<String> mimeTypes = new LinkedHashSet<>();
32
+ if (acceptTypes != null) {
33
+ for (String entry : acceptTypes) {
34
+ if (entry == null) {
35
+ continue;
36
+ }
37
+ for (String part : entry.split(",")) {
38
+ String mime = acceptEntryToMimeType(part.trim());
39
+ if (mime != null) {
40
+ mimeTypes.add(mime);
41
+ }
42
+ }
43
+ }
44
+ }
45
+ if (mimeTypes.contains("*/*")) {
46
+ mimeTypes.clear();
47
+ }
48
+ return mimeTypes;
49
+ }
50
+
51
+ static boolean isImageOnlyAcceptTypes(String[] acceptTypes) {
52
+ return matchesAllResolvedAcceptTypes(acceptTypes, (mime) -> mime.startsWith("image/"));
53
+ }
54
+
55
+ static boolean isMediaOnlyAcceptTypes(String[] acceptTypes) {
56
+ return matchesAllResolvedAcceptTypes(
57
+ acceptTypes,
58
+ (mime) -> mime.startsWith("image/") || mime.startsWith("video/") || mime.startsWith("audio/")
59
+ );
60
+ }
61
+
62
+ private static boolean matchesAllResolvedAcceptTypes(String[] acceptTypes, Predicate<String> mimePredicate) {
63
+ boolean foundMatch = false;
64
+ if (acceptTypes == null) {
65
+ return false;
66
+ }
67
+ for (String entry : acceptTypes) {
68
+ if (entry == null) {
69
+ continue;
70
+ }
71
+ for (String part : entry.split(",")) {
72
+ String token = part.trim();
73
+ if (token.isEmpty() || token.equals("undefined")) {
74
+ continue;
75
+ }
76
+ String mime = acceptEntryToMimeType(token);
77
+ if (mime == null || mime.equals("*/*")) {
78
+ return false;
79
+ }
80
+ if (!mimePredicate.test(mime)) {
81
+ return false;
82
+ }
83
+ foundMatch = true;
84
+ }
85
+ }
86
+ return foundMatch;
87
+ }
88
+
89
+ static boolean useGetContentAction(String[] acceptTypes) {
90
+ LinkedHashSet<String> mimeTypes = normalizeAcceptTypes(acceptTypes);
91
+ return isMediaOnlyAcceptTypes(acceptTypes) && mimeTypes.size() == 1;
92
+ }
93
+
94
+ static Intent createFileChooserIntent(String[] acceptTypes, boolean multiple) {
95
+ LinkedHashSet<String> mimeTypes = normalizeAcceptTypes(acceptTypes);
96
+ Intent intent = new Intent(useGetContentAction(acceptTypes) ? Intent.ACTION_GET_CONTENT : Intent.ACTION_OPEN_DOCUMENT);
97
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
98
+
99
+ if (mimeTypes.size() == 1) {
100
+ intent.setType(mimeTypes.iterator().next());
101
+ } else {
102
+ intent.setType("*/*");
103
+ if (!mimeTypes.isEmpty()) {
104
+ intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes.toArray(new String[0]));
105
+ }
106
+ }
107
+
108
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple);
109
+ return intent;
110
+ }
111
+ }
@@ -0,0 +1,53 @@
1
+ package ee.forgr.capacitor_inappbrowser;
2
+
3
+ import android.net.Uri;
4
+ import android.webkit.ValueCallback;
5
+
6
+ /**
7
+ * Request-scoped state for Android WebView file-chooser flows.
8
+ * Prevents overlapping chooser/camera requests from cross-delivering results.
9
+ */
10
+ final class FileChooserRequestSupport {
11
+
12
+ static final class FileChooserRequest {
13
+
14
+ final ValueCallback<Uri[]> callback;
15
+ final String[] acceptTypes;
16
+ final boolean multiple;
17
+ Uri tempCameraUri;
18
+
19
+ FileChooserRequest(ValueCallback<Uri[]> callback, String[] acceptTypes, boolean multiple) {
20
+ this.callback = callback;
21
+ this.acceptTypes = acceptTypes;
22
+ this.multiple = multiple;
23
+ }
24
+ }
25
+
26
+ private FileChooserRequestSupport() {}
27
+
28
+ static boolean isActive(FileChooserRequest request, FileChooserRequest active) {
29
+ return request != null && request == active;
30
+ }
31
+
32
+ static void cancel(FileChooserRequest request) {
33
+ if (request != null && request.callback != null) {
34
+ request.callback.onReceiveValue(null);
35
+ }
36
+ }
37
+
38
+ static boolean completeIfActive(FileChooserRequest request, FileChooserRequest active, Uri[] results) {
39
+ if (!isActive(request, active)) {
40
+ return false;
41
+ }
42
+ request.callback.onReceiveValue(results);
43
+ return true;
44
+ }
45
+
46
+ static boolean cancelIfActive(FileChooserRequest request, FileChooserRequest active) {
47
+ if (!isActive(request, active)) {
48
+ return false;
49
+ }
50
+ request.callback.onReceiveValue(null);
51
+ return true;
52
+ }
53
+ }
@@ -412,6 +412,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
412
412
  public static final int FILE_CHOOSER_REQUEST_CODE = 1000;
413
413
  public ValueCallback<Uri> mUploadMessage;
414
414
  public ValueCallback<Uri[]> mFilePathCallback;
415
+ FileChooserRequestSupport.FileChooserRequest activeFileChooserRequest;
415
416
  private boolean openWebViewResolved;
416
417
  private boolean isDismissing = false;
417
418
  private PermissionRequest pendingCameraLaunchPermissionRequest;
@@ -2322,21 +2323,14 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2322
2323
  ValueCallback<Uri[]> filePathCallback,
2323
2324
  FileChooserParams fileChooserParams
2324
2325
  ) {
2325
- // Get the accept type safely
2326
- String acceptType;
2327
- if (
2328
- fileChooserParams.getAcceptTypes() != null &&
2329
- fileChooserParams.getAcceptTypes().length > 0 &&
2330
- !TextUtils.isEmpty(fileChooserParams.getAcceptTypes()[0])
2331
- ) {
2332
- acceptType = fileChooserParams.getAcceptTypes()[0];
2333
- } else {
2334
- acceptType = "*/*";
2326
+ String[] acceptTypes = fileChooserParams.getAcceptTypes();
2327
+ if (acceptTypes == null || acceptTypes.length == 0) {
2328
+ acceptTypes = new String[] { "*/*" };
2335
2329
  }
2336
2330
 
2337
2331
  // DEBUG: Log details about the file chooser request
2338
2332
  Log.d("InAppBrowser", "onShowFileChooser called");
2339
- Log.d("InAppBrowser", "Accept type: " + acceptType);
2333
+ Log.d("InAppBrowser", "Accept types: " + Arrays.toString(acceptTypes));
2340
2334
  Log.d("InAppBrowser", "Current URL: " + getUrl());
2341
2335
  Log.d("InAppBrowser", "Original URL: " + (webView.getOriginalUrl() != null ? webView.getOriginalUrl() : "null"));
2342
2336
  Log.d(
@@ -2348,12 +2342,9 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2348
2342
  );
2349
2343
 
2350
2344
  // Check if the file chooser is already open
2351
- if (mFilePathCallback != null) {
2352
- mFilePathCallback.onReceiveValue(null);
2353
- mFilePathCallback = null;
2354
- }
2355
-
2356
- mFilePathCallback = filePathCallback;
2345
+ final boolean isMultiple = fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;
2346
+ beginFileChooserRequest(filePathCallback, acceptTypes, isMultiple);
2347
+ final FileChooserRequestSupport.FileChooserRequest request = activeFileChooserRequest;
2357
2348
 
2358
2349
  // Direct check for capture attribute in URL (fallback method)
2359
2350
  boolean isCaptureInUrl;
@@ -2370,8 +2361,10 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2370
2361
  isCaptureInUrl = false;
2371
2362
  }
2372
2363
 
2373
- // For image inputs, try to detect capture attribute using JavaScript
2374
- if (acceptType.equals("image/*")) {
2364
+ // For image-only inputs, try to detect capture attribute using JavaScript.
2365
+ // Mixed accept lists (e.g. "image/*,application/pdf") must skip the camera
2366
+ // path entirely — the camera can only produce images.
2367
+ if (FileChooserAcceptSupport.isImageOnlyAcceptTypes(acceptTypes)) {
2375
2368
  // Check if HTML content contains capture attribute on file inputs (synchronous check)
2376
2369
  webView.evaluateJavascript(
2377
2370
  "document.querySelector('input[type=\"file\"][capture]') !== null",
@@ -2428,12 +2421,16 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2428
2421
  """;
2429
2422
 
2430
2423
  webView.evaluateJavascript(js, (value) -> {
2424
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2425
+ return;
2426
+ }
2427
+
2431
2428
  Log.d("InAppBrowser", "Capture attribute JS result: " + value);
2432
2429
 
2433
2430
  // If we already found capture in URL, use that directly
2434
2431
  if (isCaptureInUrl) {
2435
2432
  Log.d("InAppBrowser", "Using capture from URL: " + captureMode);
2436
- launchCamera(captureMode.equals("user"));
2433
+ launchCamera(captureMode.equals("user"), request);
2437
2434
  return;
2438
2435
  }
2439
2436
 
@@ -2444,7 +2441,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2444
2441
  Log.d("InAppBrowser", "Found capture attribute: " + captureValue);
2445
2442
 
2446
2443
  if (!captureValue.isEmpty()) {
2447
- activity.runOnUiThread(() -> launchCamera(captureValue.equals("user")));
2444
+ activity.runOnUiThread(() -> launchCamera(captureValue.equals("user"), request));
2448
2445
  return;
2449
2446
  }
2450
2447
  }
@@ -2452,6 +2449,10 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2452
2449
  // Look for hints in the web page source
2453
2450
  Log.d("InAppBrowser", "Looking for camera hints in page content");
2454
2451
  webView.evaluateJavascript("(function() { return document.documentElement.innerHTML; })()", (htmlSource) -> {
2452
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2453
+ return;
2454
+ }
2455
+
2455
2456
  if (htmlSource != null && htmlSource.length() > 10) {
2456
2457
  boolean hasCameraOrSelfieKeyword =
2457
2458
  htmlSource.contains("capture=") || htmlSource.contains("camera") || htmlSource.contains("selfie");
@@ -2464,25 +2465,21 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2464
2465
  (currentUrl.contains("selfie") || currentUrl.contains("camera") || currentUrl.contains("photo"))
2465
2466
  ) {
2466
2467
  Log.d("InAppBrowser", "URL suggests camera usage, launching camera");
2467
- activity.runOnUiThread(() -> launchCamera(currentUrl.contains("selfie")));
2468
+ activity.runOnUiThread(() -> launchCamera(currentUrl.contains("selfie"), request));
2468
2469
  return;
2469
2470
  }
2470
2471
  }
2471
2472
 
2472
2473
  // If all detection methods fail, fall back to regular file picker
2473
2474
  Log.d("InAppBrowser", "No capture attribute detected, using file picker");
2474
- openFileChooser(
2475
- filePathCallback,
2476
- acceptType,
2477
- fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE
2478
- );
2475
+ openFileChooser(request);
2479
2476
  });
2480
2477
  });
2481
2478
  return true;
2482
2479
  }
2483
2480
 
2484
2481
  // For non-image types, use regular file picker
2485
- openFileChooser(filePathCallback, acceptType, fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE);
2482
+ openFileChooser(request);
2486
2483
  return true;
2487
2484
  }
2488
2485
 
@@ -2490,7 +2487,11 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2490
2487
  * Launch the camera app for capturing images
2491
2488
  * @param useFrontCamera true to use front camera, false for back camera
2492
2489
  */
2493
- private void launchCamera(boolean useFrontCamera) {
2490
+ private void launchCamera(boolean useFrontCamera, FileChooserRequestSupport.FileChooserRequest request) {
2491
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2492
+ return;
2493
+ }
2494
+
2494
2495
  Log.d("InAppBrowser", "Launching camera, front camera: " + useFrontCamera);
2495
2496
 
2496
2497
  // First check if we have camera permission
@@ -2514,8 +2515,11 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2514
2515
  Log.d("InAppBrowser", "Ignoring delayed camera permission grant during dismiss");
2515
2516
  return;
2516
2517
  }
2518
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2519
+ return;
2520
+ }
2517
2521
  // Permission granted, now launch the camera
2518
- launchCameraWithPermission(useFrontCamera);
2522
+ launchCameraWithPermission(useFrontCamera, request);
2519
2523
  }
2520
2524
 
2521
2525
  @Override
@@ -2525,9 +2529,12 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2525
2529
  Log.d("InAppBrowser", "Ignoring delayed camera permission denial during dismiss");
2526
2530
  return;
2527
2531
  }
2532
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2533
+ return;
2534
+ }
2528
2535
  // Permission denied, fall back to file picker
2529
2536
  Log.e("InAppBrowser", "Camera permission denied, falling back to file picker");
2530
- fallbackToFilePicker();
2537
+ fallbackToFilePicker(request);
2531
2538
  }
2532
2539
  };
2533
2540
 
@@ -2539,13 +2546,17 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2539
2546
  }
2540
2547
 
2541
2548
  // If we can't request permission, try launching directly
2542
- launchCameraWithPermission(useFrontCamera);
2549
+ launchCameraWithPermission(useFrontCamera, request);
2543
2550
  }
2544
2551
 
2545
2552
  /**
2546
2553
  * Launch camera after permission is granted
2547
2554
  */
2548
- private void launchCameraWithPermission(boolean useFrontCamera) {
2555
+ private void launchCameraWithPermission(boolean useFrontCamera, FileChooserRequestSupport.FileChooserRequest request) {
2556
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2557
+ return;
2558
+ }
2559
+
2549
2560
  try {
2550
2561
  Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
2551
2562
  if (takePictureIntent.resolveActivity(activity.getPackageManager()) != null) {
@@ -2554,17 +2565,18 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2554
2565
  photoFile = createImageFile();
2555
2566
  } catch (IOException ex) {
2556
2567
  Log.e("InAppBrowser", "Error creating image file", ex);
2557
- fallbackToFilePicker();
2568
+ fallbackToFilePicker(request);
2558
2569
  return;
2559
2570
  }
2560
2571
 
2561
2572
  if (photoFile != null) {
2562
- tempCameraUri = FileProvider.getUriForFile(
2573
+ request.tempCameraUri = FileProvider.getUriForFile(
2563
2574
  activity,
2564
2575
  activity.getPackageName() + ".fileprovider",
2565
2576
  photoFile
2566
2577
  );
2567
- takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, tempCameraUri);
2578
+ syncFileChooserPublicFields();
2579
+ takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, request.tempCameraUri);
2568
2580
 
2569
2581
  if (useFrontCamera) {
2570
2582
  takePictureIntent.putExtra("android.intent.extras.CAMERA_FACING", 1);
@@ -2580,15 +2592,23 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2580
2592
  "camera_capture",
2581
2593
  new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
2582
2594
  (result) -> {
2583
- if (result.getResultCode() == Activity.RESULT_OK) {
2584
- if (tempCameraUri != null) {
2585
- mFilePathCallback.onReceiveValue(new Uri[] { tempCameraUri });
2586
- }
2587
- } else {
2588
- mFilePathCallback.onReceiveValue(null);
2595
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2596
+ return;
2597
+ }
2598
+ Uri[] results = null;
2599
+ if (result.getResultCode() == Activity.RESULT_OK && request.tempCameraUri != null) {
2600
+ results = new Uri[] { request.tempCameraUri };
2601
+ }
2602
+ if (
2603
+ FileChooserRequestSupport.completeIfActive(
2604
+ request,
2605
+ activeFileChooserRequest,
2606
+ results
2607
+ )
2608
+ ) {
2609
+ request.tempCameraUri = null;
2610
+ clearActiveFileChooserRequest(request);
2589
2611
  }
2590
- mFilePathCallback = null;
2591
- tempCameraUri = null;
2592
2612
  }
2593
2613
  )
2594
2614
  .launch(takePictureIntent);
@@ -2598,26 +2618,27 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2598
2618
  }
2599
2619
  } catch (SecurityException e) {
2600
2620
  Log.e("InAppBrowser", "Security exception launching camera: " + e.getMessage(), e);
2601
- fallbackToFilePicker();
2621
+ fallbackToFilePicker(request);
2602
2622
  }
2603
2623
  } else {
2604
2624
  Log.e("InAppBrowser", "Failed to create photo URI, falling back to file picker");
2605
- fallbackToFilePicker();
2625
+ fallbackToFilePicker(request);
2606
2626
  }
2607
2627
  }
2608
2628
  } catch (Exception e) {
2609
2629
  Log.e("InAppBrowser", "Camera launch failed: " + e.getMessage(), e);
2610
- fallbackToFilePicker();
2630
+ fallbackToFilePicker(request);
2611
2631
  }
2612
2632
  }
2613
2633
 
2614
2634
  /**
2615
2635
  * Fall back to file picker when camera launch fails
2616
2636
  */
2617
- private void fallbackToFilePicker() {
2618
- if (mFilePathCallback != null) {
2619
- openFileChooser(mFilePathCallback, "image/*", false);
2637
+ private void fallbackToFilePicker(FileChooserRequestSupport.FileChooserRequest request) {
2638
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
2639
+ return;
2620
2640
  }
2641
+ openFileChooser(request);
2621
2642
  }
2622
2643
 
2623
2644
  // Grant permissions for cam
@@ -4065,45 +4086,45 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4065
4086
  }
4066
4087
  }
4067
4088
 
4068
- private void openFileChooser(ValueCallback<Uri[]> filePathCallback, String acceptType, boolean isMultiple) {
4069
- mFilePathCallback = filePathCallback;
4070
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
4071
- intent.addCategory(Intent.CATEGORY_OPENABLE);
4089
+ private void beginFileChooserRequest(ValueCallback<Uri[]> filePathCallback, String[] acceptTypes, boolean isMultiple) {
4090
+ if (activeFileChooserRequest != null) {
4091
+ FileChooserRequestSupport.cancel(activeFileChooserRequest);
4092
+ }
4093
+ activeFileChooserRequest = new FileChooserRequestSupport.FileChooserRequest(filePathCallback, acceptTypes, isMultiple);
4094
+ syncFileChooserPublicFields();
4095
+ }
4072
4096
 
4073
- // Fix MIME type handling
4074
- if (acceptType == null || acceptType.isEmpty() || acceptType.equals("undefined")) {
4075
- acceptType = "*/*";
4076
- } else {
4077
- // Handle common web input accept types
4078
- if (acceptType.equals("image/*")) {
4079
- // Keep as is - image/*
4080
- } else if (acceptType.contains("image/")) {
4081
- // Specific image type requested but keep it general for better compatibility
4082
- acceptType = "image/*";
4083
- } else if (acceptType.equals("audio/*") || acceptType.contains("audio/")) {
4084
- acceptType = "audio/*";
4085
- } else if (acceptType.equals("video/*") || acceptType.contains("video/")) {
4086
- acceptType = "video/*";
4087
- } else if (acceptType.startsWith(".") || acceptType.contains(",")) {
4088
- // Handle file extensions like ".pdf, .docx" by using a general mime type
4089
- if (acceptType.contains(".pdf")) {
4090
- acceptType = "application/pdf";
4091
- } else if (acceptType.contains(".doc") || acceptType.contains(".docx")) {
4092
- acceptType = "application/msword";
4093
- } else if (acceptType.contains(".xls") || acceptType.contains(".xlsx")) {
4094
- acceptType = "application/vnd.ms-excel";
4095
- } else if (acceptType.contains(".txt") || acceptType.contains(".text")) {
4096
- acceptType = "text/plain";
4097
- } else {
4098
- // Default for extension lists
4099
- acceptType = "*/*";
4100
- }
4101
- }
4097
+ private void clearActiveFileChooserRequest(FileChooserRequestSupport.FileChooserRequest request) {
4098
+ if (activeFileChooserRequest == request) {
4099
+ activeFileChooserRequest = null;
4100
+ syncFileChooserPublicFields();
4102
4101
  }
4102
+ }
4103
4103
 
4104
- Log.d("InAppBrowser", "File picker using MIME type: " + acceptType);
4105
- intent.setType(acceptType);
4106
- intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, isMultiple);
4104
+ private void syncFileChooserPublicFields() {
4105
+ mFilePathCallback = activeFileChooserRequest != null ? activeFileChooserRequest.callback : null;
4106
+ tempCameraUri = activeFileChooserRequest != null ? activeFileChooserRequest.tempCameraUri : null;
4107
+ }
4108
+
4109
+ void completeLegacyFileChooserResult(Uri[] results) {
4110
+ FileChooserRequestSupport.FileChooserRequest request = activeFileChooserRequest;
4111
+ if (request == null) {
4112
+ return;
4113
+ }
4114
+ if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4115
+ request.tempCameraUri = null;
4116
+ clearActiveFileChooserRequest(request);
4117
+ }
4118
+ }
4119
+
4120
+ private void openFileChooser(FileChooserRequestSupport.FileChooserRequest request) {
4121
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4122
+ return;
4123
+ }
4124
+ java.util.LinkedHashSet<String> mimeTypes = FileChooserAcceptSupport.normalizeAcceptTypes(request.acceptTypes);
4125
+ Intent intent = FileChooserAcceptSupport.createFileChooserIntent(request.acceptTypes, request.multiple);
4126
+
4127
+ Log.d("InAppBrowser", "File picker using action: " + intent.getAction() + ", MIME types: " + mimeTypes);
4107
4128
 
4108
4129
  try {
4109
4130
  if (activity instanceof androidx.activity.ComponentActivity) {
@@ -4114,26 +4135,29 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4114
4135
  "file_chooser",
4115
4136
  new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
4116
4137
  (result) -> {
4138
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4139
+ return;
4140
+ }
4141
+ Uri[] results = null;
4117
4142
  if (result.getResultCode() == Activity.RESULT_OK) {
4118
4143
  Intent data = result.getData();
4119
4144
  if (data != null) {
4120
4145
  if (data.getClipData() != null) {
4121
4146
  // Handle multiple files
4122
4147
  int count = data.getClipData().getItemCount();
4123
- Uri[] results = new Uri[count];
4148
+ results = new Uri[count];
4124
4149
  for (int i = 0; i < count; i++) {
4125
4150
  results[i] = data.getClipData().getItemAt(i).getUri();
4126
4151
  }
4127
- mFilePathCallback.onReceiveValue(results);
4128
4152
  } else if (data.getData() != null) {
4129
4153
  // Handle single file
4130
- mFilePathCallback.onReceiveValue(new Uri[] { data.getData() });
4154
+ results = new Uri[] { data.getData() };
4131
4155
  }
4132
4156
  }
4133
- } else {
4134
- mFilePathCallback.onReceiveValue(null);
4135
4157
  }
4136
- mFilePathCallback = null;
4158
+ if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4159
+ clearActiveFileChooserRequest(request);
4160
+ }
4137
4161
  }
4138
4162
  )
4139
4163
  .launch(Intent.createChooser(intent, "Select File"));
@@ -4143,8 +4167,9 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4143
4167
  }
4144
4168
  } catch (ActivityNotFoundException e) {
4145
4169
  // If no app can handle the specific MIME type, try with a more generic one
4146
- Log.e("InAppBrowser", "No app available for type: " + acceptType + ", trying with */*");
4170
+ Log.e("InAppBrowser", "No app available for types: " + mimeTypes + ", trying with */*");
4147
4171
  intent.setType("*/*");
4172
+ intent.removeExtra(Intent.EXTRA_MIME_TYPES);
4148
4173
  try {
4149
4174
  if (activity instanceof androidx.activity.ComponentActivity) {
4150
4175
  androidx.activity.ComponentActivity componentActivity = (androidx.activity.ComponentActivity) activity;
@@ -4154,26 +4179,29 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4154
4179
  "file_chooser",
4155
4180
  new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
4156
4181
  (result) -> {
4182
+ if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4183
+ return;
4184
+ }
4185
+ Uri[] results = null;
4157
4186
  if (result.getResultCode() == Activity.RESULT_OK) {
4158
4187
  Intent data = result.getData();
4159
4188
  if (data != null) {
4160
4189
  if (data.getClipData() != null) {
4161
4190
  // Handle multiple files
4162
4191
  int count = data.getClipData().getItemCount();
4163
- Uri[] results = new Uri[count];
4192
+ results = new Uri[count];
4164
4193
  for (int i = 0; i < count; i++) {
4165
4194
  results[i] = data.getClipData().getItemAt(i).getUri();
4166
4195
  }
4167
- mFilePathCallback.onReceiveValue(results);
4168
4196
  } else if (data.getData() != null) {
4169
4197
  // Handle single file
4170
- mFilePathCallback.onReceiveValue(new Uri[] { data.getData() });
4198
+ results = new Uri[] { data.getData() };
4171
4199
  }
4172
4200
  }
4173
- } else {
4174
- mFilePathCallback.onReceiveValue(null);
4175
4201
  }
4176
- mFilePathCallback = null;
4202
+ if (FileChooserRequestSupport.completeIfActive(request, activeFileChooserRequest, results)) {
4203
+ clearActiveFileChooserRequest(request);
4204
+ }
4177
4205
  }
4178
4206
  )
4179
4207
  .launch(Intent.createChooser(intent, "Select File"));
@@ -4184,9 +4212,8 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4184
4212
  } catch (ActivityNotFoundException ex) {
4185
4213
  // If still failing, report error
4186
4214
  Log.e("InAppBrowser", "No app can handle file picker", ex);
4187
- if (mFilePathCallback != null) {
4188
- mFilePathCallback.onReceiveValue(null);
4189
- mFilePathCallback = null;
4215
+ if (FileChooserRequestSupport.cancelIfActive(request, activeFileChooserRequest)) {
4216
+ clearActiveFileChooserRequest(request);
4190
4217
  }
4191
4218
  }
4192
4219
  }
@@ -6258,11 +6285,11 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
6258
6285
  }
6259
6286
 
6260
6287
  // Clear any pending callbacks to prevent memory leaks
6261
- if (mFilePathCallback != null) {
6262
- mFilePathCallback.onReceiveValue(null);
6263
- mFilePathCallback = null;
6288
+ if (activeFileChooserRequest != null) {
6289
+ FileChooserRequestSupport.cancel(activeFileChooserRequest);
6290
+ activeFileChooserRequest = null;
6291
+ syncFileChooserPublicFields();
6264
6292
  }
6265
- tempCameraUri = null;
6266
6293
 
6267
6294
  // Clear file inputs for security/privacy before destroying WebView
6268
6295
  try {
@@ -415,7 +415,7 @@ public class CapgoInAppBrowserPlugin: CAPPlugin, CAPBridgedPlugin {
415
415
  case aware = "AWARE"
416
416
  case fakeVisible = "FAKE_VISIBLE"
417
417
  }
418
- private let pluginVersion: String = "8.15.0"
418
+ private let pluginVersion: String = "8.15.2"
419
419
  public let identifier = "CapgoInAppBrowserPlugin"
420
420
  public let jsName = "CapgoInAppBrowser"
421
421
  public let pluginMethods: [CAPPluginMethod] = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-inappbrowser",
3
- "version": "8.15.0",
3
+ "version": "8.15.2",
4
4
  "description": "Capacitor plugin in app browser",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",