@capgo/capacitor-inappbrowser 8.15.1 → 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.
@@ -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
+ }
@@ -11,18 +11,14 @@ final class FileChooserRequestSupport {
11
11
 
12
12
  static final class FileChooserRequest {
13
13
 
14
- private static long nextId = 0;
15
-
16
- final long id;
17
14
  final ValueCallback<Uri[]> callback;
18
- final String acceptType;
15
+ final String[] acceptTypes;
19
16
  final boolean multiple;
20
17
  Uri tempCameraUri;
21
18
 
22
- FileChooserRequest(ValueCallback<Uri[]> callback, String acceptType, boolean multiple) {
23
- this.id = ++nextId;
19
+ FileChooserRequest(ValueCallback<Uri[]> callback, String[] acceptTypes, boolean multiple) {
24
20
  this.callback = callback;
25
- this.acceptType = acceptType;
21
+ this.acceptTypes = acceptTypes;
26
22
  this.multiple = multiple;
27
23
  }
28
24
  }
@@ -2323,21 +2323,14 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2323
2323
  ValueCallback<Uri[]> filePathCallback,
2324
2324
  FileChooserParams fileChooserParams
2325
2325
  ) {
2326
- // Get the accept type safely
2327
- String acceptType;
2328
- if (
2329
- fileChooserParams.getAcceptTypes() != null &&
2330
- fileChooserParams.getAcceptTypes().length > 0 &&
2331
- !TextUtils.isEmpty(fileChooserParams.getAcceptTypes()[0])
2332
- ) {
2333
- acceptType = fileChooserParams.getAcceptTypes()[0];
2334
- } else {
2335
- acceptType = "*/*";
2326
+ String[] acceptTypes = fileChooserParams.getAcceptTypes();
2327
+ if (acceptTypes == null || acceptTypes.length == 0) {
2328
+ acceptTypes = new String[] { "*/*" };
2336
2329
  }
2337
2330
 
2338
2331
  // DEBUG: Log details about the file chooser request
2339
2332
  Log.d("InAppBrowser", "onShowFileChooser called");
2340
- Log.d("InAppBrowser", "Accept type: " + acceptType);
2333
+ Log.d("InAppBrowser", "Accept types: " + Arrays.toString(acceptTypes));
2341
2334
  Log.d("InAppBrowser", "Current URL: " + getUrl());
2342
2335
  Log.d("InAppBrowser", "Original URL: " + (webView.getOriginalUrl() != null ? webView.getOriginalUrl() : "null"));
2343
2336
  Log.d(
@@ -2350,7 +2343,7 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2350
2343
 
2351
2344
  // Check if the file chooser is already open
2352
2345
  final boolean isMultiple = fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE;
2353
- beginFileChooserRequest(filePathCallback, acceptType, isMultiple);
2346
+ beginFileChooserRequest(filePathCallback, acceptTypes, isMultiple);
2354
2347
  final FileChooserRequestSupport.FileChooserRequest request = activeFileChooserRequest;
2355
2348
 
2356
2349
  // Direct check for capture attribute in URL (fallback method)
@@ -2368,8 +2361,10 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
2368
2361
  isCaptureInUrl = false;
2369
2362
  }
2370
2363
 
2371
- // For image inputs, try to detect capture attribute using JavaScript
2372
- 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)) {
2373
2368
  // Check if HTML content contains capture attribute on file inputs (synchronous check)
2374
2369
  webView.evaluateJavascript(
2375
2370
  "document.querySelector('input[type=\"file\"][capture]') !== null",
@@ -4091,11 +4086,11 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4091
4086
  }
4092
4087
  }
4093
4088
 
4094
- private void beginFileChooserRequest(ValueCallback<Uri[]> filePathCallback, String acceptType, boolean isMultiple) {
4089
+ private void beginFileChooserRequest(ValueCallback<Uri[]> filePathCallback, String[] acceptTypes, boolean isMultiple) {
4095
4090
  if (activeFileChooserRequest != null) {
4096
4091
  FileChooserRequestSupport.cancel(activeFileChooserRequest);
4097
4092
  }
4098
- activeFileChooserRequest = new FileChooserRequestSupport.FileChooserRequest(filePathCallback, acceptType, isMultiple);
4093
+ activeFileChooserRequest = new FileChooserRequestSupport.FileChooserRequest(filePathCallback, acceptTypes, isMultiple);
4099
4094
  syncFileChooserPublicFields();
4100
4095
  }
4101
4096
 
@@ -4126,45 +4121,10 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4126
4121
  if (!FileChooserRequestSupport.isActive(request, activeFileChooserRequest)) {
4127
4122
  return;
4128
4123
  }
4129
- String acceptType = request.acceptType;
4130
- boolean isMultiple = request.multiple;
4131
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
4132
- intent.addCategory(Intent.CATEGORY_OPENABLE);
4133
-
4134
- // Fix MIME type handling
4135
- if (acceptType == null || acceptType.isEmpty() || acceptType.equals("undefined")) {
4136
- acceptType = "*/*";
4137
- } else {
4138
- // Handle common web input accept types
4139
- if (acceptType.equals("image/*")) {
4140
- // Keep as is - image/*
4141
- } else if (acceptType.contains("image/")) {
4142
- // Specific image type requested but keep it general for better compatibility
4143
- acceptType = "image/*";
4144
- } else if (acceptType.equals("audio/*") || acceptType.contains("audio/")) {
4145
- acceptType = "audio/*";
4146
- } else if (acceptType.equals("video/*") || acceptType.contains("video/")) {
4147
- acceptType = "video/*";
4148
- } else if (acceptType.startsWith(".") || acceptType.contains(",")) {
4149
- // Handle file extensions like ".pdf, .docx" by using a general mime type
4150
- if (acceptType.contains(".pdf")) {
4151
- acceptType = "application/pdf";
4152
- } else if (acceptType.contains(".doc") || acceptType.contains(".docx")) {
4153
- acceptType = "application/msword";
4154
- } else if (acceptType.contains(".xls") || acceptType.contains(".xlsx")) {
4155
- acceptType = "application/vnd.ms-excel";
4156
- } else if (acceptType.contains(".txt") || acceptType.contains(".text")) {
4157
- acceptType = "text/plain";
4158
- } else {
4159
- // Default for extension lists
4160
- acceptType = "*/*";
4161
- }
4162
- }
4163
- }
4124
+ java.util.LinkedHashSet<String> mimeTypes = FileChooserAcceptSupport.normalizeAcceptTypes(request.acceptTypes);
4125
+ Intent intent = FileChooserAcceptSupport.createFileChooserIntent(request.acceptTypes, request.multiple);
4164
4126
 
4165
- Log.d("InAppBrowser", "File picker using MIME type: " + acceptType);
4166
- intent.setType(acceptType);
4167
- intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, isMultiple);
4127
+ Log.d("InAppBrowser", "File picker using action: " + intent.getAction() + ", MIME types: " + mimeTypes);
4168
4128
 
4169
4129
  try {
4170
4130
  if (activity instanceof androidx.activity.ComponentActivity) {
@@ -4207,8 +4167,9 @@ public class WebViewDialog extends Dialog implements ProxyResponseRouting.ProxyR
4207
4167
  }
4208
4168
  } catch (ActivityNotFoundException e) {
4209
4169
  // If no app can handle the specific MIME type, try with a more generic one
4210
- Log.e("InAppBrowser", "No app available for type: " + acceptType + ", trying with */*");
4170
+ Log.e("InAppBrowser", "No app available for types: " + mimeTypes + ", trying with */*");
4211
4171
  intent.setType("*/*");
4172
+ intent.removeExtra(Intent.EXTRA_MIME_TYPES);
4212
4173
  try {
4213
4174
  if (activity instanceof androidx.activity.ComponentActivity) {
4214
4175
  androidx.activity.ComponentActivity componentActivity = (androidx.activity.ComponentActivity) activity;
@@ -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.1"
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.1",
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",