From ccaec5daab76c4a41f6cce2c704e8cdc196fd753 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Fri, 3 Aug 2018 11:56:17 +0100 Subject: [PATCH 1/9] refactor: moves config creation from manifest to separate class Moves the creation of Configuration from the AndroidManifest to a separate class. This reduces the complexity of Client initialisation. --- .../java/com/bugsnag/android/ClientTest.java | 45 ++++--- .../main/java/com/bugsnag/android/Client.java | 108 +---------------- .../com/bugsnag/android/ConfigFactory.java | 112 ++++++++++++++++++ 3 files changed, 135 insertions(+), 130 deletions(-) create mode 100644 sdk/src/main/java/com/bugsnag/android/ConfigFactory.java diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ClientTest.java b/sdk/src/androidTest/java/com/bugsnag/android/ClientTest.java index 29a0e16d5c..e977d0c676 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/ClientTest.java +++ b/sdk/src/androidTest/java/com/bugsnag/android/ClientTest.java @@ -2,16 +2,13 @@ import static com.bugsnag.android.BugsnagTestUtils.generateClient; import static com.bugsnag.android.BugsnagTestUtils.getSharedPrefs; -import static com.bugsnag.android.BugsnagTestUtils.streamableToJson; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.content.Context; import android.content.SharedPreferences; -import android.os.Build; import android.os.Bundle; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; @@ -178,18 +175,18 @@ public void testClearUser() { public void testEmptyManifestConfig() { Bundle data = new Bundle(); Configuration protoConfig = new Configuration("api-key"); - Configuration newConfig = Client.populateConfigFromManifest(protoConfig, data); - - assertEquals(config.getApiKey(), newConfig.getApiKey()); - assertEquals(config.getBuildUUID(), newConfig.getBuildUUID()); - assertEquals(config.getAppVersion(), newConfig.getAppVersion()); - assertEquals(config.getReleaseStage(), newConfig.getReleaseStage()); - assertEquals(config.getEndpoint(), newConfig.getEndpoint()); - assertEquals(config.getSessionEndpoint(), newConfig.getSessionEndpoint()); - assertEquals(config.getSendThreads(), newConfig.getSendThreads()); - assertEquals(config.getEnableExceptionHandler(), newConfig.getEnableExceptionHandler()); + ConfigFactory.populateConfigFromManifest(protoConfig, data); + + assertEquals(config.getApiKey(), protoConfig.getApiKey()); + assertEquals(config.getBuildUUID(), protoConfig.getBuildUUID()); + assertEquals(config.getAppVersion(), protoConfig.getAppVersion()); + assertEquals(config.getReleaseStage(), protoConfig.getReleaseStage()); + assertEquals(config.getEndpoint(), protoConfig.getEndpoint()); + assertEquals(config.getSessionEndpoint(), protoConfig.getSessionEndpoint()); + assertEquals(config.getSendThreads(), protoConfig.getSendThreads()); + assertEquals(config.getEnableExceptionHandler(), protoConfig.getEnableExceptionHandler()); assertEquals(config.getPersistUserBetweenSessions(), - newConfig.getPersistUserBetweenSessions()); + protoConfig.getPersistUserBetweenSessions()); } @Test @@ -212,16 +209,16 @@ public void testFullManifestConfig() { data.putBoolean("com.bugsnag.android.AUTO_CAPTURE_SESSIONS", true); Configuration protoConfig = new Configuration("api-key"); - Configuration newConfig = Client.populateConfigFromManifest(protoConfig, data); - assertEquals(buildUuid, newConfig.getBuildUUID()); - assertEquals(appVersion, newConfig.getAppVersion()); - assertEquals(releaseStage, newConfig.getReleaseStage()); - assertEquals(endpoint, newConfig.getEndpoint()); - assertEquals(sessionEndpoint, newConfig.getSessionEndpoint()); - assertEquals(false, newConfig.getSendThreads()); - assertEquals(false, newConfig.getEnableExceptionHandler()); - assertEquals(true, newConfig.getPersistUserBetweenSessions()); - assertEquals(true, newConfig.shouldAutoCaptureSessions()); + ConfigFactory.populateConfigFromManifest(protoConfig, data); + assertEquals(buildUuid, protoConfig.getBuildUUID()); + assertEquals(appVersion, protoConfig.getAppVersion()); + assertEquals(releaseStage, protoConfig.getReleaseStage()); + assertEquals(endpoint, protoConfig.getEndpoint()); + assertEquals(sessionEndpoint, protoConfig.getSessionEndpoint()); + assertEquals(false, protoConfig.getSendThreads()); + assertEquals(false, protoConfig.getEnableExceptionHandler()); + assertEquals(true, protoConfig.getPersistUserBetweenSessions()); + assertEquals(true, protoConfig.shouldAutoCaptureSessions()); } @Test diff --git a/sdk/src/main/java/com/bugsnag/android/Client.java b/sdk/src/main/java/com/bugsnag/android/Client.java index ff5727e10c..a96f10e4f6 100644 --- a/sdk/src/main/java/com/bugsnag/android/Client.java +++ b/sdk/src/main/java/com/bugsnag/android/Client.java @@ -1,5 +1,6 @@ package com.bugsnag.android; +import static com.bugsnag.android.ConfigFactory.MF_BUILD_UUID; import static com.bugsnag.android.MapUtils.getStringFromMap; import android.app.Activity; @@ -13,10 +14,8 @@ import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.text.TextUtils; import java.util.ArrayList; import java.util.Collection; @@ -43,25 +42,10 @@ public class Client extends Observable implements Observer { private static final boolean BLOCKING = true; private static final String SHARED_PREF_KEY = "com.bugsnag.android"; - private static final String BUGSNAG_NAMESPACE = "com.bugsnag.android"; private static final String USER_ID_KEY = "user.id"; private static final String USER_NAME_KEY = "user.name"; private static final String USER_EMAIL_KEY = "user.email"; - static final String MF_API_KEY = BUGSNAG_NAMESPACE + ".API_KEY"; - static final String MF_BUILD_UUID = BUGSNAG_NAMESPACE + ".BUILD_UUID"; - static final String MF_APP_VERSION = BUGSNAG_NAMESPACE + ".APP_VERSION"; - static final String MF_ENDPOINT = BUGSNAG_NAMESPACE + ".ENDPOINT"; - static final String MF_SESSIONS_ENDPOINT = BUGSNAG_NAMESPACE + ".SESSIONS_ENDPOINT"; - static final String MF_RELEASE_STAGE = BUGSNAG_NAMESPACE + ".RELEASE_STAGE"; - static final String MF_SEND_THREADS = BUGSNAG_NAMESPACE + ".SEND_THREADS"; - static final String MF_ENABLE_EXCEPTION_HANDLER = - BUGSNAG_NAMESPACE + ".ENABLE_EXCEPTION_HANDLER"; - static final String MF_PERSIST_USER_BETWEEN_SESSIONS = - BUGSNAG_NAMESPACE + ".PERSIST_USER_BETWEEN_SESSIONS"; - static final String MF_AUTO_CAPTURE_SESSIONS = - BUGSNAG_NAMESPACE + ".AUTO_CAPTURE_SESSIONS"; - @NonNull protected final Configuration config; final Context appContext; @@ -117,7 +101,7 @@ public Client(@NonNull Context androidContext, @Nullable String apiKey, boolean enableExceptionHandler) { this(androidContext, - createNewConfiguration(androidContext, apiKey, enableExceptionHandler)); + ConfigFactory.createNewConfiguration(androidContext, apiKey, enableExceptionHandler)); } /** @@ -258,94 +242,6 @@ public void update(Observable observable, Object arg) { } } - /** - * Creates a new configuration object based on the provided parameters - * will read the API key and other configuration values from the manifest if it is not provided - * - * @param androidContext The context of the application - * @param apiKey The API key to use - * @param enableExceptionHandler should we automatically handle uncaught exceptions? - * @return The created config - */ - @NonNull - private static Configuration createNewConfiguration(@NonNull Context androidContext, - String apiKey, - boolean enableExceptionHandler) { - Context appContext = androidContext.getApplicationContext(); - - // Attempt to load API key and other config from AndroidManifest.xml, if not passed in - boolean loadFromManifest = TextUtils.isEmpty(apiKey); - - if (loadFromManifest) { - try { - PackageManager packageManager = appContext.getPackageManager(); - String packageName = appContext.getPackageName(); - ApplicationInfo ai = - packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA); - Bundle data = ai.metaData; - apiKey = data.getString(MF_API_KEY); - } catch (Exception ignore) { - Logger.warn("Bugsnag is unable to read api key from manifest."); - } - } - - if (apiKey == null) { - throw new NullPointerException("You must provide a Bugsnag API key"); - } - - // Build a configuration object - Configuration newConfig = new Configuration(apiKey); - newConfig.setEnableExceptionHandler(enableExceptionHandler); - - if (loadFromManifest) { - try { - PackageManager packageManager = appContext.getPackageManager(); - String packageName = appContext.getPackageName(); - ApplicationInfo ai = - packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA); - Bundle data = ai.metaData; - populateConfigFromManifest(newConfig, data); - } catch (Exception ignore) { - Logger.warn("Bugsnag is unable to read config from manifest."); - } - } - return newConfig; - } - - /** - * Populates the config with meta-data values supplied from the manifest as a Bundle. - * - * @param config the config to mutate - * @param data the manifest bundle - * @return the updated config - */ - @NonNull - static Configuration populateConfigFromManifest(@NonNull Configuration config, - @NonNull Bundle data) { - config.setBuildUUID(data.getString(MF_BUILD_UUID)); - config.setAppVersion(data.getString(MF_APP_VERSION)); - config.setReleaseStage(data.getString(MF_RELEASE_STAGE)); - - if (data.containsKey(MF_ENDPOINT)) { - String endpoint = data.getString(MF_ENDPOINT); - String sessionEndpoint = data.getString(MF_SESSIONS_ENDPOINT); - //noinspection ConstantConditions (pass in null/empty as this function will warn) - config.setEndpoints(endpoint, sessionEndpoint); - } - - config.setSendThreads(data.getBoolean(MF_SEND_THREADS, true)); - config.setPersistUserBetweenSessions( - data.getBoolean(MF_PERSIST_USER_BETWEEN_SESSIONS, false)); - - if (data.containsKey(MF_AUTO_CAPTURE_SESSIONS)) { - config.setAutoCaptureSessions(data.getBoolean(MF_AUTO_CAPTURE_SESSIONS)); - } - - config.setEnableExceptionHandler( - data.getBoolean(MF_ENABLE_EXCEPTION_HANDLER, true)); - return config; - } - /** * Manually starts tracking a new session. * diff --git a/sdk/src/main/java/com/bugsnag/android/ConfigFactory.java b/sdk/src/main/java/com/bugsnag/android/ConfigFactory.java new file mode 100644 index 0000000000..c44b5e86e3 --- /dev/null +++ b/sdk/src/main/java/com/bugsnag/android/ConfigFactory.java @@ -0,0 +1,112 @@ +package com.bugsnag.android; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.text.TextUtils; + +class ConfigFactory { + + private static final String BUGSNAG_NAMESPACE = "com.bugsnag.android"; + private static final String MF_APP_VERSION = BUGSNAG_NAMESPACE + ".APP_VERSION"; + private static final String MF_ENDPOINT = BUGSNAG_NAMESPACE + ".ENDPOINT"; + private static final String MF_SESSIONS_ENDPOINT = BUGSNAG_NAMESPACE + ".SESSIONS_ENDPOINT"; + private static final String MF_RELEASE_STAGE = BUGSNAG_NAMESPACE + ".RELEASE_STAGE"; + private static final String MF_SEND_THREADS = BUGSNAG_NAMESPACE + ".SEND_THREADS"; + private static final String MF_ENABLE_EXCEPTION_HANDLER = + BUGSNAG_NAMESPACE + ".ENABLE_EXCEPTION_HANDLER"; + private static final String MF_PERSIST_USER_BETWEEN_SESSIONS = + BUGSNAG_NAMESPACE + ".PERSIST_USER_BETWEEN_SESSIONS"; + private static final String MF_AUTO_CAPTURE_SESSIONS = + BUGSNAG_NAMESPACE + ".AUTO_CAPTURE_SESSIONS"; + private static final String MF_API_KEY = BUGSNAG_NAMESPACE + ".API_KEY"; + static final String MF_BUILD_UUID = BUGSNAG_NAMESPACE + ".BUILD_UUID"; + + /** + * Creates a new configuration object based on the provided parameters + * will read the API key and other configuration values from the manifest if it is not provided + * + * @param androidContext The context of the application + * @param apiKey The API key to use + * @param enableExceptionHandler should we automatically handle uncaught exceptions? + * @return The created config + */ + @NonNull + static Configuration createNewConfiguration(@NonNull Context androidContext, + String apiKey, + boolean enableExceptionHandler) { + Context appContext = androidContext.getApplicationContext(); + + // Attempt to load API key and other config from AndroidManifest.xml, if not passed in + boolean loadFromManifest = TextUtils.isEmpty(apiKey); + + if (loadFromManifest) { + try { + PackageManager packageManager = appContext.getPackageManager(); + String packageName = appContext.getPackageName(); + ApplicationInfo ai = + packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA); + Bundle data = ai.metaData; + apiKey = data.getString(MF_API_KEY); + } catch (Exception ignore) { + Logger.warn("Bugsnag is unable to read api key from manifest."); + } + } + + if (apiKey == null) { + throw new NullPointerException("You must provide a Bugsnag API key"); + } + + // Build a configuration object + Configuration newConfig = new Configuration(apiKey); + newConfig.setEnableExceptionHandler(enableExceptionHandler); + + if (loadFromManifest) { + try { + PackageManager packageManager = appContext.getPackageManager(); + String packageName = appContext.getPackageName(); + ApplicationInfo ai = + packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA); + Bundle data = ai.metaData; + populateConfigFromManifest(newConfig, data); + } catch (Exception ignore) { + Logger.warn("Bugsnag is unable to read config from manifest."); + } + } + return newConfig; + } + + /** + * Populates the config with meta-data values supplied from the manifest as a Bundle. + * + * @param config the config to mutate + * @param data the manifest bundle + */ + static void populateConfigFromManifest(@NonNull Configuration config, + @NonNull Bundle data) { + config.setBuildUUID(data.getString(MF_BUILD_UUID)); + config.setAppVersion(data.getString(MF_APP_VERSION)); + config.setReleaseStage(data.getString(MF_RELEASE_STAGE)); + + if (data.containsKey(MF_ENDPOINT)) { + String endpoint = data.getString(MF_ENDPOINT); + String sessionEndpoint = data.getString(MF_SESSIONS_ENDPOINT); + //noinspection ConstantConditions (pass in null/empty as this function will warn) + config.setEndpoints(endpoint, sessionEndpoint); + } + + config.setSendThreads(data.getBoolean(MF_SEND_THREADS, true)); + config.setPersistUserBetweenSessions( + data.getBoolean(MF_PERSIST_USER_BETWEEN_SESSIONS, false)); + + if (data.containsKey(MF_AUTO_CAPTURE_SESSIONS)) { + config.setAutoCaptureSessions(data.getBoolean(MF_AUTO_CAPTURE_SESSIONS)); + } + + config.setEnableExceptionHandler( + data.getBoolean(MF_ENABLE_EXCEPTION_HANDLER, true)); + } + +} From 14d2bfbc9a7a9c71796385570c60e40ecd657f63 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Mon, 6 Aug 2018 17:05:47 +0100 Subject: [PATCH 2/9] build: consolidate kotlin/bugsnag plugin versions, update dependencies to latest expected by android --- build.gradle | 8 ++++---- example/build.gradle | 2 +- examplelib/build.gradle | 2 +- gradle.properties | 3 +-- mazerunner/build.gradle | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index fc30a9ebf3..0c53fc693a 100644 --- a/build.gradle +++ b/build.gradle @@ -6,11 +6,11 @@ buildscript { ext.kotlin_version = '1.2.30' dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.3' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.0' classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.2' - classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.8.2' + classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.8.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -19,10 +19,10 @@ repositories { jcenter() } ext { - supportLibVersion = "27.0.0" + supportLibVersion = "27.1.1" supportTestVersion = "0.5" espressoVersion = "2.2.2" junitVersion = "4.12" mockitoVersion = "1.10.19" - kotlin_version = '1.1.51' + bugsnagPluginVersion = "3.3.0" } diff --git a/example/build.gradle b/example/build.gradle index 3cf0ec0d0f..3c69a7c741 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -5,7 +5,7 @@ buildscript { mavenLocal() } dependencies { - classpath 'com.bugsnag:bugsnag-android-gradle-plugin:3.1.0' + classpath "com.bugsnag:bugsnag-android-gradle-plugin:$bugsnagPluginVersion" } } diff --git a/examplelib/build.gradle b/examplelib/build.gradle index 24e8275ae4..6a049174f9 100644 --- a/examplelib/build.gradle +++ b/examplelib/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.bugsnag:bugsnag-android-gradle-plugin:3.1.0' + classpath "com.bugsnag:bugsnag-android-gradle-plugin:$bugsnagPluginVersion" } } diff --git a/gradle.properties b/gradle.properties index 2a6d403afb..97d25d5297 100644 --- a/gradle.properties +++ b/gradle.properties @@ -29,6 +29,5 @@ POM_ARTIFACT_ID=bugsnag-android POM_PACKAGING=aar ANDROID_MIN_SDK_VERSION=14 ANDROID_TARGET_SDK_VERSION=27 -ANDROID_BUILD_TOOLS_VERSION=27.0.0 +ANDROID_BUILD_TOOLS_VERSION=27.0.3 ANDROID_COMPILE_SDK_VERSION=27 -KOTLIN_VERSION=1.2.21 diff --git a/mazerunner/build.gradle b/mazerunner/build.gradle index 467ab41c20..51a7fc5c14 100644 --- a/mazerunner/build.gradle +++ b/mazerunner/build.gradle @@ -35,5 +35,5 @@ android { dependencies { implementation project(':sdk') - implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$project.KOTLIN_VERSION" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" } From ab1d82ab11937befd174ec401d1523d0596a9292 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Mon, 6 Aug 2018 17:17:24 +0100 Subject: [PATCH 3/9] update travis build tools version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b19f66a6a7..e0dcd0e115 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ android: components: - tools - platform-tools - - build-tools-27.0.0 + - build-tools-27.0.3 - android-16 - android-19 - android-21 From 8b3096cb48e323b37684d47f193b21df1dcd51c7 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Tue, 7 Aug 2018 09:38:20 +0100 Subject: [PATCH 4/9] update allproject repos --- build.gradle | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 0c53fc693a..f1d7edd7a1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ buildscript { repositories { google() + mavenCentral() jcenter() } ext.kotlin_version = '1.2.30' @@ -14,9 +15,12 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } -repositories { - google() - jcenter() +allprojects { + repositories { + google() + mavenCentral() + jcenter() + } } ext { supportLibVersion = "27.1.1" From 6934c6e03a4bbf51b27902beabb2c5da451b5061 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Tue, 7 Aug 2018 10:44:21 +0100 Subject: [PATCH 5/9] fix: suppress lint warning for empty stacktrace scenario using api 19 method --- .../android/mazerunner/scenarios/EmptyStacktraceScenario.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyStacktraceScenario.kt b/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyStacktraceScenario.kt index 7472a01bbe..00a8297a5e 100644 --- a/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyStacktraceScenario.kt +++ b/mazerunner/src/main/java/com/bugsnag/android/mazerunner/scenarios/EmptyStacktraceScenario.kt @@ -1,5 +1,6 @@ package com.bugsnag.android.mazerunner.scenarios +import android.annotation.SuppressLint import android.content.Context import com.bugsnag.android.Bugsnag import com.bugsnag.android.Configuration @@ -15,6 +16,7 @@ internal class EmptyStacktraceScenario(config: Configuration, Bugsnag.notify(EmptyException("EmptyStacktraceScenario")) } + @SuppressLint("NewApi") class EmptyException(message: String?) : Throwable(message, null, true, false) } From 0df59917bf3cd76b588d9a0faeb094c8d705db50 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Wed, 15 Aug 2018 15:14:57 +0100 Subject: [PATCH 6/9] feat: move maxBreadcrumbs to config, deprecate setting via client, prune breadcrumbs before serialis --- .../com/bugsnag/android/BreadcrumbsTest.java | 201 ----------------- .../com/bugsnag/android/BreadcrumbsTest.kt | 211 ++++++++++++++++++ .../java/com/bugsnag/android/ClientTest.java | 1 + .../java/com/bugsnag/android/Breadcrumbs.java | 36 ++- .../java/com/bugsnag/android/Bugsnag.java | 4 +- .../main/java/com/bugsnag/android/Client.java | 6 +- .../com/bugsnag/android/Configuration.java | 28 +++ 7 files changed, 264 insertions(+), 223 deletions(-) delete mode 100644 sdk/src/androidTest/java/com/bugsnag/android/BreadcrumbsTest.java create mode 100644 sdk/src/androidTest/java/com/bugsnag/android/BreadcrumbsTest.kt diff --git a/sdk/src/androidTest/java/com/bugsnag/android/BreadcrumbsTest.java b/sdk/src/androidTest/java/com/bugsnag/android/BreadcrumbsTest.java deleted file mode 100644 index af9ed85ea1..0000000000 --- a/sdk/src/androidTest/java/com/bugsnag/android/BreadcrumbsTest.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.bugsnag.android; - -import static com.bugsnag.android.BreadcrumbType.MANUAL; -import static com.bugsnag.android.BugsnagTestUtils.generateClient; -import static com.bugsnag.android.BugsnagTestUtils.streamableToJsonArray; -import static org.junit.Assert.assertEquals; - -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Locale; -import java.util.Queue; - - -@RunWith(AndroidJUnit4.class) -@SmallTest -public class BreadcrumbsTest { - - private Breadcrumbs breadcrumbs; - - @Before - public void setUp() throws Exception { - breadcrumbs = new Breadcrumbs(); - } - - @After - public void tearDown() throws Exception { - Async.cancelTasks(); - } - - @Test - public void testSerialization() throws JSONException, IOException { - breadcrumbs.add(new Breadcrumb("Started app")); - breadcrumbs.add(new Breadcrumb("Clicked a button")); - breadcrumbs.add(new Breadcrumb("Lorem ipsum dolor sit amet, consectetur adipiscing elit," - + " sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad " - + "minim veniam, quis nostrud exercitation ullamco laboris nisi" - + " ut aliquip ex ea commodo consequat.")); - - JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs); - assertEquals(3, breadcrumbsJson.length()); - assertEquals("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " - + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim", - breadcrumbsJson.getJSONObject(2).getJSONObject("metaData").get("message")); - } - - @Test - public void testSizeLimit() throws JSONException, IOException { - breadcrumbs.setSize(5); - breadcrumbs.add(new Breadcrumb("1")); - breadcrumbs.add(new Breadcrumb("2")); - breadcrumbs.add(new Breadcrumb("3")); - breadcrumbs.add(new Breadcrumb("4")); - breadcrumbs.add(new Breadcrumb("5")); - breadcrumbs.add(new Breadcrumb("6")); - - JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs); - assertEquals(5, breadcrumbsJson.length()); - - JSONObject firstJsonNode = breadcrumbsJson.getJSONObject(0); - JSONObject otherJsonNode = breadcrumbsJson.getJSONObject(4); - - assertEquals("2", firstJsonNode.getJSONObject("metaData").get("message")); - assertEquals("6", otherJsonNode.getJSONObject("metaData").get("message")); - } - - @Test - public void testResizePersists() throws JSONException, IOException { - breadcrumbs.add(new Breadcrumb("1")); - breadcrumbs.add(new Breadcrumb("2")); - breadcrumbs.add(new Breadcrumb("3")); - breadcrumbs.add(new Breadcrumb("4")); - breadcrumbs.add(new Breadcrumb("5")); - breadcrumbs.add(new Breadcrumb("6")); - breadcrumbs.setSize(5); - breadcrumbs.add(new Breadcrumb("7")); - - JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs); - assertEquals(5, breadcrumbsJson.length()); - - JSONObject firstJsonNode = breadcrumbsJson.getJSONObject(0); - JSONObject otherJsonNode = breadcrumbsJson.getJSONObject(4); - - assertEquals("3", firstJsonNode.getJSONObject("metaData").get("message")); - assertEquals("7", otherJsonNode.getJSONObject("metaData").get("message")); - } - - @Test - public void testResizeEmpty() throws JSONException, IOException { - breadcrumbs.add(new Breadcrumb("1")); - breadcrumbs.add(new Breadcrumb("2")); - breadcrumbs.setSize(0); - breadcrumbs.add(new Breadcrumb("3")); - breadcrumbs.add(new Breadcrumb("4")); - breadcrumbs.add(new Breadcrumb("5")); - breadcrumbs.add(new Breadcrumb("6")); - - JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs); - assertEquals(0, breadcrumbsJson.length()); - } - - @Test - public void testResizeNegative() throws JSONException, IOException { - breadcrumbs.add(new Breadcrumb("1")); - breadcrumbs.add(new Breadcrumb("2")); - breadcrumbs.setSize(-1); - - JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs); - assertEquals(2, breadcrumbsJson.length()); - } - - @Test - public void testResize() throws JSONException, IOException { - breadcrumbs.add(new Breadcrumb("1")); - breadcrumbs.add(new Breadcrumb("2")); - breadcrumbs.add(new Breadcrumb("3")); - breadcrumbs.add(new Breadcrumb("4")); - breadcrumbs.add(new Breadcrumb("5")); - breadcrumbs.add(new Breadcrumb("6")); - breadcrumbs.setSize(5); - - JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs); - assertEquals(5, breadcrumbsJson.length()); - - JSONObject firstJsonNode = breadcrumbsJson.getJSONObject(0); - JSONObject otherJsonNode = breadcrumbsJson.getJSONObject(4); - - assertEquals("2", firstJsonNode.getJSONObject("metaData").get("message")); - assertEquals("6", otherJsonNode.getJSONObject("metaData").get("message")); - } - - @Test - public void testClear() throws JSONException, IOException { - breadcrumbs.add(new Breadcrumb("1")); - breadcrumbs.add(new Breadcrumb("2")); - breadcrumbs.add(new Breadcrumb("3")); - breadcrumbs.clear(); - - JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs); - assertEquals(0, breadcrumbsJson.length()); - } - - @Test - public void testType() throws JSONException, IOException { - breadcrumbs.add(new Breadcrumb("1")); - JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs); - assertEquals("manual", breadcrumbsJson.getJSONObject(0).get("type")); - } - - @Test - public void testPayloadSizeLimit() throws JSONException, IOException { - HashMap metadata = new HashMap<>(); - for (int i = 0; i < 400; i++) { - metadata.put(String.format(Locale.US, "%d", i), "!!"); - } - breadcrumbs.add(new Breadcrumb("Rotated Menu", BreadcrumbType.STATE, metadata)); - JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs); - assertEquals(0, breadcrumbsJson.length()); - } - - @Test - public void testPayloadType() throws JSONException, IOException { - HashMap metadata = new HashMap<>(); - metadata.put("direction", "left"); - breadcrumbs.add(new Breadcrumb("Rotated Menu", BreadcrumbType.STATE, metadata)); - JSONArray breadcrumbsJson = streamableToJsonArray(breadcrumbs); - - JSONObject node = breadcrumbsJson.getJSONObject(0); - assertEquals("Rotated Menu", node.get("name")); - assertEquals("state", node.get("type")); - assertEquals("left", node.getJSONObject("metaData").get("direction")); - assertEquals(1, breadcrumbsJson.length()); - } - - @Test - public void testClientMethods() { - Client client = generateClient(); - client.leaveBreadcrumb("Hello World"); - Queue store = client.breadcrumbs.store; - int count = 0; - - for (Breadcrumb breadcrumb : store) { - if (MANUAL == breadcrumb.getType() && "manual".equals(breadcrumb.getName())) { - count++; - assertEquals("Hello World", breadcrumb.getMetadata().get("message")); - } - } - assertEquals(1, count); - } - -} diff --git a/sdk/src/androidTest/java/com/bugsnag/android/BreadcrumbsTest.kt b/sdk/src/androidTest/java/com/bugsnag/android/BreadcrumbsTest.kt new file mode 100644 index 0000000000..636e796497 --- /dev/null +++ b/sdk/src/androidTest/java/com/bugsnag/android/BreadcrumbsTest.kt @@ -0,0 +1,211 @@ +package com.bugsnag.android + +import com.bugsnag.android.BreadcrumbType.MANUAL +import org.junit.Assert.assertEquals + +import android.support.test.filters.SmallTest +import android.support.test.runner.AndroidJUnit4 +import com.bugsnag.android.BugsnagTestUtils.* + +import org.json.JSONException +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +import java.io.IOException +import java.util.HashMap +import java.util.Locale + +@RunWith(AndroidJUnit4::class) +@SmallTest +class BreadcrumbsTest { + + private lateinit var breadcrumbs: Breadcrumbs + private lateinit var config: Configuration + + @Before + fun setUp() { + config = generateConfiguration() + breadcrumbs = Breadcrumbs(config) + } + + @After + fun tearDown() { + Async.cancelTasks() + } + + /** + * Verifies that the breadcrumb message is truncated after the max limit is reached + */ + @Test + fun testMessageTruncation() { + breadcrumbs.add(Breadcrumb("Started app")) + breadcrumbs.add(Breadcrumb("Clicked a button")) + breadcrumbs.add(Breadcrumb("Lorem ipsum dolor sit amet, consectetur adipiscing elit," + + " sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad " + + "minim veniam, quis nostrud exercitation ullamco laboris nisi" + + " ut aliquip ex ea commodo consequat.")) + + val breadcrumbsJson = streamableToJsonArray(breadcrumbs) + assertEquals(3, breadcrumbsJson.length()) + assertEquals("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim", + breadcrumbsJson.getJSONObject(2).getJSONObject("metaData").get("message")) + } + + /** + * Verifies that leaving breadcrumbs drops the oldest breadcrumb after reaching the max limit + */ + @Test + @Throws(JSONException::class, IOException::class) + fun testSizeLimitBeforeAdding() { + config.maxBreadcrumbs = 5 + + for (k in 1..6) { + breadcrumbs.add(Breadcrumb("$k")) + } + + val json = streamableToJsonArray(breadcrumbs) + assertEquals(config.maxBreadcrumbs, json.length()) + + val firstCrumb = json.getJSONObject(0) + val lastCrumb = json.getJSONObject(config.maxBreadcrumbs - 1) + assertEquals("2", firstCrumb.getJSONObject("metaData").get("message")) + assertEquals("6", lastCrumb.getJSONObject("metaData").get("message")) + } + + /** + * Verifies that setting the max limit removes the oldest breadcrumb if the limit is exceeded + */ + @Test + fun testSizeLimitAfterAdding() { + val maxSize = 5 + + for (k in 1..7) { + breadcrumbs.add(Breadcrumb("$k")) + } + + config.maxBreadcrumbs = maxSize + val json = streamableToJsonArray(breadcrumbs) + assertEquals(maxSize, json.length()) + + val firstCrumb = json.getJSONObject(0) + val lastCrumb = json.getJSONObject(maxSize - 1) + assertEquals("3", firstCrumb.getJSONObject("metaData").get("message")) + assertEquals("7", lastCrumb.getJSONObject("metaData").get("message")) + } + + /** + * Verifies that no breadcrumbs are added if the size limit is set to 0 + */ + @Test + fun testSetSizeEmpty() { + breadcrumbs.add(Breadcrumb("1")) + breadcrumbs.add(Breadcrumb("2")) + + config.maxBreadcrumbs = 0 + breadcrumbs.add(Breadcrumb("3")) + assertEquals(0, streamableToJsonArray(breadcrumbs).length()) + } + + /** + * Verifies that setting a negative size has no effect + */ + @Test + fun testSetSizeNegative() { + breadcrumbs.add(Breadcrumb("1")) + config.maxBreadcrumbs = -1 + assertEquals(1, streamableToJsonArray(breadcrumbs).length()) + } + + /** + * Verifies that clearing removes all the breadcrumbs + */ + @Test + fun testClear() { + breadcrumbs.add(Breadcrumb("1")) + breadcrumbs.clear() + assertEquals(0, streamableToJsonArray(breadcrumbs).length()) + } + + /** + * Verifies that the type of a breadcrumb is manual by default + */ + @Test + fun testDefaultBreadcrumbType() { + breadcrumbs.add(Breadcrumb("1")) + val json = streamableToJsonArray(breadcrumbs) + assertEquals("manual", json.getJSONObject(0).get("type")) + } + + /** + * Ensures a breadcrumb is dropped if it exceeds the payload size limit + */ + @Test + fun testPayloadSizeLimit() { + val metadata = HashMap() + for (i in 0..399) { + metadata[String.format(Locale.US, "%d", i)] = "!!" + } + breadcrumbs.add(Breadcrumb("Rotated Menu", BreadcrumbType.STATE, metadata)) + assertEquals(0, streamableToJsonArray(breadcrumbs).length()) + } + + /** + * Verifies that breadcrumb metadata is serialised + */ + @Test + fun testPayloadType() { + val metadata = mapOf(Pair("direction", "left")) + breadcrumbs.add(Breadcrumb("Rotated Menu", BreadcrumbType.STATE, metadata)) + + val json = streamableToJsonArray(breadcrumbs) + val node = json.getJSONObject(0) + assertEquals("Rotated Menu", node.get("name")) + assertEquals("state", node.get("type")) + assertEquals("left", node.getJSONObject("metaData").get("direction")) + assertEquals(1, json.length()) + } + + /** + * Verifies that the Client methods leave breadcrumbs correctly + */ + @Test + fun testClientMethods() { + val client = generateClient() + client.leaveBreadcrumb("Hello World") + val store = client.breadcrumbs.store + var count = 0 + + for (breadcrumb in store) { + if (MANUAL == breadcrumb.type && "manual" == breadcrumb.name) { + count++ + assertEquals("Hello World", breadcrumb.metadata["message"]) + } + } + assertEquals(1, count) + } + + /** + * Verifies that the max breadcrumb accessors only allow positive numbers + */ + @Test + fun testMaxBreadcrumbAccessors() { + val config = generateConfiguration() + assertEquals(32, config.maxBreadcrumbs) + + config.maxBreadcrumbs = 50 + assertEquals(50, config.maxBreadcrumbs) + + config.maxBreadcrumbs = Int.MAX_VALUE + assertEquals(Int.MAX_VALUE, config.maxBreadcrumbs) + + config.maxBreadcrumbs = 0 + assertEquals(0, config.maxBreadcrumbs) + + config.maxBreadcrumbs = -5 + assertEquals(0, config.maxBreadcrumbs) + } + +} diff --git a/sdk/src/androidTest/java/com/bugsnag/android/ClientTest.java b/sdk/src/androidTest/java/com/bugsnag/android/ClientTest.java index e977d0c676..53df93aaa3 100644 --- a/sdk/src/androidTest/java/com/bugsnag/android/ClientTest.java +++ b/sdk/src/androidTest/java/com/bugsnag/android/ClientTest.java @@ -221,6 +221,7 @@ public void testFullManifestConfig() { assertEquals(true, protoConfig.shouldAutoCaptureSessions()); } + @SuppressWarnings("deprecation") // test backwards compatibility of client.setMaxBreadcrumbs @Test public void testMaxBreadcrumbs() { Client client = generateClient(); diff --git a/sdk/src/main/java/com/bugsnag/android/Breadcrumbs.java b/sdk/src/main/java/com/bugsnag/android/Breadcrumbs.java index f5cfd73428..ce40d4df04 100644 --- a/sdk/src/main/java/com/bugsnag/android/Breadcrumbs.java +++ b/sdk/src/main/java/com/bugsnag/android/Breadcrumbs.java @@ -9,13 +9,18 @@ class Breadcrumbs implements JsonStream.Streamable { - private static final int DEFAULT_MAX_SIZE = 32; private static final int MAX_PAYLOAD_SIZE = 4096; final Queue store = new ConcurrentLinkedQueue<>(); - private int maxSize = DEFAULT_MAX_SIZE; + + private final Configuration configuration; + + Breadcrumbs(Configuration configuration) { + this.configuration = configuration; + } @Override public void toStream(@NonNull JsonStream writer) throws IOException { + pruneBreadcrumbs(); writer.beginArray(); for (Breadcrumb breadcrumb : store) { @@ -33,19 +38,6 @@ void clear() { store.clear(); } - void setSize(int size) { - if (size < 0) { - Logger.warn("Ignoring invalid breadcrumb capacity. Must be >= 0."); - return; - } - - this.maxSize = size; - // Remove oldest breadcrumbs until reaching the required size - while (store.size() > size) { - store.poll(); - } - } - private void addToStore(@NonNull Breadcrumb breadcrumb) { try { if (breadcrumb.payloadSize() > MAX_PAYLOAD_SIZE) { @@ -53,12 +45,18 @@ private void addToStore(@NonNull Breadcrumb breadcrumb) { return; } store.add(breadcrumb); - if (store.size() > maxSize) { - // Remove oldest breadcrumb - store.poll(); - } + pruneBreadcrumbs(); } catch (IOException ex) { Logger.warn("Dropping breadcrumb because it could not be serialized", ex); } } + + private void pruneBreadcrumbs() { + int maxBreadcrumbs = configuration.getMaxBreadcrumbs(); + + // Remove oldest breadcrumb + while (store.size() > maxBreadcrumbs) { + store.poll(); + } + } } diff --git a/sdk/src/main/java/com/bugsnag/android/Bugsnag.java b/sdk/src/main/java/com/bugsnag/android/Bugsnag.java index 980458339e..7bc375ef73 100644 --- a/sdk/src/main/java/com/bugsnag/android/Bugsnag.java +++ b/sdk/src/main/java/com/bugsnag/android/Bugsnag.java @@ -601,9 +601,11 @@ public static void leaveBreadcrumb(@NonNull String name, * messages. * * @param numBreadcrumbs number of breadcrumb log messages to send + * @deprecated use {@link Configuration#setMaxBreadcrumbs(int)} instead */ + @Deprecated public static void setMaxBreadcrumbs(int numBreadcrumbs) { - getClient().setMaxBreadcrumbs(numBreadcrumbs); + getClient().config.setMaxBreadcrumbs(numBreadcrumbs); } /** diff --git a/sdk/src/main/java/com/bugsnag/android/Client.java b/sdk/src/main/java/com/bugsnag/android/Client.java index a96f10e4f6..02ef1017f8 100644 --- a/sdk/src/main/java/com/bugsnag/android/Client.java +++ b/sdk/src/main/java/com/bugsnag/android/Client.java @@ -135,7 +135,7 @@ public Client(@NonNull Context androidContext, @NonNull Configuration configurat deviceData = new DeviceData(this); // Set up breadcrumbs - breadcrumbs = new Breadcrumbs(); + breadcrumbs = new Breadcrumbs(configuration); // Set sensible defaults setProjectPackages(appContext.getPackageName()); @@ -1177,9 +1177,11 @@ void leaveBreadcrumb(@NonNull String name, * messages. * * @param numBreadcrumbs number of breadcrumb log messages to send + * @deprecated use {@link Configuration#setMaxBreadcrumbs(int)} instead */ + @Deprecated public void setMaxBreadcrumbs(int numBreadcrumbs) { - breadcrumbs.setSize(numBreadcrumbs); + config.setMaxBreadcrumbs(numBreadcrumbs); } /** diff --git a/sdk/src/main/java/com/bugsnag/android/Configuration.java b/sdk/src/main/java/com/bugsnag/android/Configuration.java index 6af820c41c..8b8cdf5404 100644 --- a/sdk/src/main/java/com/bugsnag/android/Configuration.java +++ b/sdk/src/main/java/com/bugsnag/android/Configuration.java @@ -24,6 +24,7 @@ public class Configuration extends Observable implements Observer { private static final String HEADER_API_PAYLOAD_VERSION = "Bugsnag-Payload-Version"; private static final String HEADER_API_KEY = "Bugsnag-Api-Key"; private static final String HEADER_BUGSNAG_SENT_AT = "Bugsnag-Sent-At"; + private static final int DEFAULT_MAX_SIZE = 32; @NonNull private final String apiKey; @@ -57,6 +58,7 @@ public class Configuration extends Observable implements Observer { private String notifierType; private Delivery delivery; + private int maxBreadcrumbs = DEFAULT_MAX_SIZE; /** * Construct a new Bugsnag configuration object @@ -535,6 +537,32 @@ String getNotifierType() { return notifierType; } + /** + * Set the maximum number of breadcrumbs to keep and sent to Bugsnag. + * By default, we'll keep and send the 32 most recent breadcrumb log + * messages. + * + * @param numBreadcrumbs max number of breadcrumb log messages to send + */ + public void setMaxBreadcrumbs(int numBreadcrumbs) { + if (numBreadcrumbs < 0) { + Logger.warn("Ignoring invalid breadcrumb capacity. Must be >= 0."); + return; + } + this.maxBreadcrumbs = numBreadcrumbs; + } + + /** + * Retrieves the maximum number of breadcrumbs to keep and sent to Bugsnag. + * By default, we'll keep and send the 32 most recent breadcrumb log + * messages. + * + * @return the maximum number of breadcrumb log messages to send + */ + public int getMaxBreadcrumbs() { + return maxBreadcrumbs; + } + /** * Retrieves the delivery used to make HTTP requests to Bugsnag. * From c1de62f88fa01378a3c730b972275765b269d61f Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Wed, 15 Aug 2018 15:34:36 +0100 Subject: [PATCH 7/9] docs: add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ece927071..7cd31aaa09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 4.6.1 (TBD) + +* Set maxBreadcrumbs via Configuration rather than Client [#359](https://github.com/bugsnag/bugsnag-android/pull/359) + ## 4.6.0 (2018-08-02) * Android P compatibility fixes - ensure available information on StrictMode violations is collected [#350](https://github.com/bugsnag/bugsnag-android/pull/350) From 66f75c92ad63776f2c1d9b782dbc7fb54b2ea9b7 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Mon, 20 Aug 2018 15:53:44 +0100 Subject: [PATCH 8/9] docs: update inline comment for breadcrumb max size --- sdk/src/main/java/com/bugsnag/android/Breadcrumbs.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/main/java/com/bugsnag/android/Breadcrumbs.java b/sdk/src/main/java/com/bugsnag/android/Breadcrumbs.java index ce40d4df04..a44ce82ef1 100644 --- a/sdk/src/main/java/com/bugsnag/android/Breadcrumbs.java +++ b/sdk/src/main/java/com/bugsnag/android/Breadcrumbs.java @@ -54,7 +54,7 @@ private void addToStore(@NonNull Breadcrumb breadcrumb) { private void pruneBreadcrumbs() { int maxBreadcrumbs = configuration.getMaxBreadcrumbs(); - // Remove oldest breadcrumb + // Remove oldest breadcrumbs until new max size reached while (store.size() > maxBreadcrumbs) { store.poll(); } From 4a7e931b8175dcb51c82df794e4ea3cb393fb6f2 Mon Sep 17 00:00:00 2001 From: fractalwrench Date: Tue, 21 Aug 2018 14:27:03 +0100 Subject: [PATCH 9/9] v4.6.1 --- CHANGELOG.md | 2 +- README.md | 2 +- gradle.properties | 2 +- sdk/src/main/java/com/bugsnag/android/Notifier.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efac23dcbd..56048c95ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 4.X.X (TBD) +## 4.6.1 (2018-08-21) ### Bug fixes diff --git a/README.md b/README.md index 44ef632eca..6ba64d4b71 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build status](https://travis-ci.org/bugsnag/bugsnag-android.svg?branch=master)](https://travis-ci.org/bugsnag/bugsnag-android) [![Coverage Status](https://coveralls.io/repos/github/bugsnag/bugsnag-android/badge.svg?branch=master)](https://coveralls.io/github/bugsnag/bugsnag-android?branch=master) -![Method count and size](https://img.shields.io/badge/Methods%20and%20size-78%20classes%20|%20635%20methods%20|%20312%20fields%20|%20112%20KB-e91e63.svg) +![Method count and size](https://img.shields.io/badge/Methods%20and%20size-79%20classes%20|%20638%20methods%20|%20314%20fields%20|%20116%20KB-e91e63.svg) Get comprehensive [Android crash reports](https://www.bugsnag.com/platforms/android/) to quickly debug errors. diff --git a/gradle.properties b/gradle.properties index 63e228556a..1896be615e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ org.gradle.jvmargs=-Xmx1536m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -VERSION_NAME=4.6.0 +VERSION_NAME=4.6.1 GROUP=com.bugsnag POM_SCM_URL=https://github.com/bugsnag/bugsnag-android POM_SCM_CONNECTION=scm:git@github.com:bugsnag/bugsnag-android.git diff --git a/sdk/src/main/java/com/bugsnag/android/Notifier.java b/sdk/src/main/java/com/bugsnag/android/Notifier.java index 0d6bc78a00..fd72e47925 100644 --- a/sdk/src/main/java/com/bugsnag/android/Notifier.java +++ b/sdk/src/main/java/com/bugsnag/android/Notifier.java @@ -10,7 +10,7 @@ public class Notifier implements JsonStream.Streamable { private static final String NOTIFIER_NAME = "Android Bugsnag Notifier"; - private static final String NOTIFIER_VERSION = "4.6.0"; + private static final String NOTIFIER_VERSION = "4.6.1"; private static final String NOTIFIER_URL = "https://bugsnag.com"; @NonNull