Reputation: 869
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
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.
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.
/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:
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.
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
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