I have been unable to reproduce this problem myself, but so far 5 users have reported it. I did recently publish an app update that changed the target SDK from 27 to 28 which I
Not so much a solution as a long complicated workaround.
First, I catch the SecurityException that is thrown by the notification and set a shared preference flag
try {
myNM.notify(NOTIFICATION_ALERT, n);
} catch (SecurityException ex) {
Log.e(ex);
ManagePreferences.setNotifyAbort(true);
return;
}
When the app starts up it checks this flag and it is set, prompts the user to grant the READ_EXTERNAL_PERMISSION. Not including the code because it is part of a complicated system that ties permissions to different preference settings, only allowing certain settings if a required permission is granted and changing it if the permission is not granted.
That helps, but we still means the user will not get notified the first time an alert needs to be generated. To address that, we add something to are startup initialization that checks to see if there might be a problem, and if there is, generates a regular notification and immediately cancels it.
if (audioAlert && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (! ManagePreferences.notifyCheckAbort() &&
! PermissionManager.isGranted(context, PermissionManager.READ_EXTERNAL_STORAGE)) {
Log.v("Checking Notification Security");
ManagePreferences.setNotifyCheckAbort(true);
ManageNotification.show(context, null, false, false);
NotificationManager myNM = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
assert myNM != null;
myNM.cancel(NOTIFICATION_ALERT);
}
}
Getting closer. But we still miss an alert notification if it happens after the user upgrades to Android 9 but before they open the app. To address that, I wrote a broadcast receiver that listens for android.intent.action.MY_PACKAGE_REPLACED and android.intent.action.BOOT_COMPLETED which gets called every time my app is upgraded, or the Android system is upgraded. This receiver does not do anything special. But the fact that it exists means that my app gets started up and goes through the initialization logic. Which detects that the user needs the READ_EXTERNAL_STORAGE permission and prompts them for it.