Skip to content

Commit

Permalink
Reworked how app start time tracking is handled. Added a overridable …
Browse files Browse the repository at this point in the history
…lifecycle observer mechanism. Changed APM foreground trigger from "resume" to "onStart"
  • Loading branch information
ArtursKadikis committed Jan 25, 2024
1 parent 4c12dba commit c03fb75
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 19 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ org.gradle.configureondemand=true
android.useAndroidX=true
android.enableJetifier=true
# RELEASE FIELD SECTION
VERSION_NAME=24.1.0-RC2
VERSION_NAME=24.1.0-RC3
GROUP=ly.count.android
POM_URL=https://github.com/Countly/countly-sdk-android
POM_SCM_URL=https://github.com/Countly/countly-sdk-android
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ public void testPrepareCommonRequest() {
break;
case "sdk_version":
if (a == 0) {
Assert.assertTrue(pair[1].equals("24.1.0-RC2"));
Assert.assertTrue(pair[1].equals("24.1.0-RC3"));
} else if (a == 1) {
Assert.assertTrue(pair[1].equals("123sdf.v-213"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void tearDown() {
}

@Test
public void customMetricFilter_invlidFields() {
public void customMetricFilter_invalidFields() {
Map<String, Integer> customMetrics = new HashMap<>();

mCountly.moduleAPM.removeReservedInvalidKeys(customMetrics);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package ly.count.android.sdk;

import android.app.Activity;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand All @@ -10,18 +12,100 @@

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

Activity act;
Activity act2;

CountlyConfig createAPMConfig() {
CountlyConfig config = (new CountlyConfig(getContext(), TestUtils.commonAppKey, TestUtils.commonURL)).setDeviceId(TestUtils.commonDeviceId).setLoggingEnabled(true).enableCrashReporting();
config.setRequiresConsent(true);
config.setConsentEnabled(new String[] { Countly.CountlyFeatureNames.apm, Countly.CountlyFeatureNames.location });
return config;
}

@Before
public void setUp() {
final CountlyStore countlyStore = new CountlyStore(getContext(), mock(ModuleLog.class));
countlyStore = new CountlyStore(getContext(), mock(ModuleLog.class));
countlyStore.clear();

act = mock(TestUtils.Activity2.class);
act2 = mock(TestUtils.Activity3.class);
}

@Test
public void AP_200_notEnabledNothingWorking() {
CountlyConfig config = createAPMConfig();
config.apm.appStartTimestampOverride = 10000L;

Assert.assertEquals(0, countlyStore.getRequests().length);

Countly countly = (new Countly()).init(config);

//enter foreground
countly.apm().setAppIsLoaded();
countly.onStart(act);

//go to background and back
countly.onStop();
countly.onStart(act2);

//there is only the consent request
String[] req = countlyStore.getRequests();
Assert.assertEquals(1, req.length);
Assert.assertTrue(TestUtils.getParamValueFromRequest(req[0], "consent").length() > 0);
}

@Test
public void AP_201_automaticAppStart() {
public void AP_201A_automaticAppStart() {
AP_201_automaticAppStart_base(false);
}

@Test
public void AP_201B_automaticAppStart() {
AP_201_automaticAppStart_base(true);
}

public void AP_201_automaticAppStart_base(boolean sdkInitsBeforeFirstScreen) {
CountlyConfig config = createAPMConfig();
config.apm.appStartTimestampOverride = 10000L;
config.apm.enableAppStartTimeTracking();

if (!sdkInitsBeforeFirstScreen) {
config.lifecycleObserver = () -> true;
}

Assert.assertEquals(0, countlyStore.getRequests().length);

Countly countly = (new Countly()).init(config);
String[] req;

if (sdkInitsBeforeFirstScreen) {
//enter foreground
req = countlyStore.getRequests();
Assert.assertEquals(1, req.length);
Assert.assertTrue(TestUtils.getParamValueFromRequest(req[0], "consent").length() > 0);
countly.onStart(act);
req = countlyStore.getRequests();
} else {
req = countlyStore.getRequests();
}

//there is only the consent request
Assert.assertEquals(2, req.length);
Assert.assertTrue(TestUtils.getParamValueFromRequest(req[0], "consent").length() > 0);
String apmContent = TestUtils.getParamValueFromRequest(req[1], "apm");
Assert.assertTrue(apmContent.startsWith("%7B%22type%22%3A%22device%22%2C%22name%22%3A%22app_start%22%2C+%22apm_metrics%22%3A%7B%22duration%22%3A+"));
Assert.assertTrue(apmContent.contains("%7D%2C+%22stz%22%3A+10000%2C+%22etz%22%3A"));

//go to background and back
countly.onStop();
countly.onStart(act2);

countly.apm().setAppIsLoaded();
countly.apm().setAppIsLoaded();

Assert.assertEquals(2, req.length);
}

@Test
Expand Down
18 changes: 15 additions & 3 deletions sdk/src/main/java/ly/count/android/sdk/Countly.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ of this software and associated documentation files (the "Software"), to deal
*/
public class Countly {

private final String DEFAULT_COUNTLY_SDK_VERSION_STRING = "24.1.0-RC2";
private final String DEFAULT_COUNTLY_SDK_VERSION_STRING = "24.1.0-RC3";

/**
* Used as request meta data on every request
Expand Down Expand Up @@ -104,6 +104,10 @@ public class Countly {
protected static String[] publicKeyPinCertificates;
protected static String[] certificatePinCertificates;

interface LifecycleObserver {
boolean LifeCycleAtleastStarted();
}

/**
* Enum used in Countly.initMessaging() method which controls what kind of
* app installation it is. Later (in Countly Dashboard or when calling Countly API method),
Expand Down Expand Up @@ -499,6 +503,14 @@ public synchronized Countly init(CountlyConfig config) {
};
}

if (config.lifecycleObserver == null) {
config.lifecycleObserver = new LifecycleObserver() {
@Override public boolean LifeCycleAtleastStarted() {
return lifecycleStateAtLeastStartedInternal();
}
};
}

if (config.metricProviderOverride != null) {
L.d("[Init] Custom metric provider was provided");
}
Expand Down Expand Up @@ -783,7 +795,7 @@ public void onLowMemory() {
L.d("[Countly] Global activity listeners not registred due to no Application class");
}

if (lifecycleStateAtLeastStarted()) {
if (config_.lifecycleObserver.LifeCycleAtleastStarted()) {
L.d("[Countly] SDK detects that the app is in the foreground. Increasing the activity counter.");
activityCount_++;
}
Expand Down Expand Up @@ -816,7 +828,7 @@ public boolean isInitialized() {
return sdkIsInitialised;
}

boolean lifecycleStateAtLeastStarted() {
boolean lifecycleStateAtLeastStartedInternal() {
return ProcessLifecycleOwner.get().getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED);
}

Expand Down
2 changes: 2 additions & 0 deletions sdk/src/main/java/ly/count/android/sdk/CountlyConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ public class CountlyConfig {

protected Map<String, Object> providedUserProperties = null;

protected Countly.LifecycleObserver lifecycleObserver = null;

//used to deliver this object to connection queue
//protected DeviceId deviceIdInstance = null;

Expand Down
28 changes: 19 additions & 9 deletions sdk/src/main/java/ly/count/android/sdk/ModuleAPM.java
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,12 @@ void halt() {
*/
@Override
void callbackOnActivityResumed(Activity activity) {
L.d("[Apm] Calling 'callbackOnActivityResumed', [" + activitiesOpen + "] -> [" + (activitiesOpen + 1) + "]");
L.d("[Apm] Calling 'callbackOnActivityResumed'");
}

@Override
void onActivityStarted(Activity activity, int updatedActivityCount) {
L.d("[Apm] Calling 'onActivityStarted', [" + activitiesOpen + "] -> [" + (activitiesOpen + 1) + "]");

long currentTimestamp = System.currentTimeMillis();

Expand Down Expand Up @@ -476,18 +481,23 @@ void onConsentChanged(@NonNull final List<String> consentChangeDelta, final bool

@Override
void initFinished(@NonNull CountlyConfig config) {
// we only do this adjustment if we track it automatically
if (trackForegroundBackground && !manualForegroundBackgroundTriggers && _cly.lifecycleStateAtLeastStarted()) {
if (_cly.config_.lifecycleObserver.LifeCycleAtleastStarted()) {
L.d("[ModuleAPM] SDK detects that the app is in the foreground. Increasing the activity counter.");

if (config.apm.trackAppStartTime && !config.apm.appLoadedManualTrigger) {
long currentTimestamp = System.currentTimeMillis();
recordAppStart(currentTimestamp);
}
activitiesOpen++;
}

calculateAppRunningTimes(activitiesOpen, activitiesOpen + 1);
// we only do this adjustment if we track it automatically
if (trackForegroundBackground && !manualForegroundBackgroundTriggers && _cly.config_.lifecycleObserver.LifeCycleAtleastStarted()) {
L.d("[ModuleAPM] SDK detects that the app is in the foreground. Starting to track foreground time");

activitiesOpen++;
calculateAppRunningTimes(activitiesOpen - 1, activitiesOpen);
}

if (config.apm.trackAppStartTime && !config.apm.appLoadedManualTrigger && _cly.config_.lifecycleObserver.LifeCycleAtleastStarted()) {
L.d("[ModuleAPM] SDK detects that the app is in the foreground. Recording automatic app start duration");
long currentTimestamp = System.currentTimeMillis();
recordAppStart(currentTimestamp);
}
}

Expand Down
4 changes: 2 additions & 2 deletions sdk/src/main/java/ly/count/android/sdk/ModuleSessions.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ void onConsentChanged(@NonNull final List<String> consentChangeDelta, final bool
if (consentChangeDelta.contains(Countly.CountlyFeatureNames.sessions)) {
if (newConsent) {
//if consent was just given and manual sessions sessions are not enabled, start a session if we are in the foreground
if (!manualSessionControlEnabled && _cly.lifecycleStateAtLeastStarted()) {
if (!manualSessionControlEnabled && _cly.config_.lifecycleObserver.LifeCycleAtleastStarted()) {
beginSessionInternal();
}
} else {
Expand All @@ -145,7 +145,7 @@ void onConsentChanged(@NonNull final List<String> consentChangeDelta, final bool

@Override
void initFinished(@NonNull CountlyConfig config) {
if (!manualSessionControlEnabled && _cly.lifecycleStateAtLeastStarted()) {
if (!manualSessionControlEnabled && _cly.config_.lifecycleObserver.LifeCycleAtleastStarted()) {
//start a session if we initialized in the foreground
beginSessionInternal();
}
Expand Down

0 comments on commit c03fb75

Please sign in to comment.