diff --git a/app/build.gradle b/app/build.gradle
index b26827dee1..123bf3d55d 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -291,7 +291,7 @@ dependencies {
def dnsjava_version = "2.1.9"
def openpgp_version = "12.0"
def badge_version = "1.1.22"
- def bugsnag_version = "5.9.4"
+ def bugsnag_version = "5.11.0"
def biweekly_version = "0.6.6"
def relinker_version = "1.4.3"
def markwon_version = "4.6.2"
diff --git a/app/src/main/java/com/bugsnag/android/BugsnagStateModule.kt b/app/src/main/java/com/bugsnag/android/BugsnagStateModule.kt
new file mode 100644
index 0000000000..50377ad59c
--- /dev/null
+++ b/app/src/main/java/com/bugsnag/android/BugsnagStateModule.kt
@@ -0,0 +1,36 @@
+package com.bugsnag.android
+
+import com.bugsnag.android.internal.dag.ConfigModule
+import com.bugsnag.android.internal.dag.DependencyModule
+
+/**
+ * A dependency module which constructs the objects that track state in Bugsnag. For example, this
+ * class is responsible for creating classes which track the current breadcrumb/metadata state.
+ */
+internal class BugsnagStateModule(
+ configModule: ConfigModule,
+ configuration: Configuration
+) : DependencyModule() {
+
+ private val cfg = configModule.config
+
+ val clientObservable = ClientObservable()
+
+ val callbackState = configuration.impl.callbackState.copy()
+
+ val contextState = ContextState().apply {
+ if (configuration.context != null) {
+ setManualContext(configuration.context)
+ }
+ }
+
+ val breadcrumbState = BreadcrumbState(cfg.maxBreadcrumbs, callbackState, cfg.logger)
+
+ val metadataState = copyMetadataState(configuration)
+
+ private fun copyMetadataState(configuration: Configuration): MetadataState {
+ // performs deep copy of metadata to preserve immutability of Configuration interface
+ val orig = configuration.impl.metadataState.metadata
+ return configuration.impl.metadataState.copy(metadata = orig.copy())
+ }
+}
diff --git a/app/src/main/java/com/bugsnag/android/BugsnagThreadViolationListener.java b/app/src/main/java/com/bugsnag/android/BugsnagThreadViolationListener.java
new file mode 100644
index 0000000000..22969f4b59
--- /dev/null
+++ b/app/src/main/java/com/bugsnag/android/BugsnagThreadViolationListener.java
@@ -0,0 +1,52 @@
+package com.bugsnag.android;
+
+import android.os.Build;
+import android.os.StrictMode.OnThreadViolationListener;
+import android.os.strictmode.Violation;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+/**
+ * Sends an error report to Bugsnag for each StrictMode thread policy violation that occurs in
+ * your app.
+ *
+ * You should use this class by instantiating Bugsnag in the normal way and then set the
+ * StrictMode policy with
+ * {@link android.os.StrictMode.ThreadPolicy.Builder#penaltyListener
+ * (Executor, OnThreadViolationListener)}.
+ * This functionality is only supported on API 28+.
+ */
+@RequiresApi(api = Build.VERSION_CODES.P)
+public class BugsnagThreadViolationListener implements OnThreadViolationListener {
+
+ private final Client client;
+ private final OnThreadViolationListener listener;
+
+ public BugsnagThreadViolationListener() {
+ this(Bugsnag.getClient(), null);
+ }
+
+ public BugsnagThreadViolationListener(@NonNull Client client) {
+ this(client, null);
+ }
+
+ public BugsnagThreadViolationListener(@NonNull Client client,
+ @Nullable OnThreadViolationListener listener) {
+ this.client = client;
+ this.listener = listener;
+ }
+
+ @Override
+ public void onThreadViolation(@NonNull Violation violation) {
+ if (client != null) {
+ client.notify(violation, new StrictModeOnErrorCallback(
+ "StrictMode policy violation detected: ThreadPolicy"
+ ));
+ }
+ if (listener != null) {
+ listener.onThreadViolation(violation);
+ }
+ }
+}
diff --git a/app/src/main/java/com/bugsnag/android/BugsnagVmViolationListener.java b/app/src/main/java/com/bugsnag/android/BugsnagVmViolationListener.java
new file mode 100644
index 0000000000..74faed3e18
--- /dev/null
+++ b/app/src/main/java/com/bugsnag/android/BugsnagVmViolationListener.java
@@ -0,0 +1,52 @@
+package com.bugsnag.android;
+
+import android.os.Build;
+import android.os.StrictMode.OnVmViolationListener;
+import android.os.strictmode.Violation;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+/**
+ * Sends an error report to Bugsnag for each StrictMode VM policy violation that occurs in
+ * your app.
+ *
+ * You should use this class by instantiating Bugsnag in the normal way and then set the
+ * StrictMode policy with
+ * {@link android.os.StrictMode.VmPolicy.Builder#penaltyListener
+ * (Executor, OnVmViolationListener)}.
+ * This functionality is only supported on API 28+.
+ */
+@RequiresApi(api = Build.VERSION_CODES.P)
+public class BugsnagVmViolationListener implements OnVmViolationListener {
+
+ private final Client client;
+ private final OnVmViolationListener listener;
+
+ public BugsnagVmViolationListener() {
+ this(Bugsnag.getClient(), null);
+ }
+
+ public BugsnagVmViolationListener(@NonNull Client client) {
+ this(client, null);
+ }
+
+ public BugsnagVmViolationListener(@NonNull Client client,
+ @Nullable OnVmViolationListener listener) {
+ this.client = client;
+ this.listener = listener;
+ }
+
+ @Override
+ public void onVmViolation(@NonNull Violation violation) {
+ if (client != null) {
+ client.notify(violation, new StrictModeOnErrorCallback(
+ "StrictMode policy violation detected: VmPolicy"
+ ));
+ }
+ if (listener != null) {
+ listener.onVmViolation(violation);
+ }
+ }
+}
diff --git a/app/src/main/java/com/bugsnag/android/Client.java b/app/src/main/java/com/bugsnag/android/Client.java
index 205e42c165..bdc21e2e38 100644
--- a/app/src/main/java/com/bugsnag/android/Client.java
+++ b/app/src/main/java/com/bugsnag/android/Client.java
@@ -1,19 +1,15 @@
package com.bugsnag.android;
-import static com.bugsnag.android.ContextExtensionsKt.getActivityManagerFrom;
-import static com.bugsnag.android.ContextExtensionsKt.getStorageManagerFrom;
import static com.bugsnag.android.SeverityReason.REASON_HANDLED_EXCEPTION;
-import static com.bugsnag.android.internal.ImmutableConfigKt.sanitiseConfiguration;
import com.bugsnag.android.internal.ImmutableConfig;
import com.bugsnag.android.internal.StateObserver;
+import com.bugsnag.android.internal.dag.ConfigModule;
+import com.bugsnag.android.internal.dag.ContextModule;
+import com.bugsnag.android.internal.dag.SystemServiceModule;
-import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
-import android.content.res.Resources;
-import android.os.Environment;
-import android.os.storage.StorageManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -23,12 +19,14 @@ import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.functions.Function2;
+import java.io.File;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.Callable;
import java.util.concurrent.RejectedExecutionException;
/**
@@ -67,24 +65,16 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
@NonNull
protected final EventStore eventStore;
- private final SessionStore sessionStore;
-
final SessionTracker sessionTracker;
final SystemBroadcastReceiver systemBroadcastReceiver;
- private final ActivityBreadcrumbCollector activityBreadcrumbCollector;
- private final SessionLifecycleCallback sessionLifecycleCallback;
-
- final Connectivity connectivity;
-
- @Nullable
- private final StorageManager storageManager;
final Logger logger;
+ final Connectivity connectivity;
final DeliveryDelegate deliveryDelegate;
final ClientObservable clientObservable;
- private PluginClient pluginClient;
+ PluginClient pluginClient;
final Notifier notifier = new Notifier();
@@ -121,8 +111,8 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
* @param configuration a configuration for the Client
*/
public Client(@NonNull Context androidContext, @NonNull final Configuration configuration) {
- Context ctx = androidContext.getApplicationContext();
- appContext = ctx != null ? ctx : androidContext;
+ ContextModule contextModule = new ContextModule(androidContext);
+ appContext = contextModule.getCtx();
connectivity = new ConnectivityCompat(appContext, new Function2() {
@Override
@@ -140,78 +130,53 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
});
// set sensible defaults for delivery/project packages etc if not set
- immutableConfig = sanitiseConfiguration(appContext, configuration, connectivity);
+ ConfigModule configModule = new ConfigModule(contextModule, configuration, connectivity);
+ immutableConfig = configModule.getConfig();
logger = immutableConfig.getLogger();
warnIfNotAppContext(androidContext);
- clientObservable = new ClientObservable();
-
- // Set up breadcrumbs
- callbackState = configuration.impl.callbackState.copy();
- int maxBreadcrumbs = immutableConfig.getMaxBreadcrumbs();
- breadcrumbState = new BreadcrumbState(maxBreadcrumbs, callbackState, logger);
-
- storageManager = getStorageManagerFrom(appContext);
- contextState = new ContextState();
-
- if (configuration.getContext() != null) {
- contextState.setManualContext(configuration.getContext());
- }
-
- sessionStore = new SessionStore(immutableConfig, logger, null);
- sessionTracker = new SessionTracker(immutableConfig, callbackState, this,
- sessionStore, logger, bgTaskService);
- metadataState = copyMetadataState(configuration);
-
- ActivityManager am = getActivityManagerFrom(appContext);
- launchCrashTracker = new LaunchCrashTracker(immutableConfig);
- appDataCollector = new AppDataCollector(appContext, appContext.getPackageManager(),
- immutableConfig, sessionTracker, am, launchCrashTracker, logger);
+ // setup storage as soon as possible
+ final StorageModule storageModule = new StorageModule(appContext,
+ immutableConfig, logger);
+
+ // setup state trackers for bugsnag
+ BugsnagStateModule bugsnagStateModule = new BugsnagStateModule(
+ configModule, configuration);
+ clientObservable = bugsnagStateModule.getClientObservable();
+ callbackState = bugsnagStateModule.getCallbackState();
+ breadcrumbState = bugsnagStateModule.getBreadcrumbState();
+ contextState = bugsnagStateModule.getContextState();
+ metadataState = bugsnagStateModule.getMetadataState();
+
+ // lookup system services
+ final SystemServiceModule systemServiceModule = new SystemServiceModule(contextModule);
+
+ // block until storage module has resolved everything
+ storageModule.resolveDependencies(bgTaskService, TaskType.IO);
+
+ // setup further state trackers and data collection
+ TrackerModule trackerModule = new TrackerModule(configModule,
+ storageModule, this, bgTaskService, callbackState);
+ launchCrashTracker = trackerModule.getLaunchCrashTracker();
+ sessionTracker = trackerModule.getSessionTracker();
+
+ DataCollectionModule dataCollectionModule = new DataCollectionModule(contextModule,
+ configModule, systemServiceModule, trackerModule,
+ bgTaskService, connectivity, storageModule.getDeviceId());
+ dataCollectionModule.resolveDependencies(bgTaskService, TaskType.IO);
+ appDataCollector = dataCollectionModule.getAppDataCollector();
+ deviceDataCollector = dataCollectionModule.getDeviceDataCollector();
// load the device + user information
- SharedPrefMigrator sharedPrefMigrator = new SharedPrefMigrator(appContext);
- DeviceIdStore deviceIdStore = new DeviceIdStore(appContext, sharedPrefMigrator, logger);
- String deviceId = deviceIdStore.loadDeviceId();
- UserStore userStore = new UserStore(immutableConfig, deviceId, sharedPrefMigrator, logger);
- userState = userStore.load(configuration.getUser());
- sharedPrefMigrator.deleteLegacyPrefs();
-
- DeviceBuildInfo info = DeviceBuildInfo.Companion.defaultInfo();
- Resources resources = appContext.getResources();
- deviceDataCollector = new DeviceDataCollector(connectivity, appContext,
- resources, deviceId, info, Environment.getDataDirectory(),
- new RootDetector(logger), bgTaskService, logger);
+ userState = storageModule.getUserStore().load(configuration.getUser());
+ storageModule.getSharedPrefMigrator().deleteLegacyPrefs();
- if (appContext instanceof Application) {
- Application application = (Application) appContext;
- sessionLifecycleCallback = new SessionLifecycleCallback(sessionTracker);
- application.registerActivityLifecycleCallbacks(sessionLifecycleCallback);
+ registerLifecycleCallbacks();
- if (!immutableConfig.shouldDiscardBreadcrumb(BreadcrumbType.STATE)) {
- this.activityBreadcrumbCollector = new ActivityBreadcrumbCollector(
- new Function2, Unit>() {
- @SuppressWarnings("unchecked")
- @Override
- public Unit invoke(String activity, Map metadata) {
- leaveBreadcrumb(activity, (Map) metadata,
- BreadcrumbType.STATE);
- return null;
- }
- }
- );
- application.registerActivityLifecycleCallbacks(activityBreadcrumbCollector);
- } else {
- this.activityBreadcrumbCollector = null;
- }
- } else {
- this.activityBreadcrumbCollector = null;
- this.sessionLifecycleCallback = null;
- }
-
- InternalReportDelegate delegate = new InternalReportDelegate(appContext, logger,
- immutableConfig, storageManager, appDataCollector, deviceDataCollector,
- sessionTracker, notifier, bgTaskService);
- eventStore = new EventStore(immutableConfig, logger, notifier, bgTaskService, delegate);
+ EventStorageModule eventStorageModule = new EventStorageModule(contextModule, configModule,
+ dataCollectionModule, bgTaskService, trackerModule, systemServiceModule, notifier);
+ eventStorageModule.resolveDependencies(bgTaskService, TaskType.IO);
+ eventStore = eventStorageModule.getEventStore();
deliveryDelegate = new DeliveryDelegate(logger, eventStore,
immutableConfig, breadcrumbState, notifier, bgTaskService);
@@ -223,8 +188,8 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
}
// load last run info
- lastRunInfoStore = new LastRunInfoStore(immutableConfig);
- lastRunInfo = loadLastRunInfo();
+ lastRunInfoStore = storageModule.getLastRunInfoStore();
+ lastRunInfo = storageModule.getLastRunInfo();
// initialise plugins before attempting to flush any errors
loadPlugins(configuration);
@@ -258,13 +223,9 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
@NonNull AppDataCollector appDataCollector,
@NonNull BreadcrumbState breadcrumbState,
@NonNull EventStore eventStore,
- SessionStore sessionStore,
SystemBroadcastReceiver systemBroadcastReceiver,
SessionTracker sessionTracker,
- ActivityBreadcrumbCollector activityBreadcrumbCollector,
- SessionLifecycleCallback sessionLifecycleCallback,
Connectivity connectivity,
- @Nullable StorageManager storageManager,
Logger logger,
DeliveryDelegate deliveryDelegate,
LastRunInfoStore lastRunInfoStore,
@@ -282,13 +243,9 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
this.appDataCollector = appDataCollector;
this.breadcrumbState = breadcrumbState;
this.eventStore = eventStore;
- this.sessionStore = sessionStore;
this.systemBroadcastReceiver = systemBroadcastReceiver;
this.sessionTracker = sessionTracker;
- this.activityBreadcrumbCollector = activityBreadcrumbCollector;
- this.sessionLifecycleCallback = sessionLifecycleCallback;
this.connectivity = connectivity;
- this.storageManager = storageManager;
this.logger = logger;
this.deliveryDelegate = deliveryDelegate;
this.lastRunInfoStore = lastRunInfoStore;
@@ -297,6 +254,29 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
this.exceptionHandler = exceptionHandler;
}
+ void registerLifecycleCallbacks() {
+ if (appContext instanceof Application) {
+ Application application = (Application) appContext;
+ SessionLifecycleCallback sessionCb = new SessionLifecycleCallback(sessionTracker);
+ application.registerActivityLifecycleCallbacks(sessionCb);
+
+ if (!immutableConfig.shouldDiscardBreadcrumb(BreadcrumbType.STATE)) {
+ ActivityBreadcrumbCollector activityCb = new ActivityBreadcrumbCollector(
+ new Function2, Unit>() {
+ @SuppressWarnings("unchecked")
+ @Override
+ public Unit invoke(String activity, Map metadata) {
+ leaveBreadcrumb(activity, (Map) metadata,
+ BreadcrumbType.STATE);
+ return null;
+ }
+ }
+ );
+ application.registerActivityLifecycleCallbacks(activityCb);
+ }
+ }
+ }
+
/**
* Registers listeners for system events in the background. This offloads work from the main
* thread that collects useful information from callbacks, but that don't need to be done
@@ -316,12 +296,6 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
}
}
- private LastRunInfo loadLastRunInfo() {
- LastRunInfo lastRunInfo = lastRunInfoStore.load();
- LastRunInfo currentRunInfo = new LastRunInfo(0, false, false);
- persistRunInfo(currentRunInfo);
- return lastRunInfo;
- }
/**
* Load information about the last run, and reset the persisted information to the defaults.
@@ -339,24 +313,17 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
}
}
- private void loadPlugins(@NonNull Configuration configuration) {
- NativeInterface.setClient(this);
+ private void loadPlugins(@NonNull final Configuration configuration) {
+ NativeInterface.setClient(Client.this);
Set userPlugins = configuration.getPlugins();
pluginClient = new PluginClient(userPlugins, immutableConfig, logger);
- pluginClient.loadPlugins(this);
+ pluginClient.loadPlugins(Client.this);
}
private void logNull(String property) {
logger.e("Invalid null value supplied to client." + property + ", ignoring");
}
- private MetadataState copyMetadataState(@NonNull Configuration configuration) {
- // performs deep copy of metadata to preserve immutability of Configuration interface
- Metadata orig = configuration.impl.metadataState.getMetadata();
- Metadata copy = orig.copy();
- return configuration.impl.metadataState.copy(copy);
- }
-
private void registerComponentCallbacks() {
appContext.registerComponentCallbacks(new ClientComponentCallbacks(
deviceDataCollector,
@@ -381,6 +348,11 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
}
void setupNdkPlugin() {
+ if (!setupNdkDirectory()) {
+ logger.w("Failed to setup NDK directory.");
+ return;
+ }
+
String lastRunInfoPath = lastRunInfoStore.getFile().getAbsolutePath();
int crashes = (lastRunInfo != null) ? lastRunInfo.getConsecutiveLaunchCrashes() : 0;
clientObservable.postNdkInstall(immutableConfig, lastRunInfoPath, crashes);
@@ -388,6 +360,20 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
clientObservable.postNdkDeliverPending();
}
+ private boolean setupNdkDirectory() {
+ try {
+ return bgTaskService.submitTask(TaskType.IO, new Callable() {
+ @Override
+ public Boolean call() {
+ File outFile = new File(NativeInterface.getNativeReportPath());
+ return outFile.exists() || outFile.mkdirs();
+ }
+ }).get();
+ } catch (Throwable exc) {
+ return false;
+ }
+ }
+
void addObserver(StateObserver observer) {
metadataState.addObserver(observer);
breadcrumbState.addObserver(observer);
diff --git a/app/src/main/java/com/bugsnag/android/DataCollectionModule.kt b/app/src/main/java/com/bugsnag/android/DataCollectionModule.kt
new file mode 100644
index 0000000000..89b1b4f234
--- /dev/null
+++ b/app/src/main/java/com/bugsnag/android/DataCollectionModule.kt
@@ -0,0 +1,58 @@
+package com.bugsnag.android
+
+import android.os.Environment
+import com.bugsnag.android.internal.dag.ConfigModule
+import com.bugsnag.android.internal.dag.ContextModule
+import com.bugsnag.android.internal.dag.DependencyModule
+import com.bugsnag.android.internal.dag.SystemServiceModule
+
+/**
+ * A dependency module which constructs the objects that collect data in Bugsnag. For example, this
+ * class is responsible for creating classes which capture device-specific information.
+ */
+internal class DataCollectionModule(
+ contextModule: ContextModule,
+ configModule: ConfigModule,
+ systemServiceModule: SystemServiceModule,
+ trackerModule: TrackerModule,
+ bgTaskService: BackgroundTaskService,
+ connectivity: Connectivity,
+ deviceId: String?
+) : DependencyModule() {
+
+ private val ctx = contextModule.ctx
+ private val cfg = configModule.config
+ private val logger = cfg.logger
+ private val deviceBuildInfo: DeviceBuildInfo = DeviceBuildInfo.defaultInfo()
+ private val dataDir = Environment.getDataDirectory()
+
+ val appDataCollector by future {
+ AppDataCollector(
+ ctx,
+ ctx.packageManager,
+ cfg,
+ trackerModule.sessionTracker,
+ systemServiceModule.activityManager,
+ trackerModule.launchCrashTracker,
+ logger
+ )
+ }
+
+ private val rootDetector by future {
+ RootDetector(logger = logger, deviceBuildInfo = deviceBuildInfo)
+ }
+
+ val deviceDataCollector by future {
+ DeviceDataCollector(
+ connectivity,
+ ctx,
+ ctx.resources,
+ deviceId,
+ deviceBuildInfo,
+ dataDir,
+ rootDetector,
+ bgTaskService,
+ logger
+ )
+ }
+}
diff --git a/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt b/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt
index 5620f0bacd..5587ba7491 100644
--- a/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt
+++ b/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt
@@ -1,5 +1,6 @@
package com.bugsnag.android
+import android.net.TrafficStats
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.PrintWriter
@@ -42,6 +43,7 @@ internal class DefaultDelivery(
headers: Map
): DeliveryStatus {
+ TrafficStats.setThreadStatsTag(1)
if (connectivity != null && !connectivity.hasNetworkConnection()) {
return DeliveryStatus.UNDELIVERED
}
@@ -64,7 +66,7 @@ internal class DefaultDelivery(
return DeliveryStatus.UNDELIVERED
} catch (exception: IOException) {
logger.w("IOException encountered in request", exception)
- return DeliveryStatus.FAILURE
+ return DeliveryStatus.UNDELIVERED
} catch (exception: Exception) {
logger.w("Unexpected error delivering payload", exception)
return DeliveryStatus.FAILURE
diff --git a/app/src/main/java/com/bugsnag/android/DeviceDataCollector.kt b/app/src/main/java/com/bugsnag/android/DeviceDataCollector.kt
index 5181fa8303..44c4aa2900 100644
--- a/app/src/main/java/com/bugsnag/android/DeviceDataCollector.kt
+++ b/app/src/main/java/com/bugsnag/android/DeviceDataCollector.kt
@@ -28,7 +28,7 @@ internal class DeviceDataCollector(
private val buildInfo: DeviceBuildInfo,
private val dataDirectory: File,
rootDetector: RootDetector,
- bgTaskService: BackgroundTaskService,
+ private val bgTaskService: BackgroundTaskService,
private val logger: Logger
) {
@@ -207,7 +207,12 @@ internal class DeviceDataCollector(
fun calculateFreeDisk(): Long {
// for this specific case we want the currently usable space, not
// StorageManager#allocatableBytes() as the UsableSpace lint inspection suggests
- return dataDirectory.usableSpace
+ return runCatching {
+ bgTaskService.submitTask(
+ TaskType.IO,
+ Callable { dataDirectory.usableSpace }
+ ).get()
+ }.getOrDefault(0L)
}
/**
diff --git a/app/src/main/java/com/bugsnag/android/DeviceIdStore.kt b/app/src/main/java/com/bugsnag/android/DeviceIdStore.kt
index 5581acabf5..b9db53c296 100644
--- a/app/src/main/java/com/bugsnag/android/DeviceIdStore.kt
+++ b/app/src/main/java/com/bugsnag/android/DeviceIdStore.kt
@@ -28,9 +28,7 @@ internal class DeviceIdStore @JvmOverloads constructor(
init {
try {
- if (!file.exists()) {
- file.createNewFile()
- }
+ file.createNewFile()
} catch (exc: Throwable) {
logger.w("Failed to created device ID file", exc)
}
diff --git a/app/src/main/java/com/bugsnag/android/Event.java b/app/src/main/java/com/bugsnag/android/Event.java
index 31fb88c074..fe68e04585 100644
--- a/app/src/main/java/com/bugsnag/android/Event.java
+++ b/app/src/main/java/com/bugsnag/android/Event.java
@@ -320,6 +320,10 @@ public class Event implements JsonStream.Streamable, MetadataAware, UserAware {
impl.updateSeverityInternal(severity);
}
+ protected void updateSeverityReason(@NonNull @SeverityReason.SeverityReasonType String reason) {
+ impl.updateSeverityReason(reason);
+ }
+
void setApp(@NonNull AppWithState app) {
impl.setApp(app);
}
diff --git a/app/src/main/java/com/bugsnag/android/EventInternal.kt b/app/src/main/java/com/bugsnag/android/EventInternal.kt
index 8195a2025c..06ad16bc84 100644
--- a/app/src/main/java/com/bugsnag/android/EventInternal.kt
+++ b/app/src/main/java/com/bugsnag/android/EventInternal.kt
@@ -130,12 +130,21 @@ internal class EventInternal @JvmOverloads internal constructor(
}
protected fun updateSeverityInternal(severity: Severity) {
- severityReason = SeverityReason.newInstance(
+ severityReason = SeverityReason(
severityReason.severityReasonType,
severity,
+ severityReason.unhandled,
+ severityReason.attributeValue
+ )
+ }
+
+ protected fun updateSeverityReason(@SeverityReason.SeverityReasonType reason: String) {
+ severityReason = SeverityReason(
+ reason,
+ severityReason.currentSeverity,
+ severityReason.unhandled,
severityReason.attributeValue
)
- this.severity = severity
}
fun getSeverityReasonType(): String = severityReason.severityReasonType
diff --git a/app/src/main/java/com/bugsnag/android/EventStorageModule.kt b/app/src/main/java/com/bugsnag/android/EventStorageModule.kt
new file mode 100644
index 0000000000..790fd9e45b
--- /dev/null
+++ b/app/src/main/java/com/bugsnag/android/EventStorageModule.kt
@@ -0,0 +1,38 @@
+package com.bugsnag.android
+
+import com.bugsnag.android.internal.dag.ConfigModule
+import com.bugsnag.android.internal.dag.ContextModule
+import com.bugsnag.android.internal.dag.DependencyModule
+import com.bugsnag.android.internal.dag.SystemServiceModule
+
+/**
+ * A dependency module which constructs the objects that persist events to disk in Bugsnag.
+ */
+internal class EventStorageModule(
+ contextModule: ContextModule,
+ configModule: ConfigModule,
+ dataCollectionModule: DataCollectionModule,
+ bgTaskService: BackgroundTaskService,
+ trackerModule: TrackerModule,
+ systemServiceModule: SystemServiceModule,
+ notifier: Notifier
+) : DependencyModule() {
+
+ private val cfg = configModule.config
+
+ private val delegate by future {
+ InternalReportDelegate(
+ contextModule.ctx,
+ cfg.logger,
+ cfg,
+ systemServiceModule.storageManager,
+ dataCollectionModule.appDataCollector,
+ dataCollectionModule.deviceDataCollector,
+ trackerModule.sessionTracker,
+ notifier,
+ bgTaskService
+ )
+ }
+
+ val eventStore by future { EventStore(cfg, cfg.logger, notifier, bgTaskService, delegate) }
+}
diff --git a/app/src/main/java/com/bugsnag/android/EventStore.java b/app/src/main/java/com/bugsnag/android/EventStore.java
index d22d8ddb24..e95fc6b1d4 100644
--- a/app/src/main/java/com/bugsnag/android/EventStore.java
+++ b/app/src/main/java/com/bugsnag/android/EventStore.java
@@ -11,7 +11,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import java.util.Locale;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
@@ -53,7 +52,7 @@ class EventStore extends FileStore {
Notifier notifier,
BackgroundTaskService bgTaskSevice,
Delegate delegate) {
- super(new File(config.getPersistenceDirectory(), "bugsnag-errors"),
+ super(new File(config.getPersistenceDirectory().getValue(), "bugsnag-errors"),
config.getMaxPersistedEvents(),
EVENT_COMPARATOR,
logger,
diff --git a/app/src/main/java/com/bugsnag/android/FileStore.java b/app/src/main/java/com/bugsnag/android/FileStore.java
index 4aae1e30e8..cbafdac431 100644
--- a/app/src/main/java/com/bugsnag/android/FileStore.java
+++ b/app/src/main/java/com/bugsnag/android/FileStore.java
@@ -63,13 +63,7 @@ abstract class FileStore {
*/
private boolean isStorageDirValid(@NonNull File storageDir) {
try {
- if (!storageDir.isDirectory() || !storageDir.canWrite()) {
- if (!storageDir.mkdirs()) {
- this.logger.e("Could not prepare storage directory at "
- + storageDir.getAbsolutePath());
- return false;
- }
- }
+ storageDir.mkdirs();
} catch (Exception exception) {
this.logger.e("Could not prepare file storage directory", exception);
return false;
diff --git a/app/src/main/java/com/bugsnag/android/LastRunInfoStore.kt b/app/src/main/java/com/bugsnag/android/LastRunInfoStore.kt
index 770cb65a92..4e6f125f7f 100644
--- a/app/src/main/java/com/bugsnag/android/LastRunInfoStore.kt
+++ b/app/src/main/java/com/bugsnag/android/LastRunInfoStore.kt
@@ -16,7 +16,7 @@ private const val KEY_CRASHED_DURING_LAUNCH = "crashedDuringLaunch"
*/
internal class LastRunInfoStore(config: ImmutableConfig) {
- val file: File = File(config.persistenceDirectory, "last-run-info")
+ val file: File = File(config.persistenceDirectory.value, "last-run-info")
private val logger: Logger = config.logger
private val lock = ReentrantReadWriteLock()
diff --git a/app/src/main/java/com/bugsnag/android/LibraryLoader.java b/app/src/main/java/com/bugsnag/android/LibraryLoader.java
index 850a2f82c8..6d8f216b71 100644
--- a/app/src/main/java/com/bugsnag/android/LibraryLoader.java
+++ b/app/src/main/java/com/bugsnag/android/LibraryLoader.java
@@ -18,17 +18,29 @@ class LibraryLoader {
* @param callback an OnErrorCallback
* @return true if the library was loaded, false if not
*/
- boolean loadLibrary(String name, Client client, OnErrorCallback callback) {
+ boolean loadLibrary(final String name, final Client client, final OnErrorCallback callback) {
+ try {
+ client.bgTaskService.submitTask(TaskType.IO, new Runnable() {
+ @Override
+ public void run() {
+ loadLibInternal(name, client, callback);
+ }
+ }).get();
+ return loaded;
+ } catch (Throwable exc) {
+ return false;
+ }
+ }
+
+ void loadLibInternal(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() {
diff --git a/app/src/main/java/com/bugsnag/android/NativeInterface.java b/app/src/main/java/com/bugsnag/android/NativeInterface.java
index 56a000d90c..cb845ae389 100644
--- a/app/src/main/java/com/bugsnag/android/NativeInterface.java
+++ b/app/src/main/java/com/bugsnag/android/NativeInterface.java
@@ -56,7 +56,7 @@ public class NativeInterface {
@NonNull
public static String getNativeReportPath() {
ImmutableConfig config = getClient().getConfig();
- File persistenceDirectory = config.getPersistenceDirectory();
+ File persistenceDirectory = config.getPersistenceDirectory().getValue();
return new File(persistenceDirectory, "bugsnag-native").getAbsolutePath();
}
diff --git a/app/src/main/java/com/bugsnag/android/Notifier.kt b/app/src/main/java/com/bugsnag/android/Notifier.kt
index 5302719044..946d6df62d 100644
--- a/app/src/main/java/com/bugsnag/android/Notifier.kt
+++ b/app/src/main/java/com/bugsnag/android/Notifier.kt
@@ -7,7 +7,7 @@ import java.io.IOException
*/
class Notifier @JvmOverloads constructor(
var name: String = "Android Bugsnag Notifier",
- var version: String = "5.10.1",
+ var version: String = "5.11.0",
var url: String = "https://bugsnag.com"
) : JsonStream.Streamable {
diff --git a/app/src/main/java/com/bugsnag/android/SessionStore.java b/app/src/main/java/com/bugsnag/android/SessionStore.java
index 66bfa46f55..0d84d8a677 100644
--- a/app/src/main/java/com/bugsnag/android/SessionStore.java
+++ b/app/src/main/java/com/bugsnag/android/SessionStore.java
@@ -7,7 +7,6 @@ import androidx.annotation.Nullable;
import java.io.File;
import java.util.Comparator;
-import java.util.Locale;
import java.util.UUID;
/**
@@ -37,7 +36,7 @@ class SessionStore extends FileStore {
SessionStore(@NonNull ImmutableConfig config,
@NonNull Logger logger,
@Nullable Delegate delegate) {
- super(new File(config.getPersistenceDirectory(), "bugsnag-sessions"),
+ super(new File(config.getPersistenceDirectory().getValue(), "bugsnag-sessions"),
config.getMaxPersistedSessions(),
SESSION_COMPARATOR,
logger,
diff --git a/app/src/main/java/com/bugsnag/android/StorageModule.kt b/app/src/main/java/com/bugsnag/android/StorageModule.kt
new file mode 100644
index 0000000000..2d4bff14ee
--- /dev/null
+++ b/app/src/main/java/com/bugsnag/android/StorageModule.kt
@@ -0,0 +1,47 @@
+package com.bugsnag.android
+
+import android.content.Context
+import com.bugsnag.android.internal.ImmutableConfig
+import com.bugsnag.android.internal.dag.DependencyModule
+
+/**
+ * A dependency module which constructs the objects that store information to disk in Bugsnag.
+ */
+internal class StorageModule(
+ appContext: Context,
+ immutableConfig: ImmutableConfig,
+ logger: Logger
+) : DependencyModule() {
+
+ val sharedPrefMigrator by future { SharedPrefMigrator(appContext) }
+
+ private val deviceIdStore by future {
+ DeviceIdStore(
+ appContext,
+ sharedPrefMigrator = sharedPrefMigrator,
+ logger = logger
+ )
+ }
+
+ val deviceId by future { deviceIdStore.loadDeviceId() }
+
+ val userStore by future {
+ UserStore(
+ immutableConfig,
+ deviceId,
+ sharedPrefMigrator = sharedPrefMigrator,
+ logger = logger
+ )
+ }
+
+ val lastRunInfoStore by future { LastRunInfoStore(immutableConfig) }
+
+ val sessionStore by future { SessionStore(immutableConfig, logger, null) }
+
+ val lastRunInfo by future {
+ val info = lastRunInfoStore.load()
+ val currentRunInfo = LastRunInfo(0, crashed = false, crashedDuringLaunch = false)
+ lastRunInfoStore.persist(currentRunInfo)
+ info
+ }
+}
diff --git a/app/src/main/java/com/bugsnag/android/StrictModeOnErrorCallback.kt b/app/src/main/java/com/bugsnag/android/StrictModeOnErrorCallback.kt
new file mode 100644
index 0000000000..4e2413889a
--- /dev/null
+++ b/app/src/main/java/com/bugsnag/android/StrictModeOnErrorCallback.kt
@@ -0,0 +1,11 @@
+package com.bugsnag.android
+
+internal class StrictModeOnErrorCallback(private val errMsg: String) : OnErrorCallback {
+ override fun onError(event: Event): Boolean {
+ event.updateSeverityInternal(Severity.INFO)
+ event.updateSeverityReason(SeverityReason.REASON_STRICT_MODE)
+ val error = event.errors.firstOrNull()
+ error?.errorMessage = errMsg
+ return true
+ }
+}
diff --git a/app/src/main/java/com/bugsnag/android/TrackerModule.kt b/app/src/main/java/com/bugsnag/android/TrackerModule.kt
new file mode 100644
index 0000000000..a293539f28
--- /dev/null
+++ b/app/src/main/java/com/bugsnag/android/TrackerModule.kt
@@ -0,0 +1,30 @@
+package com.bugsnag.android
+
+import com.bugsnag.android.internal.dag.ConfigModule
+import com.bugsnag.android.internal.dag.DependencyModule
+
+/**
+ * A dependency module which constructs objects that track launch/session related information
+ * in Bugsnag.
+ */
+internal class TrackerModule(
+ configModule: ConfigModule,
+ storageModule: StorageModule,
+ client: Client,
+ bgTaskService: BackgroundTaskService,
+ callbackState: CallbackState
+) : DependencyModule() {
+
+ private val config = configModule.config
+
+ val launchCrashTracker = LaunchCrashTracker(config)
+
+ val sessionTracker = SessionTracker(
+ config,
+ callbackState,
+ client,
+ storageModule.sessionStore,
+ config.logger,
+ bgTaskService
+ )
+}
diff --git a/app/src/main/java/com/bugsnag/android/UserStore.kt b/app/src/main/java/com/bugsnag/android/UserStore.kt
index 29ccd71a19..0a7b59448e 100644
--- a/app/src/main/java/com/bugsnag/android/UserStore.kt
+++ b/app/src/main/java/com/bugsnag/android/UserStore.kt
@@ -12,7 +12,7 @@ import java.util.concurrent.atomic.AtomicReference
internal class UserStore @JvmOverloads constructor(
private val config: ImmutableConfig,
private val deviceId: String?,
- file: File = File(config.persistenceDirectory, "user-info"),
+ file: File = File(config.persistenceDirectory.value, "user-info"),
private val sharedPrefMigrator: SharedPrefMigrator,
private val logger: Logger
) {
@@ -23,9 +23,7 @@ internal class UserStore @JvmOverloads constructor(
init {
try {
- if (!file.exists()) {
- file.createNewFile()
- }
+ file.createNewFile()
} catch (exc: IOException) {
logger.w("Failed to created device ID file", exc)
}
diff --git a/app/src/main/java/com/bugsnag/android/internal/ImmutableConfig.kt b/app/src/main/java/com/bugsnag/android/internal/ImmutableConfig.kt
index 5c434a7d32..3a5b42c59d 100644
--- a/app/src/main/java/com/bugsnag/android/internal/ImmutableConfig.kt
+++ b/app/src/main/java/com/bugsnag/android/internal/ImmutableConfig.kt
@@ -47,7 +47,7 @@ data class ImmutableConfig(
val maxBreadcrumbs: Int,
val maxPersistedEvents: Int,
val maxPersistedSessions: Int,
- val persistenceDirectory: File,
+ val persistenceDirectory: Lazy,
val sendLaunchCrashesSynchronously: Boolean,
// results cached here to avoid unnecessary lookups in Client.
@@ -128,7 +128,8 @@ internal fun convertToImmutableConfig(
config: Configuration,
buildUuid: String? = null,
packageInfo: PackageInfo? = null,
- appInfo: ApplicationInfo? = null
+ appInfo: ApplicationInfo? = null,
+ persistenceDir: Lazy = lazy { requireNotNull(config.persistenceDirectory) }
): ImmutableConfig {
val errorTypes = when {
config.autoDetectErrors -> config.enabledErrorTypes.copy()
@@ -158,7 +159,7 @@ internal fun convertToImmutableConfig(
maxPersistedEvents = config.maxPersistedEvents,
maxPersistedSessions = config.maxPersistedSessions,
enabledBreadcrumbTypes = config.enabledBreadcrumbTypes?.toSet(),
- persistenceDirectory = config.persistenceDirectory!!,
+ persistenceDirectory = persistenceDir,
sendLaunchCrashesSynchronously = config.sendLaunchCrashesSynchronously,
packageInfo = packageInfo,
appInfo = appInfo
@@ -214,11 +215,13 @@ internal fun sanitiseConfiguration(
if (configuration.delivery == null) {
configuration.delivery = DefaultDelivery(connectivity, configuration.logger!!)
}
-
- if (configuration.persistenceDirectory == null) {
- configuration.persistenceDirectory = appContext.cacheDir
- }
- return convertToImmutableConfig(configuration, buildUuid, packageInfo, appInfo)
+ return convertToImmutableConfig(
+ configuration,
+ buildUuid,
+ packageInfo,
+ appInfo,
+ lazy { configuration.persistenceDirectory ?: appContext.cacheDir }
+ )
}
internal const val RELEASE_STAGE_DEVELOPMENT = "development"
diff --git a/app/src/main/java/com/bugsnag/android/internal/dag/ConfigModule.kt b/app/src/main/java/com/bugsnag/android/internal/dag/ConfigModule.kt
new file mode 100644
index 0000000000..e327c8ee70
--- /dev/null
+++ b/app/src/main/java/com/bugsnag/android/internal/dag/ConfigModule.kt
@@ -0,0 +1,18 @@
+package com.bugsnag.android.internal.dag
+
+import com.bugsnag.android.Configuration
+import com.bugsnag.android.Connectivity
+import com.bugsnag.android.internal.sanitiseConfiguration
+
+/**
+ * A dependency module which constructs the configuration object that is used to alter
+ * Bugsnag's default behaviour.
+ */
+internal class ConfigModule(
+ contextModule: ContextModule,
+ configuration: Configuration,
+ connectivity: Connectivity
+) : DependencyModule() {
+
+ val config = sanitiseConfiguration(contextModule.ctx, configuration, connectivity)
+}
diff --git a/app/src/main/java/com/bugsnag/android/internal/dag/ContextModule.kt b/app/src/main/java/com/bugsnag/android/internal/dag/ContextModule.kt
new file mode 100644
index 0000000000..9ece4ff604
--- /dev/null
+++ b/app/src/main/java/com/bugsnag/android/internal/dag/ContextModule.kt
@@ -0,0 +1,17 @@
+package com.bugsnag.android.internal.dag
+
+import android.content.Context
+
+/**
+ * A dependency module which accesses the application context object, falling back to the supplied
+ * context if it is the base context.
+ */
+internal class ContextModule(
+ appContext: Context
+) : DependencyModule() {
+
+ val ctx: Context = when (appContext.applicationContext) {
+ null -> appContext
+ else -> appContext.applicationContext
+ }
+}
diff --git a/app/src/main/java/com/bugsnag/android/internal/dag/DependencyModule.kt b/app/src/main/java/com/bugsnag/android/internal/dag/DependencyModule.kt
new file mode 100644
index 0000000000..f766b0dd2c
--- /dev/null
+++ b/app/src/main/java/com/bugsnag/android/internal/dag/DependencyModule.kt
@@ -0,0 +1,37 @@
+package com.bugsnag.android.internal.dag
+
+import com.bugsnag.android.BackgroundTaskService
+import com.bugsnag.android.TaskType
+
+internal abstract class DependencyModule {
+
+ private val properties = mutableListOf>()
+
+ /**
+ * Creates a new [Lazy] property that is marked as an object that should be resolved off the
+ * main thread when [resolveDependencies] is called.
+ */
+ fun future(initializer: () -> T): Lazy {
+ val lazy = lazy {
+ initializer()
+ }
+ properties.add(lazy)
+ return lazy
+ }
+
+ /**
+ * Blocks until all dependencies in the module have been constructed. This provides the option
+ * for modules to construct objects in a background thread, then have a user block on another
+ * thread until all the objects have been constructed.
+ */
+ fun resolveDependencies(bgTaskService: BackgroundTaskService, taskType: TaskType) {
+ kotlin.runCatching {
+ bgTaskService.submitTask(
+ taskType,
+ Runnable {
+ properties.forEach { it.value }
+ }
+ ).get()
+ }
+ }
+}
diff --git a/app/src/main/java/com/bugsnag/android/internal/dag/SystemServiceModule.kt b/app/src/main/java/com/bugsnag/android/internal/dag/SystemServiceModule.kt
new file mode 100644
index 0000000000..eef01cbc63
--- /dev/null
+++ b/app/src/main/java/com/bugsnag/android/internal/dag/SystemServiceModule.kt
@@ -0,0 +1,15 @@
+package com.bugsnag.android.internal.dag
+
+import com.bugsnag.android.getActivityManager
+import com.bugsnag.android.getStorageManager
+
+/**
+ * A dependency module which provides a reference to Android system services.
+ */
+internal class SystemServiceModule(
+ contextModule: ContextModule
+) : DependencyModule() {
+
+ val storageManager = contextModule.ctx.getStorageManager()
+ val activityManager = contextModule.ctx.getActivityManager()
+}