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 решает проблему. Но зачем это нужно. 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) Это происходит только в том случае, если канал уведомлений с использованием внешнего аудиофайла был изначально настроен приложением. Это было бы верно для большинства пользователей, но в моем случае звуковой файл был настроен вручную.

Можем ли мы увидеть ваш код? Если это настраиваемое изображение, эти пользователи, вероятно, выбирают из какого-то особого места, для которого требуется разрешение.

TheWanderer 10.01.2019 03:06

Рингтон picekr не предоставил права на чтение uri?

Eugen Pechanec 10.01.2019 19:18

что возвращает ManagePreferences.notifySound()?

nandsito 10.01.2019 20:41

ManagePreferense.notifySound () возвращает URI, выбранный средством выбора мелодии звонка. То, что он возвращает, здесь не имеет значения, потому что результат используется только тогда, когда уровень сборки SDK меньше 27, и мы знаем, что фактический уровень SDK был 28. Мы действительно знаем, какое значение было возвращено, и это тот же рингтон, который был настроен в канале уведомлений (content: // media / external / audio / media / 145). Это не совпадение. Приложение использовало это настроенное значение для настройки канала уведомлений по умолчанию при первом обновлении пользователя до Android 8.

kencorbin 11.01.2019 02:13

У меня такая же проблема. Я получаю «SecurityException: нет разрешения на content: // media / external / audio / media / 3532». Чтобы воспроизвести его, я установил рингтон сообщения Hangouts для своего канала уведомлений, и у меня нет разрешения на хранение. Когда мне добавляется разрешение на хранение, проблема исчезает. Также у меня нет этой проблемы с другими мелодиями звонка.

Valery Miller 22.02.2019 11:50

@kencorbin нашли ли вы подходящее решение для этого?

Artur Latoszewski 10.03.2020 14:29

Для нас это происходит исключительно на Android 9.0+ (уровень API 28+) и только при ориентации на уровень API 28+, но у всех производителей. Кроме того, в случае наш, нет имеет значение, было ли предоставлено разрешение на хранение или нет. Во всяком случае, похоже, что это происходит из-за того, что NotificationChannel#setSound изначально вызывался с URI content:// на устройствах, обновленных с уровня API 27 и ниже. Вызов NotificationCompat.Builder#setSound позже или изменение звука в канале вручную (через пользовательский интерфейс) не вызывает проблем. См. Также: stackoverflow.com/q/57717831

caw 29.04.2021 03:15
9
7
2 087
2

Ответы 2

У меня была эта проблема. Оказалось, что создание канала уведомлений было ошибкой.

Неправильный способ:

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/....

Tapemaster 15.08.2019 10:03

Я не уверен, что слежу за тобой. Я хотел создать канал уведомлений, чтобы он был предварительно настроен на воспроизведение звука, а именно тот, который пользователь настроил в качестве звука по умолчанию для уведомлений. Если настраиваемый пользователем звук уведомления по умолчанию является настраиваемым, с URI content://media/external/..., это работает, когда сделано выше «правильным способом», и вылетает с трассировкой стека в вопросе, когда сделано вышеупомянутое «неправильным способом».

Jule 15.08.2019 14:13

Если пользователь оставит настройки канала такими, какие они есть, и изменит звук уведомления (по умолчанию), звук для уведомлений этого канала также изменится, как и должно.

Jule 15.08.2019 14:14

Не столько решение, сколько долгий сложный обходной путь.

Сначала я перехватываю 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, и запрашивает его.

Другие вопросы по теме