Мне не удалось воспроизвести эту проблему самостоятельно, но пока о ней сообщили 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 решает проблему. Но зачем это нужно. My не имел доступа к самому внешнему хранилищу и не настраивал конфигурацию канала, чтобы требовать его. Если требуется разрешение READ_EXTERNAL_STORAGE, им должен управлять диспетчер уведомлений.
Проблема с отчетами пользователей заключалась в следующем: google / taimen / taimen: 9 / PQ1A.190105.004 / 5148680: пользовательские / релиз-ключи 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: user / release-keys google / walleye / walleye: 9 / PQ1A.181205.002 / 5086253: user / release-keys
я бегу google / taimen / taimen: 9 / PQ1A.181205.002 / 5086253: пользовательские / релиз-ключи который, кажется, отстает от всех остальных, обновляется до google / taimen / taimen: 9 / PQ1A.190105.004 / 5148680: пользовательские / релиз-ключи ничего не меняет. По-прежнему отлично работает на моем устройстве.
Вот весь код с некоторыми подсказками о том, какие ветви взяты. Трассировка стека довольно ясно показывает, что исключение было сгенерировано при вызове 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) Это происходит только в том случае, если канал уведомлений с использованием внешнего аудиофайла был изначально настроен приложением. Это было бы верно для большинства пользователей, но в моем случае звуковой файл был настроен вручную.
Рингтон picekr не предоставил права на чтение uri?
что возвращает ManagePreferences.notifySound()?
ManagePreferense.notifySound () возвращает URI, выбранный средством выбора мелодии звонка. То, что он возвращает, здесь не имеет значения, потому что результат используется только тогда, когда уровень сборки SDK меньше 27, и мы знаем, что фактический уровень SDK был 28. Мы действительно знаем, какое значение было возвращено, и это тот же рингтон, который был настроен в канале уведомлений (content: // media / external / audio / media / 145). Это не совпадение. Приложение использовало это настроенное значение для настройки канала уведомлений по умолчанию при первом обновлении пользователя до Android 8.
У меня такая же проблема. Я получаю «SecurityException: нет разрешения на content: // media / external / audio / media / 3532». Чтобы воспроизвести его, я установил рингтон сообщения Hangouts для своего канала уведомлений, и у меня нет разрешения на хранение. Когда мне добавляется разрешение на хранение, проблема исчезает. Также у меня нет этой проблемы с другими мелодиями звонка.
@kencorbin нашли ли вы подходящее решение для этого?
Для нас это происходит исключительно на Android 9.0+ (уровень API 28+) и только при ориентации на уровень API 28+, но у всех производителей. Кроме того, в случае наш, нет имеет значение, было ли предоставлено разрешение на хранение или нет. Во всяком случае, похоже, что это происходит из-за того, что NotificationChannel#setSound изначально вызывался с URI content:// на устройствах, обновленных с уровня API 27 и ниже. Вызов NotificationCompat.Builder#setSound позже или изменение звука в канале вручную (через пользовательский интерфейс) не вызывает проблем. См. Также: stackoverflow.com/q/57717831
У меня была эта проблема. Оказалось, что создание канала уведомлений было ошибкой.
Неправильный способ:
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()
)
...
Но «правильный способ» установит только уведомление по умолчанию, а не настраиваемое, которое читается с content://media/external/....
Я не уверен, что слежу за тобой. Я хотел создать канал уведомлений, чтобы он был предварительно настроен на воспроизведение звука, а именно тот, который пользователь настроил в качестве звука по умолчанию для уведомлений. Если настраиваемый пользователем звук уведомления по умолчанию является настраиваемым, с URI content://media/external/..., это работает, когда сделано выше «правильным способом», и вылетает с трассировкой стека в вопросе, когда сделано вышеупомянутое «неправильным способом».
Если пользователь оставит настройки канала такими, какие они есть, и изменит звук уведомления (по умолчанию), звук для уведомлений этого канала также изменится, как и должно.
Не столько решение, сколько долгий сложный обходной путь.
Сначала я перехватываю 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, и запрашивает его.
Можем ли мы увидеть ваш код? Если это настраиваемое изображение, эти пользователи, вероятно, выбирают из какого-то особого места, для которого требуется разрешение.