Notifications, Sounds, Android 7.0, and Aggravation
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.
If you have found another workaround of note, let me know!