diff --git a/bugsnag-android-core/api/bugsnag-android-core.api b/bugsnag-android-core/api/bugsnag-android-core.api index 04f73e66b5..a1548a344c 100644 --- a/bugsnag-android-core/api/bugsnag-android-core.api +++ b/bugsnag-android-core/api/bugsnag-android-core.api @@ -841,6 +841,8 @@ public final class com/bugsnag/android/internal/BackgroundTaskService { public fun ()V public fun (Ljava/util/concurrent/ExecutorService;Ljava/util/concurrent/ExecutorService;Ljava/util/concurrent/ExecutorService;Ljava/util/concurrent/ExecutorService;Ljava/util/concurrent/ExecutorService;)V public synthetic fun (Ljava/util/concurrent/ExecutorService;Ljava/util/concurrent/ExecutorService;Ljava/util/concurrent/ExecutorService;Ljava/util/concurrent/ExecutorService;Ljava/util/concurrent/ExecutorService;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun execute (Lcom/bugsnag/android/internal/TaskType;Ljava/lang/Runnable;)V + public final fun provider (Lcom/bugsnag/android/internal/TaskType;Lkotlin/jvm/functions/Function0;)Lcom/bugsnag/android/internal/dag/RunnableProvider; public final fun shutdown ()V public final fun submitTask (Lcom/bugsnag/android/internal/TaskType;Ljava/lang/Runnable;)Ljava/util/concurrent/Future; public final fun submitTask (Lcom/bugsnag/android/internal/TaskType;Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future; @@ -999,3 +1001,16 @@ public final class com/bugsnag/android/internal/TaskType : java/lang/Enum { public static fun values ()[Lcom/bugsnag/android/internal/TaskType; } +public abstract interface class com/bugsnag/android/internal/dag/Provider { + public abstract fun get ()Ljava/lang/Object; + public abstract fun getOrNull ()Ljava/lang/Object; +} + +public abstract class com/bugsnag/android/internal/dag/RunnableProvider : com/bugsnag/android/internal/dag/Provider, java/lang/Runnable { + public fun ()V + public fun get ()Ljava/lang/Object; + public fun getOrNull ()Ljava/lang/Object; + public abstract fun invoke ()Ljava/lang/Object; + public final fun run ()V +} + diff --git a/bugsnag-android-core/detekt-baseline.xml b/bugsnag-android-core/detekt-baseline.xml index 0675574617..3d15bd8339 100644 --- a/bugsnag-android-core/detekt-baseline.xml +++ b/bugsnag-android-core/detekt-baseline.xml @@ -9,10 +9,10 @@ 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 memoryTrimState: MemoryTrimState ) 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, deviceIdStore: Future<DeviceIdStore.DeviceIds?>, memoryTrimState: MemoryTrimState ) + LongParameterList:DataCollectionModule.kt$DataCollectionModule$( contextModule: ContextModule, configModule: ConfigModule, systemServiceModule: SystemServiceModule, trackerModule: TrackerModule, bgTaskService: BackgroundTaskService, connectivity: Connectivity, deviceIdStore: Provider<DeviceIdStore>, memoryTrimState: MemoryTrimState ) 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 */ 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 deviceIdStore: Future<DeviceIdStore.DeviceIds?>, private val buildInfo: DeviceBuildInfo, private val dataDirectory: File, private val rootedFuture: Future<Boolean>?, private val bgTaskService: BackgroundTaskService, private val logger: Logger ) + LongParameterList:DeviceDataCollector.kt$DeviceDataCollector$( private val connectivity: Connectivity, private val appContext: Context, resources: Resources, private val deviceIdStore: Provider<DeviceIdStore.DeviceIds?>, private val buildInfo: DeviceBuildInfo, private val dataDirectory: File, private val rootedFuture: Provider<Boolean>?, 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:EventInternal.kt$EventInternal$( apiKey: String, logger: Logger, breadcrumbs: MutableList<Breadcrumb> = mutableListOf(), discardClasses: Set<Pattern> = setOf(), errors: MutableList<Error> = mutableListOf(), metadata: Metadata = Metadata(), featureFlags: FeatureFlags = FeatureFlags(), originalError: Throwable? = null, projectPackages: Collection<String> = setOf(), severityReason: SeverityReason = SeverityReason.newInstance(SeverityReason.REASON_HANDLED_EXCEPTION), threads: MutableList<Thread> = mutableListOf(), user: User = User(), redactionKeys: Set<Pattern>? = null ) @@ -62,13 +62,14 @@ SwallowedException:ImmutableConfig.kt$e: Exception SwallowedException:JsonHelperTest.kt$JsonHelperTest$e: IllegalArgumentException SwallowedException:PluginClient.kt$PluginClient$exc: ClassNotFoundException + SwallowedException:SharedPrefMigrator.kt$SharedPrefMigrator$e: RuntimeException ThrowsCount:JsonHelper.kt$JsonHelper$fun jsonToLong(value: Any?): Long? TooManyFunctions:ConfigInternal.kt$ConfigInternal : CallbackAwareMetadataAwareUserAwareFeatureFlagAware TooManyFunctions:DeviceDataCollector.kt$DeviceDataCollector TooManyFunctions:EventInternal.kt$EventInternal : FeatureFlagAwareStreamableMetadataAwareUserAware UnusedPrivateProperty:ManifestConfigLoader.kt$ManifestConfigLoader.Companion$private const val LAUNCH_CRASH_THRESHOLD_MS = "$BUGSNAG_NS.LAUNCH_CRASH_THRESHOLD_MS" UnusedPrivateProperty:ThreadStateTest.kt$ThreadStateTest$private val configuration = generateImmutableConfig() - UseCheckOrError:BackgroundTaskServiceTest.kt$BackgroundTaskServiceTest$throw IllegalStateException() + UseCheckOrError:BackgroundRunnableProviderServiceTest.kt$BackgroundRunnableProviderServiceTest$throw IllegalStateException() UseCheckOrError:BugsnagEventMapper.kt$BugsnagEventMapper$throw IllegalStateException("cannot find json property '$key'") UseCheckOrError:StacktraceSerializationTest.kt$StacktraceSerializationTest.Companion$throw IllegalStateException() UseCheckOrError:SynchronizedStreamableStoreTest.kt$CrashyStreamable$throw IllegalStateException() diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/BugsnagStoreMigratorTest.kt b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/BugsnagStoreMigratorTest.kt index af1e70edce..6bc4a12815 100644 --- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/BugsnagStoreMigratorTest.kt +++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/BugsnagStoreMigratorTest.kt @@ -35,7 +35,7 @@ class BugsnagStoreMigratorTest { filesToMove.forEach { (from, to) -> val file = File(tmpdir, from).apply { mkdirs() } val newDir = File(tmpdir, to) - BugsnagStoreMigrator.moveToNewDirectory(tmpdir) + BugsnagStoreMigrator.migrateLegacyFiles(lazyOf(tmpdir)) assertFalse(file.isDirectory) assertFalse(file.exists()) assertTrue(newDir.isDirectory) @@ -47,7 +47,7 @@ class BugsnagStoreMigratorTest { fun testMoveOneFileToNewDirectory() { val file = File(tmpdir, "bugsnag-native").apply { mkdirs() } val newDirFile = File(tmpdir, "bugsnag/native") - BugsnagStoreMigrator.moveToNewDirectory(tmpdir) + BugsnagStoreMigrator.migrateLegacyFiles(lazyOf(tmpdir)) assertFalse(file.isDirectory) assertFalse(file.exists()) assertTrue(newDirFile.exists()) @@ -63,7 +63,7 @@ class BugsnagStoreMigratorTest { } assertTrue(file.isDirectory) assertTrue(file.exists()) - BugsnagStoreMigrator.moveToNewDirectory(tmpdir) + BugsnagStoreMigrator.migrateLegacyFiles(lazyOf(tmpdir)) assertFalse(newDirFile.isDirectory) assertFalse(newDirFile.exists()) assertTrue(file.isDirectory) @@ -74,7 +74,7 @@ class BugsnagStoreMigratorTest { fun testDeepPathUndefinedFile() { val file = File(tmpdir, "test/tes2/test3").apply { mkdirs() } val newDirFile = File(tmpdir, "bugsnag/test/tes2/test3") - BugsnagStoreMigrator.moveToNewDirectory(tmpdir) + BugsnagStoreMigrator.migrateLegacyFiles(lazyOf(tmpdir)) assertFalse(newDirFile.exists()) assertTrue(file.isDirectory) assertTrue(file.exists()) @@ -85,7 +85,7 @@ class BugsnagStoreMigratorTest { val file = File(tmpdir, "bugsnag-sessions").apply { mkdirs() } File(file, "test1/tes2/test3").apply { mkdirs() } val newDirFile = File(tmpdir, "bugsnag/sessions/test1/tes2/test3") - BugsnagStoreMigrator.moveToNewDirectory(tmpdir) + BugsnagStoreMigrator.migrateLegacyFiles(lazyOf(tmpdir)) assertTrue(newDirFile.exists()) assertFalse(file.isDirectory) assertFalse(file.exists()) @@ -102,7 +102,7 @@ class BugsnagStoreMigratorTest { val test1moved = File(tmpdir, "bugsnag/errors/test1") val test2moved = File(tmpdir, "bugsnag/errors/test2") - BugsnagStoreMigrator.moveToNewDirectory(tmpdir) + BugsnagStoreMigrator.migrateLegacyFiles(lazyOf(tmpdir)) assertFalse(file.isDirectory) assertFalse(file.exists()) assertFalse(test1From.exists()) diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/DeviceIdStoreTest.kt b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/DeviceIdStoreTest.kt index 19c27b09fb..e2a06ef8b7 100644 --- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/DeviceIdStoreTest.kt +++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/DeviceIdStoreTest.kt @@ -68,12 +68,12 @@ internal class DeviceIdStoreTest { uuidProvider(0), nonExistentInternalFile, uuidProvider(1), - sharedPrefMigrator = ValueFuture(prefMigrator), + sharedPrefMigrator = ValueProvider(prefMigrator), logger = NoopLogger, config = generateConfig(true) ) - val loaded = store.call() + val loaded = store.load() assertEquals(ids[0], loaded?.deviceId) assertEquals(ids[1], loaded?.internalDeviceId) @@ -94,12 +94,12 @@ internal class DeviceIdStoreTest { uuidProvider(0), fileInternal, uuidProvider(1), - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), logger = NoopLogger, config = generateConfig(true) ) - val loaded = store.call() + val loaded = store.load() assertEquals(ids[0], loaded?.deviceId) assertEquals(ids[1], loaded?.internalDeviceId) @@ -118,12 +118,12 @@ internal class DeviceIdStoreTest { uuidProvider(0), fileInternal, uuidProvider(1), - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), logger = NoopLogger, config = generateConfig(true) ) - val loaded = store.call() + val loaded = store.load() assertEquals(ids[0], loaded?.deviceId) assertEquals(ids[1], loaded?.internalDeviceId) @@ -142,7 +142,7 @@ internal class DeviceIdStoreTest { uuidProvider(2), fileInternal, uuidProvider(3), - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), logger = NoopLogger, config = generateConfig(true) ) @@ -152,13 +152,13 @@ internal class DeviceIdStoreTest { uuidProvider(0), fileInternal, uuidProvider(1), - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), logger = NoopLogger, config = generateConfig(true) ) - val loadedA = storeA.call() - val loadedB = storeB.call() + val loadedA = storeA.load() + val loadedB = storeB.load() // device ID is loaded without writing file assertEquals(ids[0], loadedA?.deviceId) @@ -190,12 +190,12 @@ internal class DeviceIdStoreTest { uuidProvider(0), nonReadableInternalFile, uuidProvider(1), - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), logger = NoopLogger, config = generateConfig(true) ) - val loaded = store.call() + val loaded = store.load() assertNull(loaded) } @@ -210,7 +210,7 @@ internal class DeviceIdStoreTest { uuidProvider(0), fileInternal, uuidProvider(1), - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), logger = NoopLogger, config = generateConfig(true) ) @@ -225,7 +225,7 @@ internal class DeviceIdStoreTest { repeat(attempts) { executor.submit { - store.call()?.deviceId?.let { deviceIds.add(it) } + store.load()?.deviceId?.let { deviceIds.add(it) } latch.countDown() } } @@ -247,7 +247,7 @@ internal class DeviceIdStoreTest { uuidProvider(0), fileInternal, uuidProvider(1), - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), logger = NoopLogger, config = generateConfig(true) ) @@ -257,11 +257,9 @@ internal class DeviceIdStoreTest { context.getSharedPreferences("com.bugsnag.android", Context.MODE_PRIVATE) prefs.edit().putString("install.iud", prefsId).commit() - val prefDeviceId = prefMigrator - .apply { call() } - .loadDeviceId(false) + val prefDeviceId = prefMigrator.loadDeviceId(false) - val loaded = store.call() + val loaded = store.load() val storeDeviceId = loaded?.deviceId assertEquals(prefsId, storeDeviceId) @@ -281,12 +279,12 @@ internal class DeviceIdStoreTest { uuidProvider(0), fileInternal, uuidProvider(1), - sharedPrefMigrator = ValueFuture(prefMigrator), + sharedPrefMigrator = ValueProvider(prefMigrator), logger = NoopLogger, config = generateConfig(false) ) - val loaded = store.call() + val loaded = store.load() assertNull(loaded) } } diff --git a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/UserStoreTest.kt b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/UserStoreTest.kt index e70edd2527..74e774db67 100644 --- a/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/UserStoreTest.kt +++ b/bugsnag-android-core/src/androidTest/java/com/bugsnag/android/UserStoreTest.kt @@ -3,7 +3,6 @@ package com.bugsnag.android import android.content.Context import android.content.SharedPreferences import androidx.test.core.app.ApplicationProvider -import com.bugsnag.android.internal.ImmutableConfig import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -36,12 +35,6 @@ internal class UserStoreTest { prefs.edit().clear().commit() } - private fun generateConfig(persistUser: Boolean): ImmutableConfig { - val config = BugsnagTestUtils.generateConfiguration() - config.persistUser = persistUser - return BugsnagTestUtils.convert(config) - } - @Test fun sharedPrefMigration() { prefs.edit() @@ -51,13 +44,12 @@ internal class UserStoreTest { .putString("user.name", "Jane Fonda") .commit() - prefMigrator.call() - val store = UserStore( - generateConfig(true), - ValueFuture(DeviceIdStore.DeviceIds("0asdf", null)), + true, + ValueProvider(storageDir), + ValueProvider(DeviceIdStore.DeviceIds("0asdf", null)), file, - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), NoopLogger ) val user = store.load(User()).user @@ -74,10 +66,11 @@ internal class UserStoreTest { val nonExistentFile = File(storageDir, "foo") nonExistentFile.delete() val store = UserStore( - generateConfig(true), - ValueFuture(DeviceIdStore.DeviceIds("device-id", null)), + true, + ValueProvider(storageDir), + ValueProvider(DeviceIdStore.DeviceIds("device-id", null)), file, - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), NoopLogger ) val user = store.load(User()).user @@ -92,10 +85,11 @@ internal class UserStoreTest { @Test fun emptyFile() { val store = UserStore( - generateConfig(true), - ValueFuture(DeviceIdStore.DeviceIds("device-id", null)), + true, + ValueProvider(storageDir), + ValueProvider(DeviceIdStore.DeviceIds("device-id", null)), file, - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), NoopLogger ) val user = store.load(User()).user @@ -111,10 +105,11 @@ internal class UserStoreTest { fun invalidFileContents() { file.writeText("{\"hamster\": 2}") val store = UserStore( - generateConfig(true), - ValueFuture(DeviceIdStore.DeviceIds("device-id", null)), + true, + ValueProvider(storageDir), + ValueProvider(DeviceIdStore.DeviceIds("device-id", null)), file, - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), NoopLogger ) val user = store.load(User()).user @@ -134,10 +129,11 @@ internal class UserStoreTest { setWritable(false) } val store = UserStore( - generateConfig(true), - ValueFuture(DeviceIdStore.DeviceIds("device-id", null)), + true, + ValueProvider(storageDir), + ValueProvider(DeviceIdStore.DeviceIds("device-id", null)), nonReadableFile, - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), NoopLogger ) val user = store.load(User()).user @@ -154,10 +150,11 @@ internal class UserStoreTest { file.writeText("{\"id\":\"jf123\",\"email\":\"test@example.com\",\"name\":\"Jane Fonda\"}") val store = UserStore( - generateConfig(true), - ValueFuture(DeviceIdStore.DeviceIds("0asdf", null)), + true, + ValueProvider(storageDir), + ValueProvider(DeviceIdStore.DeviceIds("0asdf", null)), file, - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), NoopLogger ) val user = store.load(User()).user @@ -172,10 +169,11 @@ internal class UserStoreTest { @Test fun loadWithoutPersistUser() { val store = UserStore( - generateConfig(false), - ValueFuture(DeviceIdStore.DeviceIds("device-id-123", null)), + false, + ValueProvider(storageDir), + ValueProvider(DeviceIdStore.DeviceIds("device-id-123", null)), file, - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), NoopLogger ) store.load(User()).user @@ -188,10 +186,11 @@ internal class UserStoreTest { @Test fun saveWithoutPersistUser() { val store = UserStore( - generateConfig(false), - ValueFuture(DeviceIdStore.DeviceIds("", null)), + false, + ValueProvider(storageDir), + ValueProvider(DeviceIdStore.DeviceIds("", null)), file, - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), NoopLogger ) store.save(User("123", "joe@yahoo.com", "Joe Bloggs")) @@ -204,10 +203,11 @@ internal class UserStoreTest { @Test fun saveWithPersistUser() { val store = UserStore( - generateConfig(true), - ValueFuture(DeviceIdStore.DeviceIds("0asdf", null)), + true, + ValueProvider(storageDir), + ValueProvider(DeviceIdStore.DeviceIds("0asdf", null)), file, - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), NoopLogger ) val user = User("jf123", "test@example.com", "Jane Fonda") @@ -222,10 +222,11 @@ internal class UserStoreTest { @Test fun userRequiresChangeForDiskIO() { val store = UserStore( - generateConfig(true), - ValueFuture(DeviceIdStore.DeviceIds("0asdf", null)), + true, + ValueProvider(storageDir), + ValueProvider(DeviceIdStore.DeviceIds("0asdf", null)), file, - ValueFuture(prefMigrator), + ValueProvider(prefMigrator), NoopLogger ) val user = User("jf123", "test@example.com", "Jane Fonda") 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 index 2aa375226d..51d565a83d 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/BugsnagStateModule.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/BugsnagStateModule.kt @@ -10,7 +10,7 @@ import com.bugsnag.android.internal.dag.DependencyModule internal class BugsnagStateModule( cfg: ImmutableConfig, configuration: Configuration -) : DependencyModule() { +) : DependencyModule { val clientObservable = ClientObservable() 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 d98c842289..cea27385ac 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 @@ -3,7 +3,6 @@ import static com.bugsnag.android.SeverityReason.REASON_HANDLED_EXCEPTION; import com.bugsnag.android.internal.BackgroundTaskService; -import com.bugsnag.android.internal.BugsnagStoreMigrator; import com.bugsnag.android.internal.ForegroundDetector; import com.bugsnag.android.internal.ImmutableConfig; import com.bugsnag.android.internal.InternalMetrics; @@ -13,6 +12,7 @@ import com.bugsnag.android.internal.TaskType; import com.bugsnag.android.internal.dag.ConfigModule; import com.bugsnag.android.internal.dag.ContextModule; +import com.bugsnag.android.internal.dag.Provider; import com.bugsnag.android.internal.dag.SystemServiceModule; import android.app.Application; @@ -59,7 +59,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware, FeatureF private final InternalMetrics internalMetrics; private final ContextState contextState; private final CallbackState callbackState; - private final UserState userState; + private final Provider userState; private final Map configDifferences; final Context appContext; @@ -125,7 +125,7 @@ public Client(@NonNull Context androidContext, @NonNull String apiKey) { * @param configuration a configuration for the Client */ public Client(@NonNull Context androidContext, @NonNull final Configuration configuration) { - ContextModule contextModule = new ContextModule(androidContext); + ContextModule contextModule = new ContextModule(androidContext, bgTaskService); appContext = contextModule.getCtx(); notifier = configuration.getNotifier(); @@ -167,17 +167,13 @@ public Unit invoke(Boolean hasConnection, String networkState) { + "https://docs.bugsnag.com/platforms/android/#basic-configuration"); } - BugsnagStoreMigrator.moveToNewDirectory( - immutableConfig.getPersistenceDirectory().getValue() - ); - // setup storage as soon as possible final StorageModule storageModule = new StorageModule(appContext, immutableConfig, bgTaskService); // setup state trackers for bugsnag - BugsnagStateModule bugsnagStateModule = new BugsnagStateModule( - immutableConfig, configuration); + BugsnagStateModule bugsnagStateModule = + new BugsnagStateModule(immutableConfig, configuration); clientObservable = bugsnagStateModule.getClientObservable(); callbackState = bugsnagStateModule.getCallbackState(); breadcrumbState = bugsnagStateModule.getBreadcrumbState(); @@ -186,28 +182,26 @@ public Unit invoke(Boolean hasConnection, String networkState) { featureFlagState = bugsnagStateModule.getFeatureFlagState(); // lookup system services - final SystemServiceModule systemServiceModule = new SystemServiceModule(contextModule); + final SystemServiceModule systemServiceModule = + new SystemServiceModule(contextModule, bgTaskService); // 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.getDeviceIdStore(), memoryTrimState); - appDataCollector = dataCollectionModule.getAppDataCollector(); - deviceDataCollector = dataCollectionModule.getDeviceDataCollector(); // load the device + user information - userState = storageModule.getUserStore().load(configuration.getUser()); + userState = storageModule.loadUser(configuration.getUser()); EventStorageModule eventStorageModule = new EventStorageModule(contextModule, configModule, dataCollectionModule, bgTaskService, trackerModule, systemServiceModule, notifier, callbackState); - eventStore = eventStorageModule.getEventStore(); + + eventStore = eventStorageModule.getEventStore().get(); deliveryDelegate = new DeliveryDelegate(logger, eventStore, immutableConfig, callbackState, notifier, bgTaskService); @@ -215,8 +209,13 @@ public Unit invoke(Boolean hasConnection, String networkState) { exceptionHandler = new ExceptionHandler(this, logger); // load last run info - lastRunInfoStore = storageModule.getLastRunInfoStore(); - lastRunInfo = storageModule.getLastRunInfo(); + lastRunInfoStore = storageModule.getLastRunInfoStore().getOrNull(); + lastRunInfo = storageModule.getLastRunInfo().getOrNull(); + + launchCrashTracker = trackerModule.getLaunchCrashTracker(); + sessionTracker = trackerModule.getSessionTracker().get(); + appDataCollector = dataCollectionModule.getAppDataCollector().get(); + deviceDataCollector = dataCollectionModule.getDeviceDataCollector().get(); Set userPlugins = configuration.getPlugins(); pluginClient = new PluginClient(userPlugins, immutableConfig, logger); @@ -239,7 +238,7 @@ public Unit invoke(Boolean hasConnection, String networkState) { MetadataState metadataState, ContextState contextState, CallbackState callbackState, - UserState userState, + Provider userState, FeatureFlagState featureFlagState, ClientObservable clientObservable, Context appContext, @@ -446,7 +445,7 @@ void addObserver(StateObserver observer) { breadcrumbState.addObserver(observer); sessionTracker.addObserver(observer); clientObservable.addObserver(observer); - userState.addObserver(observer); + userState.get().addObserver(observer); contextState.addObserver(observer); deliveryDelegate.addObserver(observer); launchCrashTracker.addObserver(observer); @@ -459,7 +458,7 @@ void removeObserver(StateObserver observer) { breadcrumbState.removeObserver(observer); sessionTracker.removeObserver(observer); clientObservable.removeObserver(observer); - userState.removeObserver(observer); + userState.get().removeObserver(observer); contextState.removeObserver(observer); deliveryDelegate.removeObserver(observer); launchCrashTracker.removeObserver(observer); @@ -473,7 +472,7 @@ void removeObserver(StateObserver observer) { void syncInitialState() { metadataState.emitObservableEvent(); contextState.emitObservableEvent(); - userState.emitObservableEvent(); + userState.get().emitObservableEvent(); memoryTrimState.emitObservableEvent(); featureFlagState.emitObservableEvent(); } @@ -533,11 +532,10 @@ public void pauseSession() { * * stability score. * + * @return true if a previous session was resumed, false if a new session was started. * @see #startSession() * @see #pauseSession() * @see Configuration#setAutoTrackSessions(boolean) - * - * @return true if a previous session was resumed, false if a new session was started. */ public boolean resumeSession() { return sessionTracker.resumeSession(); @@ -550,7 +548,8 @@ public boolean resumeSession() { * In an android app the "context" is automatically set as the foreground Activity. * If you would like to set this value manually, you should alter this property. */ - @Nullable public String getContext() { + @Nullable + public String getContext() { return contextState.getContext(); } @@ -570,7 +569,7 @@ public void setContext(@Nullable String context) { */ @Override public void setUser(@Nullable String id, @Nullable String email, @Nullable String name) { - userState.setUser(new User(id, email, name)); + userState.get().setUser(new User(id, email, name)); } /** @@ -579,7 +578,7 @@ public void setUser(@Nullable String id, @Nullable String email, @Nullable Strin @NonNull @Override public User getUser() { - return userState.getUser(); + return userState.get().getUser(); } /** @@ -615,6 +614,7 @@ public void addOnError(@NonNull OnErrorCallback onError) { /** * Removes a previously added "on error" callback + * * @param onError the callback to remove */ @Override @@ -655,6 +655,7 @@ public void addOnBreadcrumb(@NonNull OnBreadcrumbCallback onBreadcrumb) { /** * Removes a previously added "on breadcrumb" callback + * * @param onBreadcrumb the callback to remove */ @Override @@ -695,6 +696,7 @@ public void addOnSession(@NonNull OnSessionCallback onSession) { /** * Removes a previously added "on session" callback + * * @param onSession the callback to remove */ @Override @@ -718,9 +720,9 @@ public void notify(@NonNull Throwable exception) { /** * Notify Bugsnag of a handled exception * - * @param exc the exception to send to Bugsnag - * @param onError callback invoked on the generated error report for - * additional modification + * @param exc the exception to send to Bugsnag + * @param onError callback invoked on the generated error report for + * additional modification */ public void notify(@NonNull Throwable exc, @Nullable OnErrorCallback onError) { if (exc != null) { @@ -783,7 +785,7 @@ void populateAndNotifyAndroidEvent(@NonNull Event event, event.setBreadcrumbs(breadcrumbState.copy()); // Attach user info to the event - User user = userState.getUser(); + User user = userState.get().getUser(); event.setUser(user.getId(), user.getEmail(), user.getName()); // Attach context to the event @@ -953,6 +955,7 @@ public void leaveBreadcrumb(@NonNull String message) { /** * Leave a "breadcrumb" log message representing an action or event which * occurred in your app, to aid with debugging + * * @param message A short label * @param metadata Additional diagnostic information about the app environment * @param type A category for the breadcrumb 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 index 582a33f4c6..e8967de359 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/DataCollectionModule.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/DataCollectionModule.kt @@ -2,14 +2,11 @@ package com.bugsnag.android import android.os.Environment import com.bugsnag.android.internal.BackgroundTaskService -import com.bugsnag.android.internal.TaskType +import com.bugsnag.android.internal.dag.BackgroundDependencyModule 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.Provider import com.bugsnag.android.internal.dag.SystemServiceModule -import java.util.concurrent.Callable -import java.util.concurrent.Future -import java.util.concurrent.RejectedExecutionException /** * A dependency module which constructs the objects that collect data in Bugsnag. For example, this @@ -22,9 +19,9 @@ internal class DataCollectionModule( trackerModule: TrackerModule, bgTaskService: BackgroundTaskService, connectivity: Connectivity, - deviceIdStore: Future, + deviceIdStore: Provider, memoryTrimState: MemoryTrimState -) : DependencyModule() { +) : BackgroundDependencyModule(bgTaskService) { private val ctx = contextModule.ctx private val cfg = configModule.config @@ -32,40 +29,34 @@ internal class DataCollectionModule( private val deviceBuildInfo: DeviceBuildInfo = DeviceBuildInfo.defaultInfo() private val dataDir = Environment.getDataDirectory() - val appDataCollector = + val appDataCollector = provider { AppDataCollector( ctx, ctx.packageManager, cfg, - trackerModule.sessionTracker, + trackerModule.sessionTracker.get(), systemServiceModule.activityManager, trackerModule.launchCrashTracker, memoryTrimState ) + } + + private val rootDetection = provider { + val rootDetector = RootDetector(logger = logger, deviceBuildInfo = deviceBuildInfo) + rootDetector.isRooted() + } - val deviceDataCollector = + val deviceDataCollector = provider { DeviceDataCollector( connectivity, ctx, ctx.resources, - deviceIdStore, + deviceIdStore.map { it.load() }, deviceBuildInfo, dataDir, - rootDetectionFuture(bgTaskService), + rootDetection, bgTaskService, logger ) - - private fun rootDetectionFuture(bgTaskService: BackgroundTaskService): Future? = try { - bgTaskService.submitTask( - TaskType.IO, - Callable { - val rootDetector = RootDetector(logger = logger, deviceBuildInfo = deviceBuildInfo) - rootDetector.isRooted() - } - ) - } catch (exc: RejectedExecutionException) { - logger.w("Failed to perform root detection checks", exc) - null } } 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 16fc655ac1..b818523837 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 @@ -13,6 +13,7 @@ import android.os.Build import android.provider.Settings import com.bugsnag.android.internal.BackgroundTaskService import com.bugsnag.android.internal.TaskType +import com.bugsnag.android.internal.dag.Provider import java.io.File import java.util.Date import java.util.Locale @@ -28,10 +29,10 @@ internal class DeviceDataCollector( private val connectivity: Connectivity, private val appContext: Context, resources: Resources, - private val deviceIdStore: Future, + private val deviceIdStore: Provider, private val buildInfo: DeviceBuildInfo, private val dataDirectory: File, - private val rootedFuture: Future?, + private val rootedFuture: Provider?, private val bgTaskService: BackgroundTaskService, private val logger: Logger ) { 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 b86a9d02cd..a0a2cab06f 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 @@ -1,12 +1,10 @@ package com.bugsnag.android import android.content.Context -import com.bugsnag.android.DeviceIdStore.DeviceIds import com.bugsnag.android.internal.ImmutableConfig +import com.bugsnag.android.internal.dag.Provider import java.io.File import java.util.UUID -import java.util.concurrent.Callable -import java.util.concurrent.Future /** * This class is responsible for persisting and retrieving the device ID and internal device ID, @@ -18,16 +16,15 @@ internal class DeviceIdStore @JvmOverloads @Suppress("LongParameterList") constr private val deviceIdGenerator: () -> UUID = { UUID.randomUUID() }, private val internalDeviceIdFile: File = File(context.filesDir, "internal-device-id"), private val internalDeviceIdGenerator: () -> UUID = { UUID.randomUUID() }, - private val sharedPrefMigrator: Future, + private val sharedPrefMigrator: Provider, config: ImmutableConfig, private val logger: Logger -) : Callable { +) { private lateinit var persistence: DeviceIdPersistence private lateinit var internalPersistence: DeviceIdPersistence private val generateId = config.generateAnonymousId - - var deviceIds: DeviceIds? = null + private var deviceIds: DeviceIds? = null /** * Loads the device ID from @@ -65,7 +62,11 @@ internal class DeviceIdStore @JvmOverloads @Suppress("LongParameterList") constr return internalPersistence.loadDeviceId(true) } - override fun call(): DeviceIds? { + fun load(): DeviceIds? { + if (deviceIds != null) { + return deviceIds + } + persistence = DeviceIdFilePersistence(deviceIdFile, deviceIdGenerator, logger) internalPersistence = DeviceIdFilePersistence(internalDeviceIdFile, internalDeviceIdGenerator, logger) 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 index 08def2960e..2d519eb5b1 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStorageModule.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/EventStorageModule.kt @@ -1,9 +1,9 @@ package com.bugsnag.android import com.bugsnag.android.internal.BackgroundTaskService +import com.bugsnag.android.internal.dag.BackgroundDependencyModule 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 /** @@ -18,32 +18,32 @@ internal class EventStorageModule( systemServiceModule: SystemServiceModule, notifier: Notifier, callbackState: CallbackState -) : DependencyModule() { +) : BackgroundDependencyModule(bgTaskService) { private val cfg = configModule.config - private val delegate by lazy { + private val delegate = provider { if (cfg.telemetry.contains(Telemetry.INTERNAL_ERRORS)) InternalReportDelegate( contextModule.ctx, cfg.logger, cfg, systemServiceModule.storageManager, - dataCollectionModule.appDataCollector, + dataCollectionModule.appDataCollector.get(), dataCollectionModule.deviceDataCollector, - trackerModule.sessionTracker, + trackerModule.sessionTracker.get(), notifier, bgTaskService ) else null } - val eventStore by lazy { + val eventStore = provider { EventStore( cfg, cfg.logger, notifier, bgTaskService, - delegate, + delegate.getOrNull(), callbackState ) } diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/InternalReportDelegate.java b/bugsnag-android-core/src/main/java/com/bugsnag/android/InternalReportDelegate.java index a9d6a08d93..43edb9a5b5 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/InternalReportDelegate.java +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/InternalReportDelegate.java @@ -7,6 +7,7 @@ import com.bugsnag.android.internal.ImmutableConfig; import com.bugsnag.android.internal.JsonHelper; import com.bugsnag.android.internal.TaskType; +import com.bugsnag.android.internal.dag.Provider; import android.annotation.SuppressLint; import android.content.Context; @@ -33,7 +34,7 @@ class InternalReportDelegate implements EventStore.Delegate { final StorageManager storageManager; final AppDataCollector appDataCollector; - final DeviceDataCollector deviceDataCollector; + final Provider deviceDataCollector; final Context appContext; final SessionTracker sessionTracker; final Notifier notifier; @@ -44,7 +45,7 @@ class InternalReportDelegate implements EventStore.Delegate { ImmutableConfig immutableConfig, @Nullable StorageManager storageManager, AppDataCollector appDataCollector, - DeviceDataCollector deviceDataCollector, + Provider deviceDataCollector, SessionTracker sessionTracker, Notifier notifier, BackgroundTaskService backgroundTaskService) { @@ -102,7 +103,7 @@ void recordStorageCacheBehavior(Event event) { */ void reportInternalBugsnagError(@NonNull Event event) { event.setApp(appDataCollector.generateAppWithState()); - event.setDevice(deviceDataCollector.generateDeviceWithState(new Date().getTime())); + event.setDevice(deviceDataCollector.get().generateDeviceWithState(new Date().getTime())); event.addMetadata(INTERNAL_DIAGNOSTICS_TAB, "notifierName", notifier.getName()); event.addMetadata(INTERNAL_DIAGNOSTICS_TAB, "notifierVersion", notifier.getVersion()); 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 24d4a718a9..89f7c4516f 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 @@ -2,7 +2,6 @@ package com.bugsnag.android import com.bugsnag.android.internal.ImmutableConfig import java.io.File -import java.util.concurrent.Callable import java.util.concurrent.locks.ReentrantReadWriteLock import kotlin.concurrent.withLock @@ -15,24 +14,12 @@ private const val KEY_CRASHED_DURING_LAUNCH = "crashedDuringLaunch" * Persists/loads [LastRunInfo] on disk, which allows Bugsnag to determine * whether the previous application launch crashed or not. This class is thread-safe. */ -internal class LastRunInfoStore(config: ImmutableConfig) : Callable { +internal class LastRunInfoStore(config: ImmutableConfig) { val file: File = File(config.persistenceDirectory.value, "bugsnag/last-run-info") private val logger: Logger = config.logger private val lock = ReentrantReadWriteLock() - var lastRunInfo: LastRunInfo? = null - - override fun call(): LastRunInfoStore { - val info = load() - val currentRunInfo = LastRunInfo(0, crashed = false, crashedDuringLaunch = false) - persist(currentRunInfo) - - lastRunInfo = info - - return this - } - fun persist(lastRunInfo: LastRunInfo) { lock.writeLock().withLock { try { diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionFilenameInfo.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionFilenameInfo.kt index 3ea39917cd..6ce21ac750 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionFilenameInfo.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionFilenameInfo.kt @@ -1,6 +1,5 @@ package com.bugsnag.android -import com.bugsnag.android.internal.ImmutableConfig import java.io.File import java.util.UUID @@ -34,13 +33,10 @@ internal data class SessionFilenameInfo( } @JvmStatic - fun defaultFilename( - obj: Any?, - config: ImmutableConfig - ): SessionFilenameInfo { + fun defaultFilename(obj: Any?, apiKey: String): SessionFilenameInfo { val sanitizedApiKey = when (obj) { is Session -> obj.apiKey - else -> config.apiKey + else -> apiKey } return SessionFilenameInfo( diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.kt index 86519ac5af..57f6659354 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/SessionStore.kt @@ -2,7 +2,6 @@ package com.bugsnag.android import com.bugsnag.android.SessionFilenameInfo.Companion.defaultFilename import com.bugsnag.android.SessionFilenameInfo.Companion.findTimestampInFilename -import com.bugsnag.android.internal.ImmutableConfig import java.io.File import java.util.Calendar import java.util.Comparator @@ -13,14 +12,14 @@ import java.util.Date * lack of network connectivity. */ internal class SessionStore( - private val config: ImmutableConfig, + bugsnagDir: File, + maxPersistedSessions: Int, + private val apiKey: String, logger: Logger, delegate: Delegate? ) : FileStore( - File( - config.persistenceDirectory.value, "bugsnag/sessions" - ), - config.maxPersistedSessions, + File(bugsnagDir, "sessions"), + maxPersistedSessions, SESSION_COMPARATOR, logger, delegate @@ -53,7 +52,7 @@ internal class SessionStore( } override fun getFilename(obj: Any?): String { - val sessionInfo = defaultFilename(obj, config) + val sessionInfo = defaultFilename(obj, apiKey) return sessionInfo.encode() } } diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/SharedPrefMigrator.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/SharedPrefMigrator.kt index d5365446cb..2f7ccc238f 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/SharedPrefMigrator.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/SharedPrefMigrator.kt @@ -2,61 +2,45 @@ package com.bugsnag.android import android.annotation.SuppressLint import android.content.Context -import android.os.Build -import java.util.concurrent.Callable +import android.content.SharedPreferences /** * Reads legacy information left in SharedPreferences and migrates it to the new location. */ -internal class SharedPrefMigrator(private val context: Context) : - DeviceIdPersistence, - Callable { +internal class SharedPrefMigrator(context: Context) : DeviceIdPersistence { - private var installId: String? = null - private var userId: String? = null - private var userEmail: String? = null - private var userName: String? = null - - override fun call(): SharedPrefMigrator { + private val prefs: SharedPreferences? = try { - val prefs = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE) - ?: return this - - installId = prefs.getString(INSTALL_ID_KEY, null) - userId = prefs.getString(USER_ID_KEY, null) - userEmail = prefs.getString(USER_EMAIL_KEY, null) - userName = prefs.getString(USER_NAME_KEY, null) - - @SuppressLint("ApplySharedPref") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - context.deleteSharedPreferences(SHARED_PREFS_NAME) - } else { - prefs.edit().clear().commit() - } - } catch (_: RuntimeException) { + context.getSharedPreferences("com.bugsnag.android", Context.MODE_PRIVATE) + } catch (e: RuntimeException) { + null } - return this - } - /** * This implementation will never create an ID; it will only fetch one if present. */ - override fun loadDeviceId(requestCreateIfDoesNotExist: Boolean) = installId + override fun loadDeviceId(requestCreateIfDoesNotExist: Boolean) = + prefs?.getString(INSTALL_ID_KEY, null) fun loadUser(deviceId: String?) = User( - userId ?: deviceId, - userEmail, - userName + prefs?.getString(USER_ID_KEY, deviceId), + prefs?.getString(USER_EMAIL_KEY, null), + prefs?.getString(USER_NAME_KEY, null) ) - fun hasPrefs() = installId != null + fun hasPrefs() = prefs?.contains(INSTALL_ID_KEY) == true + + @SuppressLint("ApplySharedPref") + fun deleteLegacyPrefs() { + if (hasPrefs()) { + prefs?.edit()?.clear()?.commit() + } + } companion object { private const val INSTALL_ID_KEY = "install.iud" private const val USER_ID_KEY = "user.id" private const val USER_NAME_KEY = "user.name" private const val USER_EMAIL_KEY = "user.email" - private const val SHARED_PREFS_NAME = "com.bugsnag.android" } } 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 index 0ca701141f..46a07da023 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/StorageModule.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/StorageModule.kt @@ -2,56 +2,72 @@ package com.bugsnag.android import android.content.Context import com.bugsnag.android.internal.BackgroundTaskService +import com.bugsnag.android.internal.BugsnagStoreMigrator.migrateLegacyFiles import com.bugsnag.android.internal.ImmutableConfig import com.bugsnag.android.internal.TaskType -import com.bugsnag.android.internal.dag.DependencyModule +import com.bugsnag.android.internal.dag.BackgroundDependencyModule +import com.bugsnag.android.internal.dag.Provider /** * A dependency module which constructs the objects that store information to disk in Bugsnag. */ internal class StorageModule( appContext: Context, - immutableConfig: ImmutableConfig, + private val immutableConfig: ImmutableConfig, bgTaskService: BackgroundTaskService -) : DependencyModule() { +) : BackgroundDependencyModule(bgTaskService, TaskType.IO) { - val sharedPrefMigrator = bgTaskService.submitTask( - TaskType.IO, + val bugsnagDir = provider { + migrateLegacyFiles(immutableConfig.persistenceDirectory) + } + + val sharedPrefMigrator = provider { SharedPrefMigrator(appContext) - ) + } - val deviceIdStore = bgTaskService.submitTask( - TaskType.IO, + val deviceIdStore = provider { DeviceIdStore( appContext, sharedPrefMigrator = sharedPrefMigrator, logger = immutableConfig.logger, config = immutableConfig ) - ) + } - val userStore = UserStore( - immutableConfig, - deviceIdStore, - sharedPrefMigrator = sharedPrefMigrator, - logger = immutableConfig.logger - ) + val userStore = provider { + UserStore( + immutableConfig.persistUser, + bugsnagDir, + deviceIdStore.map { it.load() }, + sharedPrefMigrator = sharedPrefMigrator, + logger = immutableConfig.logger + ) + } - val lastRunInfoStore = LastRunInfoStore(immutableConfig) + val lastRunInfoStore = provider { + LastRunInfoStore(immutableConfig) + } - val sessionStore = + val sessionStore = provider { SessionStore( - immutableConfig, + bugsnagDir.get(), + immutableConfig.maxPersistedSessions, + immutableConfig.apiKey, immutableConfig.logger, null ) + } - val lastRunInfo = lastRunInfo() - - private fun lastRunInfo(): LastRunInfo? { + val lastRunInfo = lastRunInfoStore.map { lastRunInfoStore -> val info = lastRunInfoStore.load() val currentRunInfo = LastRunInfo(0, crashed = false, crashedDuringLaunch = false) lastRunInfoStore.persist(currentRunInfo) - return info + return@map info + } + + fun loadUser(initialUser: User): Provider = provider { + val userState = userStore.get().load(initialUser) + sharedPrefMigrator.getOrNull()?.deleteLegacyPrefs() + return@provider userState } } 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 index ae1db31f2a..8eb6c47607 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/TrackerModule.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/TrackerModule.kt @@ -1,8 +1,8 @@ package com.bugsnag.android import com.bugsnag.android.internal.BackgroundTaskService +import com.bugsnag.android.internal.dag.BackgroundDependencyModule 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 @@ -14,18 +14,21 @@ internal class TrackerModule( client: Client, bgTaskService: BackgroundTaskService, callbackState: CallbackState -) : DependencyModule() { +) : BackgroundDependencyModule(bgTaskService) { private val config = configModule.config val launchCrashTracker = LaunchCrashTracker(config) - val sessionTracker = SessionTracker( - config, - callbackState, - client, - storageModule.sessionStore, - config.logger, - bgTaskService - ) + val sessionTracker = provider { + client.config + SessionTracker( + config, + callbackState, + client, + storageModule.sessionStore.get(), + 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 13701d07a2..58bf10d7ed 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 @@ -1,24 +1,23 @@ package com.bugsnag.android -import com.bugsnag.android.internal.ImmutableConfig import com.bugsnag.android.internal.StateObserver +import com.bugsnag.android.internal.dag.Provider import java.io.File -import java.util.concurrent.Future import java.util.concurrent.atomic.AtomicReference /** * This class is responsible for persisting and retrieving user information. */ -internal class UserStore @JvmOverloads constructor( - private val config: ImmutableConfig, - private val deviceIdStore: Future, - file: File = File(config.persistenceDirectory.value, "bugsnag/user-info"), - private val sharedPrefMigrator: Future, +internal class UserStore( + private val persist: Boolean, + private val persistentDir: Provider, + private val deviceIdStore: Provider, + file: File = File(persistentDir.get(), "user-info"), + private val sharedPrefMigrator: Provider, private val logger: Logger ) { private val synchronizedStreamableStore: SynchronizedStreamableStore - private val persist = config.persistUser private val previousUser = AtomicReference(null) init { diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/BackgroundTaskService.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/BackgroundTaskService.kt index ef3394fd52..738b2f7922 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/BackgroundTaskService.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/BackgroundTaskService.kt @@ -1,6 +1,7 @@ package com.bugsnag.android.internal import androidx.annotation.VisibleForTesting +import com.bugsnag.android.internal.dag.RunnableProvider import java.util.concurrent.BlockingQueue import java.util.concurrent.Callable import java.util.concurrent.ExecutorService @@ -152,7 +153,11 @@ class BackgroundTaskService( @Throws(RejectedExecutionException::class) fun submitTask(taskType: TaskType, callable: Callable): Future { val task = FutureTask(callable) + execute(taskType, task) + return SafeFuture(task, taskType) + } + fun execute(taskType: TaskType, task: Runnable) { when (taskType) { TaskType.ERROR_REQUEST -> errorExecutor.execute(task) TaskType.SESSION_REQUEST -> sessionExecutor.execute(task) @@ -160,8 +165,6 @@ class BackgroundTaskService( TaskType.INTERNAL_REPORT -> internalReportExecutor.execute(task) TaskType.DEFAULT -> defaultExecutor.execute(task) } - - return SafeFuture(task, taskType) } /** @@ -185,6 +188,18 @@ class BackgroundTaskService( ioExecutor.awaitTerminationSafe() } + inline fun provider( + taskType: TaskType, + crossinline provider: () -> R + ): RunnableProvider { + val task = object : RunnableProvider() { + override fun invoke(): R = provider() + } + + execute(taskType, task) + return task + } + private fun ExecutorService.awaitTerminationSafe() { try { awaitTermination(SHUTDOWN_WAIT_MS, TimeUnit.MILLISECONDS) diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/BugsnagStoreMigrator.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/BugsnagStoreMigrator.kt index 87b223c511..7affbf4fca 100644 --- a/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/BugsnagStoreMigrator.kt +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/BugsnagStoreMigrator.kt @@ -5,8 +5,9 @@ import java.io.File internal object BugsnagStoreMigrator { @JvmStatic - fun moveToNewDirectory(persistenceDir: File) { - val bugsnagDir = File(persistenceDir, "bugsnag") + fun migrateLegacyFiles(persistenceDir: Lazy): File { + val originalDir = persistenceDir.value + val bugsnagDir = File(originalDir, "bugsnag") if (!bugsnagDir.isDirectory) { bugsnagDir.mkdirs() } @@ -19,12 +20,12 @@ internal object BugsnagStoreMigrator { ) filesToMove.forEach { (from, to) -> - val fromFile = File(persistenceDir, from) + val fromFile = File(originalDir, from) if (fromFile.exists()) { - fromFile.renameTo( - File(bugsnagDir, to) - ) + fromFile.renameTo(File(bugsnagDir, to)) } } + + return bugsnagDir } } 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 index 2d55c808be..aece93e476 100644 --- 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 @@ -14,6 +14,6 @@ internal class ConfigModule( configuration: Configuration, connectivity: Connectivity, bgTaskExecutor: BackgroundTaskService -) : DependencyModule() { +) : BackgroundDependencyModule(bgTaskExecutor) { val config = sanitiseConfiguration(contextModule.ctx, configuration, connectivity, bgTaskExecutor) } 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 index 9ece4ff604..0d36ce64bf 100644 --- 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 @@ -1,14 +1,16 @@ package com.bugsnag.android.internal.dag import android.content.Context +import com.bugsnag.android.internal.BackgroundTaskService /** * 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() { + appContext: Context, + bgTaskService: BackgroundTaskService +) : BackgroundDependencyModule(bgTaskService) { val ctx: Context = when (appContext.applicationContext) { null -> appContext 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 index b49a0fedd7..a44ca147f4 100644 --- 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 @@ -1,3 +1,39 @@ package com.bugsnag.android.internal.dag -internal abstract class DependencyModule +import com.bugsnag.android.internal.BackgroundTaskService +import com.bugsnag.android.internal.TaskType + +internal interface DependencyModule + +internal abstract class BackgroundDependencyModule( + @JvmField + val bgTaskService: BackgroundTaskService, + @JvmField + val taskType: TaskType = TaskType.DEFAULT +) : DependencyModule { + /** + * Convenience function to create and schedule a `RunnableProvider` of [taskType] with + * [bgTaskService]. The returned `RunnableProvider` will be implemented using the `provider` + * lambda as its `invoke` implementation. + */ + inline fun provider(crossinline provider: () -> R): RunnableProvider { + return bgTaskService.provider(taskType, provider) + } + + /** + * Return a `RunnableProvider` containing the result of applying the given [mapping] to + * this `Provider`. The `RunnableProvider` will be scheduled with [bgTaskService] as a + * [taskType] when this function returns. + * + * This function behaves similar to `List.map` or `Any.let` but with `Provider` encapsulation + * to handle value reuse and threading. + */ + internal inline fun Provider.map(crossinline mapping: (E) -> R): RunnableProvider { + val task = object : RunnableProvider() { + override fun invoke(): R = mapping(this@map.get()) + } + + bgTaskService.execute(taskType, task) + return task + } +} diff --git a/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/dag/Provider.kt b/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/dag/Provider.kt new file mode 100644 index 0000000000..c021663305 --- /dev/null +++ b/bugsnag-android-core/src/main/java/com/bugsnag/android/internal/dag/Provider.kt @@ -0,0 +1,158 @@ +package com.bugsnag.android.internal.dag + +import android.os.Looper +import java.util.concurrent.atomic.AtomicInteger + +/** + * A lightweight abstraction similar to `Lazy` or `Future` allowing values to be calculated on + * separate threads, or to be pre-computed. + */ +interface Provider { + /** + * Same as [get] but will return `null` instead of throwing an exception if the value could + * not be computed. + */ + fun getOrNull(): E? + + /** + * Return the value sourced from this provider, throwing an exception if the provider failed + * to calculate a value. Anything thrown from here will have been captured when attempting + * to calculate the value. + */ + fun get(): E +} + +/** + * The primary implementation of [Provider], usually created using the + * [BackgroundDependencyModule.provider] function. Similar conceptually to + * [java.util.concurrent.FutureTask] but with a more compact implementation. The implementation + * of [RunnableProvider.get] is special because it behaves more like [Lazy.value] in that getting + * a value that is still pending will cause it to be run on the current thread instead of waiting + * for it to be run "sometime in the future". This makes RunnableProviders less bug-prone when + * dealing with single-thread executors (such as those in [BackgroundTaskService]). RunnableProvider + * also has special handling for the main-thread, ensuring no computational work (such as IO) is + * done on the main thread. + */ +abstract class RunnableProvider : Provider, Runnable { + private val state = AtomicInteger(TASK_STATE_PENDING) + + @Volatile + private var value: Any? = null + + /** + * Calculate the value of this [Provider]. This function will be called at-most once by [run]. + * Do not call this function directly, instead use [get] and [getOrNull] which implement the + * correct threading behaviour and will reuse the value if it has been previously calculated. + */ + abstract operator fun invoke(): E + + override fun getOrNull(): E? { + return getOr { return null } + } + + override fun get(): E { + return getOr { throw value as Throwable } + } + + private inline fun getOr(failureHandler: () -> E): E { + while (true) { + when (state.get()) { + TASK_STATE_RUNNING -> awaitResult() + TASK_STATE_PENDING -> { + if (isMainThread()) { + // When the calling thread is the 'main' thread, we *always* wait for the + // background workers to [invoke] this Provider, assuming that the Provider + // is performing some kind of IO that should be kept away from the main + // thread. Ideally this doesn't happen, but this behaviour avoids the + // need for complicated callback mechanisms. + awaitResult() + } else { + // If the Provider has yet to be computed, we will try and run it on the + // current thread. This potentially causes run() to happen on a different + // Thread to the expected worker (TaskType), effectively like work-stealing. + run() + } + } + + TASK_STATE_COMPLETE -> @Suppress("UNCHECKED_CAST") return value as E + TASK_STATE_FAILED -> failureHandler() + } + } + } + + private fun isMainThread(): Boolean { + return Thread.currentThread() === mainThread + } + + /** + * Cause the current thread to wait (block) until this `Provider` [isComplete]. Upon returning + * the [isComplete] function will return `true`. + */ + private fun awaitResult() { + synchronized(this) { + while (!isComplete()) { + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + (this as Object).wait() + } + } + } + + private fun isComplete() = when (state.get()) { + TASK_STATE_PENDING, TASK_STATE_RUNNING -> false + else -> true + } + + /** + * The main entry point for a provider, typically called by a worker thread from + * [BackgroundTaskService]. If [run] has already been called this will be a no-op (including + * a reentrant thread), as such the task state *must* be checked after calling this. + * + * This should not be called, and instead [get] or [getOrNull] should be used to obtain the + * value produced by [invoke]. + */ + final override fun run() { + if (state.compareAndSet(TASK_STATE_PENDING, TASK_STATE_RUNNING)) { + try { + value = invoke() + state.set(TASK_STATE_COMPLETE) + } catch (ex: Throwable) { + value = ex + state.set(TASK_STATE_FAILED) + } finally { + synchronized(this) { + // wakeup any waiting threads + @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") + (this as Object).notifyAll() + } + } + } + } + + private companion object { + /** + * The `Provider` task state before the provider has started actually running. This state + * indicates that the task has been constructed, has typically been scheduled but has + * not actually started running yet. + */ + private const val TASK_STATE_PENDING = 0 + + /** + * The `Provider` task state when running. Once the [run] function returns the state will + * be either [TASK_STATE_COMPLETE] or [TASK_STATE_FAILED]. + */ + private const val TASK_STATE_RUNNING = 1 + + /** + * The `Provider` state of a successfully completed task. When this is the state the + * provider value can be obtained immediately without error. + */ + private const val TASK_STATE_COMPLETE = 2 + + /** + * The `Provider` state of a task where [invoke] failed with an error or exception. + */ + private const val TASK_STATE_FAILED = 999 + + private val mainThread = Looper.getMainLooper().thread + } +} 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 index eef01cbc63..1c7cd6cf02 100644 --- 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 @@ -2,13 +2,15 @@ package com.bugsnag.android.internal.dag import com.bugsnag.android.getActivityManager import com.bugsnag.android.getStorageManager +import com.bugsnag.android.internal.BackgroundTaskService /** * A dependency module which provides a reference to Android system services. */ internal class SystemServiceModule( - contextModule: ContextModule -) : DependencyModule() { + contextModule: ContextModule, + bgTaskService: BackgroundTaskService +) : BackgroundDependencyModule(bgTaskService) { val storageManager = contextModule.ctx.getStorageManager() val activityManager = contextModule.ctx.getActivityManager() diff --git a/bugsnag-android-core/src/sharedTest/java/com/bugsnag/android/ValueFuture.kt b/bugsnag-android-core/src/sharedTest/java/com/bugsnag/android/ValueFuture.kt deleted file mode 100644 index 9d47936cf6..0000000000 --- a/bugsnag-android-core/src/sharedTest/java/com/bugsnag/android/ValueFuture.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.bugsnag.android - -import java.util.concurrent.Future -import java.util.concurrent.TimeUnit - -class ValueFuture(private val value: V) : Future { - override fun cancel(mayInterruptIfRunning: Boolean): Boolean = false - override fun isCancelled(): Boolean = false - override fun isDone(): Boolean = true - override fun get(): V = value - override fun get(timeout: Long, unit: TimeUnit?): V = get() -} diff --git a/bugsnag-android-core/src/sharedTest/java/com/bugsnag/android/ValueProvider.kt b/bugsnag-android-core/src/sharedTest/java/com/bugsnag/android/ValueProvider.kt new file mode 100644 index 0000000000..42a62e46ee --- /dev/null +++ b/bugsnag-android-core/src/sharedTest/java/com/bugsnag/android/ValueProvider.kt @@ -0,0 +1,8 @@ +package com.bugsnag.android + +import com.bugsnag.android.internal.dag.Provider + +class ValueProvider(private val value: E) : Provider { + override fun getOrNull(): E? = value + override fun get(): E = value +} 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 ca13a2d103..a63a5b7fc3 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 @@ -116,7 +116,7 @@ public void setUp() { metadataState, contextState, callbackState, - userState, + new ValueProvider(userState), featureFlagState, clientObservable, appContext, diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/ConfigChangeReceiverTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/ConfigChangeReceiverTest.kt index fafb9dd42a..8ba751f42d 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/ConfigChangeReceiverTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/ConfigChangeReceiverTest.kt @@ -51,10 +51,10 @@ internal class ConfigChangeReceiverTest { connectivity, appContext, resources, - ValueFuture(null), + ValueProvider(null), buildInfo, dataDirectory, - ValueFuture(false), + ValueProvider(false), bgTaskService, NoopLogger ) diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/DataCollectorTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/DataCollectorTest.kt index 357c160882..b36bc41914 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/DataCollectorTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/DataCollectorTest.kt @@ -35,10 +35,10 @@ internal class DataCollectorTest { Mockito.mock(Connectivity::class.java), Mockito.mock(Context::class.java), res, - ValueFuture(DeviceIdStore.DeviceIds("fakeDevice", "internalFakeDevice")), + ValueProvider(DeviceIdStore.DeviceIds("fakeDevice", "internalFakeDevice")), Mockito.mock(DeviceBuildInfo::class.java), File("/tmp/javatest"), - ValueFuture(false), + ValueProvider(false), Mockito.mock(BackgroundTaskService::class.java), Mockito.mock(Logger::class.java) ) diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/DeviceDataCollectorSerializationTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/DeviceDataCollectorSerializationTest.kt index da2c7b5c8b..60ccc80da7 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/DeviceDataCollectorSerializationTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/DeviceDataCollectorSerializationTest.kt @@ -53,10 +53,10 @@ internal class DeviceDataCollectorSerializationTest { connectivity, context, res, - ValueFuture(DeviceIdStore.DeviceIds("123", "456")), + ValueProvider(DeviceIdStore.DeviceIds("123", "456")), buildInfo, File(""), - ValueFuture(false), + ValueProvider(false), BackgroundTaskService(), NoopLogger ) diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/DeviceMetadataSerializationTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/DeviceMetadataSerializationTest.kt index 2a213c6850..b5549098fc 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/DeviceMetadataSerializationTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/DeviceMetadataSerializationTest.kt @@ -53,10 +53,10 @@ internal class DeviceMetadataSerializationTest { connectivity, context, res, - ValueFuture(DeviceIdStore.DeviceIds("123", "456")), + ValueProvider(DeviceIdStore.DeviceIds("123", "456")), buildInfo, File(""), - ValueFuture(false), + ValueProvider(false), BackgroundTaskService(), NoopLogger ) diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/InternalEventPayloadDelegateTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/InternalEventPayloadDelegateTest.kt index d7f7724789..6ced18091f 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/InternalEventPayloadDelegateTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/InternalEventPayloadDelegateTest.kt @@ -53,7 +53,7 @@ internal class InternalEventPayloadDelegateTest { config, storageManager, appDataCollector, - deviceDataCollector, + ValueProvider(deviceDataCollector), sessionTracker, Notifier(), BackgroundTaskService() diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionStoreMaxLimitTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionStoreMaxLimitTest.kt index 1593a85776..1a3e4c6f24 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionStoreMaxLimitTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/SessionStoreMaxLimitTest.kt @@ -79,7 +79,9 @@ class SessionStoreMaxLimitTest { private fun createSessionStore(config: ImmutableConfig): SessionStore { return SessionStore( - config, + File(config.persistenceDirectory.value, "bugsnag"), + config.maxPersistedSessions, + config.apiKey, NoopLogger, object : Delegate { override fun onErrorIOFailure( diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/SharedPrefMigratorTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/SharedPrefMigratorTest.kt index 16716a564f..21a889aa74 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/SharedPrefMigratorTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/SharedPrefMigratorTest.kt @@ -9,10 +9,11 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.Mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnitRunner @@ -39,37 +40,34 @@ internal class SharedPrefMigratorTest { @Test fun nullSharedPreferences() { `when`(context.getSharedPreferences(eq("com.bugsnag.android"), anyInt())).thenReturn(null) - prefMigrator.call() + prefMigrator = SharedPrefMigrator(context) assertFalse(prefMigrator.hasPrefs()) } @Test fun gettingSharedPreferencesWithException() { `when`(context.getSharedPreferences(eq("com.bugsnag.android"), anyInt())).thenThrow(RuntimeException()) - prefMigrator.call() + prefMigrator = SharedPrefMigrator(context) assertFalse(prefMigrator.hasPrefs()) } @Test fun nullDeviceId() { `when`(prefs.getString("install.iud", null)).thenReturn(null) - prefMigrator.call() assertNull(prefMigrator.loadDeviceId(true)) } @Test fun validDeviceId() { `when`(prefs.getString("install.iud", null)).thenReturn("f09asdfb") - prefMigrator.call() assertEquals("f09asdfb", prefMigrator.loadDeviceId(true)) } @Test fun emptyUser() { - `when`(prefs.getString(eq("user.id"), any())).thenReturn("f09asdfb") + `when`(prefs.getString("user.id", "f09asdfb")).thenReturn("f09asdfb") `when`(prefs.getString("user.email", null)).thenReturn(null) `when`(prefs.getString("user.name", null)).thenReturn(null) - prefMigrator.call() val observed = prefMigrator.loadUser("f09asdfb") assertEquals("f09asdfb", observed.id) @@ -79,10 +77,9 @@ internal class SharedPrefMigratorTest { @Test fun populatedUser() { - `when`(prefs.getString(eq("user.id"), any())).thenReturn("abc75092") + `when`(prefs.getString("user.id", "f09asdfb")).thenReturn("abc75092") `when`(prefs.getString("user.email", null)).thenReturn("test@example.com") `when`(prefs.getString("user.name", null)).thenReturn("Joe") - prefMigrator.call() val expected = User("abc75092", "test@example.com", "Joe") val observed = prefMigrator.loadUser("f09asdfb") @@ -91,17 +88,25 @@ internal class SharedPrefMigratorTest { assertEquals(expected.name, observed.name) } + @Test + fun deletePrefs() { + `when`(prefs.contains("install.iud")).thenReturn(true) + `when`(prefs.edit()).thenReturn(editor) + `when`(editor.clear()).thenReturn(editor) + prefMigrator.deleteLegacyPrefs() + verify(editor, times(1)).clear() + verify(editor, times(1)).commit() + } + @Test fun hasPrefsTrue() { - `when`(prefs.getString("install.iud", null)).thenReturn("abc123") - prefMigrator.call() + `when`(prefs.contains("install.iud")).thenReturn(true) assertTrue(prefMigrator.hasPrefs()) } @Test fun hasPrefsFalse() { - `when`(prefs.getString("install.iud", null)).thenReturn(null) - prefMigrator.call() + `when`(prefs.contains("install.iud")).thenReturn(false) assertFalse(prefMigrator.hasPrefs()) } } diff --git a/bugsnag-android-core/src/test/java/com/bugsnag/android/internal/BackgroundTaskServiceTest.kt b/bugsnag-android-core/src/test/java/com/bugsnag/android/internal/BackgroundRunnableProviderServiceTest.kt similarity index 99% rename from bugsnag-android-core/src/test/java/com/bugsnag/android/internal/BackgroundTaskServiceTest.kt rename to bugsnag-android-core/src/test/java/com/bugsnag/android/internal/BackgroundRunnableProviderServiceTest.kt index b06daee347..a258ae33dd 100644 --- a/bugsnag-android-core/src/test/java/com/bugsnag/android/internal/BackgroundTaskServiceTest.kt +++ b/bugsnag-android-core/src/test/java/com/bugsnag/android/internal/BackgroundRunnableProviderServiceTest.kt @@ -12,7 +12,7 @@ import java.util.concurrent.TimeUnit private const val WAIT_TIME_MS = 200L private const val CONFINEMENT_TEST_ATTEMPTS = 20 -internal class BackgroundTaskServiceTest { +internal class BackgroundRunnableProviderServiceTest { /** * Verifies that the task type submits a Runnable to the correct executor.