xvlaze
xvlaze

Reputation: 869

Share .mp3 file located in /res/raw/ to WhatsApp

I am writing a ringtone app in Android (API 29) and I want to share the sounds, stored in MP3 format in the /res/raw/ folder, via WhatsApp. I am using this code:

    Intent share = new Intent(Intent.ACTION_SEND);
    Uri path = Uri.parse("android.resource://"+ getPackageName() + "/" + R.raw.yeah);
    share.setPackage("com.whatsapp");
    share.setType("audio/mp3");
    share.putExtra(Intent.EXTRA_STREAM, path);
    startActivity(Intent.createChooser(share, "Share"));

But every time I select a chat to send the audio file, I get a WhatsApp error telling me the file could not be shared. I've seen other apps do what I want WITHOUT requesting permissions to write to the external storage. Any clues how this has been done?

Upvotes: 1

Views: 746

Answers (2)

xvlaze
xvlaze

Reputation: 869

Finally I managed to solve the problem by mixing CommonsWare answer, this answer and this one. Thanks to all in advance!

Step by step solution:

First, let's start with some XML.

  1. Configure AndroidManifest.xml and add the following lines inside the <application> section. I put them in between my </activity> and </application> closing tags, but please take this placement as my personal choice: it might not work for you depending on your manifest layout.

AndroidManifest.xml

<provider
   android:name="androidx.core.content.FileProvider"
   android:authorities="${applicationId}.provider"
   android:exported="false"
   android:grantUriPermissions="true">
   <meta-data
      android:name="android.support.FILE_PROVIDER_PATHS"
      android:resource="@xml/file_paths" />
</provider>

As I am using API 29, I'm AndroidX libraries. In case you want to use it too consider running the AndroidX migration wizard by clicking Refactor > Migrate to AndroidX... in Android Studio at your own risk.

  1. Now create a file in /res/xml with the name file_paths.xml and fill it as follows:

file_paths.xml

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

Note the my_sounds name is arbitrary. The / in the path field is where in the cache path your file will be stored. I just let it like that for the sake of ease. Should you not want to use a cache path, here is the complete list of available tags you can use.

Now we will head back to Java and start coding the method that will handle the sharing. First of all we need to copy the file in our resource folder to a File object. This File, however, needs to be pointing to a path created by the File Provider we configured in the XML part. Let's divide the tasks:

  1. Create an InputStream with your file's data and fill a File with it with an auxiliary procedure.

public void handleMediaSend(int position)

File sound;
try {
    InputStream inputStream = getResources().openRawResource(sounds.get(position).getSound()); // equivalent to R.raw.yoursound
    sound = File.createTempFile("sound", ".mp3");
    copyFile(inputStream, new FileOutputStream(sound));
} catch (IOException e) {
    throw new RuntimeException("Can't create temp file", e);
}

Auxiliary procedure:

private void copyFile(InputStream in, OutputStream out) throws IOException {
    byte[] buffer = new byte[1024];
    int read;
    while((read = in.read(buffer)) != -1)
        out.write(buffer, 0, read);
}

Now your resource has been successfully transferred to a cached directory (use the debugger to see which one) inside your Internal Storage.

  1. Get an Uri and share the File.
final String AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";
Uri uri = getUriForFile(getApplicationContext(), AUTHORITY, sound);
Intent share = new Intent(Intent.ACTION_SEND);
share.setType("audio/mp3"); // or whatever.
share.putExtra(Intent.EXTRA_STREAM, uri);
share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(share, "Share"));

Putting it all together we will end up with this method:

Full method

public void handleMediaSend(int position) { // Depends on your implementation.
    File sound;
    try {
        InputStream inputStream = getResources().openRawResource(sounds.get(position).getSound()); // equivalent to R.raw.yoursound
        sound = File.createTempFile("sound", ".mp3");
        copyFile(inputStream, new FileOutputStream(sound));
    } catch (IOException e) {
        throw new RuntimeException("Can't create temp file", e);
    }

    final String AUTHORITY = BuildConfig.APPLICATION_ID + ".provider";
    Uri uri = getUriForFile(getApplicationContext(), AUTHORITY, sound);
    Intent share = new Intent(Intent.ACTION_SEND);
    share.setType("audio/mp3"); // or whatever.
    share.putExtra(Intent.EXTRA_STREAM, uri);
    share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    startActivity(Intent.createChooser(share, "Share"));
}

Upvotes: 2

CommonsWare
CommonsWare

Reputation: 1007322

But every time I select a chat to send the audio file, I get a WhatsApp error telling me the file could not be shared

Few apps support the obscure android.resource scheme very well. Also, EXTRA_STREAM is documented to take a content Uri, not an android.resource Uri.

Any clues how this has been done?

Make a copy of the raw resource to a file on internal storage, such as in getCacheDir(). You can use the Resources object you get from calling getResources() a Context to get access to the raw resource (using openRawResource()). Then, configure FileProvider to serve from there, and use FileProvider.getUriForFile() to get a Uri to use with ACTION_SEND. Be sure to add FLAG_GRANT_READ_URI_PERMISSION on the Intent.

This sample Java app (and its Kotlin equivalent) demonstrate the basic technique. In my case, I am storing a PDF as an asset instead of your storing an MP3 as a raw resource, so that part will require some minor adjustments.

Alternatively, you can write your own ContentProvider from scratch to serve your raw resource, then use a Uri supported by that provider in your Intent.

Upvotes: 1

Related Questions