diff --git a/app/build.gradle b/app/build.gradle index 225bb07d72..acc2fd456f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -575,7 +575,7 @@ dependencies { def minidns_version = "1.0.5" def openpgp_version = "12.0" def badge_version = "1.1.22" - def bugsnag_version = "6.1.0" + def bugsnag_version = "6.4.0" def biweekly_version = "0.6.8" def vcard_version = "0.12.1" def relinker_version = "1.4.5" diff --git a/app/src/main/java/com/bugsnag/android/AppDataCollector.kt b/app/src/main/java/com/bugsnag/android/AppDataCollector.kt index ee23a07a72..a74579eea9 100644 --- a/app/src/main/java/com/bugsnag/android/AppDataCollector.kt +++ b/app/src/main/java/com/bugsnag/android/AppDataCollector.kt @@ -2,11 +2,26 @@ package com.bugsnag.android import android.annotation.SuppressLint import android.app.ActivityManager +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE_PRE_26 +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING_PRE_28 +import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE +import android.app.ActivityManager.RunningAppProcessInfo.REASON_PROVIDER_IN_USE +import android.app.ActivityManager.RunningAppProcessInfo.REASON_SERVICE_IN_USE import android.app.Application import android.content.Context import android.content.pm.PackageManager import android.os.Build.VERSION import android.os.Build.VERSION_CODES +import android.os.Process import android.os.SystemClock import com.bugsnag.android.internal.ImmutableConfig @@ -49,12 +64,57 @@ internal class AppDataCollector( ) } + @SuppressLint("SwitchIntDef") + @Suppress("DEPRECATION") + private fun getProcessImportance(): String? { + try { + val appInfo = ActivityManager.RunningAppProcessInfo() + if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { + ActivityManager.getMyMemoryState(appInfo) + } else { + val expectedPid = Process.myPid() + activityManager?.runningAppProcesses + ?.find { it.pid == expectedPid } + ?.let { + appInfo.importance = it.importance + appInfo.pid = expectedPid + } + } + + if (appInfo.pid == 0) { + return null + } + + return when (appInfo.importance) { + IMPORTANCE_FOREGROUND -> "foreground" + IMPORTANCE_FOREGROUND_SERVICE -> "foreground service" + IMPORTANCE_TOP_SLEEPING -> "top sleeping" + IMPORTANCE_TOP_SLEEPING_PRE_28 -> "top sleeping" + IMPORTANCE_VISIBLE -> "visible" + IMPORTANCE_PERCEPTIBLE -> "perceptible" + IMPORTANCE_PERCEPTIBLE_PRE_26 -> "perceptible" + IMPORTANCE_CANT_SAVE_STATE -> "can't save state" + IMPORTANCE_CANT_SAVE_STATE_PRE_26 -> "can't save state" + IMPORTANCE_SERVICE -> "service" + IMPORTANCE_CACHED -> "cached/background" + IMPORTANCE_GONE -> "gone" + IMPORTANCE_EMPTY -> "empty" + REASON_PROVIDER_IN_USE -> "provider in use" + REASON_SERVICE_IN_USE -> "service in use" + else -> "unknown importance (${appInfo.importance})" + } + } catch (e: Exception) { + return null + } + } + fun getAppDataMetadata(): MutableMap { val map = HashMap() map["name"] = appName map["activeScreen"] = sessionTracker.contextActivity map["lowMemory"] = memoryTrimState.isLowMemory map["memoryTrimLevel"] = memoryTrimState.trimLevelDescription + map["processImportance"] = getProcessImportance() populateRuntimeMemoryMetadata(map) @@ -128,6 +188,7 @@ internal class AppDataCollector( packageManager != null && copy != null -> { packageManager.getApplicationLabel(copy).toString() } + else -> null } } @@ -156,6 +217,7 @@ internal class AppDataCollector( VERSION.SDK_INT >= VERSION_CODES.P -> { Application.getProcessName() } + else -> { // see https://stackoverflow.com/questions/19631894 val clz = Class.forName("android.app.ActivityThread") @@ -179,5 +241,7 @@ internal class AppDataCollector( * good approximation for how long the app has been running. */ fun getDurationMs(): Long = SystemClock.elapsedRealtime() - startTimeMs + + private const val IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170 } } diff --git a/app/src/main/java/com/bugsnag/android/BugsnagEventMapper.kt b/app/src/main/java/com/bugsnag/android/BugsnagEventMapper.kt index c8a7763932..c3af9864b5 100644 --- a/app/src/main/java/com/bugsnag/android/BugsnagEventMapper.kt +++ b/app/src/main/java/com/bugsnag/android/BugsnagEventMapper.kt @@ -29,12 +29,14 @@ internal class BugsnagEventMapper( event.userImpl = convertUser(map.readEntry("user")) // populate metadata - val metadataMap: Map> = map.readEntry("metaData") + val metadataMap: Map> = + (map["metaData"] as? Map>).orEmpty() metadataMap.forEach { (key, value) -> event.addMetadata(key, value) } - val featureFlagsList: List> = map.readEntry("featureFlags") + val featureFlagsList: List> = + (map["featureFlags"] as? List>).orEmpty() featureFlagsList.forEach { featureFlagMap -> event.addFeatureFlag( featureFlagMap.readEntry("featureFlag"), @@ -43,7 +45,8 @@ internal class BugsnagEventMapper( } // populate breadcrumbs - val breadcrumbList: List> = map.readEntry("breadcrumbs") + val breadcrumbList: List> = + (map["breadcrumbs"] as? List>).orEmpty() breadcrumbList.mapTo(event.breadcrumbs) { Breadcrumb( convertBreadcrumbInternal(it), @@ -226,8 +229,7 @@ internal class BugsnagEventMapper( is T -> return value null -> throw IllegalStateException("cannot find json property '$key'") else -> throw IllegalArgumentException( - "json property '$key' not " + - "of expected type, found ${value.javaClass.name}" + "json property '$key' not of expected type, found ${value.javaClass.name}" ) } } diff --git a/app/src/main/java/com/bugsnag/android/ConfigInternal.kt b/app/src/main/java/com/bugsnag/android/ConfigInternal.kt index 5f83006426..f6565b7b3f 100644 --- a/app/src/main/java/com/bugsnag/android/ConfigInternal.kt +++ b/app/src/main/java/com/bugsnag/android/ConfigInternal.kt @@ -25,6 +25,7 @@ internal class ConfigInternal( var releaseStage: String? = null var sendThreads: ThreadSendPolicy = ThreadSendPolicy.ALWAYS var persistUser: Boolean = true + var generateAnonymousId: Boolean = true var launchDurationMillis: Long = DEFAULT_LAUNCH_CRASH_THRESHOLD_MS diff --git a/app/src/main/java/com/bugsnag/android/Configuration.java b/app/src/main/java/com/bugsnag/android/Configuration.java index ebdc8cdc7b..c0b4e68f4c 100644 --- a/app/src/main/java/com/bugsnag/android/Configuration.java +++ b/app/src/main/java/com/bugsnag/android/Configuration.java @@ -178,6 +178,26 @@ public class Configuration implements CallbackAware, MetadataAware, UserAware, F impl.setPersistUser(persistUser); } + /** + * Set whether or not Bugsnag should generate an anonymous ID and persist it in local storage + * + * If disabled, any device ID that has been persisted will not be retrieved, and no new + * device ID will be generated or stored + */ + public boolean getGenerateAnonymousId() { + return impl.getGenerateAnonymousId(); + } + + /** + * Set whether or not Bugsnag should generate an anonymous ID and persist it in local storage + * + * If disabled, any device ID that has been persisted will not be retrieved, and no new + * device ID will be generated or stored + */ + public void setGenerateAnonymousId(boolean generateAnonymousId) { + impl.setGenerateAnonymousId(generateAnonymousId); + } + /** * Sets the directory where event and session JSON payloads should be persisted if a network * request is not successful. If you use Bugsnag in multiple processes, then a unique diff --git a/app/src/main/java/com/bugsnag/android/DeviceIdStore.kt b/app/src/main/java/com/bugsnag/android/DeviceIdStore.kt index 31b7c7dc28..74dc3e87e9 100644 --- a/app/src/main/java/com/bugsnag/android/DeviceIdStore.kt +++ b/app/src/main/java/com/bugsnag/android/DeviceIdStore.kt @@ -1,6 +1,7 @@ package com.bugsnag.android import android.content.Context +import com.bugsnag.android.internal.ImmutableConfig import java.io.File import java.util.UUID @@ -8,18 +9,20 @@ import java.util.UUID * This class is responsible for persisting and retrieving the device ID and internal device ID, * which uniquely identify this device in various contexts. */ -internal class DeviceIdStore @JvmOverloads constructor( +internal class DeviceIdStore @JvmOverloads @Suppress("LongParameterList") constructor( context: Context, deviceIdfile: File = File(context.filesDir, "device-id"), deviceIdGenerator: () -> UUID = { UUID.randomUUID() }, internalDeviceIdfile: File = File(context.filesDir, "internal-device-id"), internalDeviceIdGenerator: () -> UUID = { UUID.randomUUID() }, private val sharedPrefMigrator: SharedPrefMigrator, + config: ImmutableConfig, logger: Logger ) { private val persistence: DeviceIdPersistence private val internalPersistence: DeviceIdPersistence + private val generateId = config.generateAnonymousId init { persistence = DeviceIdFilePersistence(deviceIdfile, deviceIdGenerator, logger) @@ -35,6 +38,12 @@ internal class DeviceIdStore @JvmOverloads constructor( * be used. If no value is present then a random UUID will be generated and persisted. */ fun loadDeviceId(): String? { + // If generateAnonymousId = false, return null + // so that a previously persisted device ID is not returned, + // or a new one is not generated and persisted + if (!generateId) { + return null + } var result = persistence.loadDeviceId(false) if (result != null) { return result @@ -47,6 +56,12 @@ internal class DeviceIdStore @JvmOverloads constructor( } fun loadInternalDeviceId(): String? { + // If generateAnonymousId = false, return null + // so that a previously persisted device ID is not returned, + // or a new one is not generated and persisted + if (!generateId) { + return null + } return internalPersistence.loadDeviceId(true) } } diff --git a/app/src/main/java/com/bugsnag/android/FeatureFlags.kt b/app/src/main/java/com/bugsnag/android/FeatureFlags.kt index efb57506ec..24de0276b2 100644 --- a/app/src/main/java/com/bugsnag/android/FeatureFlags.kt +++ b/app/src/main/java/com/bugsnag/android/FeatureFlags.kt @@ -1,42 +1,113 @@ package com.bugsnag.android import java.io.IOException +import kotlin.math.max -internal class FeatureFlags( - internal val store: MutableMap = mutableMapOf() +internal class FeatureFlags private constructor( + @Volatile + private var flags: Array ) : JsonStream.Streamable, FeatureFlagAware { - private val emptyVariant = "__EMPTY_VARIANT_SENTINEL__" - @Synchronized override fun addFeatureFlag(name: String) { + /* + * Implemented as *effectively* a CopyOnWriteArrayList - but since FeatureFlags are + * key/value pairs, CopyOnWriteArrayList would require external locking (in addition to it's + * internal locking) for us to be sure we are not adding duplicates. + * + * This class aims to have similar performance while also ensuring that the FeatureFlag object + * themselves don't leak, as they are mutable and we want 'copy' to be an O(1) snapshot + * operation for when an Event is created. + * + * It's assumed that *most* FeatureFlags will be added up-front, or during the normal app + * lifecycle (not during an Event). + * + * As such a copy-on-write structure allows an Event to simply capture a reference to the + * "snapshot" of FeatureFlags that were active when the Event was created. + */ + + constructor() : this(emptyArray()) + + override fun addFeatureFlag(name: String) { addFeatureFlag(name, null) } - @Synchronized override fun addFeatureFlag(name: String, variant: String?) { - store[name] = variant ?: emptyVariant - } + override fun addFeatureFlag(name: String, variant: String?) { + synchronized(this) { + val flagArray = flags + val index = flagArray.indexOfFirst { it.name == name } - @Synchronized override fun addFeatureFlags(featureFlags: Iterable) { - featureFlags.forEach { (name, variant) -> - addFeatureFlag(name, variant) + flags = when { + // this is a new FeatureFlag + index == -1 -> flagArray + FeatureFlag(name, variant) + + // this is a change to an existing FeatureFlag + flagArray[index].variant != variant -> flagArray.copyOf().also { + // replace the existing FeatureFlag in-place + it[index] = FeatureFlag(name, variant) + } + + // no actual change, so we return + else -> return + } } } - @Synchronized override fun clearFeatureFlag(name: String) { - store.remove(name) + override fun addFeatureFlags(featureFlags: Iterable) { + synchronized(this) { + val flagArray = flags + + val newFlags = ArrayList( + // try to guess a reasonable upper-bound for the output array + if (featureFlags is Collection<*>) flagArray.size + featureFlags.size + else max(flagArray.size * 2, flagArray.size) + ) + + newFlags.addAll(flagArray) + + featureFlags.forEach { (name, variant) -> + val existingIndex = newFlags.indexOfFirst { it.name == name } + when (existingIndex) { + // add a new flag to the end of the list + -1 -> newFlags.add(FeatureFlag(name, variant)) + // replace the existing flag + else -> newFlags[existingIndex] = FeatureFlag(name, variant) + } + } + + flags = newFlags.toTypedArray() + } } - @Synchronized override fun clearFeatureFlags() { - store.clear() + override fun clearFeatureFlag(name: String) { + synchronized(this) { + val flagArray = flags + val index = flagArray.indexOfFirst { it.name == name } + if (index == -1) { + return + } + + val out = arrayOfNulls(flagArray.size - 1) + flagArray.copyInto(out, 0, 0, index) + flagArray.copyInto(out, index, index + 1) + + @Suppress("UNCHECKED_CAST") + flags = out as Array + } + } + + override fun clearFeatureFlags() { + synchronized(this) { + flags = emptyArray() + } } @Throws(IOException::class) override fun toStream(stream: JsonStream) { - val storeCopy = synchronized(this) { store.toMap() } + val storeCopy = flags stream.beginArray() storeCopy.forEach { (name, variant) -> stream.beginObject() stream.name("featureFlag").value(name) - if (variant != emptyVariant) { + if (variant != null) { stream.name("variant").value(variant) } stream.endObject() @@ -44,9 +115,7 @@ internal class FeatureFlags( stream.endArray() } - @Synchronized fun toList(): List = store.entries.map { (name, variant) -> - FeatureFlag(name, variant.takeUnless { it == emptyVariant }) - } + fun toList(): List = flags.map { (name, variant) -> FeatureFlag(name, variant) } - @Synchronized fun copy() = FeatureFlags(store.toMutableMap()) + fun copy() = FeatureFlags(flags) } diff --git a/app/src/main/java/com/bugsnag/android/JsonWriter.java b/app/src/main/java/com/bugsnag/android/JsonWriter.java index 3e352b713d..cb4fdddb43 100644 --- a/app/src/main/java/com/bugsnag/android/JsonWriter.java +++ b/app/src/main/java/com/bugsnag/android/JsonWriter.java @@ -145,6 +145,7 @@ class JsonWriter implements Closeable, Flushable { */ private static final String[] REPLACEMENT_CHARS; private static final String[] HTML_SAFE_REPLACEMENT_CHARS; + static { REPLACEMENT_CHARS = new String[128]; for (int i = 0; i <= 0x1f; i++) { @@ -165,11 +166,14 @@ class JsonWriter implements Closeable, Flushable { HTML_SAFE_REPLACEMENT_CHARS['\''] = "\\u0027"; } - /** The output data, containing at most one top-level array or object. */ + /** + * The output data, containing at most one top-level array or object. + */ private final Writer out; private int[] stack = new int[32]; private int stackSize = 0; + { push(EMPTY_DOCUMENT); } @@ -337,7 +341,7 @@ class JsonWriter implements Closeable, Flushable { * given bracket. */ private JsonWriter close(int empty, int nonempty, String closeBracket) - throws IOException { + throws IOException { int context = peek(); if (context != nonempty && context != empty) { throw new IllegalStateException("Nesting problem."); @@ -437,7 +441,7 @@ class JsonWriter implements Closeable, Flushable { } writeDeferredName(); beforeValue(); - out.append(value); + out.write(value); return this; } @@ -490,17 +494,18 @@ class JsonWriter implements Closeable, Flushable { /** * Encodes {@code value}. * - * @param value a finite value. May not be {@link Double#isNaN() NaNs} or - * {@link Double#isInfinite() infinities}. + * @param value a finite value. * @return this writer. */ public JsonWriter value(double value) throws IOException { - writeDeferredName(); if (!lenient && (Double.isNaN(value) || Double.isInfinite(value))) { - throw new IllegalArgumentException("Numeric values must be finite, but was " + value); + // omit these values instead of attempting to write them + deferredName = null; + } else { + writeDeferredName(); + beforeValue(); + out.write(Double.toString(value)); } - beforeValue(); - out.append(Double.toString(value)); return this; } @@ -520,7 +525,7 @@ class JsonWriter implements Closeable, Flushable { * Encodes {@code value}. * * @param value a finite value. May not be {@link Double#isNaN() NaNs} or - * {@link Double#isInfinite() infinities}. + * {@link Double#isInfinite() infinities}. * @return this writer. */ public JsonWriter value(Number value) throws IOException { @@ -528,14 +533,16 @@ class JsonWriter implements Closeable, Flushable { return nullValue(); } - writeDeferredName(); String string = value.toString(); if (!lenient - && (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) { - throw new IllegalArgumentException("Numeric values must be finite, but was " + value); + && (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) { + // omit this value + deferredName = null; + } else { + writeDeferredName(); + beforeValue(); + out.write(string); } - beforeValue(); - out.append(string); return this; } @@ -634,7 +641,7 @@ class JsonWriter implements Closeable, Flushable { case NONEMPTY_DOCUMENT: if (!lenient) { throw new IllegalStateException( - "JSON must have only one top-level value."); + "JSON must have only one top-level value."); } // fall-through case EMPTY_DOCUMENT: // first in document @@ -647,12 +654,12 @@ class JsonWriter implements Closeable, Flushable { break; case NONEMPTY_ARRAY: // another in array - out.append(','); + out.write(','); newline(); break; case DANGLING_NAME: // value for name - out.append(separator); + out.write(separator); replaceTop(NONEMPTY_OBJECT); break; diff --git a/app/src/main/java/com/bugsnag/android/ManifestConfigLoader.kt b/app/src/main/java/com/bugsnag/android/ManifestConfigLoader.kt index 8214f192df..b357cd9a14 100644 --- a/app/src/main/java/com/bugsnag/android/ManifestConfigLoader.kt +++ b/app/src/main/java/com/bugsnag/android/ManifestConfigLoader.kt @@ -20,6 +20,7 @@ internal class ManifestConfigLoader { private const val AUTO_DETECT_ERRORS = "$BUGSNAG_NS.AUTO_DETECT_ERRORS" private const val PERSIST_USER = "$BUGSNAG_NS.PERSIST_USER" private const val SEND_THREADS = "$BUGSNAG_NS.SEND_THREADS" + private const val GENERATE_ANONYMOUS_ID = "$BUGSNAG_NS.GENERATE_ANONYMOUS_ID" // endpoints private const val ENDPOINT_NOTIFY = "$BUGSNAG_NS.ENDPOINT_NOTIFY" @@ -108,6 +109,7 @@ internal class ManifestConfigLoader { autoTrackSessions = data.getBoolean(AUTO_TRACK_SESSIONS, autoTrackSessions) autoDetectErrors = data.getBoolean(AUTO_DETECT_ERRORS, autoDetectErrors) persistUser = data.getBoolean(PERSIST_USER, persistUser) + generateAnonymousId = data.getBoolean(GENERATE_ANONYMOUS_ID, generateAnonymousId) val str = data.getString(SEND_THREADS) diff --git a/app/src/main/java/com/bugsnag/android/Notifier.kt b/app/src/main/java/com/bugsnag/android/Notifier.kt index f63f29d49d..bea14c68a8 100644 --- a/app/src/main/java/com/bugsnag/android/Notifier.kt +++ b/app/src/main/java/com/bugsnag/android/Notifier.kt @@ -7,7 +7,7 @@ import java.io.IOException */ class Notifier @JvmOverloads constructor( var name: String = "Android Bugsnag Notifier", - var version: String = "6.1.0", + var version: String = "6.4.0", var url: String = "https://bugsnag.com" ) : JsonStream.Streamable { diff --git a/app/src/main/java/com/bugsnag/android/ObjectJsonStreamer.kt b/app/src/main/java/com/bugsnag/android/ObjectJsonStreamer.kt index 5df61b8ae9..abcb927c77 100644 --- a/app/src/main/java/com/bugsnag/android/ObjectJsonStreamer.kt +++ b/app/src/main/java/com/bugsnag/android/ObjectJsonStreamer.kt @@ -11,9 +11,11 @@ internal class ObjectJsonStreamer { companion object { internal const val REDACTED_PLACEHOLDER = "[REDACTED]" internal const val OBJECT_PLACEHOLDER = "[OBJECT]" + + internal val DEFAULT_REDACTED_KEYS = setOf(Pattern.compile(".*password.*", Pattern.CASE_INSENSITIVE)) } - var redactedKeys = setOf(Pattern.compile(".*password.*", Pattern.CASE_INSENSITIVE)) + var redactedKeys = DEFAULT_REDACTED_KEYS // Write complex/nested values to a JsonStreamer @Throws(IOException::class) diff --git a/app/src/main/java/com/bugsnag/android/SessionTracker.java b/app/src/main/java/com/bugsnag/android/SessionTracker.java index bb72ddc5dd..e9e15b2a2e 100644 --- a/app/src/main/java/com/bugsnag/android/SessionTracker.java +++ b/app/src/main/java/com/bugsnag/android/SessionTracker.java @@ -36,6 +36,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi private volatile Session currentSession = null; final BackgroundTaskService backgroundTaskService; final Logger logger; + private boolean shouldSuppressFirstAutoSession = false; SessionTracker(ImmutableConfig configuration, CallbackState callbackState, @@ -76,7 +77,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi @VisibleForTesting Session startNewSession(@NonNull Date date, @Nullable User user, boolean autoCaptured) { - if (client.getConfig().shouldDiscardSession(autoCaptured)) { + if (shouldDiscardSession(autoCaptured)) { return null; } String id = UUID.randomUUID().toString(); @@ -92,12 +93,29 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi } Session startSession(boolean autoCaptured) { - if (client.getConfig().shouldDiscardSession(autoCaptured)) { + if (shouldDiscardSession(autoCaptured)) { return null; } return startNewSession(new Date(), client.getUser(), autoCaptured); } + private boolean shouldDiscardSession(boolean autoCaptured) { + if (client.getConfig().shouldDiscardSession(autoCaptured)) { + return true; + } else { + Session existingSession = currentSession; + if (autoCaptured + && existingSession != null + && !existingSession.isAutoCaptured() + && shouldSuppressFirstAutoSession) { + shouldSuppressFirstAutoSession = true; + return true; + } + } + return false; + } + + void pauseSession() { Session session = currentSession; diff --git a/app/src/main/java/com/bugsnag/android/StorageModule.kt b/app/src/main/java/com/bugsnag/android/StorageModule.kt index 5bf1d288eb..8defc58ac4 100644 --- a/app/src/main/java/com/bugsnag/android/StorageModule.kt +++ b/app/src/main/java/com/bugsnag/android/StorageModule.kt @@ -19,7 +19,8 @@ internal class StorageModule( DeviceIdStore( appContext, sharedPrefMigrator = sharedPrefMigrator, - logger = logger + logger = logger, + config = immutableConfig ) } diff --git a/app/src/main/java/com/bugsnag/android/UserStore.kt b/app/src/main/java/com/bugsnag/android/UserStore.kt index 87adf40e16..548bcdbc35 100644 --- a/app/src/main/java/com/bugsnag/android/UserStore.kt +++ b/app/src/main/java/com/bugsnag/android/UserStore.kt @@ -30,7 +30,9 @@ internal class UserStore @JvmOverloads constructor( * [Configuration.getPersistUser] is true. * * If no user is stored on disk, then a default [User] is used which uses the device ID - * as its ID. + * as its ID (unless the generateAnonymousId config option is set to false, in which case the + * device ID and therefore the user ID is set to + * null). * * The [UserState] provides a mechanism for observing value changes to its user property, * so to avoid interfering with this the method should only be called once for each [Client]. @@ -46,6 +48,8 @@ internal class UserStore @JvmOverloads constructor( val userState = when { loadedUser != null && validUser(loadedUser) -> UserState(loadedUser) + // if generateAnonymousId config option is false, the deviceId should already be null + // here else -> UserState(User(deviceId, null, null)) } diff --git a/app/src/main/java/com/bugsnag/android/internal/ImmutableConfig.kt b/app/src/main/java/com/bugsnag/android/internal/ImmutableConfig.kt index eed21dbb50..17e195727b 100644 --- a/app/src/main/java/com/bugsnag/android/internal/ImmutableConfig.kt +++ b/app/src/main/java/com/bugsnag/android/internal/ImmutableConfig.kt @@ -57,6 +57,7 @@ data class ImmutableConfig( val persistenceDirectory: Lazy, val sendLaunchCrashesSynchronously: Boolean, val attemptDeliveryOnCrash: Boolean, + val generateAnonymousId: Boolean, // results cached here to avoid unnecessary lookups in Client. val packageInfo: PackageInfo?, @@ -166,6 +167,7 @@ internal fun convertToImmutableConfig( delivery = config.delivery, endpoints = config.endpoints, persistUser = config.persistUser, + generateAnonymousId = config.generateAnonymousId, launchDurationMillis = config.launchDurationMillis, logger = config.logger!!, maxBreadcrumbs = config.maxBreadcrumbs,