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
|
||||
|
||||
import java.util.Observable
|
||||
import com.bugsnag.android.internal.StateObserver
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
internal open class BaseObservable : Observable() {
|
||||
fun notifyObservers(event: StateEvent) {
|
||||
setChanged()
|
||||
super.notifyObservers(event)
|
||||
internal open class BaseObservable {
|
||||
|
||||
internal val observers = CopyOnWriteArrayList<StateObserver>()
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
import java.io.IOException
|
||||
import java.util.Queue
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* 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(
|
||||
maxBreadcrumbs: Int,
|
||||
val callbackState: CallbackState,
|
||||
val logger: Logger
|
||||
private val maxBreadcrumbs: Int,
|
||||
private val callbackState: CallbackState,
|
||||
private val logger: Logger
|
||||
) : 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
|
||||
|
||||
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()
|
||||
}
|
||||
private val store = arrayOfNulls<Breadcrumb?>(maxBreadcrumbs)
|
||||
private val index = AtomicInteger(0)
|
||||
|
||||
fun add(breadcrumb: Breadcrumb) {
|
||||
if (!callbackState.runOnBreadcrumbTasks(breadcrumb, logger)) {
|
||||
if (maxBreadcrumbs == 0 || !callbackState.runOnBreadcrumbTasks(breadcrumb, logger)) {
|
||||
return
|
||||
}
|
||||
|
||||
store.add(breadcrumb)
|
||||
pruneBreadcrumbs()
|
||||
notifyObservers(
|
||||
// store the breadcrumb in the ring buffer
|
||||
val position = getBreadcrumbIndex()
|
||||
store[position] = breadcrumb
|
||||
|
||||
updateState {
|
||||
// use direct field access to avoid overhead of accessor method
|
||||
StateEvent.AddBreadcrumb(
|
||||
breadcrumb.message,
|
||||
breadcrumb.type,
|
||||
DateUtils.toIso8601(breadcrumb.timestamp),
|
||||
breadcrumb.metadata ?: mutableMapOf()
|
||||
breadcrumb.impl.message,
|
||||
breadcrumb.impl.type,
|
||||
DateUtils.toIso8601(breadcrumb.impl.timestamp),
|
||||
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
|
||||
while (store.size > maxBreadcrumbs) {
|
||||
store.poll()
|
||||
/**
|
||||
* Creates a copy of the breadcrumbs in the order of their addition.
|
||||
*/
|
||||
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
|
||||
|
||||
internal class ContextState(context: String? = null) : BaseObservable() {
|
||||
var context = context
|
||||
set(value) {
|
||||
field = value
|
||||
/**
|
||||
* Tracks the current context and allows observers to be notified whenever it changes.
|
||||
*
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
sealed class StateEvent {
|
||||
sealed class StateEvent { // JvmField allows direct field access optimizations
|
||||
|
||||
class Install(
|
||||
val apiKey: String,
|
||||
val autoDetectNdkCrashes: Boolean,
|
||||
val appVersion: String?,
|
||||
val buildUuid: String?,
|
||||
val releaseStage: String?,
|
||||
val lastRunInfoPath: String,
|
||||
val consecutiveLaunchCrashes: Int
|
||||
@JvmField val apiKey: String,
|
||||
@JvmField val autoDetectNdkCrashes: Boolean,
|
||||
@JvmField val appVersion: String?,
|
||||
@JvmField val buildUuid: String?,
|
||||
@JvmField val releaseStage: String?,
|
||||
@JvmField val lastRunInfoPath: String,
|
||||
@JvmField val consecutiveLaunchCrashes: Int
|
||||
) : StateEvent()
|
||||
|
||||
object DeliverPending : StateEvent()
|
||||
|
||||
class AddMetadata(val section: String, val key: String?, val value: Any?) : StateEvent()
|
||||
class ClearMetadataSection(val section: String) : StateEvent()
|
||||
class ClearMetadataValue(val section: String, val key: String?) : StateEvent()
|
||||
class AddMetadata(
|
||||
@JvmField val section: String,
|
||||
@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(
|
||||
val message: String,
|
||||
val type: BreadcrumbType,
|
||||
val timestamp: String,
|
||||
val metadata: MutableMap<String, Any?>
|
||||
@JvmField val message: String,
|
||||
@JvmField val type: BreadcrumbType,
|
||||
@JvmField val timestamp: String,
|
||||
@JvmField val metadata: MutableMap<String, Any?>
|
||||
) : StateEvent()
|
||||
|
||||
object NotifyHandled : StateEvent()
|
||||
|
||||
object NotifyUnhandled : StateEvent()
|
||||
|
||||
object PauseSession : StateEvent()
|
||||
|
||||
class StartSession(
|
||||
val id: String,
|
||||
val startedAt: String,
|
||||
val handledCount: Int,
|
||||
@JvmField val id: String,
|
||||
@JvmField val startedAt: String,
|
||||
@JvmField val handledCount: Int,
|
||||
val unhandledCount: Int
|
||||
) : StateEvent()
|
||||
|
||||
class UpdateContext(val context: String?) : StateEvent()
|
||||
class UpdateInForeground(val inForeground: Boolean, val contextActivity: String?) : StateEvent()
|
||||
class UpdateLastRunInfo(val consecutiveLaunchCrashes: Int) : StateEvent()
|
||||
class UpdateIsLaunching(val isLaunching: Boolean) : StateEvent()
|
||||
class UpdateOrientation(val orientation: String?) : StateEvent()
|
||||
class UpdateContext(@JvmField val context: String?) : StateEvent()
|
||||
|
||||
class UpdateInForeground(
|
||||
@JvmField val inForeground: Boolean,
|
||||
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