@@ -104,6 +104,7 @@ public class System extends CordovaPlugin {
104104 private Theme theme ;
105105 private CallbackContext intentHandler ;
106106 private CordovaWebView webView ;
107+ private String fileProviderAuthority ;
107108
108109 public void initialize (CordovaInterface cordova , CordovaWebView webView ) {
109110 super .initialize (cordova , webView );
@@ -879,20 +880,46 @@ private void fileAction(
879880 ) {
880881 Activity activity = this .activity ;
881882 Context context = this .context ;
882- Uri uri = this .getContentProviderUri (fileURI );
883+ Uri uri = this .getContentProviderUri (fileURI , filename );
884+ if (uri == null ) {
885+ callback .error ("Unable to access file for action " + action );
886+ return ;
887+ }
883888 try {
884889 Intent intent = new Intent (action );
885890
886891 if (mimeType .equals ("" )) {
887892 mimeType = "text/plain" ;
888893 }
889894
895+ mimeType = resolveMimeType (mimeType , uri , filename );
896+
897+ String clipLabel = null ;
898+ if (filename != null && !filename .isEmpty ()) {
899+ clipLabel = new File (filename ).getName ();
900+ }
901+ if (clipLabel == null || clipLabel .isEmpty ()) {
902+ clipLabel = uri .getLastPathSegment ();
903+ }
904+ if (clipLabel == null || clipLabel .isEmpty ()) {
905+ clipLabel = "shared-file" ;
906+ }
890907 if (action .equals (Intent .ACTION_SEND )) {
908+ intent .setType (mimeType );
909+ intent .addFlags (Intent .FLAG_GRANT_READ_URI_PERMISSION );
910+ intent .setClipData (
911+ ClipData .newUri (
912+ context .getContentResolver (),
913+ clipLabel ,
914+ uri
915+ )
916+ );
891917 intent .putExtra (Intent .EXTRA_STREAM , uri );
892- if (!filename .equals ("" )) {
918+ intent .putExtra (Intent .EXTRA_TITLE , clipLabel );
919+ intent .putExtra (Intent .EXTRA_SUBJECT , clipLabel );
920+ if (filename != null && !filename .isEmpty ()) {
893921 intent .putExtra (Intent .EXTRA_TEXT , filename );
894922 }
895- intent .setType (mimeType );
896923 } else {
897924 int flags =
898925 Intent .FLAG_GRANT_PERSISTABLE_URI_PERMISSION |
@@ -904,9 +931,42 @@ private void fileAction(
904931
905932 intent .setFlags (flags );
906933 intent .setDataAndType (uri , mimeType );
934+ intent .setClipData (
935+ ClipData .newUri (
936+ context .getContentResolver (),
937+ clipLabel ,
938+ uri
939+ )
940+ );
941+ intent .addFlags (Intent .FLAG_GRANT_READ_URI_PERMISSION );
942+ if (!clipLabel .equals ("shared-file" )) {
943+ intent .putExtra (Intent .EXTRA_TITLE , clipLabel );
944+ }
945+ if (action .equals (Intent .ACTION_EDIT )) {
946+ intent .putExtra (Intent .EXTRA_STREAM , uri );
947+ }
907948 }
908949
909- activity .startActivity (intent );
950+ int permissionFlags = Intent .FLAG_GRANT_READ_URI_PERMISSION ;
951+ if (action .equals (Intent .ACTION_EDIT )) {
952+ permissionFlags |= Intent .FLAG_GRANT_WRITE_URI_PERMISSION ;
953+ }
954+ grantUriPermissions (intent , uri , permissionFlags );
955+
956+ if (action .equals (Intent .ACTION_SEND )) {
957+ Intent chooserIntent = Intent .createChooser (intent , null );
958+ chooserIntent .addFlags (Intent .FLAG_GRANT_READ_URI_PERMISSION );
959+ activity .startActivity (chooserIntent );
960+ } else if (action .equals (Intent .ACTION_EDIT ) || action .equals (Intent .ACTION_VIEW )) {
961+ Intent chooserIntent = Intent .createChooser (intent , null );
962+ chooserIntent .addFlags (Intent .FLAG_GRANT_READ_URI_PERMISSION );
963+ if (action .equals (Intent .ACTION_EDIT )) {
964+ chooserIntent .addFlags (Intent .FLAG_GRANT_WRITE_URI_PERMISSION );
965+ }
966+ activity .startActivity (chooserIntent );
967+ } else {
968+ activity .startActivity (intent );
969+ }
910970 callback .success (uri .toString ());
911971 } catch (Exception e ) {
912972 callback .error (e .getMessage ());
@@ -1263,24 +1323,168 @@ private Uri getContentProviderUri(String fileUri) {
12631323 }
12641324
12651325 private Uri getContentProviderUri (String fileUri , String filename ) {
1326+ if (fileUri == null || fileUri .isEmpty ()) {
1327+ return null ;
1328+ }
1329+
12661330 Uri uri = Uri .parse (fileUri );
1267- String Id = context .getPackageName ();
1268- if (fileUri .matches ("file:///(.*)" )) {
1269- File file = new File (uri .getPath ());
1270- if (filename .equals ("" )) {
1271- return FileProvider .getUriForFile (context , Id + ".provider" , file );
1331+ if (uri == null ) {
1332+ return null ;
1333+ }
1334+
1335+ if ("file" .equalsIgnoreCase (uri .getScheme ())) {
1336+ File originalFile = new File (uri .getPath ());
1337+ if (!originalFile .exists ()) {
1338+ Log .e ("System" , "File does not exist for URI: " + fileUri );
1339+ return null ;
12721340 }
12731341
1274- return FileProvider .getUriForFile (
1275- context ,
1276- Id + ".provider" ,
1277- file ,
1278- filename
1279- );
1342+ String authority = getFileProviderAuthority ();
1343+ if (authority == null ) {
1344+ Log .e ("System" , "No FileProvider authority available." );
1345+ return null ;
1346+ }
1347+
1348+ try {
1349+ return FileProvider .getUriForFile (context , authority , originalFile );
1350+ } catch (IllegalArgumentException | SecurityException ex ) {
1351+ try {
1352+ File cacheCopy = ensureShareableCopy (originalFile , filename );
1353+ return FileProvider .getUriForFile (context , authority , cacheCopy );
1354+ } catch (Exception copyError ) {
1355+ Log .e ("System" , "Failed to expose file via FileProvider" , copyError );
1356+ return null ;
1357+ }
1358+ }
12801359 }
12811360 return uri ;
12821361 }
12831362
1363+ private File ensureShareableCopy (File source , String displayName ) throws IOException {
1364+ File cacheRoot = new File (context .getCacheDir (), "shared" );
1365+ if (!cacheRoot .exists () && !cacheRoot .mkdirs ()) {
1366+ throw new IOException ("Unable to create shared cache directory" );
1367+ }
1368+
1369+ if (displayName != null && !displayName .isEmpty ()) {
1370+ displayName = new File (displayName ).getName ();
1371+ }
1372+ if (displayName == null || displayName .isEmpty ()) {
1373+ displayName = source .getName ();
1374+ }
1375+ if (displayName == null || displayName .isEmpty ()) {
1376+ displayName = "shared-file" ;
1377+ }
1378+
1379+ File target = new File (cacheRoot , displayName );
1380+ target = ensureUniqueFile (target );
1381+ copyFile (source , target );
1382+ return target ;
1383+ }
1384+
1385+ private File ensureUniqueFile (File target ) {
1386+ if (!target .exists ()) {
1387+ return target ;
1388+ }
1389+
1390+ String name = target .getName ();
1391+ String prefix = name ;
1392+ String suffix = "" ;
1393+ int dotIndex = name .lastIndexOf ('.' );
1394+ if (dotIndex > 0 ) {
1395+ prefix = name .substring (0 , dotIndex );
1396+ suffix = name .substring (dotIndex );
1397+ }
1398+
1399+ int index = 1 ;
1400+ File candidate = target ;
1401+ while (candidate .exists ()) {
1402+ candidate = new File (target .getParentFile (), prefix + "-" + index + suffix );
1403+ index ++;
1404+ }
1405+ return candidate ;
1406+ }
1407+
1408+ private void copyFile (File source , File destination ) throws IOException {
1409+ try (
1410+ InputStream in = new FileInputStream (source );
1411+ OutputStream out = new FileOutputStream (destination )
1412+ ) {
1413+ byte [] buffer = new byte [8192 ];
1414+ int length ;
1415+ while ((length = in .read (buffer )) != -1 ) {
1416+ out .write (buffer , 0 , length );
1417+ }
1418+ out .flush ();
1419+ }
1420+ }
1421+
1422+ private void grantUriPermissions (Intent intent , Uri uri , int flags ) {
1423+ if (uri == null ) return ;
1424+ PackageManager pm = context .getPackageManager ();
1425+ List <ResolveInfo > resInfoList = pm .queryIntentActivities (intent , PackageManager .MATCH_DEFAULT_ONLY );
1426+ for (ResolveInfo resolveInfo : resInfoList ) {
1427+ String packageName = resolveInfo .activityInfo .packageName ;
1428+ context .grantUriPermission (packageName , uri , flags );
1429+ }
1430+ }
1431+
1432+ private String resolveMimeType (String currentMime , Uri uri , String filename ) {
1433+ if (currentMime != null && !currentMime .isEmpty () && !currentMime .equals ("*/*" )) {
1434+ return currentMime ;
1435+ }
1436+
1437+ String mime = null ;
1438+ if (uri != null ) {
1439+ mime = context .getContentResolver ().getType (uri );
1440+ }
1441+
1442+ if ((mime == null || mime .isEmpty ()) && filename != null ) {
1443+ mime = getMimeTypeFromExtension (filename );
1444+ }
1445+
1446+ if ((mime == null || mime .isEmpty ()) && uri != null ) {
1447+ String path = uri .getPath ();
1448+ if (path != null ) {
1449+ mime = getMimeTypeFromExtension (path );
1450+ }
1451+ }
1452+
1453+ return (mime != null && !mime .isEmpty ()) ? mime : "*/*" ;
1454+ }
1455+
1456+ private String getFileProviderAuthority () {
1457+ if (fileProviderAuthority != null && !fileProviderAuthority .isEmpty ()) {
1458+ return fileProviderAuthority ;
1459+ }
1460+
1461+ try {
1462+ PackageManager pm = context .getPackageManager ();
1463+ PackageInfo packageInfo = pm .getPackageInfo (
1464+ context .getPackageName (),
1465+ PackageManager .GET_PROVIDERS
1466+ );
1467+ if (packageInfo .providers != null ) {
1468+ for (ProviderInfo providerInfo : packageInfo .providers ) {
1469+ if (
1470+ providerInfo != null &&
1471+ providerInfo .name != null &&
1472+ providerInfo .name .equals (FileProvider .class .getName ())
1473+ ) {
1474+ fileProviderAuthority = providerInfo .authority ;
1475+ break ;
1476+ }
1477+ }
1478+ }
1479+ } catch (PackageManager .NameNotFoundException ignored ) {}
1480+
1481+ if (fileProviderAuthority == null || fileProviderAuthority .isEmpty ()) {
1482+ fileProviderAuthority = context .getPackageName () + ".provider" ;
1483+ }
1484+
1485+ return fileProviderAuthority ;
1486+ }
1487+
12841488 private boolean isPackageInstalled (
12851489 String packageName ,
12861490 PackageManager packageManager ,
0 commit comments