jordy mendoza jordy mendoza - 3 months ago 37
Java Question

Android 7.0 Notification Sound from File Provider Uri not playing

I'm changing my app code for supporting Android 7, but in my NotificationCompat.Builder.setSound(Uri) passing the Uri from FileProvider the Notification don't play any sound, in Android 6 using the Uri.fromFile() worked properly.

The mp3 file is in:


/Animeflv/cache/.sounds/


This is my Notification Code:


knf.animeflv.RequestBackground


NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_not_r)
.setContentTitle(NotTit)
.setContentText(mess);
...
mBuilder.setVibrate(new long[]{100, 200, 100, 500});
mBuilder.setSound(UtilSound.getSoundUri(not)); //int


This is my UtilSound.getSoundUri(int)

public static Uri getSoundUri(int not) {
switch (not) {
case 0:
return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
default:
try {
File file=new File(Environment.getExternalStorageDirectory()+"/Animeflv/cache/.sounds",getSoundsFileName(not));
if (file.exists()) {
file.setReadable(true,false);
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
return FileProvider.getUriForFile(context, "knf.animeflv.RequestsBackground",file);
}else {
return Uri.fromFile(file);
}
}else {
Log.d("Sound Uri","Not found");
return getSoundUri(0);
}
}catch (Exception e){
e.printStackTrace();
return getSoundUri(0);
}
}
}


In AndroidManifest.xml:

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


provider_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="_.sounds" path="Animeflv/cache/.sounds/"/>
</paths>

Answer

The following is from a blog post that I just published, reproduced here because, hey, why not?


You can put a custom ringtone on a Notification, via methods like setSound() on NotificationCompat.Builder. This requires a Uri, and that causes problems on Android 7.0, as is reported by a few people on Stack Overflow.

If you were using file: Uri values, they no longer work on Android 7.0 if your targetSdkVersion is 24 or higher, as the sound Uri is checked for compliance with the ban on file: Uri values.

However, if you try a content: Uri from, say, FileProvider, your sound will not be played... because Android does not have read access to that content.

Here are some options for addressing this.

The Scalpel: grantUriPermissions()

You can always grant permissions for content to other apps via grantUriPermissions(), a method available on Context. The challenge is in knowing who to grant the permissions to.

What works on a Nexus 6P (Android 6.0... still...) and a Nexus 9 (Android 7.0) is:

grantUriPermission("com.android.systemui", sound,
    Intent.FLAG_GRANT_READ_URI_PERMISSION);

(where sound is the Uri that you are using with setSound())

Whether this will hold up for all devices and all Android OS versions, I cannot say.

The Guillotine: No More User Files

android.resource as a scheme works fine for Uri values for setSound(). Instead of allowing users to choose their own ringtone from a file, you only allow them to choose one of several ringtones that you ship as raw resources in your app. If this represents a loss of app functionality, though, your users may be unimpressed.

The Axe: Use a Custom ContentProvider

FileProvider cannot be used when it is exported — it crashes on startup. However, for this case, the only content: Uri that will work without other issues is one where the provider is exported and has no read access permissions (or happens to require some permission that com.android.systemui or the equivalent happens to hold).

Eventually, I'll add options for this to my StreamProvider, as part of some "read only" provider functionality.

But, you could roll your own provider for this.

The Chainsaw: Ban the Ban

The following code snippet blocks all StrictMode checks related to VM behavior (i.e., stuff other than main application thread behavior), including the ban on file: Uri values:

StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().build());

Alternatively, you could configure your own VmPolicy with whatever rules you want, just without calling detectFileUriExposure().

This allows you to use file: Uri values anywhere. There are good reasons why Google is banning the file: Uri, and so trying to avoid the ban may bite you in unfortunate body parts in the long term.

The Nuke: Use a Lower targetSdkVersion

This also removes the ban on file: Uri values, along with all other behavior that a targetSdkVersion of 24+ opts into. Of note, this will cause your app to display a "may not work with split-screen" Toast if the user enters split-screen multi-window mode.

The Real Solution: A Fix in Android

The NotificationManager should be calling grantUriPermissions() for us, or there should be some other way for us to associate FLAG_GRANT_READ_URI_PERMISSION with the Uri that we use for custom Notification sounds. Stay tuned for further developments.