package com.bugsnag.android; import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.io.File; import java.util.Locale; import java.util.Map; import java.util.Set; /** * User-specified configuration storage object, contains information * specified at the client level, api-key and endpoint configuration. */ @SuppressWarnings("ConstantConditions") // suppress warning about making redundant null checks public class Configuration implements CallbackAware, MetadataAware, UserAware, FeatureFlagAware { private static final int MIN_BREADCRUMBS = 0; private static final int MAX_BREADCRUMBS = 100; private static final int VALID_API_KEY_LEN = 32; private static final long MIN_LAUNCH_CRASH_THRESHOLD_MS = 0; final ConfigInternal impl; /** * Constructs a new Configuration object with default values. */ public Configuration(@NonNull String apiKey) { validateApiKey(apiKey); impl = new ConfigInternal(apiKey); } /** * Loads a Configuration object from values supplied as meta-data elements in your * AndroidManifest. */ @NonNull public static Configuration load(@NonNull Context context) { return ConfigInternal.load(context); } @NonNull static Configuration load(@NonNull Context context, @NonNull String apiKey) { return ConfigInternal.load(context, apiKey); } private void validateApiKey(String value) { if (isInvalidApiKey(value)) { DebugLogger.INSTANCE.w("Invalid configuration. " + "apiKey should be a 32-character hexademical string, got " + value); } } @VisibleForTesting static boolean isInvalidApiKey(String apiKey) { if (Intrinsics.isEmpty(apiKey)) { throw new IllegalArgumentException("No Bugsnag API Key set"); } if (apiKey.length() != VALID_API_KEY_LEN) { return true; } // check whether each character is hexadecimal (either a digit or a-f). // this avoids using a regex to improve startup performance. for (int k = 0; k < VALID_API_KEY_LEN; k++) { char chr = apiKey.charAt(k); if (!Character.isDigit(chr) && (chr < 'a' || chr > 'f')) { return true; } } return false; } private void logNull(String property) { getLogger().e("Invalid null value supplied to config." + property + ", ignoring"); } /** * Retrieves the API key used for events sent to Bugsnag. */ @NonNull public String getApiKey() { return impl.getApiKey(); } /** * Changes the API key used for events sent to Bugsnag. */ public void setApiKey(@NonNull String apiKey) { validateApiKey(apiKey); impl.setApiKey(apiKey); } /** * Set the application version sent to Bugsnag. We'll automatically pull your app version * from the versionName field in your AndroidManifest.xml file. */ @Nullable public String getAppVersion() { return impl.getAppVersion(); } /** * Set the application version sent to Bugsnag. We'll automatically pull your app version * from the versionName field in your AndroidManifest.xml file. */ public void setAppVersion(@Nullable String appVersion) { impl.setAppVersion(appVersion); } /** * We'll automatically pull your versionCode from the versionCode field * in your AndroidManifest.xml file. If you'd like to override this you * can set this property. */ @Nullable public Integer getVersionCode() { return impl.getVersionCode(); } /** * We'll automatically pull your versionCode from the versionCode field * in your AndroidManifest.xml file. If you'd like to override this you * can set this property. */ public void setVersionCode(@Nullable Integer versionCode) { impl.setVersionCode(versionCode); } /** * If you would like to distinguish between errors that happen in different stages of the * application release process (development, production, etc) you can set the releaseStage * that is reported to Bugsnag. * * If you are running a debug build, we'll automatically set this to "development", * otherwise it is set to "production". You can control whether events are sent for * specific release stages using the enabledReleaseStages option. */ @Nullable public String getReleaseStage() { return impl.getReleaseStage(); } /** * If you would like to distinguish between errors that happen in different stages of the * application release process (development, production, etc) you can set the releaseStage * that is reported to Bugsnag. * * If you are running a debug build, we'll automatically set this to "development", * otherwise it is set to "production". You can control whether events are sent for * specific release stages using the enabledReleaseStages option. */ public void setReleaseStage(@Nullable String releaseStage) { impl.setReleaseStage(releaseStage); } /** * Controls whether we should capture and serialize the state of all threads at the time * of an error. * * By default sendThreads is set to Thread.ThreadSendPolicy.ALWAYS. This can be set to * Thread.ThreadSendPolicy.NEVER to disable or Thread.ThreadSendPolicy.UNHANDLED_ONLY * to only do so for unhandled errors. */ @NonNull public ThreadSendPolicy getSendThreads() { return impl.getSendThreads(); } /** * Controls whether we should capture and serialize the state of all threads at the time * of an error. * * By default sendThreads is set to Thread.ThreadSendPolicy.ALWAYS. This can be set to * Thread.ThreadSendPolicy.NEVER to disable or Thread.ThreadSendPolicy.UNHANDLED_ONLY * to only do so for unhandled errors. */ public void setSendThreads(@NonNull ThreadSendPolicy sendThreads) { if (sendThreads != null) { impl.setSendThreads(sendThreads); } else { logNull("sendThreads"); } } /** * Set whether or not Bugsnag should persist user information between application sessions. * * If enabled then any user information set will be re-used until the user information is * removed manually by calling {@link Bugsnag#setUser(String, String, String)} * with null arguments. */ public boolean getPersistUser() { return impl.getPersistUser(); } /** * Set whether or not Bugsnag should persist user information between application sessions. * * If enabled then any user information set will be re-used until the user information is * removed manually by calling {@link Bugsnag#setUser(String, String, String)} * with null arguments. */ public void setPersistUser(boolean persistUser) { impl.setPersistUser(persistUser); } /** * Sets the directory where event and session JSON payloads should be persisted if a network * request is not successful. If you use Bugsnag in multiple processes, then a unique * persistenceDirectory must be configured for each process to prevent duplicate * requests being made by each instantiation of Bugsnag. *
* The persistenceDirectory also stores user information if {@link #getPersistUser()} has been * set to true. * * By default, bugsnag sets the persistenceDirectory to {@link Context#getCacheDir()}. * * If the persistenceDirectory is changed between application launches, no attempt will be made * to deliver events or sessions cached in the previous location. */ @Nullable public File getPersistenceDirectory() { return impl.getPersistenceDirectory(); } /** * Sets the directory where event and session JSON payloads should be persisted if a network * request is not successful. If you use Bugsnag in multiple processes, then a unique * persistenceDirectory must be configured for each process to prevent duplicate * requests being made by each instantiation of Bugsnag. * * The persistenceDirectory also stores user information if {@link #getPersistUser()} has been * set to true. * * By default, bugsnag sets the persistenceDirectory to {@link Context#getCacheDir()}. * * If the persistenceDirectory is changed between application launches, no attempt will be made * to deliver events or sessions cached in the previous location. */ public void setPersistenceDirectory(@Nullable File directory) { impl.setPersistenceDirectory(directory); } /** * Deprecated. Use {@link #getLaunchDurationMillis()} instead. */ @Deprecated public long getLaunchCrashThresholdMs() { getLogger().w("The launchCrashThresholdMs configuration option is deprecated " + "and will be removed in a future release. Please use " + "launchDurationMillis instead."); return getLaunchDurationMillis(); } /** * Deprecated. Use {@link #setLaunchDurationMillis(long)} instead. */ @Deprecated public void setLaunchCrashThresholdMs(long launchCrashThresholdMs) { getLogger().w("The launchCrashThresholdMs configuration option is deprecated " + "and will be removed in a future release. Please use " + "launchDurationMillis instead."); setLaunchDurationMillis(launchCrashThresholdMs); } /** * Sets whether or not Bugsnag should send crashes synchronously that occurred during * the application's launch period. By default this behavior is enabled. * * See {@link #setLaunchDurationMillis(long)} */ public boolean getSendLaunchCrashesSynchronously() { return impl.getSendLaunchCrashesSynchronously(); } /** * Sets whether or not Bugsnag should send crashes synchronously that occurred during * the application's launch period. By default this behavior is enabled. * * See {@link #setLaunchDurationMillis(long)} */ public void setSendLaunchCrashesSynchronously(boolean sendLaunchCrashesSynchronously) { impl.setSendLaunchCrashesSynchronously(sendLaunchCrashesSynchronously); } /** * Sets the threshold in milliseconds for an uncaught error to be considered as a crash on * launch. If a crash is detected on launch, Bugsnag will attempt to send the most recent * event synchronously. * * 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 long getLaunchDurationMillis() { return impl.getLaunchDurationMillis(); } /** * Sets the threshold in milliseconds for an uncaught error to be considered as a crash on * launch. If a crash is detected on launch, Bugsnag will attempt to send the most recent * event synchronously. * * 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) { if (launchDurationMillis >= MIN_LAUNCH_CRASH_THRESHOLD_MS) { impl.setLaunchDurationMillis(launchDurationMillis); } else { getLogger().e("Invalid configuration value detected. " + "Option launchDurationMillis should be a positive long value." + "Supplied value is " + launchDurationMillis); } } /** * Sets whether or not Bugsnag should automatically capture and report User sessions whenever * the app enters the foreground. * * By default this behavior is enabled. */ public boolean getAutoTrackSessions() { return impl.getAutoTrackSessions(); } /** * Sets whether or not Bugsnag should automatically capture and report User sessions whenever * the app enters the foreground. * * By default this behavior is enabled. */ public void setAutoTrackSessions(boolean autoTrackSessions) { impl.setAutoTrackSessions(autoTrackSessions); } /** * Bugsnag will automatically detect different types of error in your application. * If you wish to control exactly which types are enabled, set this property. */ @NonNull public ErrorTypes getEnabledErrorTypes() { return impl.getEnabledErrorTypes(); } /** * Bugsnag will automatically detect different types of error in your application. * If you wish to control exactly which types are enabled, set this property. */ public void setEnabledErrorTypes(@NonNull ErrorTypes enabledErrorTypes) { if (enabledErrorTypes != null) { impl.setEnabledErrorTypes(enabledErrorTypes); } else { logNull("enabledErrorTypes"); } } /** * If you want to disable automatic detection of all errors, you can set this property to false. * By default this property is true. * * Setting autoDetectErrors to false will disable all automatic errors, regardless of the * error types enabled by enabledErrorTypes */ public boolean getAutoDetectErrors() { return impl.getAutoDetectErrors(); } /** * If you want to disable automatic detection of all errors, you can set this property to false. * By default this property is true. * * Setting autoDetectErrors to false will disable all automatic errors, regardless of the * error types enabled by enabledErrorTypes */ public void setAutoDetectErrors(boolean autoDetectErrors) { impl.setAutoDetectErrors(autoDetectErrors); } /** * If your app's codebase contains different entry-points/processes, but reports to a single * Bugsnag project, you might want to add information denoting the type of process the error * came from. * * This information can be used in the dashboard to filter errors and to determine whether * an error is limited to a subset of appTypes. * * By default, this value is set to 'android'. */ @Nullable public String getAppType() { return impl.getAppType(); } /** * If your app's codebase contains different entry-points/processes, but reports to a single * Bugsnag project, you might want to add information denoting the type of process the error * came from. * * This information can be used in the dashboard to filter errors and to determine whether * an error is limited to a subset of appTypes. * * By default, this value is set to 'android'. */ public void setAppType(@Nullable String appType) { impl.setAppType(appType); } /** * By default, the notifier's log messages will be logged using android.util.Log * with a "Bugsnag" tag unless the releaseStage is "production". * * To override this behavior, an alternative instance can be provided that implements the * Logger interface. */ @Nullable public Logger getLogger() { return impl.getLogger(); } /** * By default, the notifier's log messages will be logged using android.util.Log * with a "Bugsnag" tag unless the releaseStage is "production". * * To override this behavior, an alternative instance can be provided that implements the * Logger interface. */ public void setLogger(@Nullable Logger logger) { impl.setLogger(logger); } /** * The Delivery implementation used to make network calls to the Bugsnag * Error Reporting and * Sessions API. * * This may be useful if you have requirements such as certificate pinning and rotation, * which are not supported by the default implementation. * * To provide custom delivery functionality, create a class which implements the Delivery * interface. Please note that request bodies must match the structure specified in the * Error Reporting and * Sessions API documentation. * * You can use the return type from the deliver functions to control the strategy for * retrying the transmission at a later date. * * If DeliveryStatus.UNDELIVERED is returned, the notifier will automatically cache * the payload and trigger delivery later on. Otherwise, if either DeliveryStatus.DELIVERED * or DeliveryStatus.FAILURE is returned the notifier will removed any cached payload * and no further delivery will be attempted. */ @NonNull public Delivery getDelivery() { return impl.getDelivery(); } /** * The Delivery implementation used to make network calls to the Bugsnag * Error Reporting and * Sessions API. * * This may be useful if you have requirements such as certificate pinning and rotation, * which are not supported by the default implementation. * * To provide custom delivery functionality, create a class which implements the Delivery * interface. Please note that request bodies must match the structure specified in the * Error Reporting and * Sessions API documentation. * * You can use the return type from the deliver functions to control the strategy for * retrying the transmission at a later date. * * If DeliveryStatus.UNDELIVERED is returned, the notifier will automatically cache * the payload and trigger delivery later on. Otherwise, if either DeliveryStatus.DELIVERED * or DeliveryStatus.FAILURE is returned the notifier will removed any cached payload * and no further delivery will be attempted. */ public void setDelivery(@NonNull Delivery delivery) { if (delivery != null) { impl.setDelivery(delivery); } else { logNull("delivery"); } } /** * Set the endpoints to send data to. By default we'll send error reports to * https://notify.bugsnag.com, and sessions to https://sessions.bugsnag.com, but you can * override this if you are using Bugsnag Enterprise to point to your own Bugsnag endpoints. */ @NonNull public EndpointConfiguration getEndpoints() { return impl.getEndpoints(); } /** * Set the endpoints to send data to. By default we'll send error reports to * https://notify.bugsnag.com, and sessions to https://sessions.bugsnag.com, but you can * override this if you are using Bugsnag Enterprise to point to your own Bugsnag endpoints. */ public void setEndpoints(@NonNull EndpointConfiguration endpoints) { if (endpoints != null) { impl.setEndpoints(endpoints); } else { logNull("endpoints"); } } /** * Sets the maximum number of breadcrumbs which will be stored. Once the threshold is reached, * the oldest breadcrumbs will be deleted. * * By default, 50 breadcrumbs are stored: this can be amended up to a maximum of 100. */ public int getMaxBreadcrumbs() { return impl.getMaxBreadcrumbs(); } /** * Sets the maximum number of breadcrumbs which will be stored. Once the threshold is reached, * the oldest breadcrumbs will be deleted. * * By default, 50 breadcrumbs are stored: this can be amended up to a maximum of 100. */ public void setMaxBreadcrumbs(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. " + "Supplied value is " + maxBreadcrumbs); } } /** * Sets the maximum number of persisted events which will be stored. Once the threshold is * reached, the oldest event will be deleted. * * By default, 32 events are persisted. */ public int getMaxPersistedEvents() { return impl.getMaxPersistedEvents(); } /** * Sets the maximum number of persisted events which will be stored. Once the threshold is * reached, the oldest event will be deleted. * * By default, 32 events are persisted. */ public void setMaxPersistedEvents(int maxPersistedEvents) { if (maxPersistedEvents >= 0) { impl.setMaxPersistedEvents(maxPersistedEvents); } else { getLogger().e("Invalid configuration value detected. " + "Option maxPersistedEvents should be a positive integer." + "Supplied value is " + maxPersistedEvents); } } /** * Gets the maximum number of threads that will be reported with an event. Once the threshold is * reached, all remaining threads will be omitted. * * By default, up to 200 threads are reported. */ public int getMaxReportedThreads() { return impl.getMaxReportedThreads(); } /** * Sets the maximum number of threads that will be reported with an event. Once the threshold is * reached, all remaining threads will be omitted. * * By default, up to 200 threads are reported. */ public void setMaxReportedThreads(int maxReportedThreads) { if (maxReportedThreads >= 0) { impl.setMaxReportedThreads(maxReportedThreads); } else { getLogger().e("Invalid configuration value detected. " + "Option maxReportedThreads should be a positive integer." + "Supplied value is " + maxReportedThreads); } } /** * Sets the maximum number of persisted sessions which will be stored. Once the threshold is * reached, the oldest session will be deleted. * * By default, 128 sessions are persisted. */ public int getMaxPersistedSessions() { return impl.getMaxPersistedSessions(); } /** * Sets the maximum number of persisted sessions which will be stored. Once the threshold is * reached, the oldest session will be deleted. * * By default, 128 sessions are persisted. */ public void setMaxPersistedSessions(int maxPersistedSessions) { if (maxPersistedSessions >= 0) { impl.setMaxPersistedSessions(maxPersistedSessions); } else { getLogger().e("Invalid configuration value detected. " + "Option maxPersistedSessions should be a positive integer." + "Supplied value is " + maxPersistedSessions); } } /** * 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 impl.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) { impl.setContext(context); } /** * Sets which values should be removed from any Metadata objects before * sending them to Bugsnag. Use this if you want to ensure you don't send * sensitive data such as passwords, and credit card numbers to our * servers. Any keys which contain these strings will be filtered. * * By default, redactedKeys is set to "password" */ @NonNull public Setfalse
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) {
impl.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) {
impl.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 false
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) {
impl.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) {
impl.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 false
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) {
impl.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) {
impl.removeOnSession(onSession);
} else {
logNull("removeOnSession");
}
}
/**
* Add a callback which will be invoked prior to an event being delivered
* to Bugsnag. The callback can be used to modify events or cancel
* delivering the event altogether by returning false
. Note
* that the callback may be invoked in the current or a subsequent app
* launch depending on whether the app terminated prior to delivering the
* event.
*
* @param onSend the callback to add
* @see OnSendCallback
*/
public void addOnSend(@NonNull OnSendCallback onSend) {
if (onSend != null) {
impl.addOnSend(onSend);
} else {
logNull("addOnSend");
}
}
/**
* Remove a callback previously added with {@link Configuration#addOnSend(OnSendCallback)}
*
* @param onSend the callback to remove
*/
public void removeOnSend(@NonNull OnSendCallback onSend) {
if (onSend != null) {
impl.removeOnSend(onSend);
} else {
logNull("removeOnSend");
}
}
/**
* Adds a map of multiple metadata key-value pairs to the specified section.
*/
@Override
public void addMetadata(@NonNull String section, @NonNull Map