Updated Bugsnag

pull/215/head
M66B 9 months ago
parent caab02b0d6
commit 3989504073

@ -575,7 +575,7 @@ dependencies {
def minidns_version = "1.0.5" def minidns_version = "1.0.5"
def openpgp_version = "12.0" def openpgp_version = "12.0"
def badge_version = "1.1.22" 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 biweekly_version = "0.6.8"
def vcard_version = "0.12.1" def vcard_version = "0.12.1"
def relinker_version = "1.4.5" def relinker_version = "1.4.5"

@ -2,11 +2,26 @@ package com.bugsnag.android
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.ActivityManager 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.app.Application
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.Build.VERSION_CODES import android.os.Build.VERSION_CODES
import android.os.Process
import android.os.SystemClock import android.os.SystemClock
import com.bugsnag.android.internal.ImmutableConfig 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<String, Any?> { fun getAppDataMetadata(): MutableMap<String, Any?> {
val map = HashMap<String, Any?>() val map = HashMap<String, Any?>()
map["name"] = appName map["name"] = appName
map["activeScreen"] = sessionTracker.contextActivity map["activeScreen"] = sessionTracker.contextActivity
map["lowMemory"] = memoryTrimState.isLowMemory map["lowMemory"] = memoryTrimState.isLowMemory
map["memoryTrimLevel"] = memoryTrimState.trimLevelDescription map["memoryTrimLevel"] = memoryTrimState.trimLevelDescription
map["processImportance"] = getProcessImportance()
populateRuntimeMemoryMetadata(map) populateRuntimeMemoryMetadata(map)
@ -128,6 +188,7 @@ internal class AppDataCollector(
packageManager != null && copy != null -> { packageManager != null && copy != null -> {
packageManager.getApplicationLabel(copy).toString() packageManager.getApplicationLabel(copy).toString()
} }
else -> null else -> null
} }
} }
@ -156,6 +217,7 @@ internal class AppDataCollector(
VERSION.SDK_INT >= VERSION_CODES.P -> { VERSION.SDK_INT >= VERSION_CODES.P -> {
Application.getProcessName() Application.getProcessName()
} }
else -> { else -> {
// see https://stackoverflow.com/questions/19631894 // see https://stackoverflow.com/questions/19631894
val clz = Class.forName("android.app.ActivityThread") val clz = Class.forName("android.app.ActivityThread")
@ -179,5 +241,7 @@ internal class AppDataCollector(
* good approximation for how long the app has been running. * good approximation for how long the app has been running.
*/ */
fun getDurationMs(): Long = SystemClock.elapsedRealtime() - startTimeMs fun getDurationMs(): Long = SystemClock.elapsedRealtime() - startTimeMs
private const val IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170
} }
} }

@ -29,12 +29,14 @@ internal class BugsnagEventMapper(
event.userImpl = convertUser(map.readEntry("user")) event.userImpl = convertUser(map.readEntry("user"))
// populate metadata // populate metadata
val metadataMap: Map<String, Map<String, Any?>> = map.readEntry("metaData") val metadataMap: Map<String, Map<String, Any?>> =
(map["metaData"] as? Map<String, Map<String, Any?>>).orEmpty()
metadataMap.forEach { (key, value) -> metadataMap.forEach { (key, value) ->
event.addMetadata(key, value) event.addMetadata(key, value)
} }
val featureFlagsList: List<Map<String, Any?>> = map.readEntry("featureFlags") val featureFlagsList: List<Map<String, Any?>> =
(map["featureFlags"] as? List<Map<String, Any?>>).orEmpty()
featureFlagsList.forEach { featureFlagMap -> featureFlagsList.forEach { featureFlagMap ->
event.addFeatureFlag( event.addFeatureFlag(
featureFlagMap.readEntry("featureFlag"), featureFlagMap.readEntry("featureFlag"),
@ -43,7 +45,8 @@ internal class BugsnagEventMapper(
} }
// populate breadcrumbs // populate breadcrumbs
val breadcrumbList: List<MutableMap<String, Any?>> = map.readEntry("breadcrumbs") val breadcrumbList: List<MutableMap<String, Any?>> =
(map["breadcrumbs"] as? List<MutableMap<String, Any?>>).orEmpty()
breadcrumbList.mapTo(event.breadcrumbs) { breadcrumbList.mapTo(event.breadcrumbs) {
Breadcrumb( Breadcrumb(
convertBreadcrumbInternal(it), convertBreadcrumbInternal(it),
@ -226,8 +229,7 @@ internal class BugsnagEventMapper(
is T -> return value is T -> return value
null -> throw IllegalStateException("cannot find json property '$key'") null -> throw IllegalStateException("cannot find json property '$key'")
else -> throw IllegalArgumentException( else -> throw IllegalArgumentException(
"json property '$key' not " + "json property '$key' not of expected type, found ${value.javaClass.name}"
"of expected type, found ${value.javaClass.name}"
) )
} }
} }

@ -25,6 +25,7 @@ internal class ConfigInternal(
var releaseStage: String? = null var releaseStage: String? = null
var sendThreads: ThreadSendPolicy = ThreadSendPolicy.ALWAYS var sendThreads: ThreadSendPolicy = ThreadSendPolicy.ALWAYS
var persistUser: Boolean = true var persistUser: Boolean = true
var generateAnonymousId: Boolean = true
var launchDurationMillis: Long = DEFAULT_LAUNCH_CRASH_THRESHOLD_MS var launchDurationMillis: Long = DEFAULT_LAUNCH_CRASH_THRESHOLD_MS

@ -178,6 +178,26 @@ public class Configuration implements CallbackAware, MetadataAware, UserAware, F
impl.setPersistUser(persistUser); 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 * 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 * request is not successful. If you use Bugsnag in multiple processes, then a unique

@ -1,6 +1,7 @@
package com.bugsnag.android package com.bugsnag.android
import android.content.Context import android.content.Context
import com.bugsnag.android.internal.ImmutableConfig
import java.io.File import java.io.File
import java.util.UUID 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, * This class is responsible for persisting and retrieving the device ID and internal device ID,
* which uniquely identify this device in various contexts. * which uniquely identify this device in various contexts.
*/ */
internal class DeviceIdStore @JvmOverloads constructor( internal class DeviceIdStore @JvmOverloads @Suppress("LongParameterList") constructor(
context: Context, context: Context,
deviceIdfile: File = File(context.filesDir, "device-id"), deviceIdfile: File = File(context.filesDir, "device-id"),
deviceIdGenerator: () -> UUID = { UUID.randomUUID() }, deviceIdGenerator: () -> UUID = { UUID.randomUUID() },
internalDeviceIdfile: File = File(context.filesDir, "internal-device-id"), internalDeviceIdfile: File = File(context.filesDir, "internal-device-id"),
internalDeviceIdGenerator: () -> UUID = { UUID.randomUUID() }, internalDeviceIdGenerator: () -> UUID = { UUID.randomUUID() },
private val sharedPrefMigrator: SharedPrefMigrator, private val sharedPrefMigrator: SharedPrefMigrator,
config: ImmutableConfig,
logger: Logger logger: Logger
) { ) {
private val persistence: DeviceIdPersistence private val persistence: DeviceIdPersistence
private val internalPersistence: DeviceIdPersistence private val internalPersistence: DeviceIdPersistence
private val generateId = config.generateAnonymousId
init { init {
persistence = DeviceIdFilePersistence(deviceIdfile, deviceIdGenerator, logger) 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. * be used. If no value is present then a random UUID will be generated and persisted.
*/ */
fun loadDeviceId(): String? { 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) var result = persistence.loadDeviceId(false)
if (result != null) { if (result != null) {
return result return result
@ -47,6 +56,12 @@ internal class DeviceIdStore @JvmOverloads constructor(
} }
fun loadInternalDeviceId(): String? { 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) return internalPersistence.loadDeviceId(true)
} }
} }

@ -1,42 +1,113 @@
package com.bugsnag.android package com.bugsnag.android
import java.io.IOException import java.io.IOException
import kotlin.math.max
internal class FeatureFlags( internal class FeatureFlags private constructor(
internal val store: MutableMap<String, String?> = mutableMapOf() @Volatile
private var flags: Array<FeatureFlag>
) : JsonStream.Streamable, FeatureFlagAware { ) : 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<FeatureFlag>())
override fun addFeatureFlag(name: String) {
addFeatureFlag(name, null) addFeatureFlag(name, null)
} }
@Synchronized override fun addFeatureFlag(name: String, variant: String?) { override fun addFeatureFlag(name: String, variant: String?) {
store[name] = variant ?: emptyVariant synchronized(this) {
val flagArray = flags
val index = flagArray.indexOfFirst { it.name == name }
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 addFeatureFlags(featureFlags: Iterable<FeatureFlag>) { override fun addFeatureFlags(featureFlags: Iterable<FeatureFlag>) {
synchronized(this) {
val flagArray = flags
val newFlags = ArrayList<FeatureFlag>(
// 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) -> featureFlags.forEach { (name, variant) ->
addFeatureFlag(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()
}
} }
override fun clearFeatureFlag(name: String) {
synchronized(this) {
val flagArray = flags
val index = flagArray.indexOfFirst { it.name == name }
if (index == -1) {
return
} }
@Synchronized override fun clearFeatureFlag(name: String) { val out = arrayOfNulls<FeatureFlag>(flagArray.size - 1)
store.remove(name) flagArray.copyInto(out, 0, 0, index)
flagArray.copyInto(out, index, index + 1)
@Suppress("UNCHECKED_CAST")
flags = out as Array<FeatureFlag>
}
} }
@Synchronized override fun clearFeatureFlags() { override fun clearFeatureFlags() {
store.clear() synchronized(this) {
flags = emptyArray()
}
} }
@Throws(IOException::class) @Throws(IOException::class)
override fun toStream(stream: JsonStream) { override fun toStream(stream: JsonStream) {
val storeCopy = synchronized(this) { store.toMap() } val storeCopy = flags
stream.beginArray() stream.beginArray()
storeCopy.forEach { (name, variant) -> storeCopy.forEach { (name, variant) ->
stream.beginObject() stream.beginObject()
stream.name("featureFlag").value(name) stream.name("featureFlag").value(name)
if (variant != emptyVariant) { if (variant != null) {
stream.name("variant").value(variant) stream.name("variant").value(variant)
} }
stream.endObject() stream.endObject()
@ -44,9 +115,7 @@ internal class FeatureFlags(
stream.endArray() stream.endArray()
} }
@Synchronized fun toList(): List<FeatureFlag> = store.entries.map { (name, variant) -> fun toList(): List<FeatureFlag> = flags.map { (name, variant) -> FeatureFlag(name, variant) }
FeatureFlag(name, variant.takeUnless { it == emptyVariant })
}
@Synchronized fun copy() = FeatureFlags(store.toMutableMap()) fun copy() = FeatureFlags(flags)
} }

@ -145,6 +145,7 @@ class JsonWriter implements Closeable, Flushable {
*/ */
private static final String[] REPLACEMENT_CHARS; private static final String[] REPLACEMENT_CHARS;
private static final String[] HTML_SAFE_REPLACEMENT_CHARS; private static final String[] HTML_SAFE_REPLACEMENT_CHARS;
static { static {
REPLACEMENT_CHARS = new String[128]; REPLACEMENT_CHARS = new String[128];
for (int i = 0; i <= 0x1f; i++) { for (int i = 0; i <= 0x1f; i++) {
@ -165,11 +166,14 @@ class JsonWriter implements Closeable, Flushable {
HTML_SAFE_REPLACEMENT_CHARS['\''] = "\\u0027"; 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 final Writer out;
private int[] stack = new int[32]; private int[] stack = new int[32];
private int stackSize = 0; private int stackSize = 0;
{ {
push(EMPTY_DOCUMENT); push(EMPTY_DOCUMENT);
} }
@ -437,7 +441,7 @@ class JsonWriter implements Closeable, Flushable {
} }
writeDeferredName(); writeDeferredName();
beforeValue(); beforeValue();
out.append(value); out.write(value);
return this; return this;
} }
@ -490,17 +494,18 @@ class JsonWriter implements Closeable, Flushable {
/** /**
* Encodes {@code value}. * Encodes {@code value}.
* *
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or * @param value a finite value.
* {@link Double#isInfinite() infinities}.
* @return this writer. * @return this writer.
*/ */
public JsonWriter value(double value) throws IOException { public JsonWriter value(double value) throws IOException {
writeDeferredName();
if (!lenient && (Double.isNaN(value) || Double.isInfinite(value))) { 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(); beforeValue();
out.append(Double.toString(value)); out.write(Double.toString(value));
}
return this; return this;
} }
@ -528,14 +533,16 @@ class JsonWriter implements Closeable, Flushable {
return nullValue(); return nullValue();
} }
writeDeferredName();
String string = value.toString(); String string = value.toString();
if (!lenient if (!lenient
&& (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) { && (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value); // omit this value
} deferredName = null;
} else {
writeDeferredName();
beforeValue(); beforeValue();
out.append(string); out.write(string);
}
return this; return this;
} }
@ -647,12 +654,12 @@ class JsonWriter implements Closeable, Flushable {
break; break;
case NONEMPTY_ARRAY: // another in array case NONEMPTY_ARRAY: // another in array
out.append(','); out.write(',');
newline(); newline();
break; break;
case DANGLING_NAME: // value for name case DANGLING_NAME: // value for name
out.append(separator); out.write(separator);
replaceTop(NONEMPTY_OBJECT); replaceTop(NONEMPTY_OBJECT);
break; break;

@ -20,6 +20,7 @@ internal class ManifestConfigLoader {
private const val AUTO_DETECT_ERRORS = "$BUGSNAG_NS.AUTO_DETECT_ERRORS" private const val AUTO_DETECT_ERRORS = "$BUGSNAG_NS.AUTO_DETECT_ERRORS"
private const val PERSIST_USER = "$BUGSNAG_NS.PERSIST_USER" private const val PERSIST_USER = "$BUGSNAG_NS.PERSIST_USER"
private const val SEND_THREADS = "$BUGSNAG_NS.SEND_THREADS" private const val SEND_THREADS = "$BUGSNAG_NS.SEND_THREADS"
private const val GENERATE_ANONYMOUS_ID = "$BUGSNAG_NS.GENERATE_ANONYMOUS_ID"
// endpoints // endpoints
private const val ENDPOINT_NOTIFY = "$BUGSNAG_NS.ENDPOINT_NOTIFY" private const val ENDPOINT_NOTIFY = "$BUGSNAG_NS.ENDPOINT_NOTIFY"
@ -108,6 +109,7 @@ internal class ManifestConfigLoader {
autoTrackSessions = data.getBoolean(AUTO_TRACK_SESSIONS, autoTrackSessions) autoTrackSessions = data.getBoolean(AUTO_TRACK_SESSIONS, autoTrackSessions)
autoDetectErrors = data.getBoolean(AUTO_DETECT_ERRORS, autoDetectErrors) autoDetectErrors = data.getBoolean(AUTO_DETECT_ERRORS, autoDetectErrors)
persistUser = data.getBoolean(PERSIST_USER, persistUser) persistUser = data.getBoolean(PERSIST_USER, persistUser)
generateAnonymousId = data.getBoolean(GENERATE_ANONYMOUS_ID, generateAnonymousId)
val str = data.getString(SEND_THREADS) val str = data.getString(SEND_THREADS)

@ -7,7 +7,7 @@ import java.io.IOException
*/ */
class Notifier @JvmOverloads constructor( class Notifier @JvmOverloads constructor(
var name: String = "Android Bugsnag Notifier", var name: String = "Android Bugsnag Notifier",
var version: String = "6.1.0", var version: String = "6.4.0",
var url: String = "https://bugsnag.com" var url: String = "https://bugsnag.com"
) : JsonStream.Streamable { ) : JsonStream.Streamable {

@ -11,9 +11,11 @@ internal class ObjectJsonStreamer {
companion object { companion object {
internal const val REDACTED_PLACEHOLDER = "[REDACTED]" internal const val REDACTED_PLACEHOLDER = "[REDACTED]"
internal const val OBJECT_PLACEHOLDER = "[OBJECT]" 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 // Write complex/nested values to a JsonStreamer
@Throws(IOException::class) @Throws(IOException::class)

@ -36,6 +36,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi
private volatile Session currentSession = null; private volatile Session currentSession = null;
final BackgroundTaskService backgroundTaskService; final BackgroundTaskService backgroundTaskService;
final Logger logger; final Logger logger;
private boolean shouldSuppressFirstAutoSession = false;
SessionTracker(ImmutableConfig configuration, SessionTracker(ImmutableConfig configuration,
CallbackState callbackState, CallbackState callbackState,
@ -76,7 +77,7 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi
@VisibleForTesting @VisibleForTesting
Session startNewSession(@NonNull Date date, @Nullable User user, Session startNewSession(@NonNull Date date, @Nullable User user,
boolean autoCaptured) { boolean autoCaptured) {
if (client.getConfig().shouldDiscardSession(autoCaptured)) { if (shouldDiscardSession(autoCaptured)) {
return null; return null;
} }
String id = UUID.randomUUID().toString(); String id = UUID.randomUUID().toString();
@ -92,12 +93,29 @@ class SessionTracker extends BaseObservable implements ForegroundDetector.OnActi
} }
Session startSession(boolean autoCaptured) { Session startSession(boolean autoCaptured) {
if (client.getConfig().shouldDiscardSession(autoCaptured)) { if (shouldDiscardSession(autoCaptured)) {
return null; return null;
} }
return startNewSession(new Date(), client.getUser(), autoCaptured); 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() { void pauseSession() {
Session session = currentSession; Session session = currentSession;

@ -19,7 +19,8 @@ internal class StorageModule(
DeviceIdStore( DeviceIdStore(
appContext, appContext,
sharedPrefMigrator = sharedPrefMigrator, sharedPrefMigrator = sharedPrefMigrator,
logger = logger logger = logger,
config = immutableConfig
) )
} }

@ -30,7 +30,9 @@ internal class UserStore @JvmOverloads constructor(
* [Configuration.getPersistUser] is true. * [Configuration.getPersistUser] is true.
* *
* If no user is stored on disk, then a default [User] is used which uses the device ID * 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, * 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]. * 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 { val userState = when {
loadedUser != null && validUser(loadedUser) -> UserState(loadedUser) 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)) else -> UserState(User(deviceId, null, null))
} }

@ -57,6 +57,7 @@ data class ImmutableConfig(
val persistenceDirectory: Lazy<File>, val persistenceDirectory: Lazy<File>,
val sendLaunchCrashesSynchronously: Boolean, val sendLaunchCrashesSynchronously: Boolean,
val attemptDeliveryOnCrash: Boolean, val attemptDeliveryOnCrash: Boolean,
val generateAnonymousId: Boolean,
// results cached here to avoid unnecessary lookups in Client. // results cached here to avoid unnecessary lookups in Client.
val packageInfo: PackageInfo?, val packageInfo: PackageInfo?,
@ -166,6 +167,7 @@ internal fun convertToImmutableConfig(
delivery = config.delivery, delivery = config.delivery,
endpoints = config.endpoints, endpoints = config.endpoints,
persistUser = config.persistUser, persistUser = config.persistUser,
generateAnonymousId = config.generateAnonymousId,
launchDurationMillis = config.launchDurationMillis, launchDurationMillis = config.launchDurationMillis,
logger = config.logger!!, logger = config.logger!!,
maxBreadcrumbs = config.maxBreadcrumbs, maxBreadcrumbs = config.maxBreadcrumbs,

Loading…
Cancel
Save