2

I' m trying to create a temporary file and share it. So I created this class:

public class GenerateFile {

    public static File writeToFile(Context mcoContext, String sBody) {

        String fileName = "LOG FILE_" + String.valueOf(System.currentTimeMillis()) +".txt";
        File file = new File(mcoContext.getCacheDir(), fileName);

        try{
            FileWriter writer = new FileWriter(file);
            writer.append(sBody);
            writer.flush();
            writer.close();
            return  file;

        }catch (Exception e){
            Toast.makeText(mcoContext, "File write failed: " + e.toString(), Toast.LENGTH_LONG).show();
        }
        return null;
    }
}

to generate a file that after I will share here:

String logContent = "123";
File filePath = new File(file.getAbsolutePath(), "external_files");
filePath.mkdir();
Uri uri = FileProvider.getUriForFile(StatusActivity.this, getPackageName(), filePath);

Intent intent = ShareCompat.IntentBuilder.from(StatusActivity.this)
       .setStream(uri) // uri from FileProvider
        .setType("text/html")
        .getIntent()
        .setAction(Intent.ACTION_VIEW) //Change if needed
        .setDataAndType(uri, "text/*")
        .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);

And in the manifest there are already this permission:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="com.sec.android.provider.badge.permission.WRITE"/>
<uses-permission android:name="com.sec.android.provider.badge.permission.READ"/>

and the provider declaration

 <provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="android.getqardio.com.gmslocationtest"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths"/>
</provider>

The provider_paths class is defined in this way:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <files-path
        name="share"
        path="external_files"/>
</paths>

But it generate the message, when I try to share it by mail or telegram "Unable to attach file" or "Unsupported attachment". Also it seems to me that the file is not created.

TheOldBlackbeard
  • 395
  • 4
  • 22

3 Answers3

3

Other apps do not have access to your app's getCacheDir(). FLAG_GRANT_READ_URI_PERMISSION and FLAG_GRANT_WRITE_URI_PERMISSION are for content Uri values, not file Uri values. And, on Android 7.0+ devices, your code should crash with a FileUriExposedException.

Use FileProvider to make your content available to other apps, and use FileProvider.getUriForFile() to get the Uri to put in the Intent.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • I update my code and so question : I insert the provider in the manifest and I created the provider_paths. I also changed the code concern the file sending. But unfortunatly now I have another error: `NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference` – TheOldBlackbeard Jul 02 '19 at 13:34
  • 1
    @TheOldBlackbeard: That error indicates that `getPackageName()` is not the same as `android.getqardio.com.gmslocationtest`. The value that you use for the authority name in `getUriForFile()` needs to match the value that you use in the `android:authorities` attribute. Also, please stop adding the "snippet" stuff to your source code in the question, as it is not JavaScript and cannot be run. – CommonsWare Jul 02 '19 at 13:37
  • 2
    @TheOldBlackbeard: Also, you are saving your file in `getCacheDir()`, but you are using `` in the metadata. Those too do not match. Use `` instead. – CommonsWare Jul 02 '19 at 13:38
  • Sorry for the snippet, I didn't know how to avoid it. Thank you very much for the suggestions, I will try to edit the code. – TheOldBlackbeard Jul 02 '19 at 15:04
3

So I follow the suggestion of @CommonsWare, and I edited my code. This is the final result:

public class GenerateFile {

    public static Uri getFileURI(Context context, String nameFile, String content, String fileExtension) {
        DateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd");
        Date date = new Date();
        String fileName = dateFormat.format(date)+nameFile+fileExtension;
        File file = new File(context.getCacheDir(), fileName);

        try{
            FileWriter writer = new FileWriter(file);
            writer.append(content);
            writer.flush();
            writer.close();
            //Toast.makeText(context, "Writing to the file completed successfully", Toast.LENGTH_LONG).show();
        }catch (Exception e){
            Toast.makeText(context, "File writing failed: " + e.toString(), Toast.LENGTH_LONG).show();
        }

        File filePath = new File(context.getCacheDir(), "");
        File newFile = new File(filePath, fileName);
        return FileProvider.getUriForFile(context, "MYPACKAGE.fileprovider", newFile);
    }
}

and in another class:

private void sendFile(String nameFile, String logContent,  String fileExtension) {
        Uri contentUri = GenerateFile.getFileURI(getApplicationContext(), nameFile, logContent, fileExtension);

        Intent intent = ShareCompat.IntentBuilder.from(StatusActivity.this)
                .setStream(contentUri) // uri from FileProvider
                .setType("text/plain")
                .getIntent()
                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        startActivity(Intent.createChooser(intent, "send"));
}

so to send the file. I also deleted the permission (previously mentioned) in the manifest, because I didn't need it anymore.

And I also edited my provider and provider_path file like that:

<provider
   android:name="android.support.v4.content.FileProvider"
   android:authorities="MYPACKAGE.fileprovider"
   android:exported="false"
   android:grantUriPermissions="true">
   <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths" />
 </provider>

<?xml version="1.0" encoding="utf-8"?>
<cache-path
    name="my_files"
    path=""
/>

Now it works! Thank you very much guys for the help!

TheOldBlackbeard
  • 395
  • 4
  • 22
1

Did you specifically ask the user for those permissions? It's not enough to just put the permissions in the manifest for target sdks below 28. Also, in Android Q, you will need to work around external storage permissions altogether as this is disallowed.

Kristy Welsh
  • 7,828
  • 12
  • 64
  • 106