6

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>
Jordy Mendoza
  • 432
  • 4
  • 16
  • http://stackoverflow.com/questions/39285228/how-to-set-audio-as-ringtone-programmatically-above-android-n?noredirect=1#comment66048598_39285228 – CommonsWare Sep 07 '16 at 00:12

1 Answers1

20

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.

Community
  • 1
  • 1
CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Thanks, The Scalpel was the answer!! – Jordy Mendoza Sep 07 '16 at 17:34
  • 2
    Thanks a lot for the thorough answer. To verify the robustness of the grantUriPermission solution ('The Scalpel'), I've tested it in different physical devices (including LG, Motorola, HTC, Samsung and Sony), from API 15 up to API 24. It works correctly on all of them, so the solution seems pretty solid (although certainly hacky). – jmart Sep 07 '16 at 18:24
  • @jmart: "although certainly hacky" -- absolutely. Using a read-only `ContentProvider` (see "The Axe") is the better answer. I'll probably rig up my `StreamProvider` such that if you mark it as exported, it treats everything as read-only, which would handle this problem fairly cleanly. – CommonsWare Sep 07 '16 at 18:31
  • @CommonsWare where exactly are you calling grantUriPermission? Do you need to do it each time when building notifications, or would it be enough to grant permissions whenever the uri selection has changed? – Allan W Nov 14 '17 at 21:19
  • 1
    @AllanW: AFAIK, for that scenario, you would only need to call `grantUriPermissions()` for each distinct `Uri` value. So, in your case, when the selection has changed. That being said, I doubt there is any harm in just calling `grantUriPermissions()` every time. – CommonsWare Nov 14 '17 at 21:22
  • @CommonsWare thanks. Do you also have any idea why not calling this seems to work for some devices? For instance, I have a OP3 on Nougat (on an Oxygen based rom), and it works fine for me. Same goes for my Nexus 5 on a custom rom with nougat. I've gotten a few reports from Crashlytics, and as you've mentioned, this error seems to only be reported by a few people. – Allan W Nov 14 '17 at 21:26
  • @AllanW: No clue, sorry. – CommonsWare Nov 14 '17 at 21:30
  • 1
    Seems like it should be fixed in Android P: https://github.com/aosp-mirror/platform_frameworks_base/commit/2990141035d87028a7d31d8b0c3fbebc25fc2772 – rcell Oct 30 '18 at 18:18
  • @CommonsWare can i set notification sound from apk expansion file? – Pranav May 16 '19 at 05:18
  • @Pranav: I do not know, sorry. – CommonsWare May 16 '19 at 11:04
  • Wouldn't StrictMode be disabled in production builds? I'm surprised that it's being run by the Google code even in release bits. – fobbymaster Sep 09 '19 at 22:17
  • @fobbymaster: "Wouldn't StrictMode be disabled in production builds?" -- while you can do that, it won't help with Android 10. You might not have access to the file, and the code using the `Uri` might not have access to the file. – CommonsWare Sep 09 '19 at 23:05
  • Sorry, let me rephrase - Why is StrictMode running on production builds at all without being disabled? Why do we have to disable it at all? Shouldn't it be already disabled? – fobbymaster Sep 10 '19 at 17:10
  • @fobbymaster: I can't answer that, sorry. – CommonsWare Sep 10 '19 at 22:07
  • 1
    @CommonsWare Just an update on this, it has finally been fixed as of September of last year. – Shadow Feb 09 '21 at 19:03