I'm seeing a crash come through Crashlytics that I'm unable to reproduce or locate the cause of. The crash only ever happens on Google Pixel devices running Android 12, and the crash always happens in the background.
This is the crash log from Crashlytics:
Fatal Exception: android.app.RemoteServiceException$CannotDeliverBroadcastException: can't deliver broadcast
at android.app.ActivityThread.throwRemoteServiceException(ActivityThread.java:1939)
at android.app.ActivityThread.access$2700(ActivityThread.java:256)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2190)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7870)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
I've looked at similar questions (like this and this) but Crashlytics is showing that these users all have plenty of free memory, and nowhere in our codebase are we calling registerReceiver or sendBroadcast so the solutions in that second question aren't any help.
Based on limited logs I'm pretty sure the crash happens when the user receives a push notification, but I have a Google Pixel 4a running Android 12 and I haven't been able to reproduce it at all when sending myself notifications.
We have a custom FirebaseMessagingService to listen for notifications that we register in the Manifest and a couple of BroadcastReceivers that listen for geofencing updates and utilize WorkManager to do some work when a transition is detected. The only thing that's changed with any of those recently is we updated WorkManager to initialize itself using Android's app startup library, but I'm not sure if that's even relevant since the crash logs give me no information, and if there was a problem with our implementation it wouldn't limit itself to just Pixel devices running Android 12.
Has anyone see this before or is there a bug exclusively on Pixel devices that run Android 12? I've spent hours digging into this and am at a complete loss.
With reference to Android 13, rather than earlier issues on 12, there is a Google tracker issue here, which at the time of writing is assigned but awaiting a meaningful response.
A summary of the issue is that it only occurs on 13, and only on Pixel devices.
CommonsWare has a blog entry on this here, and the only other clue I found anywhere was in the changelog for GrapheneOS, here, which has this line entry:
Sandboxed Google Play compatibility layer: don't report CannotDeliverBroadcastException to the user
We use this Play library and experience the fault, so it's possible Graphene have encountered this and had to put in an OS fix.
Update:
I tentatively believe that we as an app have suppressed this issue, and stopped it polluting our stats.
We set an exception handler to absorb it, which is what GrapheneOS are doing - credit to them.
class CustomUncaughtExceptionHandler(
private val uncaughtExceptionHandler: Thread.UncaughtExceptionHandler) : Thread.UncaughtExceptionHandler {
override fun uncaughtException(thread: Thread, exception: Throwable) {
if (shouldAbsorb(exception)) {
return
}
uncaughtExceptionHandler.uncaughtException(thread, exception)
}
/**
* Evaluate whether to silently absorb uncaught crashes such that they
* don't crash the app. We generally want to avoid this practice - we would
* rather know about them. However in some cases there's nothing we can do
* about the crash (e.g. it is an OS fault) and we would rather not have them
* pollute our reliability stats.
*/
private fun shouldAbsorb(exception: Throwable): Boolean {
return when (exception::class.simpleName) {
"CannotDeliverBroadcastException" -> true
else -> false
}
}
}
We have to operate off class name strings because the CannotDeliverBroadcastException class is hidden and not available to us.
We install this handler early in the Application.onCreate() method, like this:
val defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler(
CustomUncaughtExceptionHandler(defaultUncaughtExceptionHandler)
)
I might be a bit premature with this, but so far this has resulted in none of these crashes appearing in Play Console. A few did appear in our other reporting platform, where what is/isn't reported has always varied.
To be clear, I'm not suggesting this is a good approach, or one that you should necessarily take. It requires a client release and it risks masking exceptions not relating to this root cause. Fixing or ignoring this issue at the point of collection is Google's responsibility. However, it has seemingly stopped the impact on our headline reliability statistics, so I thought I'd share it as a possibility.
Our team has noticed a significant downtrend in this crash over the past few months. Possible Google has begun to roll out a fix for Pixel devices. We also have the same crash happening for Pixels running Android 13 and it's also seeing a downtrend. Hopefully others are seeing this as well.
I'm not sure if this will be helpful to anyone, but once we removed WorkManager (which was being initialized via the App Startup library) the crash stopped happening. This was removed alongside a bunch of other code, so I can't say for sure if WorkManager was the problem, if the App Startup library was the problem, or if something else that we removed fixed it.
I received a pre-launch report of my app showing the same problem:
android.app.RemoteServiceException$CannotDeliverBroadcastException: can't deliver broadcast
Device: google Redfin 64-bit only
Android version: Android 13 (SDK 33)
The solution proposed by Rob Pridham fixed the problem. Just in case someone prefers to use Java...
This is the CustomUncaughtExceptionHandler class added to MainActivity:
public static final class CustomUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private final Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
public void uncaughtException(#NotNull Thread thread, #NotNull Throwable exception) {
Intrinsics.checkNotNullParameter(thread, "thread");
Intrinsics.checkNotNullParameter(exception, "exception");
if (!this.shouldAbsorb(exception)) {
this.uncaughtExceptionHandler.uncaughtException(thread, exception);
}
}
private boolean shouldAbsorb(Throwable exception) {
String var10000 = Reflection.getOrCreateKotlinClass(exception.getClass()).getSimpleName();
if (var10000 != null) {
return "CannotDeliverBroadcastException".equals(var10000);
}
return false;
}
public CustomUncaughtExceptionHandler(#NotNull Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {
super();
Intrinsics.checkNotNullParameter(uncaughtExceptionHandler, "uncaughtExceptionHandler");
this.uncaughtExceptionHandler = uncaughtExceptionHandler;
}
}
This is the code added to Oncreate:
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
assert uncaughtExceptionHandler != null;
Thread.setDefaultUncaughtExceptionHandler(new CustomUncaughtExceptionHandler(uncaughtExceptionHandler));
Related
We're in this situation where our clients use our mobile application offline 95% of the time. At the end of their work day, when they get back to the office, they synchronize all data with our servers while they have network connectivity.
We have ACRA set up with the AcraHttpSender plugin to attempt sending us the crash reports directly, however this usually fails because they're using the application offline and ACRA stores the reports instead.
From what I understand the pending reports will only be sent by ACRA when the application is restarted, through ACRA.init. The problem is the users have no reason to restart the application at the end of their work day (while they have network connectivity). I have to stress that the users are complete tech illiterates, our clients made that clear to us.
So, we would really need to be able to tell ACRA to send us any pending crash reports it has during the short time network connectivity is available. Without user interaction of any kind. I was thinking maybe in the onCreate function of our main activity.
However I've been looking at documentation and other people asking the same question for a while and haven't found anything obvious. Is this possible?
EDIT: This is the current working code with the suggestion made by #F43nd1r and #CommonsWare. It wasn't working for me with 5.4.0, but with 5.5.1 it is.
Gradle
def acraVersion = '5.5.1'
implementation "ch.acra:acra-core-ktx:$acraVersion"
implementation "ch.acra:acra-http:$acraVersion"
implementation "ch.acra:acra-advanced-scheduler:$acraVersion"
implementation "ch.acra:acra-toast:$acraVersion"
Initialization
initAcra {
setBuildConfigClass(BuildConfig::class.java)
setReportFormat(StringFormat.JSON)
plugin<ToastConfigurationBuilder> {
setResText(R.string.acra_crash_text)
setLength(Toast.LENGTH_LONG)
setEnabled(true)
}
plugin<HttpSenderConfigurationBuilder> {
setUri("${BuildConfig.protocol}://${BuildConfig.host}/${BuildConfig.codemrc}/acra")
setHttpMethod(HttpSender.Method.POST)
setBasicAuthLogin("acra")
setBasicAuthPassword("******")
setEnabled(true)
}
plugin<SchedulerConfigurationBuilder> {
setRequiresNetworkType(JobInfo.NETWORK_TYPE_ANY)
setRestartAfterCrash(true)
setResReportSendSuccessToast(R.string.acra_report_sent_text)
setEnabled(true)
}
}
// Turn this on to obtain more messages in the log to debug ACRA
ACRA.DEV_LOGGING = BuildConfig.DEBUG
As #CommonsWare stated in the comments, AdvancedSenderScheduler is the way to go.
Example usage:
implementation "ch.acra:acra-advanced-scheduler:5.5.1"
#AcraScheduler(requiresNetworkType = JobInfo.NETWORK_TYPE_UNMETERED,
requiresBatteryNotLow = true)
In case you're not satisfied with AdvancedSenderScheduler options, you could also register your own SenderScheduler, but that should rarely be necessary.
Currently, we are experiencing a DeadSystemException in our HockeyApp crash reporting. It occurs on Android 7.0 and Android 7.1. We don't experience this exception in the previous version of our application (they are currently both used by users), so I guess this exception is caused by some code change. But stack trace is not very helpful for this. Any idea?
Thanks for any suggestions.
Stack trace from HockeyApp:
java.lang.RuntimeException: android.os.DeadSystemException
at android.app.ActivityThread$StopInfo.run(ActivityThread.java:3781)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
Caused by: android.os.DeadSystemException
... 8 more
The Android Developer docs for android.os.DeadSystemException says the following:
The core Android system has died and is going through a runtime
restart. All running apps will be promptly killed.
The source code does not help much more:
package android.os;
/**
* The core Android system has died and is going through a runtime restart. All
* running apps will be promptly killed.
*/
public class DeadSystemException extends DeadObjectException {
public DeadSystemException() {
super();
}
}
Overall, it looks like this is being thrown by the OS and has nothing to do with our code.
Looking at the JavaDoc from the superclass, DeadObjectException, backs this theory up:
The object you are calling has died because its hosting process no
longer exists.
One cause was a bug in the notification service of Android version 7 and 8.
It was caused by using "vibration pattern" in the notifications, which throws an ArrayOutOfBoundsException. This leads the whole system to crash and post a DeadSystemException.
For further details you can refer to this Medium article here.
Fatal Exception: java.lang.RuntimeException: android.os.DeadSystemException
This exception was caused in one of the apps I was developing, it occurred mostly in MI devices.
After debugging I found that I was trying to start another service (Say B) in the current service (Say A) from a background thread, but when startService(itService) method was called the service A was already killed.
The only solution I found till now is to check if the current service A is running or not before you start another service B. Depending on your implementation you can use one of the various ways to check if a services is running from this answer.
I was able to reproduce this exception in one of my apps.
It turns out that I misunderstood something while implementing in-app purchases, as I wasn't expecting Google to notify also the already purchased and acknowledged products, so I was not checking if I already managed the purchased product notified about.
So I had an infinite loop in my app consisting on reloading again and again and again the same information on screen, until eventually (but a little bit randomly I must say) this exception raises.
Caused by java.lang.RuntimeException
android.os.DeadSystemException
android.app.ApplicationPackageManager.getPackageInfoAsUser (ApplicationPackageManager.java:188)
android.app.ApplicationPackageManager.getPackageInfo (ApplicationPackageManager.java:159)
com.google.android.gms.common.GooglePlayServicesUtilLight.isGooglePlayServicesAvailable (GooglePlayServicesUtilLight.java:13)
com.google.android.gms.common.GoogleApiAvailabilityLight.isGooglePlayServicesAvailable (GoogleApiAvailabilityLight.java:2)
I hope this to be useful to someone, as it's info about how to try to reproduce the exception, not how to solve it (well, in my case was just fixing the silly error and preventing the infinite loop of screen reloading, but there must be variety of different causes producing this exception).
Another example :
try {
ActivityManager.getMyMemoryState(mAppProcessInfo);
} catch (Exception exception) {
exception.printStackTrace();
return;
}
Background
I try to use Google's new Firebase services, for A/B testing. For this, we need to use both Firebase Analytics and Firebase RemoteConfig.
The problem
Using FireBase RemoteConfig, I wanted to get the variables from the server (which have different value per variant of each experiment), but it seems that on some devices it gets stuck there, not calling its callback (OnCompleteListener.onComplete) .
I used about the same code as on the samples (here) :
// init:
boolean isDebug = ... ;
mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder().setDeveloperModeEnabled(isDebug).build();
mFirebaseRemoteConfig.setConfigSettings(configSettings);
final HashMap<String, Object> defaults = new HashMap<>();
for (...)
defaults.put(...);
mFirebaseRemoteConfig.setDefaults(defaults);
//fetching the variables:
long cacheExpiration = isDebug ? 0 : java.util.concurrent.TimeUnit.HOURS.toSeconds(1);
mFirebaseRemoteConfig.fetch(cacheExpiration).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
//on some devices, I never get here at all
if (task.isSuccessful()) {
mFirebaseRemoteConfig.activateFetched();
final FirebaseAnalytics firebaseAnalytics = FirebaseAnalytics.getInstance(context);
for (...) {
String experimentVariantValue = mFirebaseRemoteConfig.getString(...);
firebaseAnalytics.setUserProperty(..., experimentVariantValue);
}
} else {
}
}
});
Thing is, the callback doesn't get called, but only on some devices:
Nexus 5 with Android 6.0.1 : almost always succeeds.
Nexus 4 with Android 5.1.1 and LG G2 with Android 4.2.2 : almost always freeze (meaning they don't get into the callback)
I've also found that when it does work, it works in the near sessions afterwards.
The question
Why does it occur? What can I do to solve this?
Just to add to what Cachapa just posted, the bug happens when the fetch() is called too early. We found two solutions to the problem (both work but not satisfactory) - call the fetch() from an onResume, or add a 3 seconds delay before actually issuing the fetch().
And about the "it works in the near sessions afterwards", once you get the callback and call activateFetched(), all sessions will get the correct values.
Update
The 3 second delay was from the Activity.onCreate(). After checking further, it only worked on one device - Nexus 4. It didn't do the trick on a Samsung S3 nor on a Moto X Pure.
We also checked on a Samsung S7, there it worked without any delay - the problem never manifested at all.
We are discussing it in a mail correspondence with Firebase support. I will update here when they get back to me..
Update 2
Firebase team claim this is solved in GPSv9.4 and I think this time they're right. They also claimed it was solved in 9.3, but then my tests disproved it. Now (after updating our dependency on gms to v9.4), I get the callbacks correctly on my dev devices. However, I still get indications that not all of our production devices are getting the correct config. My assumption is that devices that didn't update the GPS to the latest version are still faulty, but I'm not sure..
It seems to be some sort of race condition in the Firebase initialization code, according to this answer: https://stackoverflow.com/a/37664946
I tried quite a few of the posted workarounds and nothing worked reliably enough for my satisfaction. It seems that the Firebase devs are aware of the problem though, so the problem will probably be fixed soon.
Why does it occur? What can I do to solve this?
I am guessing the reason why the callbacks do not get called is because Firebase Remote Config may have several issues and those are not solved just yet.
Below is a list of things that my team and I have found so far that may be considered as the issues of Remote Config.
fetch() does not call its callbacks if the method gets called too early after app initialization.
(FIXED in 9.2.1) Gradle build process failed with "minifyEnabled" - Proguard
Debug build and Release build may affect how Remote Config behaves..?
Above list is some of the issues regarding Remote Config that my teammates and I have found so far. The first two of them are from Google Search, and the last one, "Debug build and Release build may affect how Remote Config behaves..?" is from our observation after testing Remote Config for a while, so we are not sure about it yet.
I am not sure if it SOLVES your problem, but if your issue is related to the first issue of what I have listed above, due to fetch() is called too early, then you may try calling fetch() with postDelayed which we have tried and it made better chance for Remote Config to successfully call its listeners, but overall, in my personal opinion, Remote Config is just not fully ready yet for production release..
My Android app currently uses a custom UncaughtExceptionHandler that aims to capture any crash, and schedules an app restart for several seconds in the future with AlarmManager before manually calling Process.killProcess(Process.myPid()) to avoid Android's Force Close popup as in my app's use case, the user will not be able to interact with the device to tap "ok" on the FC dialog and restart the app.
Now, I'd like to integrate with Firebase Crash reports, but I fear wrong behaviors, so here are my questions:
How should I make my code so my custom UncaughtExceptionHandler passes the exception to Firebase Crash Report before killing it's process? Would calling Thread.getDefaultUncaughtExceptionHandler() give me the Firebase Crash report UncaughtExceptionHandler so I can just call uncaughtException(...) on it?
May Process.killProcess(Process.myPid()) prevent Firebase Crash reporting library to do it's reporting work? Would Firebase have started it's crash report in a separated process before it's uncaughtException(...) returns? Does Firebase own UncaughtExceptionHandler calls back to Android default's UncaughtExceptionHandler, showing the FC dialog?
May Process.killProcess(Process.myPid()) kill Firebase Crash Reporting process in addition to the default process?
How can my custom Application class detect if it is instantiated in Firebase Crash Reporting process? Treating both processes the same way would probably lead to inconsistent states.
Thanks to anyone that tries to help me!
If you kill the process in your exception handler, you will not be able to receive crashes. It will interfere with the ability to persist the crash for either immediate or delayed transmission. It will possibly interfere with any library that has registered uncaught exception handlers that behave well.
In fact, Process.killProcess(Process.myPid()) is very much an anti-pattern for Android development. Android apps should not be concerned at all with the overall lifecycle if the process that hosts the app. The fact that Android manages the process for you is an optimization designed for the benefit of the users.
I strongly recommend, for uncaught exceptions in your app, to simply let the app die as it normally would. Masking the proper effect of the crash is like sweeping dirt under a rug. You might be resolving a short term problem, but what really needs to happen is the normal logging and handling of the error so you can fix it.
Don't depend on the fact that Firebase Crash Reporting transmits exceptions in another process. That other process will be removed in the full non-beta release.
The best situation for your Application subclass is to not depend at all which process it's operating. In fact, the Android team at Google does not recommend use of Application subclasses at all since it only leads to trouble for multi-process apps. If you must use an Application subclass, it should expect to run within multiple processes.
After some testing, I finally found a way to both ensure my app restarts properly after an UncaughtException.
I attempted three different approaches, but only the first, which is my original code, with just a little tweak to pass the uncaught Throwable to `FirebaseCrash, and ensure it is considered as a FATAL error.
The code that works:
final UncaughtExceptionHandler crashShield = new UncaughtExceptionHandler() {
private static final int RESTART_APP_REQUEST = 2;
#Override
public void uncaughtException(Thread thread, Throwable ex) {
if (BuildConfig.DEBUG) ex.printStackTrace();
reportFatalCrash(ex);
restartApp(MyApp.this, 5000L);
}
private void reportFatalCrash(Throwable exception) {
FirebaseApp firebaseApp = FirebaseApp.getInstance();
if (firebaseApp != null) {
try {
FirebaseCrash.getInstance(firebaseApp)
.zzg(exception); // Reports the exception as fatal.
} catch (com.google.firebase.crash.internal.zzb zzb) {
Timber.wtf(zzb, "Internal firebase crash reporting error");
} catch (Throwable t) {
Timber.wtf(t, "Unknown error during firebase crash reporting");
}
} else Timber.wtf("no FirebaseApp!!");
}
/**
* Schedules an app restart with {#link AlarmManager} and exit the process.
* #param restartDelay in milliseconds. Min 3s to let the user got in settings force
* close the app manually if needed.
*/
private void restartApp(Context context, #IntRange(from = 3000) long restartDelay) {
Intent restartReceiver = new Intent(context, StartReceiver_.class)
.setAction(StartReceiver.ACTION_RESTART_AFTER_CRASH);
PendingIntent restartApp = PendingIntent.getBroadcast(
context,
RESTART_APP_REQUEST,
restartReceiver,
PendingIntent.FLAG_ONE_SHOT
);
final long now = SystemClock.elapsedRealtime();
// Line below schedules an app restart 5s from now.
mAlarmManager.set(ELAPSED_REALTIME_WAKEUP, now + restartDelay, restartApp);
Timber.i("just requested app restart, killing process");
System.exit(2);
}
};
Thread.setDefaultUncaughtExceptionHandler(crashShield);
Explanation of why and unsuccessful attempts
It's weird that the hypothetically named reportFatal(Throwable ex) method from FirebaseCrash class has it's name proguarded while being still (and thankfully) public, giving it the following signature: zzg(Throwable ex).
This method should stay public, but not being obfuscated IMHO.
To ensure my app works properly with multi-process introduced by Firebase Crash Report library, I had to move code away from the application class (which was a great thing) and put it in lazily loaded singletons instead, following Doug Stevenson's advice, and it is now multi-process ready.
You can see that nowhere in my code, I called/delegated to the default UncaughtExceptionHandler, which would be Firebase Crash Reporting one here. I didn't do so because it always calls the default one, which is Android's one, which has the following issue:
All code written after the line where I pass the exception to Android's default UncaughtExceptionHandler will never be executed, because the call is blocking, and process termination is the only thing that can happen after, except already running threads.
The only way to let the app die and restart is by killing the process programmatically with System.exit(int whatever) or Process.kill(Process.myPid()) after having scheduled a restart with AlarmManager in the very near future.
Given this, I started a new Thread before calling the default UncaughtExceptionHandler, which would kill the running process after Firebase Crash Reporting library would have got the exception but before the scheduled restart fires (requires magic numbers). It worked on the first time, removing the Force Close dialog when the background thread killed the process, and then, the AlarmManager waked up my app, letting it know that it crashed and has a chance to restart.
The problem is that the second time didn't worked for some obscure and absolutely undocumented reasons. The app would never restart even though the code that schedules a restart calling the AlarmManager was properly run.
Also, the Force Close popup would never show up. After seeing that whether Firebase Crash reporting was included (thus automatically enabled) or not didn't change anything about this behavior, it was tied to Android (I tested on a Kyocera KC-S701 running Android 4.4.2).
So I finally searched what Firebase own UncaughtExceptionHandler called to report the throwable and saw that I could call the code myself and manage myself how my app behaves on an uncaught Throwable.
How Firebase could improve such scenarios
Making the hypothetically named reportFatal(Throwable ex) method non name-obfuscated and documented, or letting us decide what happens after Firebase catches the Throwable in it's UncaughtExceptionHandler instead of delegating inflexibly to the dumb Android's default UncaughtExceptionHandler would help a lot.
It would allow developers which develop critical apps that run on Android to ensure their app keep running if the user is not able to it (think about medical monitoring apps, monitoring apps, etc).
It would also allow developers to launch a custom activity to ask users to explain how it occurred, with the ability to post screenshots, etc.
BTW, my app is meant to monitor humans well-being in critical situations, hence cannot tolerate to stop running. All exceptions must be recovered to ensure user safety.
I'm trying to disable the ACRA bug reporting library for a particular service. I've set up ACRA successfully for my app.
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
// The following line triggers the initialization of ACRA
ACRA.init(this);
}
}
One service uses a library that throws exceptions from time to time but, the situation is so rare that I have no intention of changing my code to catch them, since after crashing the service is restarted immediately and my users don't even notice a problem. Moreover the library will eventually be fixed.
Since I'm using ACRA, every exception is intercepted by ACRA. Is there any way to disable this behaviour for the problematic service only? That service is run as an independent process, doesn't communicate with anything, and the app doesn't suffer at all from its rare problems. I'm also not interested in tracking its bugs because they are not so important for the app's proper behaviour.
I was looking through ACRA documentation but, I didn't find anything helpful.
After reviewing the ACRA documentation and the codebase, it looks like this functionality is not available out-of-the-box.
I think your best bet is to implement your own sender, so you can write some simple logic to filter out all reports that match that library.