diff --git a/android-sdk/build.gradle b/android-sdk/build.gradle
index fb0ae30b..b34b55ef 100644
--- a/android-sdk/build.gradle
+++ b/android-sdk/build.gradle
@@ -64,6 +64,12 @@ dependencies {
// Test
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:4.6.1'
+ testImplementation("androidx.test:core:1.5.0")
+ testImplementation("androidx.arch.core:core-testing:2.2.0")
+ testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
+ testImplementation("com.google.truth:truth:1.1.4")
+ testImplementation("io.mockk:mockk:1.13.5")
+ testImplementation("org.json:json:20140107")
}
tasks.register('androidJavadoc', Javadoc) {
diff --git a/android-sdk/src/androidTest/java/com/blueshift/ExampleInstrumentedTest.java b/android-sdk/src/androidTest/java/com/blueshift/ExampleInstrumentedTest.java
deleted file mode 100644
index 1062d033..00000000
--- a/android-sdk/src/androidTest/java/com/blueshift/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.blueshift;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Instrumentation test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() throws Exception {
- // Context of the app under test.
-// Context appContext = InstrumentationRegistry.getTargetContext();
-
-// assertEquals("com.blueshift.test", appContext.getPackageName());
- }
-}
diff --git a/android-sdk/src/androidTest/java/com/blueshift/core/events/BlueshiftEventRepositoryImplTest.kt b/android-sdk/src/androidTest/java/com/blueshift/core/events/BlueshiftEventRepositoryImplTest.kt
new file mode 100644
index 00000000..0219afcf
--- /dev/null
+++ b/android-sdk/src/androidTest/java/com/blueshift/core/events/BlueshiftEventRepositoryImplTest.kt
@@ -0,0 +1,112 @@
+package com.blueshift.core.events
+
+import androidx.test.platform.app.InstrumentationRegistry
+import kotlinx.coroutines.runBlocking
+import org.json.JSONObject
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+class BlueshiftEventRepositoryImplTest {
+ private lateinit var repository: BlueshiftEventRepositoryImpl
+
+ @Before
+ fun setUp() {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ repository = BlueshiftEventRepositoryImpl(context)
+ }
+
+ @After
+ fun tearDown() = runBlocking {
+ repository.clear()
+ }
+
+ @Test
+ fun insertEvent_insertsEventsToTheSQLiteDatabase() = runBlocking {
+ val name = "test_event"
+ val json = "{\"key\":\"val\"}"
+ val timestamp = System.currentTimeMillis()
+ val event = BlueshiftEvent(
+ eventName = name, eventParams = JSONObject(json), timestamp = timestamp
+ )
+
+ repository.insertEvent(event)
+ val events = repository.readOneBatch()
+
+ assert(events.size == 1)
+ assert(events[0].eventName == name)
+ assert(events[0].eventParams.toString() == json)
+ assert(events[0].timestamp == timestamp)
+ }
+
+ @Test
+ fun deleteEvents_deletesEventsFromTheSQLiteDatabase() = runBlocking {
+ val name = "test_event"
+ val json = "{\"key\":\"val\"}"
+ val timestamp = 0L
+ val event = BlueshiftEvent(
+ eventName = name, eventParams = JSONObject(json), timestamp = timestamp
+ )
+
+ repository.insertEvent(event)
+ var events = repository.readOneBatch()
+
+ assert(events.size == 1)
+
+ repository.deleteEvents(events)
+ events = repository.readOneBatch()
+
+ assert(events.isEmpty())
+ }
+
+ @Test
+ fun readOneBatch_retrievesAListOfHundredEventsWhenCountIsNotSpecified() = runBlocking {
+ for (i in 1..200) {
+ val name = "test_event_$i"
+ val json = "{\"key\":\"val\"}"
+ val timestamp = 0L
+ val event = BlueshiftEvent(
+ eventName = name, eventParams = JSONObject(json), timestamp = timestamp
+ )
+ repository.insertEvent(event)
+ }
+
+ val events = repository.readOneBatch()
+ assert(events.size == 100)
+ }
+
+ @Test
+ fun readOneBatch_retrievesAListOfTenEventsWhenCountIsSetToTen() = runBlocking {
+ for (i in 1..200) {
+ val name = "test_event_$i"
+ val json = "{\"key\":\"val\"}"
+ val timestamp = 0L
+ val event = BlueshiftEvent(
+ eventName = name, eventParams = JSONObject(json), timestamp = timestamp
+ )
+ repository.insertEvent(event)
+ }
+
+ val events = repository.readOneBatch(batchCount = 10)
+ assert(events.size == 10)
+ }
+
+ @Test
+ fun readOneBatch_retrievesAListOfEventsInTheSameOrderTheyAreStoredInTheDatabase() =
+ runBlocking {
+ for (i in 1..10) {
+ val name = "test_event_$i"
+ val json = "{\"key\":\"val\"}"
+ val timestamp = 0L
+ val event = BlueshiftEvent(
+ eventName = name, eventParams = JSONObject(json), timestamp = timestamp
+ )
+ repository.insertEvent(event)
+ }
+
+ val events = repository.readOneBatch(batchCount = 10)
+ for (i in 1..9) {
+ assert((events[i].id - events[i - 1].id) == 1L)
+ }
+ }
+}
diff --git a/android-sdk/src/androidTest/java/com/blueshift/core/network/BlueshiftNetworkRequestRepositoryImplTest.kt b/android-sdk/src/androidTest/java/com/blueshift/core/network/BlueshiftNetworkRequestRepositoryImplTest.kt
new file mode 100644
index 00000000..a948e196
--- /dev/null
+++ b/android-sdk/src/androidTest/java/com/blueshift/core/network/BlueshiftNetworkRequestRepositoryImplTest.kt
@@ -0,0 +1,234 @@
+package com.blueshift.core.network
+
+import androidx.test.platform.app.InstrumentationRegistry
+import kotlinx.coroutines.runBlocking
+import org.json.JSONObject
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+class BlueshiftNetworkRequestRepositoryImplTest {
+ private lateinit var repository: BlueshiftNetworkRequestRepositoryImpl
+
+ @Before
+ fun setUp() {
+ val context = InstrumentationRegistry.getInstrumentation().targetContext
+ repository = BlueshiftNetworkRequestRepositoryImpl(context)
+ }
+
+ @After
+ fun tearDown() = runBlocking {
+ repository.clear()
+ }
+
+ @Test
+ fun insertRequest_insertsRequestsToTheSQLiteDatabase(): Unit = runBlocking {
+ val url = "https://example.com"
+ val method = BlueshiftNetworkRequest.Method.GET
+ val header = JSONObject(mapOf("Content-Type" to "application/json"))
+ val body = JSONObject()
+ val authRequired = true
+ val retryBalance = 1
+ val retryTimestamp = 1234567890L
+ val timestamp = 1234567890L
+
+ val request = BlueshiftNetworkRequest(
+ url = url,
+ method = method,
+ header = header,
+ body = body,
+ authorizationRequired = authRequired,
+ retryAttemptBalance = retryBalance,
+ retryAttemptTimestamp = retryTimestamp,
+ timestamp = timestamp
+ )
+
+ repository.insertRequest(request)
+ val request2 = repository.readNextRequest()
+
+ assert(request2 != null)
+ request2?.let {
+ assert(it.url == url)
+ assert(it.method == method)
+ assert(it.header.toString() == header.toString())
+ assert(it.body.toString() == body.toString())
+ assert(it.authorizationRequired == authRequired)
+ assert(it.retryAttemptBalance == retryBalance)
+ assert(it.retryAttemptTimestamp == retryTimestamp)
+ assert(it.timestamp == timestamp)
+ }
+ }
+
+ @Test
+ fun updateRequest_updatesRequestsInTheSQLiteDatabase(): Unit = runBlocking {
+ val url = "https://example.com"
+ val method = BlueshiftNetworkRequest.Method.GET
+ val header = JSONObject(mapOf("Content-Type" to "application/json"))
+ val body = JSONObject()
+ val authRequired = true
+ val retryBalance = 1
+ val retryTimestamp = 1234567890L
+ val timestamp = 1234567890L
+
+ val request = BlueshiftNetworkRequest(
+ url = url,
+ method = method,
+ header = header,
+ body = body,
+ authorizationRequired = authRequired,
+ retryAttemptBalance = retryBalance,
+ retryAttemptTimestamp = retryTimestamp,
+ timestamp = timestamp
+ )
+
+ repository.insertRequest(request)
+
+ // the code only allows updating the following two fields.
+ val retryBalance2 = 2
+ val retryTimestamp2 = 9876543210L
+
+ val request2 = repository.readNextRequest()
+ request2?.let {
+ it.retryAttemptBalance = retryBalance2
+ it.retryAttemptTimestamp = retryTimestamp2
+
+ repository.updateRequest(it)
+ }
+
+ val request3 = repository.readNextRequest()
+ assert(request3 != null)
+ request3?.let {
+ assert(it.retryAttemptBalance == retryBalance2)
+ assert(it.retryAttemptTimestamp == retryTimestamp2)
+ }
+ }
+
+ @Test
+ fun deleteRequest_deletesRequestsInTheSQLiteDatabase(): Unit = runBlocking {
+ val url = "https://example.com"
+ val method = BlueshiftNetworkRequest.Method.GET
+ val header = JSONObject(mapOf("Content-Type" to "application/json"))
+ val body = JSONObject()
+ val authRequired = true
+ val retryBalance = 1
+ val retryTimestamp = 1234567890L
+ val timestamp = 1234567890L
+
+ val request = BlueshiftNetworkRequest(
+ url = url,
+ method = method,
+ header = header,
+ body = body,
+ authorizationRequired = authRequired,
+ retryAttemptBalance = retryBalance,
+ retryAttemptTimestamp = retryTimestamp,
+ timestamp = timestamp
+ )
+
+ repository.insertRequest(request)
+
+ val request2 = repository.readNextRequest()
+ request2?.let {
+ repository.deleteRequest(it)
+ }
+
+ val request3 = repository.readNextRequest()
+ assert(request3 == null)
+ }
+
+ @Test
+ fun readNextRequest_shouldReturnTheFirstRequestWhenAllRequestsInTheQueueRetryAttemptBalanceGreaterThanZero(): Unit =
+ runBlocking {
+ for (i in 1..3) {
+ val url = "https://api.com/$i"
+ val method = BlueshiftNetworkRequest.Method.GET
+ val header = JSONObject(mapOf("Content-Type" to "application/json"))
+ val body = JSONObject()
+ val authRequired = true
+ val retryBalance = 3
+ val retryTimestamp = 0L
+ val timestamp = System.currentTimeMillis()
+ val request = BlueshiftNetworkRequest(
+ url = url,
+ method = method,
+ header = header,
+ body = body,
+ authorizationRequired = authRequired,
+ retryAttemptBalance = retryBalance,
+ retryAttemptTimestamp = retryTimestamp,
+ timestamp = timestamp
+ )
+ repository.insertRequest(request)
+ }
+
+ val request = repository.readNextRequest()
+ assert(request != null)
+ request?.let {
+ assert(it.url == "https://api.com/1")
+ }
+ }
+
+ @Test
+ fun readNextRequest_shouldReturnNullWhenAllRequestsInTheQueueHasRetryAttemptBalanceEqualToZero(): Unit =
+ runBlocking {
+ for (i in 1..3) {
+ val url = "https://api.com/$i"
+ val method = BlueshiftNetworkRequest.Method.GET
+ val header = JSONObject(mapOf("Content-Type" to "application/json"))
+ val body = JSONObject()
+ val authRequired = true
+ val retryBalance = 0
+ val retryTimestamp = 0L
+ val timestamp = System.currentTimeMillis()
+ val request = BlueshiftNetworkRequest(
+ url = url,
+ method = method,
+ header = header,
+ body = body,
+ authorizationRequired = authRequired,
+ retryAttemptBalance = retryBalance,
+ retryAttemptTimestamp = retryTimestamp,
+ timestamp = timestamp
+ )
+ repository.insertRequest(request)
+ }
+
+ val request = repository.readNextRequest()
+ assert(request == null)
+ }
+
+ @Test
+ fun readNextRequest_shouldReturnTheRequestWithRetryAttemptTimestampLessThanCurrentTime(): Unit =
+ runBlocking {
+ for (i in 1..2) {
+ val fiveMinutes = 5 * 60 * 1000
+ val url = "https://api.com/$i"
+ val method = BlueshiftNetworkRequest.Method.GET
+ val header = JSONObject(mapOf("Content-Type" to "application/json"))
+ val body = JSONObject()
+ val authRequired = true
+ val retryBalance = 1
+ val retryTimestamp = if (i % 2 == 0) System.currentTimeMillis() + fiveMinutes else System.currentTimeMillis() - fiveMinutes
+ val timestamp = System.currentTimeMillis()
+ val request = BlueshiftNetworkRequest(
+ url = url,
+ method = method,
+ header = header,
+ body = body,
+ authorizationRequired = authRequired,
+ retryAttemptBalance = retryBalance,
+ retryAttemptTimestamp = retryTimestamp,
+ timestamp = timestamp
+ )
+ repository.insertRequest(request)
+ }
+
+ // i = 1 -> current time - 5min
+ // i = 2 -> current time + 5min
+ val request = repository.readNextRequest()
+ assert(request != null)
+ request?.let {
+ assert(it.url == "https://api.com/1")
+ }
+ }
+}
diff --git a/android-sdk/src/main/AndroidManifest.xml b/android-sdk/src/main/AndroidManifest.xml
index 6b3173a5..02efbf35 100644
--- a/android-sdk/src/main/AndroidManifest.xml
+++ b/android-sdk/src/main/AndroidManifest.xml
@@ -1,6 +1,5 @@
-
+
@@ -57,27 +56,15 @@
-
-
-
-
-
-
-
-
-
+
-
-
\ No newline at end of file
+
diff --git a/android-sdk/src/main/java/com/blueshift/BlueShiftPreference.java b/android-sdk/src/main/java/com/blueshift/BlueShiftPreference.java
index 004795fa..4b5b2f3c 100644
--- a/android-sdk/src/main/java/com/blueshift/BlueShiftPreference.java
+++ b/android-sdk/src/main/java/com/blueshift/BlueShiftPreference.java
@@ -2,7 +2,6 @@
import android.content.Context;
import android.content.SharedPreferences;
-import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationManagerCompat;
@@ -13,8 +12,8 @@
* This class is responsible for tracking the preferences of sdk.
*
* @author Rahul Raveendran V P
- * Created on 17/11/16 @ 1:07 PM
- * https://github.com/rahulrvp
+ * Created on 17/11/16 @ 1:07 PM
+ * https://github.com/rahulrvp
*/
public class BlueShiftPreference {
@@ -22,12 +21,48 @@ public class BlueShiftPreference {
private static final String PREF_FILE = "com.blueshift.sdk_preferences";
private static final String PREF_KEY_APP_VERSION = "blueshift_app_version";
private static final String PREF_KEY_DEVICE_ID = "blueshift_device_id";
+ private static final String PREF_KEY_DEVICE_TOKEN = "blueshift_device_token";
private static final String PREF_KEY_PUSH_ENABLED = "blueshift_push_enabled";
+ private static final String PREF_KEY_LEGACY_SYNC_COMPLETE = "blueshift_legacy_sync_complete";
private static final String PREF_KEY_APP_OPEN_TRACKED_AT = "blueshift_app_open_tracked_at";
private static final String PREF_FILE_EMAIL = "BsftEmailPrefFile";
private static final String TAG = "BlueShiftPreference";
+ static String getSavedDeviceToken(Context context) {
+ String token = null;
+
+ SharedPreferences preferences = getBlueshiftPreferences(context);
+ if (preferences != null) {
+ token = preferences.getString(PREF_KEY_DEVICE_TOKEN, token);
+ }
+
+ return token;
+ }
+
+ static void saveDeviceToken(Context context, String token) {
+ SharedPreferences preferences = getBlueshiftPreferences(context);
+ if (preferences != null) {
+ preferences.edit().putString(PREF_KEY_DEVICE_TOKEN, token).apply();
+ }
+ }
+
+ static void markLegacyEventSyncAsComplete(Context context) {
+ SharedPreferences preferences = getBlueshiftPreferences(context);
+ if (preferences != null) {
+ preferences.edit().putBoolean(PREF_KEY_LEGACY_SYNC_COMPLETE, true).apply();
+ }
+ }
+
+ static boolean isLegacyEventSyncComplete(Context context) {
+ boolean isComplete = false;
+ SharedPreferences preferences = getBlueshiftPreferences(context);
+ if (preferences != null) {
+ isComplete = preferences.getBoolean(PREF_KEY_LEGACY_SYNC_COMPLETE, isComplete);
+ }
+ return isComplete;
+ }
+
static void saveAppVersionString(Context context, String appVersionString) {
SharedPreferences preferences = getBlueshiftPreferences(context);
if (preferences != null) {
diff --git a/android-sdk/src/main/java/com/blueshift/Blueshift.java b/android-sdk/src/main/java/com/blueshift/Blueshift.java
index 8318de38..1f3f8386 100644
--- a/android-sdk/src/main/java/com/blueshift/Blueshift.java
+++ b/android-sdk/src/main/java/com/blueshift/Blueshift.java
@@ -14,10 +14,18 @@
import androidx.core.content.ContextCompat;
import com.blueshift.batch.BulkEventManager;
-import com.blueshift.batch.Event;
import com.blueshift.batch.EventsTable;
import com.blueshift.batch.FailedEventsTable;
-import com.blueshift.httpmanager.Method;
+import com.blueshift.core.BlueshiftEventManager;
+import com.blueshift.core.BlueshiftLambdaQueue;
+import com.blueshift.core.BlueshiftNetworkRequestQueueManager;
+import com.blueshift.core.app.BlueshiftInstallationStatus;
+import com.blueshift.core.app.BlueshiftInstallationStatusHelper;
+import com.blueshift.core.events.BlueshiftEventRepositoryImpl;
+import com.blueshift.core.network.BlueshiftNetworkConfiguration;
+import com.blueshift.core.network.BlueshiftNetworkRepositoryImpl;
+import com.blueshift.core.network.BlueshiftNetworkRequestRepositoryImpl;
+import com.blueshift.core.schedule.network.BlueshiftNetworkChangeScheduler;
import com.blueshift.httpmanager.Request;
import com.blueshift.inappmessage.InAppActionCallback;
import com.blueshift.inappmessage.InAppApiCallback;
@@ -38,7 +46,6 @@
import com.blueshift.util.CommonUtils;
import com.blueshift.util.DeviceUtils;
import com.blueshift.util.NetworkUtils;
-import com.google.gson.Gson;
import org.json.JSONException;
import org.json.JSONObject;
@@ -47,21 +54,19 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
import java.util.Set;
/**
* @author Rahul Raveendran V P
* Created on 17/2/15 @ 3:08 PM
- * https://github.com/rahulrvp
+ * ...
*/
public class Blueshift {
private static final String LOG_TAG = Blueshift.class.getSimpleName();
private static final String UTF8_SPACE = "%20";
private Context mContext;
- private static Configuration mConfiguration;
+ private Configuration mConfiguration;
private static Blueshift instance = null;
private static BlueshiftPushListener blueshiftPushListener;
private static BlueshiftInAppListener blueshiftInAppListener;
@@ -162,6 +167,9 @@ public static void setTrackingEnabled(Context context, boolean isEnabled, boolea
EventsTable.getInstance(context).deleteAllAsync();
// Failed events table. These will also get batched periodically.
FailedEventsTable.getInstance(context).deleteAllAsync();
+
+ // Delete the data from events and request queue
+ BlueshiftEventManager.INSTANCE.clearAsync();
}
}
@@ -354,122 +362,117 @@ public void getLiveContentByCustomerId(@NonNull String slot, HashMap {
+ trackEvent(BlueshiftConstants.EVENT_APP_INSTALL, helper.getEventAttributes(status, previousAppVersion), false);
+ BlueShiftPreference.saveAppVersionString(mContext, appVersion);
+ }
+ case APP_UPDATE -> {
+ trackEvent(BlueshiftConstants.EVENT_APP_UPDATE, helper.getEventAttributes(status, previousAppVersion), false);
+ BlueShiftPreference.saveAppVersionString(mContext, appVersion);
+ }
}
- // pull latest font from server
+
+ handleAppOpenEvent(mContext);
+
InAppMessageIconFont.getInstance(mContext).updateFont(mContext);
+ InAppManager.fetchInAppFromServer(mContext, null);
- // fetch from API
- if (mConfiguration != null && !mConfiguration.isInAppManualTriggerEnabled()) {
- InAppManager.fetchInAppFromServer(mContext, null);
- }
+ doAutomaticIdentifyChecks(mContext);
}
- /**
- * This method checks for app installs and app updates by looking at the app version changes.
- * When a change is detected, an event will be sent to Blueshift to report the same.
- */
- void doAppVersionChecks(Context context) {
- List