Updated Bugsnag to v5.30.0

This commit is contained in:
M66B 2023-06-28 08:31:24 +02:00
parent 03de3b55c9
commit 21c71ffcc8
19 changed files with 108 additions and 69 deletions

View File

@ -3,47 +3,56 @@ package com.bugsnag.android
import android.app.Activity
import android.app.Application
import android.os.Bundle
import java.util.WeakHashMap
internal class ActivityBreadcrumbCollector(
private val cb: (message: String, method: Map<String, Any>) -> Unit
) : Application.ActivityLifecycleCallbacks {
var prevState: String? = null
private val prevState = WeakHashMap<Activity, String>()
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) =
leaveBreadcrumb(getActivityName(activity), "onCreate()", savedInstanceState != null)
leaveBreadcrumb(activity, "onCreate()", savedInstanceState != null)
override fun onActivityStarted(activity: Activity) =
leaveBreadcrumb(getActivityName(activity), "onStart()")
leaveBreadcrumb(activity, "onStart()")
override fun onActivityResumed(activity: Activity) =
leaveBreadcrumb(getActivityName(activity), "onResume()")
leaveBreadcrumb(activity, "onResume()")
override fun onActivityPaused(activity: Activity) =
leaveBreadcrumb(getActivityName(activity), "onPause()")
leaveBreadcrumb(activity, "onPause()")
override fun onActivityStopped(activity: Activity) =
leaveBreadcrumb(getActivityName(activity), "onStop()")
leaveBreadcrumb(activity, "onStop()")
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) =
leaveBreadcrumb(getActivityName(activity), "onSaveInstanceState()")
leaveBreadcrumb(activity, "onSaveInstanceState()")
override fun onActivityDestroyed(activity: Activity) =
leaveBreadcrumb(getActivityName(activity), "onDestroy()")
override fun onActivityDestroyed(activity: Activity) {
leaveBreadcrumb(activity, "onDestroy()")
prevState.remove(activity)
}
private fun getActivityName(activity: Activity) = activity.javaClass.simpleName
private fun leaveBreadcrumb(activityName: String, lifecycleCallback: String, hasBundle: Boolean? = null) {
private fun leaveBreadcrumb(
activity: Activity,
lifecycleCallback: String,
hasBundle: Boolean? = null
) {
val metadata = mutableMapOf<String, Any>()
if (hasBundle != null) {
metadata["hasBundle"] = hasBundle
}
val previousVal = prevState
val previousVal = prevState[activity]
if (previousVal != null) {
metadata["previous"] = previousVal
}
val activityName = getActivityName(activity)
cb("$activityName#$lifecycleCallback", metadata)
prevState = lifecycleCallback
prevState[activity] = lifecycleCallback
}
}

View File

@ -2,11 +2,13 @@ package com.bugsnag.android;
import static com.bugsnag.android.SeverityReason.REASON_HANDLED_EXCEPTION;
import com.bugsnag.android.internal.BackgroundTaskService;
import com.bugsnag.android.internal.ImmutableConfig;
import com.bugsnag.android.internal.InternalMetrics;
import com.bugsnag.android.internal.InternalMetricsImpl;
import com.bugsnag.android.internal.InternalMetricsNoop;
import com.bugsnag.android.internal.StateObserver;
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.SystemServiceModule;
@ -303,7 +305,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware, FeatureF
registerListenersInBackground();
// Leave auto breadcrumb
Map<String, Object> data = Collections.emptyMap();
Map<String, Object> data = new HashMap<>();
leaveAutoBreadcrumb("Bugsnag loaded", BreadcrumbType.STATE, data);
logger.d("Bugsnag loaded");

View File

@ -2,6 +2,7 @@ package com.bugsnag.android;
import android.content.Context;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@ -305,7 +306,7 @@ public class Configuration implements CallbackAware, MetadataAware, UserAware, F
* By default, this value is set at 5,000ms. Setting the value to 0 will count all crashes
* as launch crashes until markLaunchCompleted() is called.
*/
public void setLaunchDurationMillis(long launchDurationMillis) {
public void setLaunchDurationMillis(@IntRange(from = 0) long launchDurationMillis) {
if (launchDurationMillis >= MIN_LAUNCH_CRASH_THRESHOLD_MS) {
impl.setLaunchDurationMillis(launchDurationMillis);
} else {
@ -524,12 +525,12 @@ public class Configuration implements CallbackAware, MetadataAware, UserAware, F
*
* By default, 100 breadcrumbs are stored: this can be amended up to a maximum of 500.
*/
public void setMaxBreadcrumbs(int maxBreadcrumbs) {
public void setMaxBreadcrumbs(@IntRange(from = 0, to = 500) int maxBreadcrumbs) {
if (maxBreadcrumbs >= MIN_BREADCRUMBS && maxBreadcrumbs <= MAX_BREADCRUMBS) {
impl.setMaxBreadcrumbs(maxBreadcrumbs);
} else {
getLogger().e("Invalid configuration value detected. "
+ "Option maxBreadcrumbs should be an integer between 0-100. "
+ "Option maxBreadcrumbs should be an integer between 0-500. "
+ "Supplied value is " + maxBreadcrumbs);
}
}
@ -540,8 +541,8 @@ public class Configuration implements CallbackAware, MetadataAware, UserAware, F
*
* By default, 32 events are persisted.
*/
public int getMaxPersistedEvents() {
return impl.getMaxPersistedEvents();
public int getMaxPersistedEvents() {
return impl.getMaxPersistedEvents();
}
/**
@ -550,7 +551,7 @@ public class Configuration implements CallbackAware, MetadataAware, UserAware, F
*
* By default, 32 events are persisted.
*/
public void setMaxPersistedEvents(int maxPersistedEvents) {
public void setMaxPersistedEvents(@IntRange(from = 0) int maxPersistedEvents) {
if (maxPersistedEvents >= 0) {
impl.setMaxPersistedEvents(maxPersistedEvents);
} else {
@ -576,7 +577,7 @@ public class Configuration implements CallbackAware, MetadataAware, UserAware, F
*
* By default, up to 200 threads are reported.
*/
public void setMaxReportedThreads(int maxReportedThreads) {
public void setMaxReportedThreads(@IntRange(from = 0) int maxReportedThreads) {
if (maxReportedThreads >= 0) {
impl.setMaxReportedThreads(maxReportedThreads);
} else {
@ -602,7 +603,7 @@ public class Configuration implements CallbackAware, MetadataAware, UserAware, F
*
* By default, 128 sessions are persisted.
*/
public void setMaxPersistedSessions(int maxPersistedSessions) {
public void setMaxPersistedSessions(@IntRange(from = 0) int maxPersistedSessions) {
if (maxPersistedSessions >= 0) {
impl.setMaxPersistedSessions(maxPersistedSessions);
} else {
@ -628,7 +629,7 @@ public class Configuration implements CallbackAware, MetadataAware, UserAware, F
*
* By default, the limit is 10,000.
*/
public void setMaxStringValueLength(int maxStringValueLength) {
public void setMaxStringValueLength(@IntRange(from = 0) int maxStringValueLength) {
if (maxStringValueLength >= 0) {
impl.setMaxStringValueLength(maxStringValueLength);
} else {

View File

@ -1,6 +1,7 @@
package com.bugsnag.android
import android.os.Environment
import com.bugsnag.android.internal.BackgroundTaskService
import com.bugsnag.android.internal.dag.ConfigModule
import com.bugsnag.android.internal.dag.ContextModule
import com.bugsnag.android.internal.dag.DependencyModule

View File

@ -2,7 +2,9 @@ package com.bugsnag.android;
import static com.bugsnag.android.SeverityReason.REASON_PROMISE_REJECTION;
import com.bugsnag.android.internal.BackgroundTaskService;
import com.bugsnag.android.internal.ImmutableConfig;
import com.bugsnag.android.internal.TaskType;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;

View File

@ -11,6 +11,8 @@ import android.content.res.Resources
import android.os.BatteryManager
import android.os.Build
import android.provider.Settings
import com.bugsnag.android.internal.BackgroundTaskService
import com.bugsnag.android.internal.TaskType
import java.io.File
import java.util.Date
import java.util.Locale

View File

@ -1,5 +1,6 @@
package com.bugsnag.android
import com.bugsnag.android.internal.BackgroundTaskService
import com.bugsnag.android.internal.dag.ConfigModule
import com.bugsnag.android.internal.dag.ContextModule
import com.bugsnag.android.internal.dag.DependencyModule

View File

@ -1,6 +1,8 @@
package com.bugsnag.android;
import com.bugsnag.android.internal.BackgroundTaskService;
import com.bugsnag.android.internal.ImmutableConfig;
import com.bugsnag.android.internal.TaskType;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

View File

@ -3,8 +3,10 @@ package com.bugsnag.android;
import static com.bugsnag.android.DeliveryHeadersKt.HEADER_INTERNAL_ERROR;
import static com.bugsnag.android.SeverityReason.REASON_UNHANDLED_EXCEPTION;
import com.bugsnag.android.internal.BackgroundTaskService;
import com.bugsnag.android.internal.ImmutableConfig;
import com.bugsnag.android.internal.JsonHelper;
import com.bugsnag.android.internal.TaskType;
import android.annotation.SuppressLint;
import android.content.Context;

View File

@ -1,5 +1,7 @@
package com.bugsnag.android;
import com.bugsnag.android.internal.TaskType;
import java.util.concurrent.atomic.AtomicBoolean;
class LibraryLoader {

View File

@ -265,6 +265,14 @@ public class NativeInterface {
getClient().addMetadata(tab, key, value);
}
/**
* Add metadata to subsequent exception reports with a Hashmap
*/
public static void addMetadata(@NonNull final String tab,
@NonNull final Map<String, ?> metadata) {
getClient().addMetadata(tab, metadata);
}
/**
* Return the client report release stage
*/

View File

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

View File

@ -15,10 +15,9 @@ internal class PluginClient(
}
private val plugins: Set<Plugin>
private val ndkPlugin = instantiatePlugin(NDK_PLUGIN)
private val anrPlugin = instantiatePlugin(ANR_PLUGIN)
private val rnPlugin = instantiatePlugin(RN_PLUGIN)
private val ndkPlugin = instantiatePlugin(NDK_PLUGIN, immutableConfig.enabledErrorTypes.ndkCrashes)
private val anrPlugin = instantiatePlugin(ANR_PLUGIN, immutableConfig.enabledErrorTypes.anrs)
private val rnPlugin = instantiatePlugin(RN_PLUGIN, immutableConfig.enabledErrorTypes.unhandledRejections)
init {
val set = mutableSetOf<Plugin>()
@ -32,12 +31,14 @@ internal class PluginClient(
plugins = set.toSet()
}
private fun instantiatePlugin(clz: String): Plugin? {
private fun instantiatePlugin(clz: String, isWarningEnabled: Boolean): Plugin? {
return try {
val pluginClz = Class.forName(clz)
pluginClz.newInstance() as Plugin
} catch (exc: ClassNotFoundException) {
logger.d("Plugin '$clz' is not on the classpath - functionality will not be enabled.")
if (isWarningEnabled) {
logger.d("Plugin '$clz' is not on the classpath - functionality will not be enabled.")
}
null
} catch (exc: Throwable) {
logger.e("Failed to load plugin '$clz'", exc)

View File

@ -1,10 +1,9 @@
package com.bugsnag.android
import androidx.annotation.VisibleForTesting
import java.io.BufferedReader
import java.io.File
import java.io.IOException
import java.util.concurrent.atomic.AtomicBoolean
import java.io.Reader
/**
* Attempts to detect whether the device is rooted. Root detection errs on the side of false
@ -41,12 +40,13 @@ internal class RootDetector @JvmOverloads constructor(
)
}
private val libraryLoaded = AtomicBoolean(false)
@Volatile
private var libraryLoaded = false
init {
try {
System.loadLibrary("bugsnag-root-detection")
libraryLoaded.set(true)
libraryLoaded = true
} catch (ignored: UnsatisfiedLinkError) {
// library couldn't load. This could be due to root detection countermeasures,
// or down to genuine OS level bugs with library loading - in either case
@ -107,7 +107,7 @@ internal class RootDetector @JvmOverloads constructor(
line.replace("\\s".toRegex(), "")
}.filter { line ->
line.startsWith("ro.debuggable=[1]") || line.startsWith("ro.secure=[0]")
}.count() > 0
}.any()
}
}
return false
@ -120,8 +120,7 @@ internal class RootDetector @JvmOverloads constructor(
var process: Process? = null
return try {
process = processBuilder.start()
val output = process.inputStream.bufferedReader().use(BufferedReader::readText)
output.isNotBlank()
process.inputStream.bufferedReader().use { it.isNotBlank() }
} catch (ignored: IOException) {
false
} finally {
@ -131,11 +130,21 @@ internal class RootDetector @JvmOverloads constructor(
private external fun performNativeRootChecks(): Boolean
private fun Reader.isNotBlank(): Boolean {
while (true) {
val ch = read()
when {
ch == -1 -> return false
!ch.toChar().isWhitespace() -> return true
}
}
}
/**
* Performs root checks which require native code.
*/
private fun nativeCheckRoot(): Boolean = when {
libraryLoaded.get() -> performNativeRootChecks()
libraryLoaded -> performNativeRootChecks()
else -> false
}
}

View File

@ -1,7 +1,9 @@
package com.bugsnag.android;
import com.bugsnag.android.internal.BackgroundTaskService;
import com.bugsnag.android.internal.DateUtils;
import com.bugsnag.android.internal.ImmutableConfig;
import com.bugsnag.android.internal.TaskType;
import android.os.SystemClock;
@ -10,22 +12,21 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import java.io.File;
import java.util.Collection;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Date;
import java.util.Deque;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
class SessionTracker extends BaseObservable {
private static final int DEFAULT_TIMEOUT_MS = 30000;
private final Collection<String>
foregroundActivities = new ConcurrentLinkedQueue<>();
private final Deque<String>
foregroundActivities = new ArrayDeque<>();
private final long timeoutMs;
private final ImmutableConfig configuration;
@ -132,7 +133,7 @@ class SessionTracker extends BaseObservable {
private void notifySessionStartObserver(final Session session) {
final String startedAt = DateUtils.toIso8601(session.getStartedAt());
updateState(new StateEvent.StartSession(session.getId(), startedAt,
session.getHandledCount(), session.getUnhandledCount()));
session.getHandledCount(), session.getUnhandledCount()));
}
/**
@ -358,22 +359,23 @@ class SessionTracker extends BaseObservable {
void updateForegroundTracker(String activityName, boolean activityStarting, long nowMs) {
if (activityStarting) {
long noActivityRunningForMs = nowMs - lastExitedForegroundMs.get();
synchronized (foregroundActivities) {
if (foregroundActivities.isEmpty()) {
lastEnteredForegroundMs.set(nowMs);
//FUTURE:SM Race condition between isEmpty and put
if (foregroundActivities.isEmpty()) {
lastEnteredForegroundMs.set(nowMs);
if (noActivityRunningForMs >= timeoutMs
&& configuration.getAutoTrackSessions()) {
startNewSession(new Date(), client.getUser(), true);
if (noActivityRunningForMs >= timeoutMs
&& configuration.getAutoTrackSessions()) {
startNewSession(new Date(), client.getUser(), true);
}
}
foregroundActivities.add(activityName);
}
foregroundActivities.add(activityName);
} else {
foregroundActivities.remove(activityName);
if (foregroundActivities.isEmpty()) {
lastExitedForegroundMs.set(nowMs);
synchronized (foregroundActivities) {
foregroundActivities.removeLastOccurrence(activityName);
if (foregroundActivities.isEmpty()) {
lastExitedForegroundMs.set(nowMs);
}
}
}
client.getContextState().setAutomaticContext(getContextActivity());
@ -397,14 +399,8 @@ class SessionTracker extends BaseObservable {
@Nullable
String getContextActivity() {
if (foregroundActivities.isEmpty()) {
return null;
} else {
// linked hash set retains order of added activity and ensures uniqueness
// therefore obtain the most recently added
int size = foregroundActivities.size();
String[] activities = foregroundActivities.toArray(new String[size]);
return activities[size - 1];
synchronized (foregroundActivities) {
return foregroundActivities.peekLast();
}
}
}

View File

@ -1,5 +1,6 @@
package com.bugsnag.android
import com.bugsnag.android.internal.BackgroundTaskService
import com.bugsnag.android.internal.dag.ConfigModule
import com.bugsnag.android.internal.dag.DependencyModule

View File

@ -1,4 +1,4 @@
package com.bugsnag.android
package com.bugsnag.android.internal
import androidx.annotation.VisibleForTesting
import java.util.concurrent.BlockingQueue
@ -18,7 +18,7 @@ import java.lang.Thread as JThread
* The type of task which is being submitted. This determines which execution queue
* the task will be added to.
*/
internal enum class TaskType {
enum class TaskType {
/**
* A task that sends an error request. Any filesystem operations
@ -91,7 +91,7 @@ internal fun createExecutor(name: String, type: TaskType, keepAlive: Boolean): E
* It also avoids short-running operations being held up by long-running operations submitted
* to the same executor.
*/
internal class BackgroundTaskService(
class BackgroundTaskService(
// these executors must remain single-threaded - the SDK makes assumptions
// about synchronization based on this.
@get:VisibleForTesting

View File

@ -1,7 +1,7 @@
package com.bugsnag.android.internal.dag
import com.bugsnag.android.BackgroundTaskService
import com.bugsnag.android.TaskType
import com.bugsnag.android.internal.BackgroundTaskService
import com.bugsnag.android.internal.TaskType
internal abstract class DependencyModule {

View File

@ -10,7 +10,7 @@ buildscript {
classpath 'com.android.tools.build:gradle:8.0.1'
// https://github.com/bugsnag/bugsnag-android-gradle-plugin
// https://mvnrepository.com/artifact/com.bugsnag/bugsnag-android-gradle-plugin
classpath "com.bugsnag:bugsnag-android-gradle-plugin:7.3.1"
classpath "com.bugsnag:bugsnag-android-gradle-plugin:8.0.1"
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-gradle-plugin
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:1.8.20"