diff --git a/CHANGELOG.md b/CHANGELOG.md
index bac92b14f1..b7626ec89d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# Changelog
+## TBD
+
+### Bug fixes
+
+* Address pre-existing StrictMode violations
+ [#1328](https://github.com/bugsnag/bugsnag-android/pull/1328)
+
## 5.10.1 (2021-07-15)
### Bug fixes
diff --git a/bugsnag-android-core/detekt-baseline.xml b/bugsnag-android-core/detekt-baseline.xml
index d222bee612..d05058b93c 100644
--- a/bugsnag-android-core/detekt-baseline.xml
+++ b/bugsnag-android-core/detekt-baseline.xml
@@ -7,11 +7,13 @@
LongParameterList:AppDataCollector.kt$AppDataCollector$( appContext: Context, private val packageManager: PackageManager?, private val config: ImmutableConfig, private val sessionTracker: SessionTracker, private val activityManager: ActivityManager?, private val launchCrashTracker: LaunchCrashTracker, private val logger: Logger )
LongParameterList:AppWithState.kt$AppWithState$( binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, buildUuid: String?, type: String?, versionCode: Number?, /** * The number of milliseconds the application was running before the event occurred */ var duration: Number?, /** * The number of milliseconds the application was running in the foreground before the * event occurred */ var durationInForeground: Number?, /** * Whether the application was in the foreground when the event occurred */ var inForeground: Boolean?, /** * Whether the application was launching when the event occurred */ var isLaunching: Boolean? )
LongParameterList:AppWithState.kt$AppWithState$( config: ImmutableConfig, binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, duration: Number?, durationInForeground: Number?, inForeground: Boolean?, isLaunching: Boolean? )
+ LongParameterList:DataCollectionModule.kt$DataCollectionModule$( contextModule: ContextModule, configModule: ConfigModule, systemServiceModule: SystemServiceModule, trackerModule: TrackerModule, bgTaskService: BackgroundTaskService, connectivity: Connectivity, deviceId: String? )
LongParameterList:Device.kt$Device$( buildInfo: DeviceBuildInfo, /** * The Application Binary Interface used */ var cpuAbi: Array<String>?, /** * Whether the device has been jailbroken */ var jailbroken: Boolean?, /** * A UUID generated by Bugsnag and used for the individual application on a device */ var id: String?, /** * The IETF language tag of the locale used */ var locale: String?, /** * The total number of bytes of memory on the device */ var totalMemory: Long?, /** * A collection of names and their versions of the primary languages, frameworks or * runtimes that the application is running on */ var runtimeVersions: MutableMap<String, Any>? )
LongParameterList:DeviceBuildInfo.kt$DeviceBuildInfo$( val manufacturer: String?, val model: String?, val osVersion: String?, val apiLevel: Int?, val osBuild: String?, val fingerprint: String?, val tags: String?, val brand: String?, val cpuAbis: Array<String>? )
- LongParameterList:DeviceDataCollector.kt$DeviceDataCollector$( private val connectivity: Connectivity, private val appContext: Context, resources: Resources, private val deviceId: String?, private val buildInfo: DeviceBuildInfo, private val dataDirectory: File, rootDetector: RootDetector, bgTaskService: BackgroundTaskService, private val logger: Logger )
+ LongParameterList:DeviceDataCollector.kt$DeviceDataCollector$( private val connectivity: Connectivity, private val appContext: Context, resources: Resources, private val deviceId: String?, private val buildInfo: DeviceBuildInfo, private val dataDirectory: File, rootDetector: RootDetector, private val bgTaskService: BackgroundTaskService, private val logger: Logger )
LongParameterList:DeviceWithState.kt$DeviceWithState$( buildInfo: DeviceBuildInfo, jailbroken: Boolean?, id: String?, locale: String?, totalMemory: Long?, runtimeVersions: MutableMap<String, Any>, /** * The number of free bytes of storage available on the device */ var freeDisk: Long?, /** * The number of free bytes of memory available on the device */ var freeMemory: Long?, /** * The orientation of the device when the event occurred: either portrait or landscape */ var orientation: String?, /** * The timestamp on the device when the event occurred */ var time: Date? )
LongParameterList:EventFilenameInfo.kt$EventFilenameInfo.Companion$( obj: Any, uuid: String = UUID.randomUUID().toString(), apiKey: String?, timestamp: Long = System.currentTimeMillis(), config: ImmutableConfig, isLaunching: Boolean? = null )
+ LongParameterList:EventStorageModule.kt$EventStorageModule$( contextModule: ContextModule, configModule: ConfigModule, dataCollectionModule: DataCollectionModule, bgTaskService: BackgroundTaskService, trackerModule: TrackerModule, systemServiceModule: SystemServiceModule, notifier: Notifier )
LongParameterList:StateEvent.kt$StateEvent.Install$( @JvmField val apiKey: String, @JvmField val autoDetectNdkCrashes: Boolean, @JvmField val appVersion: String?, @JvmField val buildUuid: String?, @JvmField val releaseStage: String?, @JvmField val lastRunInfoPath: String, @JvmField val consecutiveLaunchCrashes: Int )
LongParameterList:ThreadState.kt$ThreadState$( stackTraces: MutableMap<java.lang.Thread, Array<StackTraceElement>>, currentThread: java.lang.Thread, exc: Throwable?, isUnhandled: Boolean, projectPackages: Collection<String>, logger: Logger )
MagicNumber:DefaultDelivery.kt$DefaultDelivery$299
@@ -28,6 +30,7 @@
SwallowedException:ConnectivityCompat.kt$ConnectivityLegacy$catch (e: NullPointerException) { // in some rare cases we get a remote NullPointerException via Parcel.readException null }
SwallowedException:ContextExtensions.kt$catch (exc: RuntimeException) { null }
SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exc: Exception) { false }
+ SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exc: RejectedExecutionException) { null }
SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exception: Exception) { logger.w("Could not get battery status") }
SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exception: Exception) { logger.w("Could not get locationStatus") }
SwallowedException:DeviceIdStore.kt$DeviceIdStore$catch (exc: OverlappingFileLockException) { Thread.sleep(FILE_LOCK_WAIT_MS) }
diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ClientUserTest.kt b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ClientUserTest.kt
index a7ca2fc60d..30ad604c0f 100644
--- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ClientUserTest.kt
+++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/ClientUserTest.kt
@@ -78,7 +78,7 @@ class ClientUserTest {
client.setUser(USER_ID, USER_EMAIL, USER_NAME)
// Check that the user was persisted
- val file = File(config.persistenceDirectory, "user-info")
+ val file = File(client.config.persistenceDirectory.value, "user-info")
val expected = "{\"id\":\"123456\",\"email\":\"mr.test@email.com\",\"name\":\"Mr Test\"}"
assertEquals(expected, file.readText())
}
@@ -89,7 +89,7 @@ class ClientUserTest {
client = Client(context, config)
// Check that the user was not persisted
- val file = File(config.persistenceDirectory, "user-info")
+ val file = File(client.config.persistenceDirectory.value, "user-info")
assertEquals("", file.readText())
client.setUser(USER_ID, USER_EMAIL, USER_NAME)
assertEquals("", file.readText())
diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/LastRunInfoStoreTest.kt b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/LastRunInfoStoreTest.kt
index e6b503264d..fbff235001 100644
--- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/LastRunInfoStoreTest.kt
+++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/LastRunInfoStoreTest.kt
@@ -26,7 +26,7 @@ internal class LastRunInfoStoreTest {
packageInfo = null,
appInfo = null
)
- file = File(config.persistenceDirectory, "last-run-info")
+ file = File(config.persistenceDirectory.value, "last-run-info")
file.delete()
lastRunInfoStore = LastRunInfoStore(config)
}
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/BugsnagStateModule.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/BugsnagStateModule.kt
new file mode 100644
index 0000000000..3167025131
--- /dev/null
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/BugsnagStateModule.kt
@@ -0,0 +1,37 @@
+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
+ val copy = orig.copy()
+ return configuration.impl.metadataState.copy(metadata = copy)
+ }
+}
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java
index 205e42c165..328d85be90 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java
+++ b/bugsnag-android-core/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;
@@ -29,7 +25,10 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* A Bugsnag Client instance allows you to use Bugsnag in your Android app.
@@ -67,24 +66,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 +112,8 @@ public Client(@NonNull Context androidContext, @NonNull String apiKey) {
* @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 +131,52 @@ public Unit invoke(Boolean hasConnection, String networkState) {
});
// 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, bgTaskService, 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());
+ 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);
-
- 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;
- }
+ registerLifecycleCallbacks();
- 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 Unit invoke(String activity, Map metadata) {
}
// 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 Unit invoke(String activity, Map metadata) {
@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 Unit invoke(String activity, Map metadata) {
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 Unit invoke(String activity, Map metadata) {
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 void run() {
}
}
- 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,27 @@ public void run() {
}
}
- private void loadPlugins(@NonNull Configuration configuration) {
- NativeInterface.setClient(this);
- Set userPlugins = configuration.getPlugins();
- pluginClient = new PluginClient(userPlugins, immutableConfig, logger);
- pluginClient.loadPlugins(this);
+ private void loadPlugins(@NonNull final Configuration configuration) {
+ try {
+ bgTaskService.submitTask(TaskType.DEFAULT, new Runnable() {
+ @Override
+ public void run() {
+ NativeInterface.setClient(Client.this);
+ Set userPlugins = configuration.getPlugins();
+ pluginClient = new PluginClient(userPlugins, immutableConfig, logger);
+ pluginClient.loadPlugins(Client.this);
+ }
+ }).get(3, TimeUnit.SECONDS);
+ } catch (RejectedExecutionException | InterruptedException
+ | ExecutionException | TimeoutException exc) {
+ logger.w("Failed to load plugins", exc);
+ }
}
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,
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/DataCollectionModule.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/DataCollectionModule.kt
new file mode 100644
index 0000000000..d779e4d1ec
--- /dev/null
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/DataCollectionModule.kt
@@ -0,0 +1,64 @@
+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
+import com.bugsnag.android.internal.dag.loadDepModuleIoObjects
+import java.util.concurrent.Future
+
+/**
+ * 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 lazy {
+ AppDataCollector(
+ ctx,
+ ctx.packageManager,
+ cfg,
+ trackerModule.sessionTracker,
+ systemServiceModule.activityManager,
+ trackerModule.launchCrashTracker,
+ logger
+ )
+ }
+
+ private val rootDetector by lazy {
+ RootDetector(logger = logger, deviceBuildInfo = deviceBuildInfo)
+ }
+
+ val deviceDataCollector by lazy {
+ DeviceDataCollector(
+ connectivity, ctx,
+ ctx.resources, deviceId, deviceBuildInfo, dataDir,
+ rootDetector, bgTaskService, logger
+ )
+ }
+
+ // trigger initialization on a background thread. Client will then block on the main
+ // thread with resolveDependencies() if these have not completed by the appropriate time.
+ private val future: Future<*>? = loadDepModuleIoObjects(bgTaskService) { rootDetector }
+
+ override fun resolveDependencies(bgTaskService: BackgroundTaskService, taskType: TaskType) {
+ runCatching {
+ future?.get()
+ }
+ }
+}
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/DefaultDelivery.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/DefaultDelivery.kt
index a7995164cb..5587ba7491 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/DefaultDelivery.kt
+++ b/bugsnag-android-core/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
}
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/DeviceDataCollector.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/DeviceDataCollector.kt
index 5181fa8303..44c4aa2900 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/DeviceDataCollector.kt
+++ b/bugsnag-android-core/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/bugsnag-android-core/src/main/java/com/bugsnag/android/DeviceIdStore.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/DeviceIdStore.kt
index 5581acabf5..b9db53c296 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/DeviceIdStore.kt
+++ b/bugsnag-android-core/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/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStorageModule.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStorageModule.kt
new file mode 100644
index 0000000000..23d46e6bdb
--- /dev/null
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStorageModule.kt
@@ -0,0 +1,50 @@
+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
+import com.bugsnag.android.internal.dag.loadDepModuleIoObjects
+import java.util.concurrent.Future
+
+/**
+ * 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 lazy {
+ InternalReportDelegate(
+ contextModule.ctx,
+ cfg.logger,
+ cfg,
+ systemServiceModule.storageManager,
+ dataCollectionModule.appDataCollector,
+ dataCollectionModule.deviceDataCollector,
+ trackerModule.sessionTracker,
+ notifier,
+ bgTaskService
+ )
+ }
+
+ val eventStore by lazy { EventStore(cfg, cfg.logger, notifier, bgTaskService, delegate) }
+
+ // trigger initialization on a background thread. Client will then block on the main
+ // thread if these have not completed by the appropriate time.
+ private val future: Future<*>? = loadDepModuleIoObjects(bgTaskService) { eventStore }
+
+ override fun resolveDependencies(bgTaskService: BackgroundTaskService, taskType: TaskType) {
+ runCatching {
+ future?.get()
+ }
+ }
+}
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.java
index d22d8ddb24..e95fc6b1d4 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.java
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStore.java
@@ -11,7 +11,6 @@
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 @@ public int compare(File lhs, File rhs) {
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/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.java
index 4aae1e30e8..cbafdac431 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.java
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/FileStore.java
@@ -63,13 +63,7 @@ interface Delegate {
*/
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/bugsnag-android-core/src/main/java/com/bugsnag/android/LastRunInfoStore.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/LastRunInfoStore.kt
index 770cb65a92..4e6f125f7f 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/LastRunInfoStore.kt
+++ b/bugsnag-android-core/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/bugsnag-android-core/src/main/java/com/bugsnag/android/NativeInterface.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/NativeInterface.java
index 56a000d90c..cb845ae389 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/NativeInterface.java
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/NativeInterface.java
@@ -56,7 +56,7 @@ public static String getContext() {
@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/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.java
index 66bfa46f55..0d84d8a677 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.java
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.java
@@ -7,7 +7,6 @@
import java.io.File;
import java.util.Comparator;
-import java.util.Locale;
import java.util.UUID;
/**
@@ -37,7 +36,7 @@ public int compare(File lhs, File rhs) {
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/bugsnag-android-core/src/main/java/com/bugsnag/android/StorageModule.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/StorageModule.kt
new file mode 100644
index 0000000000..ed80f66934
--- /dev/null
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/StorageModule.kt
@@ -0,0 +1,68 @@
+package com.bugsnag.android
+
+import android.content.Context
+import com.bugsnag.android.internal.ImmutableConfig
+import com.bugsnag.android.internal.dag.DependencyModule
+import com.bugsnag.android.internal.dag.loadDepModuleIoObjects
+import java.util.concurrent.Future
+
+/**
+ * A dependency module which constructs the objects that store information to disk in Bugsnag.
+ */
+internal class StorageModule(
+ appContext: Context,
+ immutableConfig: ImmutableConfig,
+ bgTaskService: BackgroundTaskService,
+ logger: Logger
+) : DependencyModule {
+
+ val sharedPrefMigrator by lazy { SharedPrefMigrator(appContext) }
+
+ private val deviceIdStore by lazy {
+ DeviceIdStore(
+ appContext,
+ sharedPrefMigrator = sharedPrefMigrator,
+ logger = logger
+ )
+ }
+
+ val deviceId by lazy { deviceIdStore.loadDeviceId() }
+
+ val userStore by lazy {
+ UserStore(
+ immutableConfig,
+ deviceId,
+ sharedPrefMigrator = sharedPrefMigrator,
+ logger = logger
+ )
+ }
+
+ val lastRunInfoStore by lazy { LastRunInfoStore(immutableConfig) }
+
+ val sessionStore by lazy { SessionStore(immutableConfig, logger, null) }
+
+ val lastRunInfo by lazy {
+ val info = lastRunInfoStore.load()
+ val currentRunInfo = LastRunInfo(0, crashed = false, crashedDuringLaunch = false)
+ lastRunInfoStore.persist(currentRunInfo)
+ info
+ }
+
+ // trigger initialization on a background thread. Client will then block on the main
+ // thread if these have not completed by the appropriate time.
+ private val future: Future<*>? = loadDepModuleIoObjects(bgTaskService) {
+ sharedPrefMigrator
+ deviceIdStore
+ deviceId
+ userStore
+ lastRunInfoStore
+ lastRunInfo
+ sessionStore
+ }
+
+ override fun resolveDependencies(bgTaskService: BackgroundTaskService, taskType: TaskType) {
+ runCatching {
+ future?.get()
+ }
+ }
+}
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/TrackerModule.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/TrackerModule.kt
new file mode 100644
index 0000000000..00f8a10780
--- /dev/null
+++ b/bugsnag-android-core/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/bugsnag-android-core/src/main/java/com/bugsnag/android/UserStore.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/UserStore.kt
index 29ccd71a19..0a7b59448e 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/UserStore.kt
+++ b/bugsnag-android-core/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/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/ImmutableConfig.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/ImmutableConfig.kt
index 5c434a7d32..3a5b42c59d 100644
--- a/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/ImmutableConfig.kt
+++ b/bugsnag-android-core/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/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/dag/ConfigModule.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/dag/ConfigModule.kt
new file mode 100644
index 0000000000..1429989235
--- /dev/null
+++ b/bugsnag-android-core/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/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/dag/ContextModule.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/dag/ContextModule.kt
new file mode 100644
index 0000000000..14ecd1bb94
--- /dev/null
+++ b/bugsnag-android-core/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/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/dag/DependencyModule.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/dag/DependencyModule.kt
new file mode 100644
index 0000000000..82e604f5fe
--- /dev/null
+++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/dag/DependencyModule.kt
@@ -0,0 +1,29 @@
+package com.bugsnag.android.internal.dag
+
+import com.bugsnag.android.BackgroundTaskService
+import com.bugsnag.android.TaskType
+
+/**
+ * A collection of related objects which are used to inject dependencies. This is somewhat
+ * analogous to Dagger's concept of modules - although this implementation is much simpler.
+ */
+internal interface DependencyModule {
+
+ /**
+ * 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) = Unit
+}
+
+/**
+ * Creates a [Future] for loading objects on the IO thread.
+ */
+internal fun loadDepModuleIoObjects(bgTaskService: BackgroundTaskService, action: () -> Unit) =
+ runCatching {
+ bgTaskService.submitTask(
+ TaskType.IO,
+ Runnable(action)
+ )
+ }.getOrNull()
diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/dag/SystemServiceModule.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/dag/SystemServiceModule.kt
new file mode 100644
index 0000000000..8537aa489a
--- /dev/null
+++ b/bugsnag-android-core/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()
+}
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/ClientFacadeTest.java b/bugsnag-android-core/src/test/java/com/bugsnag/android/ClientFacadeTest.java
index a3af0e1cff..facadde7bf 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/ClientFacadeTest.java
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/ClientFacadeTest.java
@@ -11,7 +11,6 @@
import com.bugsnag.android.internal.StateObserver;
import android.content.Context;
-import android.os.storage.StorageManager;
import androidx.annotation.NonNull;
@@ -64,27 +63,15 @@ public class ClientFacadeTest {
@Mock
EventStore eventStore;
- @Mock
- SessionStore sessionStore;
-
@Mock
SystemBroadcastReceiver systemBroadcastReceiver;
@Mock
SessionTracker sessionTracker;
- @Mock
- ActivityBreadcrumbCollector activityBreadcrumbCollector;
-
- @Mock
- SessionLifecycleCallback sessionLifecycleCallback;
-
@Mock
Connectivity connectivity;
- @Mock
- StorageManager storageManager;
-
@Mock
DeliveryDelegate deliveryDelegate;
@@ -124,13 +111,9 @@ public void setUp() {
appDataCollector,
breadcrumbState,
eventStore,
- sessionStore,
systemBroadcastReceiver,
sessionTracker,
- activityBreadcrumbCollector,
- sessionLifecycleCallback,
connectivity,
- storageManager,
logger,
deliveryDelegate,
lastRunInfoStore,
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/ImmutableConfigTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/ImmutableConfigTest.kt
index 7eb173d81a..697d8317bd 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/ImmutableConfigTest.kt
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/ImmutableConfigTest.kt
@@ -179,7 +179,7 @@ internal class ImmutableConfigTest {
assertEquals("development", config.releaseStage)
assertEquals(55, config.versionCode)
assertNotNull(config.delivery)
- assertEquals(cacheDir, config.persistenceDirectory)
+ assertEquals(cacheDir, config.persistenceDirectory.value)
}
@Test
@@ -201,6 +201,6 @@ internal class ImmutableConfigTest {
assertEquals("production", config.releaseStage)
assertEquals(55, config.versionCode)
assertNotNull(config.delivery)
- assertEquals(cacheDir, config.persistenceDirectory)
+ assertEquals(cacheDir, config.persistenceDirectory.value)
}
}
diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/NativeInterfaceApiTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/NativeInterfaceApiTest.kt
index b0e03023f6..a1b1f819ec 100644
--- a/bugsnag-android-core/src/test/java/com/bugsnag/android/NativeInterfaceApiTest.kt
+++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/NativeInterfaceApiTest.kt
@@ -75,7 +75,7 @@ internal class NativeInterfaceApiTest {
@Test
fun getNativeReportPathPersistenceDirectory() {
val customDir = Files.createTempDirectory("custom").toFile()
- `when`(immutableConfig.persistenceDirectory).thenReturn(customDir)
+ `when`(immutableConfig.persistenceDirectory).thenReturn(lazy { customDir })
val observed = NativeInterface.getNativeReportPath()
assertEquals("${customDir.absolutePath}/bugsnag-native", observed)
}
diff --git a/bugsnag-plugin-react-native/src/test/java/com/bugsnag/android/TestData.java b/bugsnag-plugin-react-native/src/test/java/com/bugsnag/android/TestData.java
index 58bccc8f1b..0161992b05 100644
--- a/bugsnag-plugin-react-native/src/test/java/com/bugsnag/android/TestData.java
+++ b/bugsnag-plugin-react-native/src/test/java/com/bugsnag/android/TestData.java
@@ -2,6 +2,10 @@
import com.bugsnag.android.internal.ImmutableConfig;
+import kotlin.LazyKt;
+import kotlin.jvm.functions.Function0;
+
+import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Collections;
@@ -33,7 +37,16 @@ static ImmutableConfig generateConfig() throws IOException {
22,
32,
32,
- Files.createTempDirectory("foo").toFile(),
+ LazyKt.lazy(new Function0() {
+ @Override
+ public File invoke() {
+ try {
+ return Files.createTempDirectory("foo").toFile();
+ } catch (IOException ignored) {
+ return null;
+ }
+ }
+ }),
true,
null,
null
diff --git a/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/StartupANRBehaviour.kt b/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/StartupANRBehaviour.kt
index c63caf5361..cdbb900291 100644
--- a/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/StartupANRBehaviour.kt
+++ b/features/fixtures/mazerunner/app/src/main/java/com/bugsnag/android/mazerunner/StartupANRBehaviour.kt
@@ -2,6 +2,8 @@ package com.bugsnag.android.mazerunner
import android.app.Application
import android.content.Context
+import android.os.Handler
+import android.os.Looper
import android.util.Log
import com.bugsnag.android.Bugsnag
import java.util.concurrent.TimeUnit
@@ -20,23 +22,26 @@ fun Application.triggerStartupAnrIfRequired() {
// we have to startup Bugsnag at this point
Bugsnag.start(this)
- thread {
- // This is a dirty hack to work around the limitations of our current testing
- // systems - where external key-events are pushed through our main thread (which we
- // are pausing to test for ANRs)
+ // wait for Bugsnag's ANR handler to install first
+ Handler(Looper.getMainLooper()).post {
+ thread {
+ // This is a dirty hack to work around the limitations of our current testing
+ // systems - where external key-events are pushed through our main thread (which we
+ // are pausing to test for ANRs)
- // if there is a startup delay, we assume we are testing ANRs and send a "BACK"
- // key-press from the *system* in an attempt to cause an ANR
- try {
- Thread.sleep(TimeUnit.SECONDS.toMillis(1L))
- Runtime.getRuntime()
- .exec("input keyevent 4")
- .waitFor()
- } catch (ex: Exception) {
- Log.w("StartupANR", "Couldn't send keyevent for BACK", ex)
+ // if there is a startup delay, we assume we are testing ANRs and send a "BACK"
+ // key-press from the *system* in an attempt to cause an ANR
+ try {
+ Thread.sleep(TimeUnit.SECONDS.toMillis(1L))
+ Runtime.getRuntime()
+ .exec("input keyevent 4")
+ .waitFor()
+ } catch (ex: Exception) {
+ Log.w("StartupANR", "Couldn't send keyevent for BACK", ex)
+ }
}
- }
- Thread.sleep(TimeUnit.SECONDS.toMillis(startupDelay))
+ Thread.sleep(TimeUnit.SECONDS.toMillis(startupDelay))
+ }
}
}