Skip to content

Commit

Permalink
Merge pull request #420 from Countly/staging
Browse files Browse the repository at this point in the history
Staging
  • Loading branch information
arifBurakDemiray authored Nov 12, 2024
2 parents d6a4d71 + 31d9d8a commit 74cd211
Show file tree
Hide file tree
Showing 18 changed files with 301 additions and 90 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/build_and_test_sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ on:
branches:
- master
- staging

jobs:
setup:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -51,6 +50,9 @@ jobs:

- name: Connect to the Emulator
run: adb connect localhost:5555

- name: Set AAPT2 for android 35
run: echo "android.aapt2FromMavenOverride=/usr/local/lib/android/sdk/build-tools/35.0.0/aapt2" | tee -a ${{ github.workspace }}/gradle.properties

- name: Build the SDK
if: always()
Expand Down
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
## XX.XX.XX
* Mitigated an issue where visibility could have been wrongly assigned if a view was closed while going to background. (Experimental!)

## 24.7.5
* ! Minor breaking change ! All active views will now automatically stop when consent for "views" is revoked.

* The Android SDK now supports Android 15 (API level 35)
* The views will be stopped and restarted now while going to the background or foreground instead of resuming and pausing.

* Added further intent redirection vulnarability checks.
* Added new functions to ease the presenting the feedback widgets. Functions present the first matching feedback widget from the list.
* presentNPS(Context)
* presentNPS(Context, String)
* presentNPS(Context, String, FeedbackCallback)
* presentSurvey(Context)
* presentSurvey(Context, String)
* presentSurvey(Context, String, FeedbackCallback)
* presentRating(Context)
* presentRating(Context, String)
* presentRating(Context, String, FeedbackCallback)

* Mitigated an issue where content communication was done twice.
* Mititgated an issue where a segmentation key was removed if it included a list with an unsupported value instead of sanitizing.
* Mitigated a concurrency issue while restarting stopped views.
* Fixed an issue where SDK was not able to set experimental visibility flag correctly by adding additional foreground state capture.

## 24.7.4
* Disabled caching for webviews.
Expand Down
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.7.4
VERSION_NAME=24.7.5
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 @@ -807,8 +807,8 @@ public void recordEvent_visibilityTracking_onlyAddingItToViewsAndEvents() throws
countly.events().recordEvent(ModuleFeedback.RATING_EVENT_KEY, TestUtils.map());
validateEventInRQ(ModuleFeedback.RATING_EVENT_KEY, TestUtils.map(), 1, 0.0d, 0.0d, 4);

countly.events().recordEvent(ModuleViews.VIEW_EVENT_KEY, TestUtils.map());
validateEventInRQ(ModuleViews.VIEW_EVENT_KEY, TestUtils.map("cly_v", 0), 1, 0.0d, 0.0d, 5);
countly.events().recordEvent(ModuleViews.VIEW_EVENT_KEY, TestUtils.map("visit", 1));
validateEventInRQ(ModuleViews.VIEW_EVENT_KEY, TestUtils.map("cly_v", 0, "visit", 1), 1, 0.0d, 0.0d, 5);

countly.events().recordEvent(ModuleViews.ORIENTATION_EVENT_KEY, TestUtils.map());
validateEventInRQ(ModuleViews.ORIENTATION_EVENT_KEY, TestUtils.map(), 1, 0.0d, 0.0d, 6);
Expand All @@ -834,16 +834,16 @@ public void recordEvent_visibilityTracking_bgFgSwitch() throws JSONException {

countly.onStart(Mockito.mock(Activity.class)); //foreground

countly.events().recordEvent(ModuleViews.VIEW_EVENT_KEY, TestUtils.map());
validateEventInRQ(ModuleViews.VIEW_EVENT_KEY, TestUtils.map("cly_v", 1), 1, 0.0d, 0.0d, 2);
countly.events().recordEvent(ModuleViews.VIEW_EVENT_KEY, TestUtils.map("visit", 1));
validateEventInRQ(ModuleViews.VIEW_EVENT_KEY, TestUtils.map("cly_v", 1, "visit", 1), 1, 0.0d, 0.0d, 2);

countly.events().recordEvent("fg", TestUtils.map());
validateEventInRQ("fg", TestUtils.map("cly_v", 1), 1, 0.0d, 0.0d, 3);

countly.onStop(); //background

countly.events().recordEvent(ModuleViews.VIEW_EVENT_KEY, TestUtils.map());
validateEventInRQ(ModuleViews.VIEW_EVENT_KEY, TestUtils.map("cly_v", 0), 1, 0.0d, 0.0d, 5);
countly.events().recordEvent(ModuleViews.VIEW_EVENT_KEY, TestUtils.map("visit", 1));
validateEventInRQ(ModuleViews.VIEW_EVENT_KEY, TestUtils.map("cly_v", 0, "visit", 1), 1, 0.0d, 0.0d, 5);

countly.events().recordEvent("bg", TestUtils.map());
validateEventInRQ("bg", TestUtils.map("cly_v", 0), 1, 0.0d, 0.0d, 6);
Expand All @@ -864,16 +864,16 @@ public void recordEvent_visibilityTracking_notEnabled() throws JSONException {

countly.onStart(Mockito.mock(Activity.class)); //foreground

countly.events().recordEvent(ModuleViews.VIEW_EVENT_KEY, TestUtils.map());
validateEventInRQ(ModuleViews.VIEW_EVENT_KEY, TestUtils.map(), 1, 0.0d, 0.0d, 2);
countly.events().recordEvent(ModuleViews.VIEW_EVENT_KEY, TestUtils.map("visit", 1));
validateEventInRQ(ModuleViews.VIEW_EVENT_KEY, TestUtils.map("visit", 1), 1, 0.0d, 0.0d, 2);

countly.events().recordEvent("fg", TestUtils.map());
validateEventInRQ("fg", TestUtils.map(), 1, 0.0d, 0.0d, 3);

countly.onStop(); //background

countly.events().recordEvent(ModuleViews.VIEW_EVENT_KEY, TestUtils.map());
validateEventInRQ(ModuleViews.VIEW_EVENT_KEY, TestUtils.map(), 1, 0.0d, 0.0d, 5);
countly.events().recordEvent(ModuleViews.VIEW_EVENT_KEY, TestUtils.map("visit", 1));
validateEventInRQ(ModuleViews.VIEW_EVENT_KEY, TestUtils.map("visit", 1), 1, 0.0d, 0.0d, 5);

countly.events().recordEvent("bg", TestUtils.map());
validateEventInRQ("bg", TestUtils.map(), 1, 0.0d, 0.0d, 6);
Expand Down
108 changes: 107 additions & 1 deletion sdk/src/androidTest/java/ly/count/android/sdk/ModuleViewsTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -1855,6 +1855,113 @@ public void recordView_previousViewName() throws JSONException {
validateView("test2", 0.0, 1, 2, false, true, TestUtils.map(), "_CLY_", "_CLY_", "test");
}

/**
* "startView" with consent removal
* Validate that all running views are stopped when the view consent is removed
*
* @throws JSONException if the JSON is not valid
*/
@Test
public void startView_consentRemoval() throws JSONException {
CountlyConfig countlyConfig = TestUtils.createBaseConfig();
countlyConfig.setRequiresConsent(true);
countlyConfig.setLoggingEnabled(true);
countlyConfig.giveAllConsents();
countlyConfig.setEventQueueSizeToSend(1);

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

countly.views().startView("test");
ModuleConsentTests.validateAllConsentRequest(TestUtils.commonDeviceId, 0);
validateView("test", 0.0, 1, 2, true, true, TestUtils.map(), "_CLY_", "_CLY_", null);

countly.views().startView("test2");
validateView("test2", 0.0, 2, 3, false, true, TestUtils.map(), "_CLY_", "_CLY_", null);

countly.consent().removeConsent(new String[] { Countly.CountlyFeatureNames.views });
validateView("test", 0.0, 3, 6, false, false, TestUtils.map(), "_CLY_", "_CLY_", null);
validateView("test2", 0.0, 4, 6, false, false, TestUtils.map(), "_CLY_", "_CLY_", null);
ModuleConsentTests.validateConsentRequest(TestUtils.commonDeviceId, 5, new boolean[] { true, true, true, true, true, true, true, true, true, true, true, true, false, true, true });

countly.consent().giveConsent(new String[] { Countly.CountlyFeatureNames.views });
ModuleConsentTests.validateAllConsentRequest(TestUtils.commonDeviceId, 6);
Assert.assertEquals(7, TestUtils.getCurrentRQ().length);
}

/**
* "startAutoStoppedView" with consent removal
* Validate that running view is stopped when the view consent is removed
*
* @throws JSONException if the JSON is not valid
*/
@Test
public void startAutoStoppedView_consentRemoval() throws JSONException {
CountlyConfig countlyConfig = TestUtils.createBaseConfig();
countlyConfig.setRequiresConsent(true);
countlyConfig.setLoggingEnabled(true);
countlyConfig.giveAllConsents();
countlyConfig.setEventQueueSizeToSend(1);

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

countly.views().startAutoStoppedView("test");
ModuleConsentTests.validateAllConsentRequest(TestUtils.commonDeviceId, 0);
validateView("test", 0.0, 1, 2, true, true, TestUtils.map(), "_CLY_", "_CLY_", null);

countly.views().startAutoStoppedView("test2");
validateView("test", 0.0, 2, 4, false, false, TestUtils.map(), "_CLY_", "_CLY_", null);
validateView("test2", 0.0, 3, 4, false, true, TestUtils.map(), "_CLY_", "_CLY_", null);

countly.consent().removeConsent(new String[] { Countly.CountlyFeatureNames.views });
validateView("test2", 0.0, 4, 6, false, false, TestUtils.map(), "_CLY_", "_CLY_", null);
ModuleConsentTests.validateConsentRequest(TestUtils.commonDeviceId, 5, new boolean[] { true, true, true, true, true, true, true, true, true, true, true, true, false, true, true });

countly.consent().giveConsent(new String[] { Countly.CountlyFeatureNames.views });
ModuleConsentTests.validateAllConsentRequest(TestUtils.commonDeviceId, 6);
Assert.assertEquals(7, TestUtils.getCurrentRQ().length);
}

/**
* Auto view tracking with consent removal
* Validate that running view is stopped when the view consent is removed
*
* @throws JSONException if the JSON is not valid
*/
@Test
public void autoViewTracking_consentRemoval() throws JSONException {
CountlyConfig countlyConfig = TestUtils.createBaseConfig(TestUtils.getContext());
countlyConfig.setRequiresConsent(true);
countlyConfig.setLoggingEnabled(true);
countlyConfig.enableAutomaticViewTracking();
countlyConfig.giveAllConsents();
countlyConfig.setEventQueueSizeToSend(1);

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

Activity activity = Mockito.mock(Activity.class);
countly.onStart(activity);

ModuleConsentTests.validateAllConsentRequest(TestUtils.commonDeviceId, 0);
ModuleSessionsTests.validateSessionBeginRequest(1, TestUtils.commonDeviceId);
ModuleEventsTests.validateEventInRQ("[CLY]_orientation", TestUtils.map("mode", "portrait"), 1, 0, 0, 2, 4);

validateView(activity.getClass().getName(), 0.0, 3, 4, true, true, TestUtils.map(), "_CLY_", "_CLY_", null);

Activity activity2 = Mockito.mock(Activity.class);
countly.onStart(activity2);
validateView(activity.getClass().getName(), 0.0, 4, 6, false, false, TestUtils.map(), "_CLY_", "_CLY_", null);
validateView(activity2.getClass().getName(), 0.0, 5, 6, false, true, TestUtils.map(), "_CLY_", "_CLY_", null);

countly.consent().removeConsent(new String[] { Countly.CountlyFeatureNames.views });
validateView(activity2.getClass().getName(), 0.0, 6, 8, false, false, TestUtils.map(), "_CLY_", "_CLY_", null);
ModuleConsentTests.validateConsentRequest(TestUtils.commonDeviceId, 7, new boolean[] { true, true, true, true, true, true, true, true, true, true, true, true, false, true, true });

countly.consent().giveConsent(new String[] { Countly.CountlyFeatureNames.views });

ModuleConsentTests.validateAllConsentRequest(TestUtils.commonDeviceId, 8);
Assert.assertEquals(9, TestUtils.getCurrentRQ().length);
}

static void validateView(String viewName, Double viewDuration, int idx, int size, boolean start, boolean visit, Map<String, Object> customSegmentation, String id, String pvid) throws JSONException {
validateView(viewName, viewDuration, idx, size, start, visit, customSegmentation, id, pvid, null);
}
Expand All @@ -1877,6 +1984,5 @@ static void validateView(String viewName, Double viewDuration, int idx, int size

ModuleEventsTests.validateEventInRQ(TestUtils.commonDeviceId, ModuleViews.VIEW_EVENT_KEY, viewSegmentation, 1, 0.0, viewDuration, id, pvid, "_CLY_", "_CLY_", idx, size, 0, 1);
}

//todo extract orientation tests
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class TestUtils {
public final static String commonAppKey = "appkey";
public final static String commonDeviceId = "1234";
public final static String SDK_NAME = "java-native-android";
public final static String SDK_VERSION = "24.7.4";
public final static String SDK_VERSION = "24.7.5";
public static final int MAX_THREAD_COUNT_PER_STACK_TRACE = 50;

public static class Activity2 extends Activity {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,13 +352,16 @@ public void removeUnsupportedDataTypes_lists() {

Assert.assertTrue(UtilsInternalLimits.removeUnsupportedDataTypes(segmentation, Mockito.mock(ModuleLog.class)));

Assert.assertEquals(6, segmentation.size());
Assert.assertEquals(9, segmentation.size());
Assert.assertEquals(aa1, segmentation.get("aa1"));
Assert.assertEquals(aa2, segmentation.get("aa2"));
Assert.assertEquals(aa3, segmentation.get("aa3"));
Assert.assertEquals(aa4, segmentation.get("aa4"));
Assert.assertEquals(aa5, segmentation.get("aa5"));
Assert.assertEquals(aa6, segmentation.get("aa6"));
Assert.assertEquals(Arrays.asList(1, 2, "ABC", true, 3.3d, 4.4f, 5L), segmentation.get("aa7"));
Assert.assertEquals(new ArrayList<>(), segmentation.get("aa8"));
Assert.assertEquals(new ArrayList<>(), segmentation.get("aa9"));
}

@Test
Expand Down Expand Up @@ -425,7 +428,7 @@ public void removeUnsupportedDataTypes_jsonArray() {
Assert.assertEquals(9, segmentation.size());

Assert.assertTrue(UtilsInternalLimits.removeUnsupportedDataTypes(segmentation, Mockito.mock(ModuleLog.class)));
Assert.assertEquals(8, segmentation.size());
Assert.assertEquals(9, segmentation.size());
Assert.assertEquals(empty, segmentation.get("empty"));
Assert.assertEquals(arrInt, segmentation.get("arrInt"));
Assert.assertEquals(arrStr, segmentation.get("arrStr"));
Expand All @@ -434,6 +437,7 @@ public void removeUnsupportedDataTypes_jsonArray() {
Assert.assertEquals(arrFloat, segmentation.get("arrFloat"));
Assert.assertEquals(arrLong, segmentation.get("arrLong"));
Assert.assertEquals(arrObj, segmentation.get("arrObj"));
Assert.assertEquals(new JSONArray(), segmentation.get("arrObjUltra"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ public void SE_206_CR_CNG_A_id_change() throws InterruptedException {

flowAutomaticSessions(countly, new TestLifecycleObserver());

Assert.assertEquals(5, TestUtils.getCurrentRQ().length);
Assert.assertEquals(6, TestUtils.getCurrentRQ().length);
validateSessionConsentRequest(0, false, TestUtils.commonDeviceId);
validateRequest(TestUtils.map("location", ""), 1);
TestUtils.validateRequest("newID", TestUtils.map("old_device_id", TestUtils.commonDeviceId), 2);
Expand Down
5 changes: 3 additions & 2 deletions sdk/src/main/java/ly/count/android/sdk/Countly.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ of this software and associated documentation files (the "Software"), to deal
*/
public class Countly {

private final String DEFAULT_COUNTLY_SDK_VERSION_STRING = "24.7.4";
private final String DEFAULT_COUNTLY_SDK_VERSION_STRING = "24.7.5";

/**
* Used as request meta data on every request
Expand Down Expand Up @@ -792,8 +792,9 @@ public void onLowMemory() {
}

if (config_.lifecycleObserver.LifeCycleAtleastStarted()) {
L.d("[Countly] SDK detects that the app is in the foreground. Increasing the activity counter.");
L.d("[Countly] SDK detects that the app is in the foreground. Increasing the activity counter and setting the foreground state.");
activityCount_++;
config.deviceInfo.inForeground();
}

L.i("[Init] About to call module 'initFinished'");
Expand Down
31 changes: 10 additions & 21 deletions sdk/src/main/java/ly/count/android/sdk/CountlyWebViewClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import android.util.Log;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -19,6 +19,15 @@ public CountlyWebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
Log.v(Countly.TAG, "[CountlyWebViewClient] shouldOverrideUrlLoading, url: [" + url + "]");
try {
url = URLDecoder.decode(url, "UTF-8");
} catch (Exception e) {
Log.e(Countly.TAG, "[CountlyWebViewClient] shouldOverrideUrlLoading, Failed to decode url", e);
return false;
}

Log.d(Countly.TAG, "[CountlyWebViewClient] shouldOverrideUrlLoading, urlDecoded: [" + url + "]");

for (WebViewUrlListener listener : listeners) {
if (listener.onUrl(url, view)) {
Expand All @@ -29,26 +38,6 @@ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request
return false;
}

@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) { return null ; }

@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
Log.d(Countly.TAG, "[WebClient] Intercepted request URL: [" + url + "]");

// Call listeners for specific actions
for (WebViewUrlListener listener : listeners) {
boolean handled = listener.onUrl(url, view);
if (handled) {
Log.d(Countly.TAG, "Request handled by listener: " + url);
break;
}
}

return super.shouldInterceptRequest(view, request);
}

public void registerWebViewUrlListeners(List<WebViewUrlListener> listener) {
this.listeners.addAll(listener);
}
Expand Down
3 changes: 3 additions & 0 deletions sdk/src/main/java/ly/count/android/sdk/ModuleBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ void deviceIdChanged(boolean withoutMerge) {
void onConsentChanged(@NonNull final List<String> consentChangeDelta, final boolean newConsent, @NonNull final ModuleConsent.ConsentChangeSource changeSource) {
}

void consentWillChange(@NonNull List<String> consentThatWillChange, final boolean isConsentGiven) {
}

//notify the SDK modules that internal configuration was updated
void sdkConfigurationChanged() {

Expand Down
Loading

0 comments on commit 74cd211

Please sign in to comment.