mirror of https://github.com/M66B/FairEmail.git
parent
45db3c29b5
commit
d1d70d321f
@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="RunConfigurationProducerService">
|
|
||||||
<option name="ignoredProducers">
|
|
||||||
<set>
|
|
||||||
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
|
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
|
||||||
</set>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
@ -1,10 +1,46 @@
|
|||||||
package com.bugsnag.android
|
package com.bugsnag.android
|
||||||
|
|
||||||
import java.util.Observable
|
import com.bugsnag.android.internal.StateObserver
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|
||||||
internal open class BaseObservable : Observable() {
|
internal open class BaseObservable {
|
||||||
fun notifyObservers(event: StateEvent) {
|
|
||||||
setChanged()
|
internal val observers = CopyOnWriteArrayList<StateObserver>()
|
||||||
super.notifyObservers(event)
|
|
||||||
|
/**
|
||||||
|
* Adds an observer that can react to [StateEvent] messages.
|
||||||
|
*/
|
||||||
|
fun addObserver(observer: StateObserver) {
|
||||||
|
observers.addIfAbsent(observer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a previously added observer that reacts to [StateEvent] messages.
|
||||||
|
*/
|
||||||
|
fun removeObserver(observer: StateObserver) {
|
||||||
|
observers.remove(observer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should be invoked when the notifier's state has changed. If an observer
|
||||||
|
* has been set, it will be notified of the [StateEvent] message so that it can react
|
||||||
|
* appropriately. If no observer has been set then this method will no-op.
|
||||||
|
*/
|
||||||
|
internal inline fun updateState(provider: () -> StateEvent) {
|
||||||
|
// optimization to avoid unnecessary iterator and StateEvent construction
|
||||||
|
if (observers.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct the StateEvent object and notify observers
|
||||||
|
val event = provider()
|
||||||
|
observers.forEach { it.onStateChange(event) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An eager version of [updateState], which is intended primarily for use in Java code.
|
||||||
|
* If the event will occur very frequently, you should consider calling the lazy method
|
||||||
|
* instead.
|
||||||
|
*/
|
||||||
|
fun updateState(event: StateEvent) = updateState { event }
|
||||||
}
|
}
|
||||||
|
@ -1,55 +1,96 @@
|
|||||||
package com.bugsnag.android
|
package com.bugsnag.android
|
||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.Queue
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores breadcrumbs added to the [Client] in a ring buffer. If the number of breadcrumbs exceeds
|
||||||
|
* the maximum configured limit then the oldest breadcrumb in the ring buffer will be overwritten.
|
||||||
|
*
|
||||||
|
* When the breadcrumbs are required for generation of an event a [List] is constructed and
|
||||||
|
* breadcrumbs added in the order of their addition.
|
||||||
|
*/
|
||||||
internal class BreadcrumbState(
|
internal class BreadcrumbState(
|
||||||
maxBreadcrumbs: Int,
|
private val maxBreadcrumbs: Int,
|
||||||
val callbackState: CallbackState,
|
private val callbackState: CallbackState,
|
||||||
val logger: Logger
|
private val logger: Logger
|
||||||
) : BaseObservable(), JsonStream.Streamable {
|
) : BaseObservable(), JsonStream.Streamable {
|
||||||
|
|
||||||
val store: Queue<Breadcrumb> = ConcurrentLinkedQueue()
|
/*
|
||||||
|
* We use the `index` as both a pointer to the tail of our ring-buffer, and also as "cheat"
|
||||||
|
* semaphore. When the ring-buffer is being copied - the index is set to a negative number,
|
||||||
|
* which is an invalid array-index. By masking the `expected` value in a `compareAndSet` with
|
||||||
|
* `validIndexMask`: the CAS operation will only succeed if it wouldn't interrupt a concurrent
|
||||||
|
* `copy()` call.
|
||||||
|
*/
|
||||||
|
private val validIndexMask: Int = Int.MAX_VALUE
|
||||||
|
|
||||||
private val maxBreadcrumbs: Int
|
private val store = arrayOfNulls<Breadcrumb?>(maxBreadcrumbs)
|
||||||
|
private val index = AtomicInteger(0)
|
||||||
init {
|
|
||||||
when {
|
|
||||||
maxBreadcrumbs > 0 -> this.maxBreadcrumbs = maxBreadcrumbs
|
|
||||||
else -> this.maxBreadcrumbs = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
|
||||||
override fun toStream(writer: JsonStream) {
|
|
||||||
pruneBreadcrumbs()
|
|
||||||
writer.beginArray()
|
|
||||||
store.forEach { it.toStream(writer) }
|
|
||||||
writer.endArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun add(breadcrumb: Breadcrumb) {
|
fun add(breadcrumb: Breadcrumb) {
|
||||||
if (!callbackState.runOnBreadcrumbTasks(breadcrumb, logger)) {
|
if (maxBreadcrumbs == 0 || !callbackState.runOnBreadcrumbTasks(breadcrumb, logger)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
store.add(breadcrumb)
|
// store the breadcrumb in the ring buffer
|
||||||
pruneBreadcrumbs()
|
val position = getBreadcrumbIndex()
|
||||||
notifyObservers(
|
store[position] = breadcrumb
|
||||||
|
|
||||||
|
updateState {
|
||||||
|
// use direct field access to avoid overhead of accessor method
|
||||||
StateEvent.AddBreadcrumb(
|
StateEvent.AddBreadcrumb(
|
||||||
breadcrumb.message,
|
breadcrumb.impl.message,
|
||||||
breadcrumb.type,
|
breadcrumb.impl.type,
|
||||||
DateUtils.toIso8601(breadcrumb.timestamp),
|
DateUtils.toIso8601(breadcrumb.impl.timestamp),
|
||||||
breadcrumb.metadata ?: mutableMapOf()
|
breadcrumb.impl.metadata ?: mutableMapOf()
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the index in the ring buffer where the breadcrumb should be stored.
|
||||||
|
*/
|
||||||
|
private fun getBreadcrumbIndex(): Int {
|
||||||
|
while (true) {
|
||||||
|
val currentValue = index.get() and validIndexMask
|
||||||
|
val nextValue = (currentValue + 1) % maxBreadcrumbs
|
||||||
|
if (index.compareAndSet(currentValue, nextValue)) {
|
||||||
|
return currentValue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun pruneBreadcrumbs() {
|
/**
|
||||||
// Remove oldest breadcrumbState until new max size reached
|
* Creates a copy of the breadcrumbs in the order of their addition.
|
||||||
while (store.size > maxBreadcrumbs) {
|
*/
|
||||||
store.poll()
|
fun copy(): List<Breadcrumb> {
|
||||||
|
if (maxBreadcrumbs == 0) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a negative value that stops any other thread from adding a breadcrumb.
|
||||||
|
// This handles reentrancy by waiting here until the old value has been reset.
|
||||||
|
var tail = -1
|
||||||
|
while (tail == -1) {
|
||||||
|
tail = index.getAndSet(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val result = arrayOfNulls<Breadcrumb>(maxBreadcrumbs)
|
||||||
|
store.copyInto(result, 0, tail, maxBreadcrumbs)
|
||||||
|
store.copyInto(result, maxBreadcrumbs - tail, 0, tail)
|
||||||
|
return result.filterNotNull()
|
||||||
|
} finally {
|
||||||
|
index.set(tail)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun toStream(writer: JsonStream) {
|
||||||
|
val crumbs = copy()
|
||||||
|
writer.beginArray()
|
||||||
|
crumbs.forEach { it.toStream(writer) }
|
||||||
|
writer.endArray()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
package com.bugsnag.android
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
|
|
||||||
internal class ConfigChangeReceiver(
|
|
||||||
private val deviceDataCollector: DeviceDataCollector,
|
|
||||||
private val cb: (oldOrientation: String?, newOrientation: String?) -> Unit
|
|
||||||
) : BroadcastReceiver() {
|
|
||||||
|
|
||||||
var orientation = deviceDataCollector.calculateOrientation()
|
|
||||||
|
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
|
||||||
val newOrientation = deviceDataCollector.calculateOrientation()
|
|
||||||
|
|
||||||
if (!newOrientation.equals(orientation)) {
|
|
||||||
cb(orientation, newOrientation)
|
|
||||||
orientation = newOrientation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,36 @@
|
|||||||
package com.bugsnag.android
|
package com.bugsnag.android
|
||||||
|
|
||||||
internal class ContextState(context: String? = null) : BaseObservable() {
|
/**
|
||||||
var context = context
|
* Tracks the current context and allows observers to be notified whenever it changes.
|
||||||
set(value) {
|
*
|
||||||
field = value
|
* The default behaviour is to track [SessionTracker.getContextActivity]. However, any value
|
||||||
|
* that the user sets via [Bugsnag.setContext] will override this and be returned instead.
|
||||||
|
*/
|
||||||
|
internal class ContextState : BaseObservable() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MANUAL = "__BUGSNAG_MANUAL_CONTEXT__"
|
||||||
|
}
|
||||||
|
|
||||||
|
private var manualContext: String? = null
|
||||||
|
private var automaticContext: String? = null
|
||||||
|
|
||||||
|
fun setManualContext(context: String?) {
|
||||||
|
manualContext = context
|
||||||
|
automaticContext = MANUAL
|
||||||
|
emitObservableEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAutomaticContext(context: String?) {
|
||||||
|
if (automaticContext !== MANUAL) {
|
||||||
|
automaticContext = context
|
||||||
emitObservableEvent()
|
emitObservableEvent()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun emitObservableEvent() = notifyObservers(StateEvent.UpdateContext(context))
|
fun getContext(): String? {
|
||||||
|
return automaticContext.takeIf { it !== MANUAL } ?: manualContext
|
||||||
|
}
|
||||||
|
|
||||||
fun copy() = ContextState(context)
|
fun emitObservableEvent() = updateState { StateEvent.UpdateContext(getContext()) }
|
||||||
}
|
}
|
||||||
|
@ -1,47 +1,66 @@
|
|||||||
package com.bugsnag.android
|
package com.bugsnag.android
|
||||||
|
|
||||||
sealed class StateEvent {
|
sealed class StateEvent { // JvmField allows direct field access optimizations
|
||||||
|
|
||||||
class Install(
|
class Install(
|
||||||
val apiKey: String,
|
@JvmField val apiKey: String,
|
||||||
val autoDetectNdkCrashes: Boolean,
|
@JvmField val autoDetectNdkCrashes: Boolean,
|
||||||
val appVersion: String?,
|
@JvmField val appVersion: String?,
|
||||||
val buildUuid: String?,
|
@JvmField val buildUuid: String?,
|
||||||
val releaseStage: String?,
|
@JvmField val releaseStage: String?,
|
||||||
val lastRunInfoPath: String,
|
@JvmField val lastRunInfoPath: String,
|
||||||
val consecutiveLaunchCrashes: Int
|
@JvmField val consecutiveLaunchCrashes: Int
|
||||||
) : StateEvent()
|
) : StateEvent()
|
||||||
|
|
||||||
object DeliverPending : StateEvent()
|
object DeliverPending : StateEvent()
|
||||||
|
|
||||||
class AddMetadata(val section: String, val key: String?, val value: Any?) : StateEvent()
|
class AddMetadata(
|
||||||
class ClearMetadataSection(val section: String) : StateEvent()
|
@JvmField val section: String,
|
||||||
class ClearMetadataValue(val section: String, val key: String?) : StateEvent()
|
@JvmField val key: String?,
|
||||||
|
@JvmField val value: Any?
|
||||||
|
) : StateEvent()
|
||||||
|
|
||||||
|
class ClearMetadataSection(@JvmField val section: String) : StateEvent()
|
||||||
|
|
||||||
|
class ClearMetadataValue(
|
||||||
|
@JvmField val section: String,
|
||||||
|
@JvmField val key: String?
|
||||||
|
) : StateEvent()
|
||||||
|
|
||||||
class AddBreadcrumb(
|
class AddBreadcrumb(
|
||||||
val message: String,
|
@JvmField val message: String,
|
||||||
val type: BreadcrumbType,
|
@JvmField val type: BreadcrumbType,
|
||||||
val timestamp: String,
|
@JvmField val timestamp: String,
|
||||||
val metadata: MutableMap<String, Any?>
|
@JvmField val metadata: MutableMap<String, Any?>
|
||||||
) : StateEvent()
|
) : StateEvent()
|
||||||
|
|
||||||
object NotifyHandled : StateEvent()
|
object NotifyHandled : StateEvent()
|
||||||
|
|
||||||
object NotifyUnhandled : StateEvent()
|
object NotifyUnhandled : StateEvent()
|
||||||
|
|
||||||
object PauseSession : StateEvent()
|
object PauseSession : StateEvent()
|
||||||
|
|
||||||
class StartSession(
|
class StartSession(
|
||||||
val id: String,
|
@JvmField val id: String,
|
||||||
val startedAt: String,
|
@JvmField val startedAt: String,
|
||||||
val handledCount: Int,
|
@JvmField val handledCount: Int,
|
||||||
val unhandledCount: Int
|
val unhandledCount: Int
|
||||||
) : StateEvent()
|
) : StateEvent()
|
||||||
|
|
||||||
class UpdateContext(val context: String?) : StateEvent()
|
class UpdateContext(@JvmField val context: String?) : StateEvent()
|
||||||
class UpdateInForeground(val inForeground: Boolean, val contextActivity: String?) : StateEvent()
|
|
||||||
class UpdateLastRunInfo(val consecutiveLaunchCrashes: Int) : StateEvent()
|
class UpdateInForeground(
|
||||||
class UpdateIsLaunching(val isLaunching: Boolean) : StateEvent()
|
@JvmField val inForeground: Boolean,
|
||||||
class UpdateOrientation(val orientation: String?) : StateEvent()
|
val contextActivity: String?
|
||||||
|
) : StateEvent()
|
||||||
|
|
||||||
|
class UpdateLastRunInfo(@JvmField val consecutiveLaunchCrashes: Int) : StateEvent()
|
||||||
|
|
||||||
|
class UpdateIsLaunching(@JvmField val isLaunching: Boolean) : StateEvent()
|
||||||
|
|
||||||
|
class UpdateOrientation(@JvmField val orientation: String?) : StateEvent()
|
||||||
|
|
||||||
class UpdateUser(val user: User) : StateEvent()
|
class UpdateUser(@JvmField val user: User) : StateEvent()
|
||||||
|
|
||||||
class UpdateMemoryTrimEvent(val isLowMemory: Boolean) : StateEvent()
|
class UpdateMemoryTrimEvent(@JvmField val isLowMemory: Boolean) : StateEvent()
|
||||||
}
|
}
|
||||||
|
@ -1,191 +0,0 @@
|
|||||||
package com.bugsnag.android;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to automatically create breadcrumbs for system events
|
|
||||||
* Broadcast actions and categories can be found in text files in the android folder
|
|
||||||
* e.g. ~/Library/Android/sdk/platforms/android-9/data/broadcast_actions.txt
|
|
||||||
* See http://stackoverflow.com/a/27601497
|
|
||||||
*/
|
|
||||||
class SystemBroadcastReceiver extends BroadcastReceiver {
|
|
||||||
|
|
||||||
private static final String INTENT_ACTION_KEY = "Intent Action";
|
|
||||||
|
|
||||||
private final Client client;
|
|
||||||
private final Logger logger;
|
|
||||||
private final Map<String, BreadcrumbType> actions;
|
|
||||||
|
|
||||||
SystemBroadcastReceiver(@NonNull Client client, Logger logger) {
|
|
||||||
this.client = client;
|
|
||||||
this.logger = logger;
|
|
||||||
this.actions = buildActions();
|
|
||||||
}
|
|
||||||
|
|
||||||
static SystemBroadcastReceiver register(final Client client,
|
|
||||||
final Logger logger,
|
|
||||||
BackgroundTaskService bgTaskService) {
|
|
||||||
final SystemBroadcastReceiver receiver = new SystemBroadcastReceiver(client, logger);
|
|
||||||
if (receiver.getActions().size() > 0) {
|
|
||||||
try {
|
|
||||||
bgTaskService.submitTask(TaskType.DEFAULT, new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
IntentFilter intentFilter = receiver.getIntentFilter();
|
|
||||||
Context context = client.appContext;
|
|
||||||
ContextExtensionsKt.registerReceiverSafe(context,
|
|
||||||
receiver, intentFilter, logger);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (RejectedExecutionException ex) {
|
|
||||||
logger.w("Failed to register for automatic breadcrumb broadcasts", ex);
|
|
||||||
}
|
|
||||||
return receiver;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
|
|
||||||
try {
|
|
||||||
Map<String, Object> meta = new HashMap<>();
|
|
||||||
String fullAction = intent.getAction();
|
|
||||||
|
|
||||||
if (fullAction == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String shortAction = shortenActionNameIfNeeded(fullAction);
|
|
||||||
meta.put(INTENT_ACTION_KEY, fullAction); // always add the Intent Action
|
|
||||||
|
|
||||||
Bundle extras = intent.getExtras();
|
|
||||||
if (extras != null) {
|
|
||||||
for (String key : extras.keySet()) {
|
|
||||||
Object valObj = extras.get(key);
|
|
||||||
if (valObj == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String val = valObj.toString();
|
|
||||||
|
|
||||||
if (isAndroidKey(key)) { // shorten the Intent action
|
|
||||||
meta.put("Extra", String.format("%s: %s", shortAction, val));
|
|
||||||
} else {
|
|
||||||
meta.put(key, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BreadcrumbType type = actions.get(fullAction);
|
|
||||||
|
|
||||||
if (type == null) {
|
|
||||||
type = BreadcrumbType.STATE;
|
|
||||||
}
|
|
||||||
client.leaveBreadcrumb(shortAction, meta, type);
|
|
||||||
|
|
||||||
} catch (Exception ex) {
|
|
||||||
logger.w("Failed to leave breadcrumb in SystemBroadcastReceiver: "
|
|
||||||
+ ex.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isAndroidKey(@NonNull String actionName) {
|
|
||||||
return actionName.startsWith("android.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
static String shortenActionNameIfNeeded(@NonNull String action) {
|
|
||||||
if (isAndroidKey(action)) {
|
|
||||||
return action.substring(action.lastIndexOf(".") + 1);
|
|
||||||
} else {
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds a map of intent actions and their breadcrumb type (if enabled).
|
|
||||||
*
|
|
||||||
* Noisy breadcrumbs are omitted, along with anything that involves a state change.
|
|
||||||
* @return the action map
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
private Map<String, BreadcrumbType> buildActions() {
|
|
||||||
|
|
||||||
Map<String, BreadcrumbType> actions = new HashMap<>();
|
|
||||||
if (client.getConfig().shouldRecordBreadcrumbType(BreadcrumbType.USER)) {
|
|
||||||
actions.put("android.appwidget.action.APPWIDGET_DELETED", BreadcrumbType.USER);
|
|
||||||
actions.put("android.appwidget.action.APPWIDGET_DISABLED", BreadcrumbType.USER);
|
|
||||||
actions.put("android.appwidget.action.APPWIDGET_ENABLED", BreadcrumbType.USER);
|
|
||||||
actions.put("android.intent.action.CAMERA_BUTTON", BreadcrumbType.USER);
|
|
||||||
actions.put("android.intent.action.CLOSE_SYSTEM_DIALOGS", BreadcrumbType.USER);
|
|
||||||
actions.put("android.intent.action.DOCK_EVENT", BreadcrumbType.USER);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client.getConfig().shouldRecordBreadcrumbType(BreadcrumbType.STATE)) {
|
|
||||||
actions.put("android.appwidget.action.APPWIDGET_HOST_RESTORED", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.appwidget.action.APPWIDGET_RESTORED", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.appwidget.action.APPWIDGET_UPDATE", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.appwidget.action.APPWIDGET_UPDATE_OPTIONS", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.ACTION_POWER_CONNECTED", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.ACTION_POWER_DISCONNECTED", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.ACTION_SHUTDOWN", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.AIRPLANE_MODE", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.BATTERY_LOW", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.BATTERY_OKAY", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.BOOT_COMPLETED", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.CONFIGURATION_CHANGED", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.CONTENT_CHANGED", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.DATE_CHANGED", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.DEVICE_STORAGE_LOW", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.DEVICE_STORAGE_OK", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.INPUT_METHOD_CHANGED", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.LOCALE_CHANGED", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.REBOOT", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.SCREEN_OFF", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.SCREEN_ON", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.TIMEZONE_CHANGED", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.intent.action.TIME_SET", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.os.action.DEVICE_IDLE_MODE_CHANGED", BreadcrumbType.STATE);
|
|
||||||
actions.put("android.os.action.POWER_SAVE_MODE_CHANGED", BreadcrumbType.STATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client.getConfig().shouldRecordBreadcrumbType(BreadcrumbType.NAVIGATION)) {
|
|
||||||
actions.put("android.intent.action.DREAMING_STARTED", BreadcrumbType.NAVIGATION);
|
|
||||||
actions.put("android.intent.action.DREAMING_STOPPED", BreadcrumbType.NAVIGATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the enabled actions
|
|
||||||
*/
|
|
||||||
public Map<String, BreadcrumbType> getActions() {
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Intent filter with all the intents to record breadcrumbs for
|
|
||||||
*
|
|
||||||
* @return The intent filter
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public IntentFilter getIntentFilter() {
|
|
||||||
IntentFilter filter = new IntentFilter();
|
|
||||||
|
|
||||||
for (String action : actions.keySet()) {
|
|
||||||
filter.addAction(action);
|
|
||||||
}
|
|
||||||
return filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,130 @@
|
|||||||
|
package com.bugsnag.android
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import java.util.HashMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to automatically create breadcrumbs for system events
|
||||||
|
* Broadcast actions and categories can be found in text files in the android folder
|
||||||
|
* e.g. ~/Library/Android/sdk/platforms/android-9/data/broadcast_actions.txt
|
||||||
|
* See http://stackoverflow.com/a/27601497
|
||||||
|
*/
|
||||||
|
internal class SystemBroadcastReceiver(
|
||||||
|
private val client: Client,
|
||||||
|
private val logger: Logger
|
||||||
|
) : BroadcastReceiver() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val INTENT_ACTION_KEY = "Intent Action"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun register(ctx: Context, receiver: SystemBroadcastReceiver, logger: Logger) {
|
||||||
|
if (receiver.actions.isNotEmpty()) {
|
||||||
|
val filter = IntentFilter()
|
||||||
|
receiver.actions.keys.forEach(filter::addAction)
|
||||||
|
ctx.registerReceiverSafe(receiver, filter, logger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isAndroidKey(actionName: String): Boolean {
|
||||||
|
return actionName.startsWith("android.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shortenActionNameIfNeeded(action: String): String {
|
||||||
|
return if (isAndroidKey(action)) {
|
||||||
|
action.substringAfterLast('.')
|
||||||
|
} else {
|
||||||
|
action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val actions: Map<String, BreadcrumbType> = buildActions()
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
try {
|
||||||
|
val meta: MutableMap<String, Any> = HashMap()
|
||||||
|
val fullAction = intent.action ?: return
|
||||||
|
val shortAction = shortenActionNameIfNeeded(fullAction)
|
||||||
|
meta[INTENT_ACTION_KEY] = fullAction // always add the Intent Action
|
||||||
|
addExtrasToMetadata(intent, meta, shortAction)
|
||||||
|
|
||||||
|
val type = actions[fullAction] ?: BreadcrumbType.STATE
|
||||||
|
client.leaveBreadcrumb(shortAction, meta, type)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
logger.w("Failed to leave breadcrumb in SystemBroadcastReceiver: ${ex.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addExtrasToMetadata(
|
||||||
|
intent: Intent,
|
||||||
|
meta: MutableMap<String, Any>,
|
||||||
|
shortAction: String
|
||||||
|
) {
|
||||||
|
val extras = intent.extras
|
||||||
|
extras?.keySet()?.forEach { key ->
|
||||||
|
val valObj = extras[key] ?: return@forEach
|
||||||
|
val strVal = valObj.toString()
|
||||||
|
if (isAndroidKey(key)) { // shorten the Intent action
|
||||||
|
meta["Extra"] = "$shortAction: $strVal"
|
||||||
|
} else {
|
||||||
|
meta[key] = strVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a map of intent actions and their breadcrumb type (if enabled).
|
||||||
|
*
|
||||||
|
* Noisy breadcrumbs are omitted, along with anything that involves a state change.
|
||||||
|
* @return the action map
|
||||||
|
*/
|
||||||
|
private fun buildActions(): Map<String, BreadcrumbType> {
|
||||||
|
val actions: MutableMap<String, BreadcrumbType> = HashMap()
|
||||||
|
val config = client.config
|
||||||
|
|
||||||
|
if (!config.shouldDiscardBreadcrumb(BreadcrumbType.USER)) {
|
||||||
|
actions["android.appwidget.action.APPWIDGET_DELETED"] = BreadcrumbType.USER
|
||||||
|
actions["android.appwidget.action.APPWIDGET_DISABLED"] = BreadcrumbType.USER
|
||||||
|
actions["android.appwidget.action.APPWIDGET_ENABLED"] = BreadcrumbType.USER
|
||||||
|
actions["android.intent.action.CAMERA_BUTTON"] = BreadcrumbType.USER
|
||||||
|
actions["android.intent.action.CLOSE_SYSTEM_DIALOGS"] = BreadcrumbType.USER
|
||||||
|
actions["android.intent.action.DOCK_EVENT"] = BreadcrumbType.USER
|
||||||
|
}
|
||||||
|
if (!config.shouldDiscardBreadcrumb(BreadcrumbType.STATE)) {
|
||||||
|
actions["android.appwidget.action.APPWIDGET_HOST_RESTORED"] = BreadcrumbType.STATE
|
||||||
|
actions["android.appwidget.action.APPWIDGET_RESTORED"] = BreadcrumbType.STATE
|
||||||
|
actions["android.appwidget.action.APPWIDGET_UPDATE"] = BreadcrumbType.STATE
|
||||||
|
actions["android.appwidget.action.APPWIDGET_UPDATE_OPTIONS"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.ACTION_POWER_CONNECTED"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.ACTION_POWER_DISCONNECTED"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.ACTION_SHUTDOWN"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.AIRPLANE_MODE"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.BATTERY_LOW"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.BATTERY_OKAY"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.BOOT_COMPLETED"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.CONFIGURATION_CHANGED"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.CONTENT_CHANGED"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.DATE_CHANGED"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.DEVICE_STORAGE_LOW"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.DEVICE_STORAGE_OK"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.INPUT_METHOD_CHANGED"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.LOCALE_CHANGED"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.REBOOT"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.SCREEN_OFF"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.SCREEN_ON"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.TIMEZONE_CHANGED"] = BreadcrumbType.STATE
|
||||||
|
actions["android.intent.action.TIME_SET"] = BreadcrumbType.STATE
|
||||||
|
actions["android.os.action.DEVICE_IDLE_MODE_CHANGED"] = BreadcrumbType.STATE
|
||||||
|
actions["android.os.action.POWER_SAVE_MODE_CHANGED"] = BreadcrumbType.STATE
|
||||||
|
}
|
||||||
|
if (!config.shouldDiscardBreadcrumb(BreadcrumbType.NAVIGATION)) {
|
||||||
|
actions["android.intent.action.DREAMING_STARTED"] = BreadcrumbType.NAVIGATION
|
||||||
|
actions["android.intent.action.DREAMING_STOPPED"] = BreadcrumbType.NAVIGATION
|
||||||
|
}
|
||||||
|
return actions
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.bugsnag.android.internal;
|
||||||
|
|
||||||
|
import com.bugsnag.android.StateEvent;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public interface StateObserver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is called whenever the notifier's state is altered, so that observers can react
|
||||||
|
* appropriately. This is intended for internal use only.
|
||||||
|
*/
|
||||||
|
void onStateChange(@NonNull StateEvent event);
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
From 3270faf44aea11754c940ba43ee6db72b7462f14 Mon Sep 17 00:00:00 2001
|
||||||
|
From: M66B <M66B@users.noreply.github.com>
|
||||||
|
Date: Sat, 15 May 2021 22:07:24 +0200
|
||||||
|
Subject: [PATCH] Bugsnag failure on I/O error
|
||||||
|
|
||||||
|
---
|
||||||
|
app/src/main/java/com/bugsnag/android/DefaultDelivery.kt | 2 +-
|
||||||
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt b/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt
|
||||||
|
index a7995164cb4e..5620f0bacd80 100644
|
||||||
|
--- a/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt
|
||||||
|
+++ b/app/src/main/java/com/bugsnag/android/DefaultDelivery.kt
|
||||||
|
@@ -64,7 +64,7 @@ internal class DefaultDelivery(
|
||||||
|
return DeliveryStatus.UNDELIVERED
|
||||||
|
} catch (exception: IOException) {
|
||||||
|
logger.w("IOException encountered in request", exception)
|
||||||
|
- return DeliveryStatus.UNDELIVERED
|
||||||
|
+ return DeliveryStatus.FAILURE
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
logger.w("Unexpected error delivering payload", exception)
|
||||||
|
return DeliveryStatus.FAILURE
|
Loading…
Reference in new issue