Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] enable/disable error handling server conf #419

Open
wants to merge 4 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## XX.XX.XX
* Added a new server configuration to manage uncaught crash reporting.
* Mitigated an issue where visibility could have been wrongly assigned if a view was closed while going to background. (Experimental!)

## 24.7.5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ public void setUp() {
@Override public boolean getTrackingEnabled() {
return true;
}

@Override public boolean getCrashReportingEnabled() {
return true;
}
};

Countly.sharedInstance().setLoggingEnabled(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.After;
Expand All @@ -10,20 +11,21 @@
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.mockito.Mockito.mock;

@RunWith(AndroidJUnit4.class)
public class ModuleConfigurationTests {
CountlyStore countlyStore;

@Before
public void setUp() {
countlyStore = new CountlyStore(TestUtils.getContext(), mock(ModuleLog.class));
countlyStore = TestUtils.getCountyStore();
countlyStore.clear();
Countly.sharedInstance().halt();
}

@After
public void tearDown() {
TestUtils.getCountyStore().clear();
Countly.sharedInstance().halt();
}

/**
Expand Down Expand Up @@ -64,7 +66,7 @@ public void init_enabled_storageEmpty() {
*/
@Test
public void init_enabled_storageAllowing() throws JSONException {
countlyStore.setServerConfig(getStorageString(true, true));
countlyStore.setServerConfig(getStorageString(true, true, true));
CountlyConfig config = TestUtils.createConfigurationConfig(true, null);
Countly countly = (new Countly()).init(config);

Expand All @@ -82,14 +84,15 @@ public void init_enabled_storageAllowing() throws JSONException {
*/
@Test
public void init_enabled_storageForbidding() throws JSONException {
countlyStore.setServerConfig(getStorageString(false, false));
countlyStore.setServerConfig(getStorageString(false, false, false));
CountlyConfig config = TestUtils.createConfigurationConfig(true, null);
Countly countly = (new Countly()).init(config);

Assert.assertTrue(countly.moduleConfiguration.serverConfigEnabled);
Assert.assertNotNull(countlyStore.getServerConfig());
Assert.assertFalse(countly.moduleConfiguration.getNetworkingEnabled());
Assert.assertFalse(countly.moduleConfiguration.getTrackingEnabled());
Assert.assertFalse(countly.moduleConfiguration.getCrashReportingEnabled());
}

/**
Expand All @@ -101,7 +104,7 @@ public void init_enabled_storageForbidding() throws JSONException {
*/
@Test
public void init_disabled_storageAllowing() throws JSONException {
countlyStore.setServerConfig(getStorageString(true, true));
countlyStore.setServerConfig(getStorageString(true, true, true));
CountlyConfig config = TestUtils.createConfigurationConfig(false, null);
Countly countly = Countly.sharedInstance().init(config);

Expand All @@ -119,7 +122,7 @@ public void init_disabled_storageAllowing() throws JSONException {
*/
@Test
public void init_disabled_storageForbidding() throws JSONException {
countlyStore.setServerConfig(getStorageString(false, false));
countlyStore.setServerConfig(getStorageString(false, false, false));
CountlyConfig config = TestUtils.createConfigurationConfig(false, null);
Countly countly = (new Countly()).init(config);

Expand Down Expand Up @@ -165,13 +168,14 @@ public void validatingTrackingConfig() throws JSONException {
Assert.assertEquals("", countlyStore.getRequestQueueRaw());
Assert.assertEquals(0, countlyStore.getEvents().length);

countlyStore.setServerConfig(getStorageString(false, false));
countlyStore.setServerConfig(getStorageString(false, false, false));

CountlyConfig config = TestUtils.createConfigurationConfig(true, null);
Countly countly = (new Countly()).init(config);

Assert.assertFalse(countly.moduleConfiguration.getNetworkingEnabled());
Assert.assertFalse(countly.moduleConfiguration.getTrackingEnabled());
Assert.assertFalse(countly.moduleConfiguration.getCrashReportingEnabled());

//try events
countly.events().recordEvent("d");
Expand All @@ -189,6 +193,64 @@ public void validatingTrackingConfig() throws JSONException {
Assert.assertEquals(0, countlyStore.getEvents().length);
}

/**
* Only disable crashes to try out unhandled crash reporting
* Make sure that call is called but no request is added to the RQ
* Call count to the unhandled crash reporting call should be 1 because countly SDK won't call and override the default handler
* And validate that no crash request is generated
*/
@Test
public void validatingCrashReportingConfig() throws JSONException {
AtomicInteger callCount = new AtomicInteger(0);
RuntimeException unhandledException = new RuntimeException("Simulated unhandled exception");
// Create a new thread to simulate unhandled exception
Thread threadThrows = new Thread(() -> {
// This will throw an unhandled exception in this thread
throw unhandledException;
});

Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
Assert.assertEquals(unhandledException, throwable);
Assert.assertEquals(threadThrows, thread);
callCount.incrementAndGet();
});

TestUtils.getCountyStore().setServerConfig(getStorageString(true, true, false));
CountlyConfig config = TestUtils.createBaseConfig();
config.enableServerConfiguration().setEventQueueSizeToSend(2);
config.crashes.enableCrashReporting(); // this call will enable unhandled crash reporting
Countly countly = new Countly().init(config);

Assert.assertTrue(countly.moduleConfiguration.getNetworkingEnabled());
Assert.assertTrue(countly.moduleConfiguration.getTrackingEnabled());
Assert.assertFalse(countly.moduleConfiguration.getCrashReportingEnabled());

// Start the thread and wait for it to terminate
threadThrows.start();
try {
threadThrows.join(); // Wait for thread to finish
} catch (InterruptedException ignored) {
}

//try events
countly.events().recordEvent("d");
countly.events().recordEvent("1");
Assert.assertEquals(1, callCount.get());

//try a non event recording
countly.crashes().recordHandledException(new Exception());

//try a direct request
countly.requestQueue().addDirectRequest(new HashMap<>());

countly.requestQueue().attemptToSendStoredRequests();

// There are two requests in total, but they are not containing unhandled exception
Assert.assertEquals(2, TestUtils.getCurrentRQ("Simulated unhandled exception").length);
Assert.assertNull(TestUtils.getCurrentRQ("Simulated unhandled exception")[0]);
Assert.assertNull(TestUtils.getCurrentRQ("Simulated unhandled exception")[1]);
}

/**
* Making sure that bad config responses are rejected
*/
Expand Down Expand Up @@ -239,6 +301,7 @@ Countly initAndValidateConfigParsingResult(String targetResponse, boolean respon
void assertConfigDefault(Countly countly) {
Assert.assertTrue(countly.moduleConfiguration.getNetworkingEnabled());
Assert.assertTrue(countly.moduleConfiguration.getTrackingEnabled());
Assert.assertTrue(countly.moduleConfiguration.getCrashReportingEnabled());
}

ImmediateRequestGenerator createIRGForSpecificResponse(final String targetResponse) {
Expand Down Expand Up @@ -267,12 +330,13 @@ ImmediateRequestGenerator createIRGForSpecificResponse(final String targetRespon
}

//creates the stringified storage object with all the required properties
String getStorageString(boolean tracking, boolean networking) throws JSONException {
String getStorageString(boolean tracking, boolean networking, boolean crashes) throws JSONException {
JSONObject jsonObject = new JSONObject();
JSONObject jsonObjectConfig = new JSONObject();

jsonObjectConfig.put("tracking", tracking);
jsonObjectConfig.put("networking", networking);
jsonObjectConfig.put("crashes", crashes);

jsonObject.put("v", 1);
jsonObject.put("t", 1_681_808_287_464L);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ interface ConfigurationProvider {
boolean getNetworkingEnabled();

boolean getTrackingEnabled();

boolean getCrashReportingEnabled();
}
25 changes: 23 additions & 2 deletions sdk/src/main/java/ly/count/android/sdk/ModuleConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class ModuleConfiguration extends ModuleBase implements ConfigurationProvider {
//config keys
final static String keyTracking = "tracking";
final static String keyNetworking = "networking";
final static String keyCrashReporting = "crashes";

//request keys
final static String keyRTimestamp = "t";
Expand All @@ -23,9 +24,11 @@ class ModuleConfiguration extends ModuleBase implements ConfigurationProvider {

final static boolean defaultVTracking = true;
final static boolean defaultVNetworking = true;
final static boolean defaultVCrashReporting = true;

boolean currentVTracking = true;
boolean currentVNetworking = true;
boolean currentVCrashReporting = true;
boolean configurationFetched = false;

ModuleConfiguration(@NonNull Countly cly, @NonNull CountlyConfig config) {
Expand Down Expand Up @@ -92,6 +95,7 @@ void updateConfigVariables() {
//set all to defaults
currentVNetworking = defaultVNetworking;
currentVTracking = defaultVTracking;
currentVCrashReporting = defaultVCrashReporting;

if (latestRetrievedConfiguration == null) {
//no config, don't continue
Expand All @@ -103,7 +107,7 @@ void updateConfigVariables() {
try {
currentVNetworking = latestRetrievedConfiguration.getBoolean(keyNetworking);
} catch (JSONException e) {
L.w("[ModuleConfiguration] updateConfigs, failed to load 'networking', " + e);
L.w("[ModuleConfiguration] updateConfigVariables, failed to load 'networking', " + e);
}
}

Expand All @@ -112,7 +116,16 @@ void updateConfigVariables() {
try {
currentVTracking = latestRetrievedConfiguration.getBoolean(keyTracking);
} catch (JSONException e) {
L.w("[ModuleConfiguration] updateConfigs, failed to load 'tracking', " + e);
L.w("[ModuleConfiguration] updateConfigVariables, failed to load 'tracking', " + e);
}
}

//tracking
if (latestRetrievedConfiguration.has(keyCrashReporting)) {
try {
currentVCrashReporting = latestRetrievedConfiguration.getBoolean(keyCrashReporting);
} catch (JSONException e) {
L.w("[ModuleConfiguration] updateConfigVariables, failed to load 'crash_reporting', " + e);
}
}
}
Expand Down Expand Up @@ -231,4 +244,12 @@ public boolean getTrackingEnabled() {
}
return currentVTracking;
}

@Override
public boolean getCrashReportingEnabled() {
if (!serverConfigEnabled) {
return defaultVCrashReporting;
}
return currentVCrashReporting;
}
}
4 changes: 3 additions & 1 deletion sdk/src/main/java/ly/count/android/sdk/ModuleCrash.java
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,10 @@ Countly addBreadcrumbInternal(@Nullable String breadcrumb) {
@Override
void initFinished(@NonNull CountlyConfig config) {
//enable unhandled crash reporting
if (config.crashes.enableUnhandledCrashReporting) {
if (config.crashes.enableUnhandledCrashReporting && (config.configProvider == null || config.configProvider.getCrashReportingEnabled())) {
enableCrashReporting();
} else if (config.crashes.enableUnhandledCrashReporting) {
L.w("[ModuleCrash] initFinished, Crash reporting is enabled in the configuration, but it is not enabled in the config provider");
}

//check for previous native crash dumps
Expand Down
Loading