mirror of https://github.com/M66B/FairEmail.git
Updated Bugsnag
This commit is contained in:
parent
21d8652947
commit
7202865777
|
@ -268,7 +268,7 @@ dependencies {
|
|||
def dnsjava_version = "2.1.9"
|
||||
def openpgp_version = "12.0"
|
||||
def badge_version = "1.1.22"
|
||||
def bugsnag_version = "5.9.2"
|
||||
def bugsnag_version = "5.9.4"
|
||||
def biweekly_version = "0.6.6"
|
||||
def relinker_version = "1.4.3"
|
||||
def markwon_version = "4.6.2"
|
||||
|
|
|
@ -6,7 +6,6 @@ import android.content.pm.ApplicationInfo
|
|||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.SystemClock
|
||||
import java.util.HashMap
|
||||
|
||||
/**
|
||||
* Collects various data on the application state
|
||||
|
@ -31,13 +30,19 @@ internal class AppDataCollector(
|
|||
private val releaseStage = config.releaseStage
|
||||
private val versionName = config.appVersion ?: packageInfo?.versionName
|
||||
|
||||
fun generateApp(): App = App(config, binaryArch, packageName, releaseStage, versionName, codeBundleId)
|
||||
fun generateApp(): App =
|
||||
App(config, binaryArch, packageName, releaseStage, versionName, codeBundleId)
|
||||
|
||||
fun generateAppWithState(): AppWithState = AppWithState(
|
||||
config, binaryArch, packageName, releaseStage, versionName, codeBundleId,
|
||||
getDurationMs(), calculateDurationInForeground(), sessionTracker.isInForeground,
|
||||
launchCrashTracker.isLaunching()
|
||||
)
|
||||
fun generateAppWithState(): AppWithState {
|
||||
val inForeground = sessionTracker.isInForeground
|
||||
val durationInForeground = calculateDurationInForeground(inForeground)
|
||||
|
||||
return AppWithState(
|
||||
config, binaryArch, packageName, releaseStage, versionName, codeBundleId,
|
||||
getDurationMs(), durationInForeground, inForeground,
|
||||
launchCrashTracker.isLaunching()
|
||||
)
|
||||
}
|
||||
|
||||
fun getAppDataMetadata(): MutableMap<String, Any?> {
|
||||
val map = HashMap<String, Any?>()
|
||||
|
@ -102,9 +107,21 @@ internal class AppDataCollector(
|
|||
*
|
||||
* @return the duration in ms
|
||||
*/
|
||||
internal fun calculateDurationInForeground(): Long? {
|
||||
internal fun calculateDurationInForeground(inForeground: Boolean? = sessionTracker.isInForeground): Long? {
|
||||
if (inForeground == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val nowMs = System.currentTimeMillis()
|
||||
return sessionTracker.getDurationInForegroundMs(nowMs)
|
||||
var durationMs: Long = 0
|
||||
|
||||
val sessionStartTimeMs: Long = sessionTracker.lastEnteredForegroundMs
|
||||
|
||||
if (inForeground && sessionStartTimeMs != 0L) {
|
||||
durationMs = nowMs - sessionStartTimeMs
|
||||
}
|
||||
|
||||
return if (durationMs > 0) durationMs else 0
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -94,6 +94,11 @@ public class Breadcrumb implements JsonStream.Streamable {
|
|||
return impl.getTimestamp();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
String getStringTimestamp() {
|
||||
return DateUtils.toIso8601(impl.getTimestamp());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toStream(@NonNull JsonStream stream) throws IOException {
|
||||
impl.toStream(stream);
|
||||
|
|
|
@ -19,6 +19,7 @@ import androidx.annotation.Nullable;
|
|||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import kotlin.Unit;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
import kotlin.jvm.functions.Function2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -83,7 +84,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();
|
||||
|
@ -93,6 +94,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
|
||||
|
@ -142,6 +144,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
|
|||
immutableConfig = sanitiseConfiguration(appContext, configuration, connectivity);
|
||||
logger = immutableConfig.getLogger();
|
||||
warnIfNotAppContext(androidContext);
|
||||
clientObservable = new ClientObservable();
|
||||
|
||||
// Set up breadcrumbs
|
||||
callbackState = configuration.impl.callbackState.copy();
|
||||
|
@ -213,14 +216,16 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
|
|||
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
|
||||
systemBroadcastReceiver = SystemBroadcastReceiver.register(this, logger, bgTaskService);
|
||||
|
||||
registerOrientationChangeListener();
|
||||
registerMemoryTrimListener();
|
||||
|
||||
// load last run info
|
||||
lastRunInfoStore = new LastRunInfoStore(immutableConfig);
|
||||
|
@ -249,6 +254,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
|
|||
ContextState contextState,
|
||||
CallbackState callbackState,
|
||||
UserState userState,
|
||||
ClientObservable clientObservable,
|
||||
Context appContext,
|
||||
@NonNull DeviceDataCollector deviceDataCollector,
|
||||
@NonNull AppDataCollector appDataCollector,
|
||||
|
@ -264,13 +270,15 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
|
|||
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;
|
||||
|
@ -288,6 +296,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
|
|||
this.lastRunInfoStore = lastRunInfoStore;
|
||||
this.launchCrashTracker = launchCrashTracker;
|
||||
this.lastRunInfo = null;
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
}
|
||||
|
||||
private LastRunInfo loadLastRunInfo() {
|
||||
|
@ -350,6 +359,18 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
|
|||
ContextExtensionsKt.registerReceiverSafe(appContext, receiver, configFilter, logger);
|
||||
}
|
||||
|
||||
private void registerMemoryTrimListener() {
|
||||
appContext.registerComponentCallbacks(new ClientComponentCallbacks(
|
||||
new Function1<Boolean, Unit>() {
|
||||
@Override
|
||||
public Unit invoke(Boolean isLowMemory) {
|
||||
clientObservable.postMemoryTrimEvent(isLowMemory);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
void setupNdkPlugin() {
|
||||
String lastRunInfoPath = lastRunInfoStore.getFile().getAbsolutePath();
|
||||
int crashes = (lastRunInfo != null) ? lastRunInfo.getConsecutiveLaunchCrashes() : 0;
|
||||
|
@ -369,6 +390,17 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
|
|||
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.
|
||||
*/
|
||||
|
@ -990,13 +1022,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
|
|||
@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() {
|
||||
|
@ -1006,4 +1032,18 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package com.bugsnag.android
|
||||
|
||||
import android.content.ComponentCallbacks
|
||||
import android.content.res.Configuration
|
||||
|
||||
internal class ClientComponentCallbacks(
|
||||
val callback: (Boolean) -> Unit
|
||||
) : ComponentCallbacks {
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {}
|
||||
|
||||
override fun onLowMemory() {
|
||||
callback(true)
|
||||
}
|
||||
}
|
|
@ -6,6 +6,10 @@ internal class ClientObservable : BaseObservable() {
|
|||
notifyObservers(StateEvent.UpdateOrientation(orientation))
|
||||
}
|
||||
|
||||
fun postMemoryTrimEvent(isLowMemory: Boolean) {
|
||||
notifyObservers(StateEvent.UpdateMemoryTrimEvent(isLowMemory))
|
||||
}
|
||||
|
||||
fun postNdkInstall(conf: ImmutableConfig, lastRunInfoPath: String, consecutiveLaunchCrashes: Int) {
|
||||
notifyObservers(
|
||||
StateEvent.Install(
|
||||
|
|
|
@ -85,8 +85,7 @@ internal class DeviceDataCollector(
|
|||
|
||||
fun getDeviceMetadata(): Map<String, Any?> {
|
||||
val map = HashMap<String, Any?>()
|
||||
map["batteryLevel"] = getBatteryLevel()
|
||||
map["charging"] = isCharging()
|
||||
populateBatteryInfo(into = map)
|
||||
map["locationStatus"] = getLocationStatus()
|
||||
map["networkAccess"] = getNetworkAccess()
|
||||
map["brand"] = buildInfo.brand
|
||||
|
@ -126,41 +125,31 @@ internal class DeviceDataCollector(
|
|||
private fun getScreenDensityDpi(): Int? = displayMetrics?.densityDpi
|
||||
|
||||
/**
|
||||
* Get the current battery charge level, eg 0.3
|
||||
* Populate the current Battery Info into the specified MutableMap
|
||||
*/
|
||||
private fun getBatteryLevel(): Float? {
|
||||
private fun populateBatteryInfo(into: MutableMap<String, Any?>) {
|
||||
try {
|
||||
val ifilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
|
||||
val batteryStatus = appContext.registerReceiverSafe(null, ifilter, logger)
|
||||
|
||||
if (batteryStatus != null) {
|
||||
return batteryStatus.getIntExtra(
|
||||
"level",
|
||||
-1
|
||||
) / batteryStatus.getIntExtra("scale", -1).toFloat()
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
logger.w("Could not get batteryLevel")
|
||||
}
|
||||
return null
|
||||
}
|
||||
val level = batteryStatus.getIntExtra("level", -1)
|
||||
val scale = batteryStatus.getIntExtra("scale", -1)
|
||||
|
||||
/**
|
||||
* Is the device currently charging/full battery?
|
||||
*/
|
||||
private fun isCharging(): Boolean? {
|
||||
try {
|
||||
val ifilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
|
||||
val batteryStatus = appContext.registerReceiverSafe(null, ifilter, logger)
|
||||
if (level != -1 || scale != -1) {
|
||||
val batteryLevel: Float = level.toFloat() / scale.toFloat()
|
||||
into["batteryLevel"] = batteryLevel
|
||||
}
|
||||
|
||||
if (batteryStatus != null) {
|
||||
val status = batteryStatus.getIntExtra("status", -1)
|
||||
return status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL
|
||||
val charging =
|
||||
status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL
|
||||
|
||||
into["charging"] = charging
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
logger.w("Could not get charging status")
|
||||
logger.w("Could not get battery status")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -4,7 +4,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
|
||||
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.
|
||||
|
@ -21,6 +22,7 @@ class LibraryLoader {
|
|||
if (!attemptedLoad.getAndSet(true)) {
|
||||
try {
|
||||
System.loadLibrary(name);
|
||||
loaded = true;
|
||||
return true;
|
||||
} catch (UnsatisfiedLinkError error) {
|
||||
client.notify(error, callback);
|
||||
|
@ -28,4 +30,8 @@ class LibraryLoader {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isLoaded() {
|
||||
return loaded;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -404,4 +404,24 @@ public class NativeInterface {
|
|||
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) {
|
||||
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) {
|
||||
getClient().setAutoDetectAnrs(autoDetectAnrs);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import java.io.IOException
|
|||
*/
|
||||
class Notifier @JvmOverloads constructor(
|
||||
var name: String = "Android Bugsnag Notifier",
|
||||
var version: String = "5.9.3",
|
||||
var version: String = "5.9.4",
|
||||
var url: String = "https://bugsnag.com"
|
||||
) : JsonStream.Streamable {
|
||||
|
||||
|
|
|
@ -2,11 +2,21 @@ 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>()
|
||||
|
@ -14,13 +24,9 @@ internal class PluginClient(
|
|||
|
||||
// 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()
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -369,21 +369,8 @@ class SessionTracker extends BaseObservable {
|
|||
return foregroundDetector.isInForeground();
|
||||
}
|
||||
|
||||
//FUTURE:SM This shouldnt be here
|
||||
@Nullable
|
||||
Long getDurationInForegroundMs(long nowMs) {
|
||||
long durationMs = 0;
|
||||
long sessionStartTimeMs = lastEnteredForegroundMs.get();
|
||||
|
||||
Boolean inForeground = isInForeground();
|
||||
|
||||
if (inForeground == null) {
|
||||
return null;
|
||||
}
|
||||
if (inForeground && sessionStartTimeMs != 0) {
|
||||
durationMs = nowMs - sessionStartTimeMs;
|
||||
}
|
||||
return durationMs > 0 ? durationMs : 0;
|
||||
long getLastEnteredForegroundMs() {
|
||||
return lastEnteredForegroundMs.get();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
|
|
@ -42,4 +42,6 @@ sealed class StateEvent {
|
|||
class UpdateOrientation(val orientation: String?) : StateEvent()
|
||||
|
||||
class UpdateUser(val user: User) : StateEvent()
|
||||
|
||||
class UpdateMemoryTrimEvent(val isLowMemory: Boolean) : StateEvent()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue