Android 9.0 NotificationManager.notify() выбрасывает java.lang.SecurityException
Я не смог воспроизвести эту проблему сам, но пока 5 пользователей сообщили об этом. Недавно я опубликовал обновление приложения, которое изменило целевой SDK с 27 на 28, и я уверен, что это сыграло свою роль. Все 5 пользователей работают под управлением Android 9 на каком-то устройстве Pixel. Как и я
Приложение реагирует на ситуацию оповещения, вызывая настройку уведомления и вызывая NotificationManager.notify(). Это уведомление ссылается на канал уведомления, который пытается воспроизвести аудиофайл, расположенный на внешнем хранилище. Мое приложение содержит разрешение READ_EXTERNAL_STORAGE в манифесте. Но поскольку он сам не имеет доступа к чему-либо во внешнем хранилище, он не попросил пользователя предоставить ему такое разрешение.
Когда я делаю это на моем Pixel, это работает просто отлично. Но 5 пользователей сообщили, что выбрасывают исключение, подобное
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)
Я сказал всем 4 пользователям вручную предоставить разрешение "Хранилище" и AFAIK, который решает проблему. Но зачем это нужно? Мой сам не получил доступ к внешнему хранилищу или настроил конфигурацию канала так, чтобы это требовалось. Если требуется разрешение READ_EXTERNAL_STORAGE, диспетчер уведомлений должен управлять этим.
Проблемы с отчетами пользователей выполнялись следующим образом: google/taimen/taimen:9/PQ1A.190105.004/5148680: пользователь /release-keys google/crosshatch/crosshatch:9/PQ1A.190105.004/5148680:user/release-keys google/marlin/marlin:9/PQ1A.181205.002.A1/5129870: ключи пользователя / релиза google/sailfish/sailfish:9/PQ1A.181205.002.A1/5129870: ключи пользователя / релиза google/walleye/walleye:9/PQ1A.181205.002/5086253: пользователь / Release-клавиши
Я использую google/taimen/taimen:9/PQ1A.181205.002/5086253: пользовательские / release-ключи, которые, похоже, стоят за всеми, обновляются до google/taimen/taimen:9/PQ1A.190105.004/5148680:user/release- ключи ничего не меняют. Все еще отлично работает на моем устройстве.
Вот весь код с некоторыми подсказками относительно того, какие ветви взяты. Трассировка стека довольно ясна, что исключение было сгенерировано в вызове notify (). И это было прервано, потому что у приложения не было безопасного доступа к аудиофайлу, указанному каналом.
// 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();
}
Последние новости. Вчера вечером я опубликовал обновление, поддерживающее целевой SDK до 27 из 28. За ночь еще 2 пользователя сообщили об этом конкретном сбое на телефонах Pixel под управлением Android 9. Оба работали с версией, ориентированной на SDK 28. Один из них вернулся ко мне и подтвердил, что проблема исчезли, когда они установили версию приложения SDK 27. Это подтверждает, что это проблема с приложениями, ориентированными на SDK 28, вероятно, связана с изменением, запрещающим приложениям использовать разрешения файловой системы доступа к миру для преодоления ограничений изолированной программной среды приложения.
До сих пор остается загадкой, почему это касается одних пользователей, но не других. Конкретно я. Когда у меня будет время, я попытаюсь воспроизвести проблему на моем телефоне. Две теории: 1) Это касается только людей, которые никогда не давали разрешения READ_EXTERNAL_STORAGE. Первоначально мое разрешение было предоставлено, и я отозвал его при попытке воспроизвести проблему. 2) Это происходит только тогда, когда приложение изначально установило канал уведомления с использованием внешнего аудиофайла. Это было бы верно для большинства пользователей, но в моем случае звуковой файл был настроен вручную.
0 ответов
Не столько решение, сколько долгий сложный обходной путь.
Сначала я перехватываю SecurityException, генерируемую уведомлением, и устанавливаю флаг общих предпочтений
try {
myNM.notify(NOTIFICATION_ALERT, n);
} catch (SecurityException ex) {
Log.e(ex);
ManagePreferences.setNotifyAbort(true);
return;
}
Когда приложение запускается, оно проверяет этот флаг и, когда он установлен, предлагает пользователю предоставить READ_EXTERNAL_PERMISSION. Не включая код, потому что он является частью сложной системы, которая связывает разрешения с различными настройками предпочтений, разрешая только определенные настройки, если требуется разрешение, и изменяя их, если разрешение не предоставлено.
Это помогает, но мы по-прежнему имеем в виду, что пользователь не получит уведомления, когда необходимо сгенерировать предупреждение в первый раз. Чтобы решить эту проблему, мы добавляем кое-что к инициализации запуска, которая проверяет, может ли быть проблема, а если есть, генерирует регулярное уведомление и немедленно отменяет его.
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);
}
}
Подхожу ближе. Но мы по-прежнему пропускаем уведомление, если оно происходит после обновления пользователя до Android 9, но до того, как он откроет приложение. Чтобы решить эту проблему, я написал широковещательный приемник, который прослушивает android.intent.action.MY_PACKAGE_REPLACED и android.intent.action.BOOT_COMPLETED, который вызывается каждый раз, когда обновляется мое приложение или система Android. Этот ресивер не делает ничего особенного. Но тот факт, что он существует, означает, что мое приложение запускается и проходит через логику инициализации. Это определяет, что пользователю нужно разрешение READ_EXTERNAL_STORAGE, и запрашивает его.
У меня была эта проблема. Оказалось, что создание канала уведомлений было ошибкой.
Неправильный способ:
val notifictionChannel = NotificationChannel(...)
notificationChannel.setSound(
RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.TYPE_NOTIFICATION),
AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build()
)
notificationManager.createNotificationChannel(notificationChannel)
Правильно:
...
notificationChannel.setSound(
Settings.System.DEFAULT_NOTIFICATION_URI,
AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build()
)
...