Skip to content

Commit

Permalink
Merge pull request #1233 from bugsnag/android-auto-notify
Browse files Browse the repository at this point in the history
Unity: add methods for setting autoNotify and autoDetectAnrs
  • Loading branch information
fractalwrench authored May 4, 2021
2 parents 7252596 + c08e2a4 commit dd7f804
Show file tree
Hide file tree
Showing 25 changed files with 552 additions and 63 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## TBD

### Enhancements

* Unity: add methods for setting autoNotify and autoDetectAnrs
[#1233](https://github.com/bugsnag/bugsnag-android/pull/1233)

## 5.9.1 (2021-04-22)

### Bug fixes
Expand Down
46 changes: 36 additions & 10 deletions bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
final Logger logger;
final DeliveryDelegate deliveryDelegate;

final ClientObservable clientObservable = new ClientObservable();
final ClientObservable clientObservable;
private PluginClient pluginClient;

final Notifier notifier = new Notifier();
Expand All @@ -88,6 +88,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
final LastRunInfoStore lastRunInfoStore;
final LaunchCrashTracker launchCrashTracker;
final BackgroundTaskService bgTaskService = new BackgroundTaskService();
private final ExceptionHandler exceptionHandler;

/**
* Initialize a Bugsnag client
Expand Down Expand Up @@ -137,6 +138,7 @@ public Unit invoke(Boolean hasConnection, String networkState) {
immutableConfig = sanitiseConfiguration(appContext, configuration, connectivity);
logger = immutableConfig.getLogger();
warnIfNotAppContext(androidContext);
clientObservable = new ClientObservable();

// Set up breadcrumbs
callbackState = configuration.impl.callbackState.copy();
Expand Down Expand Up @@ -209,8 +211,9 @@ public Unit invoke(String activity, Map<String, ?> metadata) {
immutableConfig, breadcrumbState, notifier, bgTaskService);

// Install a default exception handler with this client
exceptionHandler = new ExceptionHandler(this, logger);
if (immutableConfig.getEnabledErrorTypes().getUnhandledExceptions()) {
new ExceptionHandler(this, logger);
exceptionHandler.install();
}

// register a receiver for automatic breadcrumbs
Expand Down Expand Up @@ -245,6 +248,7 @@ public Unit invoke(String activity, Map<String, ?> metadata) {
ContextState contextState,
CallbackState callbackState,
UserState userState,
ClientObservable clientObservable,
Context appContext,
@NonNull DeviceDataCollector deviceDataCollector,
@NonNull AppDataCollector appDataCollector,
Expand All @@ -260,13 +264,15 @@ public Unit invoke(String activity, Map<String, ?> metadata) {
Logger logger,
DeliveryDelegate deliveryDelegate,
LastRunInfoStore lastRunInfoStore,
LaunchCrashTracker launchCrashTracker
LaunchCrashTracker launchCrashTracker,
ExceptionHandler exceptionHandler
) {
this.immutableConfig = immutableConfig;
this.metadataState = metadataState;
this.contextState = contextState;
this.callbackState = callbackState;
this.userState = userState;
this.clientObservable = clientObservable;
this.appContext = appContext;
this.deviceDataCollector = deviceDataCollector;
this.appDataCollector = appDataCollector;
Expand All @@ -284,6 +290,7 @@ public Unit invoke(String activity, Map<String, ?> metadata) {
this.lastRunInfoStore = lastRunInfoStore;
this.launchCrashTracker = launchCrashTracker;
this.lastRunInfo = null;
this.exceptionHandler = exceptionHandler;
}

private LastRunInfo loadLastRunInfo() {
Expand Down Expand Up @@ -365,6 +372,17 @@ void registerObserver(Observer observer) {
launchCrashTracker.addObserver(observer);
}

void unregisterObserver(Observer observer) {
metadataState.deleteObserver(observer);
breadcrumbState.deleteObserver(observer);
sessionTracker.deleteObserver(observer);
clientObservable.deleteObserver(observer);
userState.deleteObserver(observer);
contextState.deleteObserver(observer);
deliveryDelegate.deleteObserver(observer);
launchCrashTracker.deleteObserver(observer);
}

/**
* Sends initial state values for Metadata/User/Context to any registered observers.
*/
Expand Down Expand Up @@ -985,13 +1003,7 @@ Logger getLogger() {
@SuppressWarnings("rawtypes")
@Nullable
Plugin getPlugin(@NonNull Class clz) {
Set<Plugin> plugins = pluginClient.getPlugins();
for (Plugin plugin : plugins) {
if (plugin.getClass().equals(clz)) {
return plugin;
}
}
return null;
return pluginClient.findPlugin(clz);
}

Notifier getNotifier() {
Expand All @@ -1001,4 +1013,18 @@ Notifier getNotifier() {
MetadataState getMetadataState() {
return metadataState;
}

void setAutoNotify(boolean autoNotify) {
pluginClient.setAutoNotify(this, autoNotify);

if (autoNotify) {
exceptionHandler.install();
} else {
exceptionHandler.uninstall();
}
}

void setAutoDetectAnrs(boolean autoDetectAnrs) {
pluginClient.setAutoDetectAnrs(this, autoDetectAnrs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,16 @@ class ExceptionHandler implements UncaughtExceptionHandler {
this.client = client;
this.logger = logger;
this.originalHandler = Thread.getDefaultUncaughtExceptionHandler();
}

void install() {
Thread.setDefaultUncaughtExceptionHandler(this);
}

void uninstall() {
Thread.setDefaultUncaughtExceptionHandler(originalHandler);
}

@Override
public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) {
boolean strictModeThrowable = strictModeHandler.isStrictModeThrowable(throwable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

class LibraryLoader {

private AtomicBoolean attemptedLoad = new AtomicBoolean();
private final AtomicBoolean attemptedLoad = new AtomicBoolean();
private boolean loaded = false;

/**
* Attempts to load a native library, returning false if the load was unsuccessful.
Expand All @@ -21,11 +22,16 @@ boolean loadLibrary(String name, Client client, OnErrorCallback callback) {
if (!attemptedLoad.getAndSet(true)) {
try {
System.loadLibrary(name);
loaded = true;
return true;
} catch (UnsatisfiedLinkError error) {
client.notify(error, callback);
}
}
return false;
}

boolean isLoaded() {
return loaded;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -405,11 +405,23 @@ public static Logger getLogger() {
return getClient().getConfig().getLogger();
}

/**
* Switches automatic error detection on/off after Bugsnag has initialized.
* This is required to support legacy functionality in Unity.
*
* @param autoNotify whether errors should be automatically detected.
*/
public static void setAutoNotify(boolean autoNotify) {
// TODO implement me
getClient().setAutoNotify(autoNotify);
}

/**
* Switches automatic ANR detection on/off after Bugsnag has initialized.
* This is required to support legacy functionality in Unity.
*
* @param autoDetectAnrs whether ANRs should be automatically detected.
*/
public static void setAutoDetectAnrs(boolean autoDetectAnrs) {
// TODO implement me
getClient().setAutoDetectAnrs(autoDetectAnrs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,31 @@ package com.bugsnag.android

internal class PluginClient(
userPlugins: Set<Plugin>,
immutableConfig: ImmutableConfig,
private val immutableConfig: ImmutableConfig,
private val logger: Logger
) {

protected val plugins: Set<Plugin>
companion object {
private const val NDK_PLUGIN = "com.bugsnag.android.NdkPlugin"
private const val ANR_PLUGIN = "com.bugsnag.android.AnrPlugin"
private const val RN_PLUGIN = "com.bugsnag.android.BugsnagReactNativePlugin"
}

private val plugins: Set<Plugin>

private val ndkPlugin = instantiatePlugin(NDK_PLUGIN)
private val anrPlugin = instantiatePlugin(ANR_PLUGIN)
private val rnPlugin = instantiatePlugin(RN_PLUGIN)

init {
val set = mutableSetOf<Plugin>()
set.addAll(userPlugins)

// instantiate ANR + NDK plugins by reflection as bugsnag-android-core has no
// direct dependency on the artefacts
if (immutableConfig.enabledErrorTypes.ndkCrashes) {
instantiatePlugin("com.bugsnag.android.NdkPlugin")?.let { set.add(it) }
}
if (immutableConfig.enabledErrorTypes.anrs) {
instantiatePlugin("com.bugsnag.android.AnrPlugin")?.let { set.add(it) }
}
instantiatePlugin("com.bugsnag.android.BugsnagReactNativePlugin")?.let { set.add(it) }
ndkPlugin?.let(set::add)
anrPlugin?.let(set::add)
rnPlugin?.let(set::add)
plugins = set.toSet()
}

Expand All @@ -37,11 +43,51 @@ internal class PluginClient(
}
}

fun loadPlugins(client: Client) = plugins.forEach {
try {
it.load(client)
} catch (exc: Throwable) {
logger.e("Failed to load plugin $it, continuing with initialisation.", exc)
fun loadPlugins(client: Client) {
plugins.forEach { plugin ->
try {
loadPluginInternal(plugin, client)
} catch (exc: Throwable) {
logger.e("Failed to load plugin $plugin, continuing with initialisation.", exc)
}
}
}

fun setAutoNotify(client: Client, autoNotify: Boolean) {
setAutoDetectAnrs(client, autoNotify)

if (autoNotify) {
ndkPlugin?.load(client)
} else {
ndkPlugin?.unload()
}
}

fun setAutoDetectAnrs(client: Client, autoDetectAnrs: Boolean) {
if (autoDetectAnrs) {
anrPlugin?.load(client)
} else {
anrPlugin?.unload()
}
}

fun findPlugin(clz: Class<*>): Plugin? = plugins.find { it.javaClass == clz }

private fun loadPluginInternal(plugin: Plugin, client: Client) {
val name = plugin.javaClass.name
val errorTypes = immutableConfig.enabledErrorTypes

// only initialize NDK/ANR plugins if automatic detection enabled
if (name == NDK_PLUGIN) {
if (errorTypes.ndkCrashes) {
plugin.load(client)
}
} else if (name == ANR_PLUGIN) {
if (errorTypes.anrs) {
plugin.load(client)
}
} else {
plugin.load(client)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,9 @@ public void metadataValid() {
public void dateValid() {
assertEquals(new Date(0).getTime(), crumb.getTimestamp().getTime());
}

@Test
public void stringDateValid() {
assertEquals("1970-01-01T00:00:00.000Z", crumb.getStringTimestamp());
}
}
Loading

0 comments on commit dd7f804

Please sign in to comment.