@capgo/inappbrowser 7.6.4 → 7.6.7

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.
@@ -19,6 +19,8 @@ import android.graphics.PorterDuffColorFilter;
19
19
  import android.net.Uri;
20
20
  import android.net.http.SslError;
21
21
  import android.os.Build;
22
+ import android.os.Environment;
23
+ import android.provider.MediaStore;
22
24
  import android.text.TextUtils;
23
25
  import android.util.Base64;
24
26
  import android.util.Log;
@@ -43,6 +45,9 @@ import android.widget.ImageView;
43
45
  import android.widget.TextView;
44
46
  import android.widget.Toast;
45
47
  import android.widget.Toolbar;
48
+ import androidx.activity.result.ActivityResult;
49
+ import androidx.activity.result.contract.ActivityResultContracts;
50
+ import androidx.core.content.FileProvider;
46
51
  import androidx.core.graphics.Insets;
47
52
  import androidx.core.view.ViewCompat;
48
53
  import androidx.core.view.WindowInsetsCompat;
@@ -51,6 +56,7 @@ import com.caverock.androidsvg.SVG;
51
56
  import com.caverock.androidsvg.SVGParseException;
52
57
  import com.getcapacitor.JSObject;
53
58
  import java.io.ByteArrayInputStream;
59
+ import java.io.File;
54
60
  import java.io.IOException;
55
61
  import java.io.InputStream;
56
62
  import java.net.URI;
@@ -110,6 +116,9 @@ public class WebViewDialog extends Dialog {
110
116
  public ValueCallback<Uri> mUploadMessage;
111
117
  public ValueCallback<Uri[]> mFilePathCallback;
112
118
 
119
+ // Temporary URI for storing camera capture
120
+ public Uri tempCameraUri;
121
+
113
122
  public interface PermissionHandler {
114
123
  void handleCameraPermissionRequest(PermissionRequest request);
115
124
 
@@ -336,21 +345,206 @@ public class WebViewDialog extends Dialog {
336
345
  FileChooserParams fileChooserParams
337
346
  ) {
338
347
  // Get the accept type safely
339
- String acceptType = "*/*"; // Default to all file types
348
+ String acceptType;
340
349
  if (
341
350
  fileChooserParams.getAcceptTypes() != null &&
342
351
  fileChooserParams.getAcceptTypes().length > 0 &&
343
352
  !TextUtils.isEmpty(fileChooserParams.getAcceptTypes()[0])
344
353
  ) {
345
354
  acceptType = fileChooserParams.getAcceptTypes()[0];
355
+ } else {
356
+ acceptType = "*/*";
346
357
  }
347
358
 
359
+ // DEBUG: Log details about the file chooser request
360
+ Log.d("InAppBrowser", "onShowFileChooser called");
361
+ Log.d("InAppBrowser", "Accept type: " + acceptType);
362
+ Log.d(
363
+ "InAppBrowser",
364
+ "Current URL: " +
365
+ (webView.getUrl() != null ? webView.getUrl() : "null")
366
+ );
367
+ Log.d(
368
+ "InAppBrowser",
369
+ "Original URL: " +
370
+ (webView.getOriginalUrl() != null
371
+ ? webView.getOriginalUrl()
372
+ : "null")
373
+ );
374
+ Log.d(
375
+ "InAppBrowser",
376
+ "Has camera permission: " +
377
+ (activity != null &&
378
+ activity.checkSelfPermission(
379
+ android.Manifest.permission.CAMERA
380
+ ) ==
381
+ android.content.pm.PackageManager.PERMISSION_GRANTED)
382
+ );
383
+
348
384
  // Check if the file chooser is already open
349
385
  if (mFilePathCallback != null) {
350
386
  mFilePathCallback.onReceiveValue(null);
351
387
  mFilePathCallback = null;
352
388
  }
353
389
 
390
+ mFilePathCallback = filePathCallback;
391
+
392
+ // Direct check for capture attribute in URL (fallback method)
393
+ boolean isCaptureInUrl;
394
+ String captureMode;
395
+ String currentUrl = webView.getUrl();
396
+
397
+ // Look for capture in URL parameters - sometimes the attribute shows up in URL
398
+ if (currentUrl != null && currentUrl.contains("capture=")) {
399
+ isCaptureInUrl = true;
400
+ captureMode = currentUrl.contains("capture=user")
401
+ ? "user"
402
+ : "environment";
403
+ Log.d("InAppBrowser", "Found capture in URL: " + captureMode);
404
+ } else {
405
+ captureMode = null;
406
+ isCaptureInUrl = false;
407
+ }
408
+
409
+ // For image inputs, try to detect capture attribute using JavaScript
410
+ if (acceptType.equals("image/*")) {
411
+ // Check if HTML content contains capture attribute on file inputs (synchronous check)
412
+ webView.evaluateJavascript(
413
+ "document.querySelector('input[type=\"file\"][capture]') !== null",
414
+ hasCaptureValue -> {
415
+ Log.d(
416
+ "InAppBrowser",
417
+ "Quick capture check: " + hasCaptureValue
418
+ );
419
+ if (Boolean.parseBoolean(hasCaptureValue.replace("\"", ""))) {
420
+ Log.d(
421
+ "InAppBrowser",
422
+ "Found capture attribute in quick check"
423
+ );
424
+ }
425
+ }
426
+ );
427
+
428
+ // Fixed JavaScript with proper error handling
429
+ String js =
430
+ "try {" +
431
+ " (function() {" +
432
+ " var captureAttr = null;" +
433
+ " // Check active element first" +
434
+ " if (document.activeElement && " +
435
+ " document.activeElement.tagName === 'INPUT' && " +
436
+ " document.activeElement.type === 'file') {" +
437
+ " if (document.activeElement.hasAttribute('capture')) {" +
438
+ " captureAttr = document.activeElement.getAttribute('capture') || 'environment';" +
439
+ " return captureAttr;" +
440
+ " }" +
441
+ " }" +
442
+ " // Try to find any input with capture attribute" +
443
+ " var inputs = document.querySelectorAll('input[type=\"file\"][capture]');" +
444
+ " if (inputs && inputs.length > 0) {" +
445
+ " captureAttr = inputs[0].getAttribute('capture') || 'environment';" +
446
+ " return captureAttr;" +
447
+ " }" +
448
+ " // Try to extract from HTML attributes" +
449
+ " var allInputs = document.getElementsByTagName('input');" +
450
+ " for (var i = 0; i < allInputs.length; i++) {" +
451
+ " var input = allInputs[i];" +
452
+ " if (input.type === 'file') {" +
453
+ " if (input.hasAttribute('capture')) {" +
454
+ " captureAttr = input.getAttribute('capture') || 'environment';" +
455
+ " return captureAttr;" +
456
+ " }" +
457
+ " // Look for the accept attribute containing image/* as this might be a camera input" +
458
+ " var acceptAttr = input.getAttribute('accept');" +
459
+ " if (acceptAttr && acceptAttr.indexOf('image/*') >= 0) {" +
460
+ " console.log('Found input with image/* accept');" +
461
+ " }" +
462
+ " }" +
463
+ " }" +
464
+ " return '';" +
465
+ " })();" +
466
+ "} catch(e) { console.error('Capture detection error:', e); return ''; }";
467
+
468
+ webView.evaluateJavascript(js, value -> {
469
+ Log.d("InAppBrowser", "Capture attribute JS result: " + value);
470
+
471
+ // If we already found capture in URL, use that directly
472
+ if (isCaptureInUrl) {
473
+ Log.d("InAppBrowser", "Using capture from URL: " + captureMode);
474
+ launchCamera(captureMode.equals("user"));
475
+ return;
476
+ }
477
+
478
+ // Process JavaScript result
479
+ if (value != null && value.length() > 2) {
480
+ // Clean up the value (remove quotes)
481
+ String captureValue = value.replace("\"", "");
482
+ Log.d(
483
+ "InAppBrowser",
484
+ "Found capture attribute: " + captureValue
485
+ );
486
+
487
+ if (!captureValue.isEmpty()) {
488
+ activity.runOnUiThread(() ->
489
+ launchCamera(captureValue.equals("user"))
490
+ );
491
+ return;
492
+ }
493
+ }
494
+
495
+ // Look for hints in the web page source
496
+ Log.d("InAppBrowser", "Looking for camera hints in page content");
497
+ webView.evaluateJavascript(
498
+ "(function() { return document.documentElement.innerHTML; })()",
499
+ htmlSource -> {
500
+ if (htmlSource != null && htmlSource.length() > 10) {
501
+ boolean hasCameraOrSelfieKeyword =
502
+ htmlSource.contains("capture=") ||
503
+ htmlSource.contains("camera") ||
504
+ htmlSource.contains("selfie");
505
+
506
+ Log.d(
507
+ "InAppBrowser",
508
+ "Page contains camera keywords: " +
509
+ hasCameraOrSelfieKeyword
510
+ );
511
+
512
+ if (
513
+ hasCameraOrSelfieKeyword &&
514
+ currentUrl != null &&
515
+ (currentUrl.contains("selfie") ||
516
+ currentUrl.contains("camera") ||
517
+ currentUrl.contains("photo"))
518
+ ) {
519
+ Log.d(
520
+ "InAppBrowser",
521
+ "URL suggests camera usage, launching camera"
522
+ );
523
+ activity.runOnUiThread(() ->
524
+ launchCamera(currentUrl.contains("selfie"))
525
+ );
526
+ return;
527
+ }
528
+ }
529
+
530
+ // If all detection methods fail, fall back to regular file picker
531
+ Log.d(
532
+ "InAppBrowser",
533
+ "No capture attribute detected, using file picker"
534
+ );
535
+ openFileChooser(
536
+ filePathCallback,
537
+ acceptType,
538
+ fileChooserParams.getMode() ==
539
+ FileChooserParams.MODE_OPEN_MULTIPLE
540
+ );
541
+ }
542
+ );
543
+ });
544
+ return true;
545
+ }
546
+
547
+ // For non-image types, use regular file picker
354
548
  openFileChooser(
355
549
  filePathCallback,
356
550
  acceptType,
@@ -359,6 +553,160 @@ public class WebViewDialog extends Dialog {
359
553
  return true;
360
554
  }
361
555
 
556
+ /**
557
+ * Launch the camera app for capturing images
558
+ * @param useFrontCamera true to use front camera, false for back camera
559
+ */
560
+ private void launchCamera(boolean useFrontCamera) {
561
+ Log.d(
562
+ "InAppBrowser",
563
+ "Launching camera, front camera: " + useFrontCamera
564
+ );
565
+
566
+ // First check if we have camera permission
567
+ if (activity != null && permissionHandler != null) {
568
+ // Create a temporary permission request to check camera permission
569
+ android.webkit.PermissionRequest tempRequest =
570
+ new android.webkit.PermissionRequest() {
571
+ @Override
572
+ public Uri getOrigin() {
573
+ return Uri.parse("file:///android_asset/");
574
+ }
575
+
576
+ @Override
577
+ public String[] getResources() {
578
+ return new String[] {
579
+ PermissionRequest.RESOURCE_VIDEO_CAPTURE,
580
+ };
581
+ }
582
+
583
+ @Override
584
+ public void grant(String[] resources) {
585
+ // Permission granted, now launch the camera
586
+ launchCameraWithPermission(useFrontCamera);
587
+ }
588
+
589
+ @Override
590
+ public void deny() {
591
+ // Permission denied, fall back to file picker
592
+ Log.e(
593
+ "InAppBrowser",
594
+ "Camera permission denied, falling back to file picker"
595
+ );
596
+ fallbackToFilePicker();
597
+ }
598
+ };
599
+
600
+ // Request camera permission through the plugin
601
+ permissionHandler.handleCameraPermissionRequest(tempRequest);
602
+ return;
603
+ }
604
+
605
+ // If we can't request permission, try launching directly
606
+ launchCameraWithPermission(useFrontCamera);
607
+ }
608
+
609
+ /**
610
+ * Launch camera after permission is granted
611
+ */
612
+ private void launchCameraWithPermission(boolean useFrontCamera) {
613
+ try {
614
+ Intent takePictureIntent = new Intent(
615
+ MediaStore.ACTION_IMAGE_CAPTURE
616
+ );
617
+ if (
618
+ takePictureIntent.resolveActivity(activity.getPackageManager()) !=
619
+ null
620
+ ) {
621
+ File photoFile = null;
622
+ try {
623
+ photoFile = createImageFile();
624
+ } catch (IOException ex) {
625
+ Log.e("InAppBrowser", "Error creating image file", ex);
626
+ fallbackToFilePicker();
627
+ return;
628
+ }
629
+
630
+ if (photoFile != null) {
631
+ tempCameraUri = FileProvider.getUriForFile(
632
+ activity,
633
+ activity.getPackageName() + ".fileprovider",
634
+ photoFile
635
+ );
636
+ takePictureIntent.putExtra(
637
+ MediaStore.EXTRA_OUTPUT,
638
+ tempCameraUri
639
+ );
640
+
641
+ if (useFrontCamera) {
642
+ takePictureIntent.putExtra(
643
+ "android.intent.extras.CAMERA_FACING",
644
+ 1
645
+ );
646
+ }
647
+
648
+ try {
649
+ if (activity instanceof androidx.activity.ComponentActivity) {
650
+ androidx.activity.ComponentActivity componentActivity =
651
+ (androidx.activity.ComponentActivity) activity;
652
+ componentActivity
653
+ .getActivityResultRegistry()
654
+ .register(
655
+ "camera_capture",
656
+ new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
657
+ result -> {
658
+ if (result.getResultCode() == Activity.RESULT_OK) {
659
+ if (tempCameraUri != null) {
660
+ mFilePathCallback.onReceiveValue(
661
+ new Uri[] { tempCameraUri }
662
+ );
663
+ }
664
+ } else {
665
+ mFilePathCallback.onReceiveValue(null);
666
+ }
667
+ mFilePathCallback = null;
668
+ tempCameraUri = null;
669
+ }
670
+ )
671
+ .launch(takePictureIntent);
672
+ } else {
673
+ // Fallback for non-ComponentActivity
674
+ activity.startActivityForResult(
675
+ takePictureIntent,
676
+ FILE_CHOOSER_REQUEST_CODE
677
+ );
678
+ }
679
+ } catch (SecurityException e) {
680
+ Log.e(
681
+ "InAppBrowser",
682
+ "Security exception launching camera: " + e.getMessage(),
683
+ e
684
+ );
685
+ fallbackToFilePicker();
686
+ }
687
+ } else {
688
+ Log.e(
689
+ "InAppBrowser",
690
+ "Failed to create photo URI, falling back to file picker"
691
+ );
692
+ fallbackToFilePicker();
693
+ }
694
+ }
695
+ } catch (Exception e) {
696
+ Log.e("InAppBrowser", "Camera launch failed: " + e.getMessage(), e);
697
+ fallbackToFilePicker();
698
+ }
699
+ }
700
+
701
+ /**
702
+ * Fall back to file picker when camera launch fails
703
+ */
704
+ private void fallbackToFilePicker() {
705
+ if (mFilePathCallback != null) {
706
+ openFileChooser(mFilePathCallback, "image/*", false);
707
+ }
708
+ }
709
+
362
710
  // Grant permissions for cam
363
711
  @Override
364
712
  public void onPermissionRequest(final PermissionRequest request) {
@@ -479,11 +827,8 @@ public class WebViewDialog extends Dialog {
479
827
  if (
480
828
  toolbarView != null &&
481
829
  toolbarView.getParent() instanceof
482
- com.google.android.material.appbar.AppBarLayout
830
+ com.google.android.material.appbar.AppBarLayout appBarLayout
483
831
  ) {
484
- com.google.android.material.appbar.AppBarLayout appBarLayout =
485
- (com.google.android.material.appbar.AppBarLayout) toolbarView.getParent();
486
-
487
832
  // Remove elevation to eliminate shadows (only on Android 15+)
488
833
  appBarLayout.setElevation(0);
489
834
  appBarLayout.setStateListAnimator(null);
@@ -703,7 +1048,7 @@ public class WebViewDialog extends Dialog {
703
1048
  _options.getPreShowScript() +
704
1049
  '\n' +
705
1050
  "};\n" +
706
- "preShowFunction().then(() => window.PreShowScriptInterface.success()).catch(err => { console.error('Preshow error', err); window.PreShowScriptInterface.error(JSON.stringify(err, Object.getOwnPropertyNames(err))) })";
1051
+ "preShowFunction().then(() => window.PreShowScriptInterface.success()).catch(err => { console.error('Pre show error', err); window.PreShowScriptInterface.error(JSON.stringify(err, Object.getOwnPropertyNames(err))) })";
707
1052
 
708
1053
  Log.i(
709
1054
  "InjectPreShowScript",
@@ -804,10 +1149,47 @@ public class WebViewDialog extends Dialog {
804
1149
  intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, isMultiple);
805
1150
 
806
1151
  try {
807
- activity.startActivityForResult(
808
- Intent.createChooser(intent, "Select File"),
809
- FILE_CHOOSER_REQUEST_CODE
810
- );
1152
+ if (activity instanceof androidx.activity.ComponentActivity) {
1153
+ androidx.activity.ComponentActivity componentActivity =
1154
+ (androidx.activity.ComponentActivity) activity;
1155
+ componentActivity
1156
+ .getActivityResultRegistry()
1157
+ .register(
1158
+ "file_chooser",
1159
+ new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
1160
+ result -> {
1161
+ if (result.getResultCode() == Activity.RESULT_OK) {
1162
+ Intent data = result.getData();
1163
+ if (data != null) {
1164
+ if (data.getClipData() != null) {
1165
+ // Handle multiple files
1166
+ int count = data.getClipData().getItemCount();
1167
+ Uri[] results = new Uri[count];
1168
+ for (int i = 0; i < count; i++) {
1169
+ results[i] = data.getClipData().getItemAt(i).getUri();
1170
+ }
1171
+ mFilePathCallback.onReceiveValue(results);
1172
+ } else if (data.getData() != null) {
1173
+ // Handle single file
1174
+ mFilePathCallback.onReceiveValue(
1175
+ new Uri[] { data.getData() }
1176
+ );
1177
+ }
1178
+ }
1179
+ } else {
1180
+ mFilePathCallback.onReceiveValue(null);
1181
+ }
1182
+ mFilePathCallback = null;
1183
+ }
1184
+ )
1185
+ .launch(Intent.createChooser(intent, "Select File"));
1186
+ } else {
1187
+ // Fallback for non-ComponentActivity
1188
+ activity.startActivityForResult(
1189
+ Intent.createChooser(intent, "Select File"),
1190
+ FILE_CHOOSER_REQUEST_CODE
1191
+ );
1192
+ }
811
1193
  } catch (ActivityNotFoundException e) {
812
1194
  // If no app can handle the specific MIME type, try with a more generic one
813
1195
  Log.e(
@@ -816,10 +1198,47 @@ public class WebViewDialog extends Dialog {
816
1198
  );
817
1199
  intent.setType("*/*");
818
1200
  try {
819
- activity.startActivityForResult(
820
- Intent.createChooser(intent, "Select File"),
821
- FILE_CHOOSER_REQUEST_CODE
822
- );
1201
+ if (activity instanceof androidx.activity.ComponentActivity) {
1202
+ androidx.activity.ComponentActivity componentActivity =
1203
+ (androidx.activity.ComponentActivity) activity;
1204
+ componentActivity
1205
+ .getActivityResultRegistry()
1206
+ .register(
1207
+ "file_chooser",
1208
+ new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
1209
+ result -> {
1210
+ if (result.getResultCode() == Activity.RESULT_OK) {
1211
+ Intent data = result.getData();
1212
+ if (data != null) {
1213
+ if (data.getClipData() != null) {
1214
+ // Handle multiple files
1215
+ int count = data.getClipData().getItemCount();
1216
+ Uri[] results = new Uri[count];
1217
+ for (int i = 0; i < count; i++) {
1218
+ results[i] = data.getClipData().getItemAt(i).getUri();
1219
+ }
1220
+ mFilePathCallback.onReceiveValue(results);
1221
+ } else if (data.getData() != null) {
1222
+ // Handle single file
1223
+ mFilePathCallback.onReceiveValue(
1224
+ new Uri[] { data.getData() }
1225
+ );
1226
+ }
1227
+ }
1228
+ } else {
1229
+ mFilePathCallback.onReceiveValue(null);
1230
+ }
1231
+ mFilePathCallback = null;
1232
+ }
1233
+ )
1234
+ .launch(Intent.createChooser(intent, "Select File"));
1235
+ } else {
1236
+ // Fallback for non-ComponentActivity
1237
+ activity.startActivityForResult(
1238
+ Intent.createChooser(intent, "Select File"),
1239
+ FILE_CHOOSER_REQUEST_CODE
1240
+ );
1241
+ }
823
1242
  } catch (ActivityNotFoundException ex) {
824
1243
  // If still failing, report error
825
1244
  Log.e("InAppBrowser", "No app can handle file picker", ex);
@@ -1191,15 +1610,6 @@ public class WebViewDialog extends Dialog {
1191
1610
  }
1192
1611
  }
1193
1612
 
1194
- if (inputStream == null) {
1195
- Log.e(
1196
- "InAppBrowser",
1197
- "Failed to load SVG icon: " + buttonNearDone.getIcon()
1198
- );
1199
- buttonNearDoneView.setVisibility(View.GONE);
1200
- return;
1201
- }
1202
-
1203
1613
  // Parse and render SVG
1204
1614
  SVG svg = SVG.getFromInputStream(inputStream);
1205
1615
  if (svg == null) {
@@ -1880,6 +2290,18 @@ public class WebViewDialog extends Dialog {
1880
2290
  @Override
1881
2291
  public void dismiss() {
1882
2292
  if (_webView != null) {
2293
+ // Reset file inputs to prevent WebView from caching them
2294
+ _webView.evaluateJavascript(
2295
+ "(function() {" +
2296
+ " var inputs = document.querySelectorAll('input[type=\"file\"]');" +
2297
+ " for (var i = 0; i < inputs.length; i++) {" +
2298
+ " inputs[i].value = '';" +
2299
+ " }" +
2300
+ " return true;" +
2301
+ "})();",
2302
+ null
2303
+ );
2304
+
1883
2305
  _webView.loadUrl("about:blank");
1884
2306
  _webView.onPause();
1885
2307
  _webView.removeAllViews();
@@ -1977,25 +2399,92 @@ public class WebViewDialog extends Dialog {
1977
2399
 
1978
2400
  // This script adds minimal fixes for date inputs to use Material Design
1979
2401
  String script =
1980
- "(function() {\n" +
1981
- " // Find all date inputs\n" +
1982
- " const dateInputs = document.querySelectorAll('input[type=\"date\"]');\n" +
1983
- " dateInputs.forEach(input => {\n" +
1984
- " // Ensure change events propagate correctly\n" +
1985
- " let lastValue = input.value;\n" +
1986
- " input.addEventListener('change', () => {\n" +
1987
- " if (input.value !== lastValue) {\n" +
1988
- " lastValue = input.value;\n" +
1989
- " // Dispatch an input event to ensure frameworks detect the change\n" +
1990
- " input.dispatchEvent(new Event('input', { bubbles: true }));\n" +
1991
- " }\n" +
1992
- " });\n" +
1993
- " });\n" +
1994
- "})();";
2402
+ """
2403
+ (function() {
2404
+ // Find all date inputs
2405
+ const dateInputs = document.querySelectorAll('input[type="date"]');
2406
+ dateInputs.forEach(input => {
2407
+ // Ensure change events propagate correctly
2408
+ let lastValue = input.value;
2409
+ input.addEventListener('change', () => {
2410
+ if (input.value !== lastValue) {
2411
+ lastValue = input.value;
2412
+ // Dispatch an input event to ensure frameworks detect the change
2413
+ input.dispatchEvent(new Event('input', { bubbles: true }));
2414
+ }
2415
+ });
2416
+ });
2417
+ })();""";
1995
2418
 
1996
2419
  // Execute the script in the WebView
1997
2420
  _webView.post(() -> _webView.evaluateJavascript(script, null));
1998
2421
 
1999
2422
  Log.d("InAppBrowser", "Applied minimal date picker fixes");
2000
2423
  }
2424
+
2425
+ /**
2426
+ * Creates a temporary URI for storing camera capture
2427
+ * @return URI for the temporary file or null if creation failed
2428
+ */
2429
+ private Uri createTempImageUri() {
2430
+ try {
2431
+ String fileName = "capture_" + System.currentTimeMillis() + ".jpg";
2432
+ java.io.File cacheDir = _context.getCacheDir();
2433
+
2434
+ // Make sure cache directory exists
2435
+ if (!cacheDir.exists() && !cacheDir.mkdirs()) {
2436
+ return null;
2437
+ }
2438
+
2439
+ // Create temporary file
2440
+ java.io.File tempFile = new java.io.File(cacheDir, fileName);
2441
+ if (!tempFile.createNewFile()) {
2442
+ return null;
2443
+ }
2444
+
2445
+ // Get content URI through FileProvider
2446
+ try {
2447
+ return androidx.core.content.FileProvider.getUriForFile(
2448
+ _context,
2449
+ _context.getPackageName() + ".fileprovider",
2450
+ tempFile
2451
+ );
2452
+ } catch (IllegalArgumentException e) {
2453
+ // Try using external storage as fallback
2454
+ java.io.File externalCacheDir = _context.getExternalCacheDir();
2455
+ if (externalCacheDir != null) {
2456
+ tempFile = new java.io.File(externalCacheDir, fileName);
2457
+ final boolean newFile = tempFile.createNewFile();
2458
+ if (!newFile) {
2459
+ Log.d("InAppBrowser", "Error creating new file");
2460
+ }
2461
+ return androidx.core.content.FileProvider.getUriForFile(
2462
+ _context,
2463
+ _context.getPackageName() + ".fileprovider",
2464
+ tempFile
2465
+ );
2466
+ }
2467
+ }
2468
+ return null;
2469
+ } catch (Exception e) {
2470
+ return null;
2471
+ }
2472
+ }
2473
+
2474
+ private File createImageFile() throws IOException {
2475
+ // Create an image file name
2476
+ String timeStamp = new java.text.SimpleDateFormat("yyyyMMdd_HHmmss").format(
2477
+ new java.util.Date()
2478
+ );
2479
+ String imageFileName = "JPEG_" + timeStamp + "_";
2480
+ File storageDir = activity.getExternalFilesDir(
2481
+ Environment.DIRECTORY_PICTURES
2482
+ );
2483
+ File image = File.createTempFile(
2484
+ imageFileName,/* prefix */
2485
+ ".jpg",/* suffix */
2486
+ storageDir/* directory */
2487
+ );
2488
+ return image;
2489
+ }
2001
2490
  }
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <paths xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <!-- Internal cache for most operations -->
4
+ <cache-path name="camera_captures" path="." />
5
+
6
+ <!-- External cache for fallback -->
7
+ <external-cache-path name="external_cache" path="." />
8
+
9
+ <!-- External files for additional compatibility -->
10
+ <external-files-path name="external_files" path="." />
11
+
12
+ <!-- Allow sharing app-specific files if needed -->
13
+ <files-path name="app_files" path="." />
14
+ </paths>