FairEmail/app/src/main/java/com/bugsnag/android/Client.java

1006 lines
37 KiB
Java

package com.bugsnag.android;
import static com.bugsnag.android.ImmutableConfigKt.sanitiseConfiguration;
import static com.bugsnag.android.SeverityReason.REASON_HANDLED_EXCEPTION;
import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.Environment;
import android.os.storage.StorageManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import kotlin.Unit;
import kotlin.jvm.functions.Function2;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observer;
import java.util.Set;
import java.util.concurrent.RejectedExecutionException;
/**
* A Bugsnag Client instance allows you to use Bugsnag in your Android app.
* Typically you'd instead use the static access provided in the Bugsnag class.
* <p/>
* Example usage:
* <p/>
* Client client = new Client(this, "your-api-key");
* client.notify(new RuntimeException("something broke!"));
*
* @see Bugsnag
*/
@SuppressWarnings({"checkstyle:JavadocTagContinuationIndentation", "ConstantConditions"})
public class Client implements MetadataAware, CallbackAware, UserAware {
final ImmutableConfig immutableConfig;
final MetadataState metadataState;
private final ContextState contextState;
private final CallbackState callbackState;
private final UserState userState;
final Context appContext;
@NonNull
final DeviceDataCollector deviceDataCollector;
@NonNull
final AppDataCollector appDataCollector;
@NonNull
final BreadcrumbState breadcrumbState;
@NonNull
protected final EventStore eventStore;
private final SessionStore sessionStore;
final SessionTracker sessionTracker;
private final SystemBroadcastReceiver systemBroadcastReceiver;
private final ActivityBreadcrumbCollector activityBreadcrumbCollector;
private final SessionLifecycleCallback sessionLifecycleCallback;
private final Connectivity connectivity;
private final StorageManager storageManager;
final Logger logger;
final DeliveryDelegate deliveryDelegate;
final ClientObservable clientObservable = new ClientObservable();
private PluginClient pluginClient;
final Notifier notifier = new Notifier();
@Nullable
final LastRunInfo lastRunInfo;
final LastRunInfoStore lastRunInfoStore;
final LaunchCrashTracker launchCrashTracker;
final BackgroundTaskService bgTaskService = new BackgroundTaskService();
/**
* Initialize a Bugsnag client
*
* @param androidContext an Android context, usually <code>this</code>
*/
public Client(@NonNull Context androidContext) {
this(androidContext, Configuration.load(androidContext));
}
/**
* Initialize a Bugsnag client
*
* @param androidContext an Android context, usually <code>this</code>
* @param apiKey your Bugsnag API key from your Bugsnag dashboard
*/
public Client(@NonNull Context androidContext, @NonNull String apiKey) {
this(androidContext, Configuration.load(androidContext, apiKey));
}
/**
* Initialize a Bugsnag client
*
* @param androidContext an Android context, usually <code>this</code>
* @param configuration a configuration for the Client
*/
public Client(@NonNull Context androidContext, @NonNull final Configuration configuration) {
Context ctx = androidContext.getApplicationContext();
appContext = ctx != null ? ctx : androidContext;
connectivity = new ConnectivityCompat(appContext, new Function2<Boolean, String, Unit>() {
@Override
public Unit invoke(Boolean hasConnection, String networkState) {
Map<String, Object> data = new HashMap<>();
data.put("hasConnection", hasConnection);
data.put("networkState", networkState);
leaveAutoBreadcrumb("Connectivity changed", BreadcrumbType.STATE, data);
if (hasConnection) {
eventStore.flushAsync();
sessionTracker.flushAsync();
}
return null;
}
});
// set sensible defaults for delivery/project packages etc if not set
immutableConfig = sanitiseConfiguration(appContext, configuration, connectivity);
logger = immutableConfig.getLogger();
warnIfNotAppContext(androidContext);
// Set up breadcrumbs
callbackState = configuration.impl.callbackState.copy();
int maxBreadcrumbs = immutableConfig.getMaxBreadcrumbs();
breadcrumbState = new BreadcrumbState(maxBreadcrumbs, callbackState, logger);
storageManager = (StorageManager) appContext.getSystemService(Context.STORAGE_SERVICE);
contextState = new ContextState();
contextState.setContext(configuration.getContext());
sessionStore = new SessionStore(immutableConfig, logger, null);
sessionTracker = new SessionTracker(immutableConfig, callbackState, this,
sessionStore, logger, bgTaskService);
metadataState = copyMetadataState(configuration);
ActivityManager am =
(ActivityManager) appContext.getSystemService(Context.ACTIVITY_SERVICE);
launchCrashTracker = new LaunchCrashTracker(immutableConfig);
appDataCollector = new AppDataCollector(appContext, appContext.getPackageManager(),
immutableConfig, sessionTracker, am, launchCrashTracker, logger);
// load the device + user information
SharedPrefMigrator sharedPrefMigrator = new SharedPrefMigrator(appContext);
DeviceIdStore deviceIdStore = new DeviceIdStore(appContext, sharedPrefMigrator, logger);
String deviceId = deviceIdStore.loadDeviceId();
UserStore userStore = new UserStore(immutableConfig, deviceId, sharedPrefMigrator, logger);
userState = userStore.load(configuration.getUser());
sharedPrefMigrator.deleteLegacyPrefs();
DeviceBuildInfo info = DeviceBuildInfo.Companion.defaultInfo();
Resources resources = appContext.getResources();
deviceDataCollector = new DeviceDataCollector(connectivity, appContext,
resources, deviceId, info, Environment.getDataDirectory(),
new RootDetector(logger), bgTaskService, logger);
if (appContext instanceof Application) {
Application application = (Application) appContext;
sessionLifecycleCallback = new SessionLifecycleCallback(sessionTracker);
application.registerActivityLifecycleCallbacks(sessionLifecycleCallback);
if (immutableConfig.shouldRecordBreadcrumbType(BreadcrumbType.STATE)) {
this.activityBreadcrumbCollector = new ActivityBreadcrumbCollector(
new Function2<String, Map<String, ? extends Object>, Unit>() {
@SuppressWarnings("unchecked")
@Override
public Unit invoke(String activity, Map<String, ?> metadata) {
leaveBreadcrumb(activity, (Map<String, Object>) metadata,
BreadcrumbType.STATE);
return null;
}
}
);
application.registerActivityLifecycleCallbacks(activityBreadcrumbCollector);
} else {
this.activityBreadcrumbCollector = null;
}
} else {
this.activityBreadcrumbCollector = null;
this.sessionLifecycleCallback = null;
}
InternalReportDelegate delegate = new InternalReportDelegate(appContext, logger,
immutableConfig, storageManager, appDataCollector, deviceDataCollector,
sessionTracker, notifier, bgTaskService);
eventStore = new EventStore(immutableConfig, logger, notifier, bgTaskService, delegate);
deliveryDelegate = new DeliveryDelegate(logger, eventStore,
immutableConfig, breadcrumbState, notifier, bgTaskService);
// Install a default exception handler with this client
if (immutableConfig.getEnabledErrorTypes().getUnhandledExceptions()) {
new ExceptionHandler(this, logger);
}
// register a receiver for automatic breadcrumbs
systemBroadcastReceiver = SystemBroadcastReceiver.register(this, logger, bgTaskService);
registerOrientationChangeListener();
// load last run info
lastRunInfoStore = new LastRunInfoStore(immutableConfig);
lastRunInfo = loadLastRunInfo();
// initialise plugins before attempting to flush any errors
loadPlugins(configuration);
connectivity.registerForNetworkChanges();
// Flush any on-disk errors and sessions
eventStore.flushOnLaunch();
eventStore.flushAsync();
sessionTracker.flushAsync();
// leave auto breadcrumb
Map<String, Object> data = Collections.emptyMap();
leaveAutoBreadcrumb("Bugsnag loaded", BreadcrumbType.STATE, data);
logger.d("Bugsnag loaded");
}
@VisibleForTesting
Client(
ImmutableConfig immutableConfig,
MetadataState metadataState,
ContextState contextState,
CallbackState callbackState,
UserState userState,
Context appContext,
@NonNull DeviceDataCollector deviceDataCollector,
@NonNull AppDataCollector appDataCollector,
@NonNull BreadcrumbState breadcrumbState,
@NonNull EventStore eventStore,
SessionStore sessionStore,
SystemBroadcastReceiver systemBroadcastReceiver,
SessionTracker sessionTracker,
ActivityBreadcrumbCollector activityBreadcrumbCollector,
SessionLifecycleCallback sessionLifecycleCallback,
Connectivity connectivity,
StorageManager storageManager,
Logger logger,
DeliveryDelegate deliveryDelegate,
LastRunInfoStore lastRunInfoStore,
LaunchCrashTracker launchCrashTracker
) {
this.immutableConfig = immutableConfig;
this.metadataState = metadataState;
this.contextState = contextState;
this.callbackState = callbackState;
this.userState = userState;
this.appContext = appContext;
this.deviceDataCollector = deviceDataCollector;
this.appDataCollector = appDataCollector;
this.breadcrumbState = breadcrumbState;
this.eventStore = eventStore;
this.sessionStore = sessionStore;
this.systemBroadcastReceiver = systemBroadcastReceiver;
this.sessionTracker = sessionTracker;
this.activityBreadcrumbCollector = activityBreadcrumbCollector;
this.sessionLifecycleCallback = sessionLifecycleCallback;
this.connectivity = connectivity;
this.storageManager = storageManager;
this.logger = logger;
this.deliveryDelegate = deliveryDelegate;
this.lastRunInfoStore = lastRunInfoStore;
this.launchCrashTracker = launchCrashTracker;
this.lastRunInfo = null;
}
private LastRunInfo loadLastRunInfo() {
LastRunInfo lastRunInfo = lastRunInfoStore.load();
LastRunInfo currentRunInfo = new LastRunInfo(0, false, false);
persistRunInfo(currentRunInfo);
return lastRunInfo;
}
/**
* Load information about the last run, and reset the persisted information to the defaults.
*/
private void persistRunInfo(final LastRunInfo runInfo) {
try {
bgTaskService.submitTask(TaskType.IO, new Runnable() {
@Override
public void run() {
lastRunInfoStore.persist(runInfo);
}
});
} catch (RejectedExecutionException exc) {
logger.w("Failed to persist last run info", exc);
}
}
private void loadPlugins(@NonNull Configuration configuration) {
NativeInterface.setClient(this);
Set<Plugin> userPlugins = configuration.getPlugins();
pluginClient = new PluginClient(userPlugins, immutableConfig, logger);
pluginClient.loadPlugins(this);
}
private void logNull(String property) {
logger.e("Invalid null value supplied to client." + property + ", ignoring");
}
private MetadataState copyMetadataState(@NonNull Configuration configuration) {
// performs deep copy of metadata to preserve immutability of Configuration interface
Metadata orig = configuration.impl.metadataState.getMetadata();
Metadata copy = orig.copy();
return configuration.impl.metadataState.copy(copy);
}
private void registerOrientationChangeListener() {
IntentFilter configFilter = new IntentFilter();
configFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
ConfigChangeReceiver receiver = new ConfigChangeReceiver(deviceDataCollector,
new Function2<String, String, Unit>() {
@Override
public Unit invoke(String oldOrientation, String newOrientation) {
Map<String, Object> data = new HashMap<>();
data.put("from", oldOrientation);
data.put("to", newOrientation);
leaveAutoBreadcrumb("Orientation changed", BreadcrumbType.STATE, data);
clientObservable.postOrientationChange(newOrientation);
return null;
}
}
);
ContextExtensionsKt.registerReceiverSafe(appContext, receiver, configFilter, logger);
}
void setupNdkPlugin() {
String lastRunInfoPath = lastRunInfoStore.getFile().getAbsolutePath();
int crashes = (lastRunInfo != null) ? lastRunInfo.getConsecutiveLaunchCrashes() : 0;
clientObservable.postNdkInstall(immutableConfig, lastRunInfoPath, crashes);
syncInitialState();
clientObservable.postNdkDeliverPending();
}
void registerObserver(Observer observer) {
metadataState.addObserver(observer);
breadcrumbState.addObserver(observer);
sessionTracker.addObserver(observer);
clientObservable.addObserver(observer);
userState.addObserver(observer);
contextState.addObserver(observer);
deliveryDelegate.addObserver(observer);
launchCrashTracker.addObserver(observer);
}
/**
* Sends initial state values for Metadata/User/Context to any registered observers.
*/
void syncInitialState() {
metadataState.emitObservableEvent();
contextState.emitObservableEvent();
userState.emitObservableEvent();
}
/**
* Starts tracking a new session. You should disable automatic session tracking via
* {@link Configuration#setAutoTrackSessions(boolean)} if you call this method.
* <p/>
* You should call this at the appropriate time in your application when you wish to start a
* session. Any subsequent errors which occur in your application will still be reported to
* Bugsnag but will not count towards your application's
* <a href="https://docs.bugsnag.com/product/releases/releases-dashboard/#stability-score">
* stability score</a>. This will start a new session even if there is already an existing
* session; you should call {@link #resumeSession()} if you only want to start a session
* when one doesn't already exist.
*
* @see #resumeSession()
* @see #pauseSession()
* @see Configuration#setAutoTrackSessions(boolean)
*/
public void startSession() {
sessionTracker.startSession(false);
}
/**
* Pauses tracking of a session. You should disable automatic session tracking via
* {@link Configuration#setAutoTrackSessions(boolean)} if you call this method.
* <p/>
* You should call this at the appropriate time in your application when you wish to pause a
* session. Any subsequent errors which occur in your application will still be reported to
* Bugsnag but will not count towards your application's
* <a href="https://docs.bugsnag.com/product/releases/releases-dashboard/#stability-score">
* stability score</a>. This can be advantageous if, for example, you do not wish the
* stability score to include crashes in a background service.
*
* @see #startSession()
* @see #resumeSession()
* @see Configuration#setAutoTrackSessions(boolean)
*/
public void pauseSession() {
sessionTracker.pauseSession();
}
/**
* Resumes a session which has previously been paused, or starts a new session if none exists.
* If a session has already been resumed or started and has not been paused, calling this
* method will have no effect. You should disable automatic session tracking via
* {@link Configuration#setAutoTrackSessions(boolean)} if you call this method.
* <p/>
* It's important to note that sessions are stored in memory for the lifetime of the
* application process and are not persisted on disk. Therefore calling this method on app
* startup would start a new session, rather than continuing any previous session.
* <p/>
* You should call this at the appropriate time in your application when you wish to resume
* a previously started session. Any subsequent errors which occur in your application will
* still be reported to Bugsnag but will not count towards your application's
* <a href="https://docs.bugsnag.com/product/releases/releases-dashboard/#stability-score">
* stability score</a>.
*
* @see #startSession()
* @see #pauseSession()
* @see Configuration#setAutoTrackSessions(boolean)
*
* @return true if a previous session was resumed, false if a new session was started.
*/
public boolean resumeSession() {
return sessionTracker.resumeSession();
}
/**
* Bugsnag uses the concept of "contexts" to help display and group your errors. Contexts
* represent what was happening in your application at the time an error occurs.
*
* In an android app the "context" is automatically set as the foreground Activity.
* If you would like to set this value manually, you should alter this property.
*/
@Nullable public String getContext() {
return contextState.getContext();
}
/**
* Bugsnag uses the concept of "contexts" to help display and group your errors. Contexts
* represent what was happening in your application at the time an error occurs.
*
* In an android app the "context" is automatically set as the foreground Activity.
* If you would like to set this value manually, you should alter this property.
*/
public void setContext(@Nullable String context) {
contextState.setContext(context);
}
/**
* Sets the user associated with the event.
*/
@Override
public void setUser(@Nullable String id, @Nullable String email, @Nullable String name) {
userState.setUser(new User(id, email, name));
}
/**
* Returns the currently set User information.
*/
@NonNull
@Override
public User getUser() {
return userState.getUser();
}
/**
* Add a "on error" callback, to execute code at the point where an error report is
* captured in Bugsnag.
*
* You can use this to add or modify information attached to an Event
* before it is sent to your dashboard. You can also return
* <code>false</code> from any callback to prevent delivery. "on error"
* callbacks do not run before reports generated in the event
* of immediate app termination from crashes in C/C++ code.
*
* For example:
*
* Bugsnag.addOnError(new OnErrorCallback() {
* public boolean run(Event event) {
* event.setSeverity(Severity.INFO);
* return true;
* }
* })
*
* @param onError a callback to run before sending errors to Bugsnag
* @see OnErrorCallback
*/
@Override
public void addOnError(@NonNull OnErrorCallback onError) {
if (onError != null) {
callbackState.addOnError(onError);
} else {
logNull("addOnError");
}
}
/**
* Removes a previously added "on error" callback
* @param onError the callback to remove
*/
@Override
public void removeOnError(@NonNull OnErrorCallback onError) {
if (onError != null) {
callbackState.removeOnError(onError);
} else {
logNull("removeOnError");
}
}
/**
* Add an "on breadcrumb" callback, to execute code before every
* breadcrumb captured by Bugsnag.
*
* You can use this to modify breadcrumbs before they are stored by Bugsnag.
* You can also return <code>false</code> from any callback to ignore a breadcrumb.
*
* For example:
*
* Bugsnag.onBreadcrumb(new OnBreadcrumbCallback() {
* public boolean run(Breadcrumb breadcrumb) {
* return false; // ignore the breadcrumb
* }
* })
*
* @param onBreadcrumb a callback to run before a breadcrumb is captured
* @see OnBreadcrumbCallback
*/
@Override
public void addOnBreadcrumb(@NonNull OnBreadcrumbCallback onBreadcrumb) {
if (onBreadcrumb != null) {
callbackState.addOnBreadcrumb(onBreadcrumb);
} else {
logNull("addOnBreadcrumb");
}
}
/**
* Removes a previously added "on breadcrumb" callback
* @param onBreadcrumb the callback to remove
*/
@Override
public void removeOnBreadcrumb(@NonNull OnBreadcrumbCallback onBreadcrumb) {
if (onBreadcrumb != null) {
callbackState.removeOnBreadcrumb(onBreadcrumb);
} else {
logNull("removeOnBreadcrumb");
}
}
/**
* Add an "on session" callback, to execute code before every
* session captured by Bugsnag.
*
* You can use this to modify sessions before they are stored by Bugsnag.
* You can also return <code>false</code> from any callback to ignore a session.
*
* For example:
*
* Bugsnag.onSession(new OnSessionCallback() {
* public boolean run(Session session) {
* return false; // ignore the session
* }
* })
*
* @param onSession a callback to run before a session is captured
* @see OnSessionCallback
*/
@Override
public void addOnSession(@NonNull OnSessionCallback onSession) {
if (onSession != null) {
callbackState.addOnSession(onSession);
} else {
logNull("addOnSession");
}
}
/**
* Removes a previously added "on session" callback
* @param onSession the callback to remove
*/
@Override
public void removeOnSession(@NonNull OnSessionCallback onSession) {
if (onSession != null) {
callbackState.removeOnSession(onSession);
} else {
logNull("removeOnSession");
}
}
/**
* Notify Bugsnag of a handled exception
*
* @param exception the exception to send to Bugsnag
*/
public void notify(@NonNull Throwable exception) {
notify(exception, null);
}
/**
* Notify Bugsnag of a handled exception
*
* @param exc the exception to send to Bugsnag
* @param onError callback invoked on the generated error report for
* additional modification
*/
public void notify(@NonNull Throwable exc, @Nullable OnErrorCallback onError) {
if (exc != null) {
SeverityReason severityReason = SeverityReason.newInstance(REASON_HANDLED_EXCEPTION);
Metadata metadata = metadataState.getMetadata();
Event event = new Event(exc, immutableConfig, severityReason, metadata, logger);
populateAndNotifyAndroidEvent(event, onError);
} else {
logNull("notify");
}
}
/**
* Caches an error then attempts to notify.
*
* Should only ever be called from the {@link ExceptionHandler}.
*/
void notifyUnhandledException(@NonNull Throwable exc, Metadata metadata,
@SeverityReason.SeverityReasonType String severityReason,
@Nullable String attributeValue) {
SeverityReason handledState
= SeverityReason.newInstance(severityReason, Severity.ERROR, attributeValue);
Metadata data = Metadata.Companion.merge(metadataState.getMetadata(), metadata);
Event event = new Event(exc, immutableConfig, handledState, data, logger);
populateAndNotifyAndroidEvent(event, null);
// persist LastRunInfo so that on relaunch users can check the app crashed
int consecutiveLaunchCrashes = lastRunInfo == null ? 0
: lastRunInfo.getConsecutiveLaunchCrashes();
boolean launching = launchCrashTracker.isLaunching();
if (launching) {
consecutiveLaunchCrashes += 1;
}
LastRunInfo runInfo = new LastRunInfo(consecutiveLaunchCrashes, true, launching);
persistRunInfo(runInfo);
// suspend execution of any further background tasks, waiting for previously
// submitted ones to complete.
bgTaskService.shutdown();
}
void populateAndNotifyAndroidEvent(@NonNull Event event,
@Nullable OnErrorCallback onError) {
// Capture the state of the app and device and attach diagnostics to the event
event.setDevice(deviceDataCollector.generateDeviceWithState(new Date().getTime()));
event.addMetadata("device", deviceDataCollector.getDeviceMetadata());
// add additional info that belongs in metadata
// generate new object each time, as this can be mutated by end-users
event.setApp(appDataCollector.generateAppWithState());
event.addMetadata("app", appDataCollector.getAppDataMetadata());
// Attach breadcrumbState to the event
event.setBreadcrumbs(new ArrayList<>(breadcrumbState.getStore()));
// Attach user info to the event
User user = userState.getUser();
event.setUser(user.getId(), user.getEmail(), user.getName());
// Attach default context from active activity
if (Intrinsics.isEmpty(event.getContext())) {
String context = contextState.getContext();
event.setContext(context != null ? context : appDataCollector.getActiveScreenClass());
}
notifyInternal(event, onError);
}
void notifyInternal(@NonNull Event event,
@Nullable OnErrorCallback onError) {
String type = event.getImpl().getSeverityReasonType();
logger.d("Client#notifyInternal() - event captured by Client, type=" + type);
// Don't notify if this event class should be ignored
if (event.shouldDiscardClass()) {
logger.d("Skipping notification - should not notify for this class");
return;
}
if (!immutableConfig.shouldNotifyForReleaseStage()) {
logger.d("Skipping notification - should not notify for this release stage");
return;
}
// set the redacted keys on the event as this
// will not have been set for RN/Unity events
Set<String> redactedKeys = metadataState.getMetadata().getRedactedKeys();
Metadata eventMetadata = event.getImpl().getMetadata();
eventMetadata.setRedactedKeys(redactedKeys);
// get session for event
Session currentSession = sessionTracker.getCurrentSession();
if (currentSession != null
&& (immutableConfig.getAutoTrackSessions() || !currentSession.isAutoCaptured())) {
event.setSession(currentSession);
}
// Run on error tasks, don't notify if any return false
if (!callbackState.runOnErrorTasks(event, logger)
|| (onError != null && !onError.onError(event))) {
logger.d("Skipping notification - onError task returned false");
return;
}
deliveryDelegate.deliver(event);
}
/**
* Returns the current buffer of breadcrumbs that will be sent with captured events. This
* ordered list represents the most recent breadcrumbs to be captured up to the limit
* set in {@link Configuration#getMaxBreadcrumbs()}.
*
* The returned collection is readonly and mutating the list will cause no effect on the
* Client's state. If you wish to alter the breadcrumbs collected by the Client then you should
* use {@link Configuration#setEnabledBreadcrumbTypes(Set)} and
* {@link Configuration#addOnBreadcrumb(OnBreadcrumbCallback)} instead.
*
* @return a list of collected breadcrumbs
*/
@NonNull
public List<Breadcrumb> getBreadcrumbs() {
return new ArrayList<>(breadcrumbState.getStore());
}
@NonNull
AppDataCollector getAppDataCollector() {
return appDataCollector;
}
@NonNull
DeviceDataCollector getDeviceDataCollector() {
return deviceDataCollector;
}
/**
* Adds a map of multiple metadata key-value pairs to the specified section.
*/
@Override
public void addMetadata(@NonNull String section, @NonNull Map<String, ?> value) {
if (section != null && value != null) {
metadataState.addMetadata(section, value);
} else {
logNull("addMetadata");
}
}
/**
* Adds the specified key and value in the specified section. The value can be of
* any primitive type or a collection such as a map, set or array.
*/
@Override
public void addMetadata(@NonNull String section, @NonNull String key, @Nullable Object value) {
if (section != null && key != null) {
metadataState.addMetadata(section, key, value);
} else {
logNull("addMetadata");
}
}
/**
* Removes all the data from the specified section.
*/
@Override
public void clearMetadata(@NonNull String section) {
if (section != null) {
metadataState.clearMetadata(section);
} else {
logNull("clearMetadata");
}
}
/**
* Removes data with the specified key from the specified section.
*/
@Override
public void clearMetadata(@NonNull String section, @NonNull String key) {
if (section != null && key != null) {
metadataState.clearMetadata(section, key);
} else {
logNull("clearMetadata");
}
}
/**
* Returns a map of data in the specified section.
*/
@Nullable
@Override
public Map<String, Object> getMetadata(@NonNull String section) {
if (section != null) {
return metadataState.getMetadata(section);
} else {
logNull("getMetadata");
return null;
}
}
/**
* Returns the value of the specified key in the specified section.
*/
@Override
@Nullable
public Object getMetadata(@NonNull String section, @NonNull String key) {
if (section != null && key != null) {
return metadataState.getMetadata(section, key);
} else {
logNull("getMetadata");
return null;
}
}
@NonNull
Map<String, Object> getMetadata() {
return metadataState.getMetadata().toMap();
}
/**
* Leave a "breadcrumb" log message, representing an action that occurred
* in your app, to aid with debugging.
*
* @param message the log message to leave
*/
public void leaveBreadcrumb(@NonNull String message) {
if (message != null) {
breadcrumbState.add(new Breadcrumb(message, logger));
} else {
logNull("leaveBreadcrumb");
}
}
/**
* Leave a "breadcrumb" log message representing an action or event which
* occurred in your app, to aid with debugging
* @param message A short label
* @param metadata Additional diagnostic information about the app environment
* @param type A category for the breadcrumb
*/
public void leaveBreadcrumb(@NonNull String message,
@NonNull Map<String, Object> metadata,
@NonNull BreadcrumbType type) {
if (message != null && type != null && metadata != null) {
breadcrumbState.add(new Breadcrumb(message, type, metadata, new Date(), logger));
} else {
logNull("leaveBreadcrumb");
}
}
/**
* Intended for internal use only - leaves a breadcrumb if the type is enabled for automatic
* breadcrumbs.
*
* @param message A short label
* @param type A category for the breadcrumb
* @param metadata Additional diagnostic information about the app environment
*/
void leaveAutoBreadcrumb(@NonNull String message,
@NonNull BreadcrumbType type,
@NonNull Map<String, Object> metadata) {
if (immutableConfig.shouldRecordBreadcrumbType(type)) {
breadcrumbState.add(new Breadcrumb(message, type, metadata, new Date(), logger));
}
}
/**
* Retrieves information about the last launch of the application, if it has been run before.
*
* For example, this allows checking whether the app crashed on its last launch, which could
* be used to perform conditional behaviour to recover from crashes, such as clearing the
* app data cache.
*/
@Nullable
public LastRunInfo getLastRunInfo() {
return lastRunInfo;
}
/**
* Informs Bugsnag that the application has finished launching. Once this has been called
* {@link AppWithState#isLaunching()} will always be false in any new error reports,
* and synchronous delivery will not be attempted on the next launch for any fatal crashes.
*
* By default this method will be called after Bugsnag is initialized when
* {@link Configuration#getLaunchDurationMillis()} has elapsed. Invoking this method manually
* has precedence over the value supplied via the launchDurationMillis configuration option.
*/
public void markLaunchCompleted() {
launchCrashTracker.markLaunchCompleted();
}
SessionTracker getSessionTracker() {
return sessionTracker;
}
@NonNull
EventStore getEventStore() {
return eventStore;
}
/**
* Finalize by removing the receiver
*
* @throws Throwable if something goes wrong
*/
@SuppressWarnings("checkstyle:NoFinalizer")
protected void finalize() throws Throwable {
if (systemBroadcastReceiver != null) {
try {
ContextExtensionsKt.unregisterReceiverSafe(appContext,
systemBroadcastReceiver, logger);
} catch (IllegalArgumentException exception) {
logger.w("Receiver not registered");
}
}
super.finalize();
}
private void warnIfNotAppContext(Context androidContext) {
if (!(androidContext instanceof Application)) {
logger.w("Warning - Non-Application context detected! Please ensure that you are "
+ "initializing Bugsnag from a custom Application class.");
}
}
ImmutableConfig getConfig() {
return immutableConfig;
}
void setBinaryArch(String binaryArch) {
getAppDataCollector().setBinaryArch(binaryArch);
}
Context getAppContext() {
return appContext;
}
/**
* Intended for internal use only - sets the code bundle id for React Native
*/
@Nullable
String getCodeBundleId() {
return appDataCollector.getCodeBundleId();
}
/**
* Intended for internal use only - sets the code bundle id for React Native
*/
void setCodeBundleId(@Nullable String codeBundleId) {
appDataCollector.setCodeBundleId(codeBundleId);
}
void addRuntimeVersionInfo(@NonNull String key, @NonNull String value) {
deviceDataCollector.addRuntimeVersionInfo(key, value);
}
@VisibleForTesting
void close() {
connectivity.unregisterForNetworkChanges();
bgTaskService.shutdown();
}
Logger getLogger() {
return logger;
}
/**
* Retrieves an instantiated plugin of the given type, or null if none has been created
*/
@SuppressWarnings("rawtypes")
@Nullable
Plugin getPlugin(@NonNull Class clz) {
Set<Plugin> plugins = pluginClient.getPlugins();
for (Plugin plugin : plugins) {
if (plugin.getClass().equals(clz)) {
return plugin;
}
}
return null;
}
Notifier getNotifier() {
return notifier;
}
MetadataState getMetadataState() {
return metadataState;
}
}