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 sure plays a part in this. All 5 users are running some flavor Android 9 on some kind of Pixel device. As am I.
The app responds to an alert situation by calling setting up a notification and calling NotificationManager.notify(). This notification references a notification channel that tries to play an audio file located on external storage. My app does include the READ_EXTERNAL_STORAGE permission in the manifest. But since it is not, itself, accessing anything in external storage, it has not asked the user to grant it that permission.
When I do this on my Pixel, this works just fine. But 5 users reported it throwing an exception like
java.lang.RuntimeException: Unable to start activity ComponentInfo{net.anei.cadpage/net.anei.cadpage.CadPageActivity}: java.lang.SecurityException: UID 10132 does not have permission to content://media/external/audio/media/145 [user 0]
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2914)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3049)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1809)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6680)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Caused by: java.lang.SecurityException: UID 10132 does not have permission to content://media/external/audio/media/145 [user 0]
at android.os.Parcel.createException(Parcel.java:1950)
at android.os.Parcel.readException(Parcel.java:1918)
at android.os.Parcel.readException(Parcel.java:1868)
at android.app.INotificationManager$Stub$Proxy.enqueueNotificationWithTag(INotificationManager.java:1559)
at android.app.NotificationManager.notifyAsUser(NotificationManager.java:405)
at android.app.NotificationManager.notify(NotificationManager.java:370)
at android.app.NotificationManager.notify(NotificationManager.java:346)
at net.anei.cadpage.ManageNotification.show(ManageNotification.java:186)
at net.anei.cadpage.ReminderReceiver.scheduleNotification(ReminderReceiver.java:46)
at net.anei.cadpage.ManageNotification.show(ManageNotification.java:161)
at net.anei.cadpage.CadPageActivity.startup(CadPageActivity.java:211)
at net.anei.cadpage.CadPageActivity.onCreate(CadPageActivity.java:93)
at android.app.Activity.performCreate(Activity.java:7144)
at android.app.Activity.performCreate(Activity.java:7135)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2894)
... 11 more
Caused by: android.os.RemoteException: Remote stack trace:
at com.android.server.am.ActivityManagerService.checkGrantUriPermissionLocked(ActivityManagerService.java:9752)
at com.android.server.am.ActivityManagerService.checkGrantUriPermission(ActivityManagerService.java:9769)
at com.android.server.notification.NotificationRecord.visitGrantableUri(NotificationRecord.java:1096)
at com.android.server.notification.NotificationRecord.calculateGrantableUris(NotificationRecord.java:1072)
at com.android.server.notification.NotificationRecord.<init>(NotificationRecord.java:201)
I have told all 4 users to manually grant the "Storage" permission, and AFAIK that resolves the issue. But why should this be necessary. My did not access external storage itself, or set up the channel configuration to require it. If READ_EXTERNAL_STORAGE permission is needed, the Notification Manager should be managing that.
User reporting problem were running the following: google/taimen/taimen:9/PQ1A.190105.004/5148680:user/release-keys google/crosshatch/crosshatch:9/PQ1A.190105.004/5148680:user/release-keys google/marlin/marlin:9/PQ1A.181205.002.A1/5129870:user/release-keys google/sailfish/sailfish:9/PQ1A.181205.002.A1/5129870:user/release-keys google/walleye/walleye:9/PQ1A.181205.002/5086253:user/release-keys
I am running google/taimen/taimen:9/PQ1A.181205.002/5086253:user/release-keys which seems to be behind everyone else, updating to google/taimen/taimen:9/PQ1A.190105.004/5148680:user/release-keys doesn't change anything. Still works fine on my device.
Here is all the code with some hints as to which branches are taken. The stack trace is pretty clear that the exception was thrown in the notify() call. And that the abort was thrown because the app did not have security access to the audio file specified by the channel.
// Build and launch the notification
Notification n = buildNotification(context, message);
NotificationManager myNM = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
assert myNM != null;
// Seems this is needed for the number value to take effect on the Notification
activeNotice = true;
myNM.cancel(NOTIFICATION_ALERT);
myNM.notify(NOTIFICATION_ALERT, n);
........
private static Notification buildNotification(Context context, SmsMmsMessage message) {
/*
* Ok, let's create our Notification object and set up all its parameters.
*/
NotificationCompat.Builder nbuild = new NotificationCompat.Builder(context, ALERT_CHANNEL_ID);
// Set auto-cancel flag
nbuild.setAutoCancel(true);
// Set display icon
nbuild.setSmallIcon(R.drawable.ic_stat_notify);
// From Oreo on, these are set at the notification channel level
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { // False
// Maximum priority
nbuild.setPriority(NotificationCompat.PRIORITY_MAX);
// Message category
nbuild.setCategory(NotificationCompat.CATEGORY_CALL);
// Set public visibility
nbuild.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
// Set up LED pattern and color
if (ManagePreferences.flashLED()) {
/*
* Set up LED blinking pattern
*/
int col = getLEDColor(context);
int[] led_pattern = getLEDPattern(context);
nbuild.setLights(col, led_pattern[0], led_pattern[1]);
}
/*
* Set up vibrate pattern
*/
// If vibrate is ON, or if phone is set to vibrate
AudioManager AM = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
assert AM != null;
if ((ManagePreferences.vibrate() || AudioManager.RINGER_MODE_VIBRATE == AM.getRingerMode())) {
long[] vibrate_pattern = getVibratePattern(context);
if (vibrate_pattern != null) {
nbuild.setVibrate(vibrate_pattern);
} else {
nbuild.setDefaults(Notification.DEFAULT_VIBRATE);
}
}
}
if ( ManagePreferences.notifyEnabled()) { // false
// Are we doing are own alert sound?
if (ManagePreferences.notifyOverride()) {
// Save previous volume and set volume to max
overrideVolumeControl(context);
// Start Media Player
startMediaPlayer(context, 0);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O){
Uri alarmSoundURI = Uri.parse(ManagePreferences.notifySound());
nbuild.setSound(alarmSoundURI);
}
}
String call = message.getTitle();
nbuild.setContentTitle(context.getString(R.string.cadpage_alert));
nbuild.setContentText(call);
nbuild.setStyle(new NotificationCompat.InboxStyle().addLine(call).addLine(message.getAddress()));
nbuild.setWhen(message.getIncidentDate().getTime());
// The default intent when the notification is clicked (Inbox)
Intent smsIntent = CadPageActivity.getLaunchIntent(context, true);
PendingIntent notifIntent = PendingIntent.getActivity(context, 0, smsIntent, 0);
nbuild.setContentIntent(notifIntent);
// Set intent to execute if the "clear all" notifications button is pressed -
// basically stop any future reminders.
Intent deleteIntent = new Intent(new Intent(context, ReminderReceiver.class));
deleteIntent.setAction(Intent.ACTION_DELETE);
PendingIntent pendingDeleteIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, 0);
nbuild.setDeleteIntent(pendingDeleteIntent);
return nbuild.build();
}
Latest news. Last night I published an update backing the target SDK back to 27 from 28. Overnight 2 more users reported this particular crash on Pixel phones running Android 9. Both were running the version targeting SDK 28. One got back to me and confirmed that the problem disappeared when they installed the SDK 27 version of the app. This confirms that this is an issue with apps targeting SDK 28, probably related to the change disallowing apps from using world access file system permissions to defeat the application sandbox restrictions.
It is still a mystery why it affects some users but not others. Specifically me. When I get some time, I am going to make another attempt to reproduce the problem on my phone. Two theories are 1) It only hits people who have never granted READ_EXTERNAL_STORAGE permission. Mine had originally been granted that permission and I revoked it when attempting to reproduce the problem. 2) It only happens when the notification channel using the external audio file was originally set up by the app. That would have been true for most users, but in my case the sound file was set up manually.