Android 9.0 NotificationManager.notify()引发java.lang.SecurityException

时间:2019-01-10 01:18:38

标签: android notifications android-9.0-pie android-securityexception

我自己无法重现此问题,但到目前为止有5位用户报告了此问题。我最近确实发布了一个应用程序更新,将目标SDK从27更改为28,我当然也参与其中。所有5位用户都在某种Pixel设备上运行某种Android 9。我也是。

应用程序通过调用设置通知并调用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: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

我在跑步 google / taimen / taimen:9 / PQ1A.181205.002 / 5086253:user / release-keys 似乎落后于所有人 google / taimen / taimen:9 / PQ1A.190105.004 / 5148680:user / release-keys 不会改变任何东西。在我的设备上仍然可以正常工作。

所有代码都带有一些提示,说明采用了哪些分支。堆栈跟踪非常清楚,该异常是在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从28恢复到27。隔夜有2个用户报告了运行Android 9的Pixel手机上的这一特殊崩溃。两个都运行了针对SDK 28的版本。一个回到我身边并确认了问题他们安装了该应用程序的SDK 27版本后消失了。这确认这是针对SDK 28的应用程序的问题,可能与更改有关,该更改禁止应用程序使用世界访问文件系统权限来突破应用程序沙箱限制。

它影响某些用户却不影响其他用户仍然是一个谜。特别是我。有空时,我将再次尝试在手机上重现该问题。两种理论是 1)它只会击中从未授予READ_EXTERNAL_STORAGE权限的人。我的最初是被授予该权限的,在尝试重现该问题时,我已将其撤销。 2)仅当使用外部音频文件的通知通道最初由该应用程序设置时才会发生。对于大多数用户而言,这是正确的,但是在我的情况下,声音文件是手动设置的。

2 个答案:

答案 0 :(得分:0)

我遇到了这个问题。原来通知通道的创建有误。

错误的方式:

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()
)
...

答案 1 :(得分: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权限并提示他们。