Skip to content

Commit

Permalink
v5.19.0
Browse files Browse the repository at this point in the history
  • Loading branch information
fractalwrench authored Jan 12, 2022
2 parents 2fd4ec6 + 4527d26 commit 28daf75
Show file tree
Hide file tree
Showing 130 changed files with 6,451 additions and 1,920 deletions.
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
# Changelog

## 5.19.0 (2022-01-12)

* New APIs to support forthcoming feature flag and experiment functionality. For more information, please see https://docs.bugsnag.com/product/features-experiments.

### Enhancements

* Explicitly define Kotlin api/language versions
[#1564](https://github.com/bugsnag/bugsnag-android/pull/1564)

* Build project with Kotlin 1.4, maintain compat with Kotlin 1.3
[#1565](https://github.com/bugsnag/bugsnag-android/pull/1565)

## 5.18.0 (2022-01-05)

### Enhancements

* Improve the memory use and performance overhead when handling the delivery response status codes
[#1558](https://github.com/bugsnag/bugsnag-android/pull/1558)

* Harden ndk layer through use of const keyword
[#1566](https://github.com/bugsnag/bugsnag-android/pull/1566)

Expand Down
4 changes: 3 additions & 1 deletion bugsnag-android-core/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<ID>LongParameterList:DeviceDataCollector.kt$DeviceDataCollector$( private val connectivity: Connectivity, private val appContext: Context, resources: Resources, private val deviceId: String?, private val buildInfo: DeviceBuildInfo, private val dataDirectory: File, rootDetector: RootDetector, private val bgTaskService: BackgroundTaskService, private val logger: Logger )</ID>
<ID>LongParameterList:DeviceWithState.kt$DeviceWithState$( buildInfo: DeviceBuildInfo, jailbroken: Boolean?, id: String?, locale: String?, totalMemory: Long?, runtimeVersions: MutableMap&lt;String, Any>, /** * The number of free bytes of storage available on the device */ var freeDisk: Long?, /** * The number of free bytes of memory available on the device */ var freeMemory: Long?, /** * The orientation of the device when the event occurred: either portrait or landscape */ var orientation: String?, /** * The timestamp on the device when the event occurred */ var time: Date? )</ID>
<ID>LongParameterList:EventFilenameInfo.kt$EventFilenameInfo.Companion$( obj: Any, uuid: String = UUID.randomUUID().toString(), apiKey: String?, timestamp: Long = System.currentTimeMillis(), config: ImmutableConfig, isLaunching: Boolean? = null )</ID>
<ID>LongParameterList:EventInternal.kt$EventInternal$( apiKey: String, breadcrumbs: MutableList&lt;Breadcrumb> = mutableListOf(), discardClasses: Set&lt;String> = setOf(), errors: MutableList&lt;Error> = mutableListOf(), metadata: Metadata = Metadata(), originalError: Throwable? = null, projectPackages: Collection&lt;String> = setOf(), severityReason: SeverityReason = SeverityReason.newInstance(SeverityReason.REASON_HANDLED_EXCEPTION), threads: MutableList&lt;Thread> = mutableListOf(), user: User = User(), redactionKeys: Set&lt;String>? = null )</ID>
<ID>LongParameterList:EventInternal.kt$EventInternal$( apiKey: String, breadcrumbs: MutableList&lt;Breadcrumb> = mutableListOf(), discardClasses: Set&lt;String> = setOf(), errors: MutableList&lt;Error> = mutableListOf(), metadata: Metadata = Metadata(), featureFlags: FeatureFlags = FeatureFlags(), originalError: Throwable? = null, projectPackages: Collection&lt;String> = setOf(), severityReason: SeverityReason = SeverityReason.newInstance(SeverityReason.REASON_HANDLED_EXCEPTION), threads: MutableList&lt;Thread> = mutableListOf(), user: User = User(), redactionKeys: Set&lt;String>? = null )</ID>
<ID>LongParameterList:EventStorageModule.kt$EventStorageModule$( contextModule: ContextModule, configModule: ConfigModule, dataCollectionModule: DataCollectionModule, bgTaskService: BackgroundTaskService, trackerModule: TrackerModule, systemServiceModule: SystemServiceModule, notifier: Notifier, callbackState: CallbackState )</ID>
<ID>LongParameterList:NativeStackframe.kt$NativeStackframe$( /** * The name of the method that was being executed */ var method: String?, /** * The location of the source file */ var file: String?, /** * The line number within the source file this stackframe refers to */ var lineNumber: Number?, /** * The address of the instruction where the event occurred. */ var frameAddress: Long?, /** * The address of the function where the event occurred. */ var symbolAddress: Long?, /** * The address of the library where the event occurred. */ var loadAddress: Long?, /** * Whether this frame identifies the program counter */ var isPC: Boolean?, /** * The type of the error */ var type: ErrorType? = null )</ID>
<ID>LongParameterList:StateEvent.kt$StateEvent.Install$( @JvmField val apiKey: String, @JvmField val autoDetectNdkCrashes: Boolean, @JvmField val appVersion: String?, @JvmField val buildUuid: String?, @JvmField val releaseStage: String?, @JvmField val lastRunInfoPath: String, @JvmField val consecutiveLaunchCrashes: Int, @JvmField val sendThreads: ThreadSendPolicy )</ID>
Expand All @@ -39,6 +39,8 @@
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$catch (exception: Exception) { logger.w("Could not get locationStatus") }</ID>
<ID>SwallowedException:DeviceIdStore.kt$DeviceIdStore$catch (exc: OverlappingFileLockException) { Thread.sleep(FILE_LOCK_WAIT_MS) }</ID>
<ID>SwallowedException:PluginClient.kt$PluginClient$catch (exc: ClassNotFoundException) { logger.d("Plugin '$clz' is not on the classpath - functionality will not be enabled.") null }</ID>
<ID>TooManyFunctions:ConfigInternal.kt$ConfigInternal : CallbackAwareMetadataAwareUserAwareFeatureFlagAware</ID>
<ID>TooManyFunctions:EventInternal.kt$EventInternal : FeatureFlagAwareStreamableMetadataAwareUserAware</ID>
<ID>UnnecessaryAbstractClass:DependencyModule.kt$DependencyModule</ID>
<ID>UnusedPrivateMember:ThreadStateTest.kt$ThreadStateTest$private val configuration = generateImmutableConfig()</ID>
</CurrentIssues>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnitRunner

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;

Expand Down Expand Up @@ -132,4 +133,13 @@ public static AppWithState generateAppWithState() {
public static App generateApp() {
return new App(generateImmutableConfig(), null, null, null, null, null);
}

static Comparator<FeatureFlag> featureFlagComparator() {
return new Comparator<FeatureFlag>() {
@Override
public int compare(FeatureFlag f1, FeatureFlag f2) {
return f1.getName().compareTo(f2.getName());
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ public void testMetadataCloned() {
@Test
public void testClientMultiNotify() throws InterruptedException {
// concurrently call notify()
client = BugsnagTestUtils.generateClient();
client = BugsnagTestUtils.generateClient();
Executor executor = Executors.newSingleThreadExecutor();
int count = 200;
final CountDownLatch latch = new CountDownLatch(count);
Expand All @@ -257,4 +257,31 @@ public void run() {
// wait for all events to be delivered
assertTrue(latch.await(5, TimeUnit.SECONDS));
}

@Test
public void testFeatureFlagsCloned() {
config.addFeatureFlag("sample_group", "a");
client = new Client(context, config);
client.addFeatureFlag("demo_mode");

assertNotSame(config.impl.featureFlagState, client.featureFlagState);

FeatureFlagState configFlags = config.impl.featureFlagState;
FeatureFlagState clientFlags = client.featureFlagState;
assertNotSame(configFlags, clientFlags);

List<FeatureFlag> configExpected = Collections.singletonList(
new FeatureFlag("sample_group", "a"));
assertEquals(configExpected, config.impl.featureFlagState.toList());

List<FeatureFlag> clientExpected = Arrays.asList(
new FeatureFlag("demo_mode"),
new FeatureFlag("sample_group", "a")
);

List<FeatureFlag> clientActual = client.featureFlagState.toList();
Collections.sort(clientActual, BugsnagTestUtils.featureFlagComparator());

assertEquals(clientExpected, clientActual);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -404,6 +403,59 @@ public static void markLaunchCompleted() {
getClient().markLaunchCompleted();
}

/**
* Add a single feature flag with no variant. If there is an existing feature flag with the
* same name, it will be overwritten to have no variant.
*
* @param name the name of the feature flag to add
* @see #addFeatureFlag(String, String)
*/
public static void addFeatureFlag(@NonNull String name) {
getClient().addFeatureFlag(name);
}

/**
* Add a single feature flag with an optional variant. If there is an existing feature
* flag with the same name, it will be overwritten with the new variant. If the variant is
* {@code null} this method has the same behaviour as {@link #addFeatureFlag(String)}.
*
* @param name the name of the feature flag to add
* @param variant the variant to set the feature flag to, or {@code null} to specify a feature
* flag with no variant
*/
public static void addFeatureFlag(@NonNull String name, @Nullable String variant) {
getClient().addFeatureFlag(name, variant);
}

/**
* Add a collection of feature flags. This method behaves exactly the same as calling
* {@link #addFeatureFlag(String, String)} for each of the {@code FeatureFlag} objects.
*
* @param featureFlags the feature flags to add
* @see #addFeatureFlag(String, String)
*/
public static void addFeatureFlags(@NonNull Iterable<FeatureFlag> featureFlags) {
getClient().addFeatureFlags(featureFlags);
}

/**
* Remove a single feature flag regardless of its current status. This will stop the specified
* feature flag from being reported. If the named feature flag does not exist this will
* have no effect.
*
* @param name the name of the feature flag to remove
*/
public static void clearFeatureFlag(@NonNull String name) {
getClient().clearFeatureFlag(name);
}

/**
* Clear all of the feature flags. This will stop all feature flags from being reported.
*/
public static void clearFeatureFlags() {
getClient().clearFeatureFlags();
}

/**
* Get the current Bugsnag Client instance.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ internal class BugsnagStateModule(

val metadataState = copyMetadataState(configuration)

val featureFlagState = configuration.impl.featureFlagState.copy()

private fun copyMetadataState(configuration: Configuration): MetadataState {
// performs deep copy of metadata to preserve immutability of Configuration interface
val orig = configuration.impl.metadataState.metadata
Expand Down
76 changes: 73 additions & 3 deletions bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@
* @see Bugsnag
*/
@SuppressWarnings({"checkstyle:JavadocTagContinuationIndentation", "ConstantConditions"})
public class Client implements MetadataAware, CallbackAware, UserAware {
public class Client implements MetadataAware, CallbackAware, UserAware, FeatureFlagAware {

final ImmutableConfig immutableConfig;

final MetadataState metadataState;
final FeatureFlagState featureFlagState;

private final ContextState contextState;
private final CallbackState callbackState;
Expand Down Expand Up @@ -153,6 +154,7 @@ public Unit invoke(Boolean hasConnection, String networkState) {
breadcrumbState = bugsnagStateModule.getBreadcrumbState();
contextState = bugsnagStateModule.getContextState();
metadataState = bugsnagStateModule.getMetadataState();
featureFlagState = bugsnagStateModule.getFeatureFlagState();

// lookup system services
final SystemServiceModule systemServiceModule = new SystemServiceModule(contextModule);
Expand Down Expand Up @@ -224,6 +226,7 @@ public Unit invoke(Boolean hasConnection, String networkState) {
ContextState contextState,
CallbackState callbackState,
UserState userState,
FeatureFlagState featureFlagState,
ClientObservable clientObservable,
Context appContext,
@NonNull DeviceDataCollector deviceDataCollector,
Expand All @@ -245,6 +248,7 @@ public Unit invoke(Boolean hasConnection, String networkState) {
this.contextState = contextState;
this.callbackState = callbackState;
this.userState = userState;
this.featureFlagState = featureFlagState;
this.clientObservable = clientObservable;
this.appContext = appContext;
this.deviceDataCollector = deviceDataCollector;
Expand Down Expand Up @@ -404,6 +408,7 @@ void addObserver(StateObserver observer) {
deliveryDelegate.addObserver(observer);
launchCrashTracker.addObserver(observer);
memoryTrimState.addObserver(observer);
featureFlagState.addObserver(observer);
}

void removeObserver(StateObserver observer) {
Expand All @@ -416,6 +421,7 @@ void removeObserver(StateObserver observer) {
deliveryDelegate.removeObserver(observer);
launchCrashTracker.removeObserver(observer);
memoryTrimState.removeObserver(observer);
featureFlagState.removeObserver(observer);
}

/**
Expand All @@ -426,6 +432,7 @@ void syncInitialState() {
contextState.emitObservableEvent();
userState.emitObservableEvent();
memoryTrimState.emitObservableEvent();
featureFlagState.emitObservableEvent();
}

/**
Expand Down Expand Up @@ -679,7 +686,9 @@ public void notify(@NonNull Throwable exc, @Nullable OnErrorCallback onError) {
}
SeverityReason severityReason = SeverityReason.newInstance(REASON_HANDLED_EXCEPTION);
Metadata metadata = metadataState.getMetadata();
Event event = new Event(exc, immutableConfig, severityReason, metadata, logger);
FeatureFlags featureFlags = featureFlagState.getFeatureFlags();
Event event = new Event(exc, immutableConfig, severityReason, metadata, featureFlags,
logger);
populateAndNotifyAndroidEvent(event, onError);
} else {
logNull("notify");
Expand All @@ -697,7 +706,8 @@ void notifyUnhandledException(@NonNull Throwable exc, Metadata metadata,
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);
Event event = new Event(exc, immutableConfig, handledState,
data, featureFlagState.getFeatureFlags(), logger);
populateAndNotifyAndroidEvent(event, null);

// persist LastRunInfo so that on relaunch users can check the app crashed
Expand Down Expand Up @@ -945,6 +955,62 @@ private void leaveErrorBreadcrumb(@NonNull Event event) {
}
}

/**
* {@inheritDoc}
*/
@Override
public void addFeatureFlag(@NonNull String name) {
if (name != null) {
featureFlagState.addFeatureFlag(name);
} else {
logNull("addFeatureFlag");
}
}

/**
* {@inheritDoc}
*/
@Override
public void addFeatureFlag(@NonNull String name, @Nullable String variant) {
if (name != null) {
featureFlagState.addFeatureFlag(name, variant);
} else {
logNull("addFeatureFlag");
}
}

/**
* {@inheritDoc}
*/
@Override
public void addFeatureFlags(@NonNull Iterable<FeatureFlag> featureFlags) {
if (featureFlags != null) {
featureFlagState.addFeatureFlags(featureFlags);
} else {
logNull("addFeatureFlags");
}
}

/**
* {@inheritDoc}
*/
@Override
public void clearFeatureFlag(@NonNull String name) {
if (name != null) {
featureFlagState.clearFeatureFlag(name);
} else {
logNull("clearFeatureFlag");
}
}

/**
* {@inheritDoc}
*/
@Override
public void clearFeatureFlags() {
featureFlagState.clearFeatureFlags();
}

/**
* Retrieves information about the last launch of the application, if it has been run before.
*
Expand Down Expand Up @@ -1069,6 +1135,10 @@ MetadataState getMetadataState() {
return metadataState;
}

FeatureFlagState getFeatureFlagState() {
return featureFlagState;
}

ContextState getContextState() {
return contextState;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package com.bugsnag.android
import android.content.Context
import java.io.File

internal class ConfigInternal(var apiKey: String) : CallbackAware, MetadataAware, UserAware {
internal class ConfigInternal(
var apiKey: String
) : CallbackAware, MetadataAware, UserAware, FeatureFlagAware {

private var user = User()

Expand All @@ -13,6 +15,9 @@ internal class ConfigInternal(var apiKey: String) : CallbackAware, MetadataAware
@JvmField
internal val metadataState: MetadataState = MetadataState()

@JvmField
internal val featureFlagState: FeatureFlagState = FeatureFlagState()

var appVersion: String? = null
var versionCode: Int? = 0
var releaseStage: String? = null
Expand Down Expand Up @@ -73,6 +78,14 @@ internal class ConfigInternal(var apiKey: String) : CallbackAware, MetadataAware
override fun getMetadata(section: String) = metadataState.getMetadata(section)
override fun getMetadata(section: String, key: String) = metadataState.getMetadata(section, key)

override fun addFeatureFlag(name: String) = featureFlagState.addFeatureFlag(name)
override fun addFeatureFlag(name: String, variant: String?) =
featureFlagState.addFeatureFlag(name, variant)
override fun addFeatureFlags(featureFlags: Iterable<FeatureFlag>) =
featureFlagState.addFeatureFlags(featureFlags)
override fun clearFeatureFlag(name: String) = featureFlagState.clearFeatureFlag(name)
override fun clearFeatureFlags() = featureFlagState.clearFeatureFlags()

override fun getUser(): User = user
override fun setUser(id: String?, email: String?, name: String?) {
user = User(id, email, name)
Expand Down
Loading

0 comments on commit 28daf75

Please sign in to comment.