diff --git a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt index 8a4f591c..e274f77c 100644 --- a/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt +++ b/parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt @@ -221,6 +221,7 @@ class FunctionalTests { // when startTimestamp = System.currentTimeMillis().milliseconds + parselyTracker.trackPageview("url", null, null, null) parselyTracker.startEngagement(engagementUrl, null) } diff --git a/parsely/src/main/java/com/parsely/parselyandroid/EventsBuilder.java b/parsely/src/main/java/com/parsely/parselyandroid/EventsBuilder.java deleted file mode 100644 index c4bfab2a..00000000 --- a/parsely/src/main/java/com/parsely/parselyandroid/EventsBuilder.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.parsely.parselyandroid; - -import static com.parsely.parselyandroid.Logging.log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.Calendar; -import java.util.HashMap; -import java.util.Map; -import java.util.TimeZone; - -class EventsBuilder { - private static final String VIDEO_START_ID_KEY = "vsid"; - private static final String PAGE_VIEW_ID_KEY = "pvid"; - - private final String siteId; - - @NonNull - private final DeviceInfoRepository deviceInfoRepository; - - public EventsBuilder(@NonNull final DeviceInfoRepository deviceInfoRepository, @NonNull final String siteId) { - this.siteId = siteId; - this.deviceInfoRepository = deviceInfoRepository; - } - - /** - * Create an event Map - * - * @param url The URL identifying the pageview/heartbeat - * @param action Action to use (e.g. pageview, heartbeat, videostart, vheartbeat) - * @param metadata Metadata to attach to the event. - * @param extraData A Map of additional information to send with the event. - * @return A Map object representing the event to be sent to Parse.ly. - */ - @NonNull - Map buildEvent( - String url, - String urlRef, - String action, - ParselyMetadata metadata, - Map extraData, - @Nullable String uuid - ) { - log("buildEvent called for %s/%s", action, url); - - Calendar now = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - - // Main event info - Map event = new HashMap<>(); - event.put("url", url); - event.put("urlref", urlRef); - event.put("idsite", siteId); - event.put("action", action); - - // Make a copy of extraData and add some things. - Map data = new HashMap<>(); - if (extraData != null) { - data.putAll(extraData); - } - - final Map deviceInfo = deviceInfoRepository.collectDeviceInfo(); - data.put("ts", now.getTimeInMillis()); - data.putAll(deviceInfo); - - event.put("data", data); - - if (metadata != null) { - event.put("metadata", metadata.toMap()); - } - - if (action.equals("videostart") || action.equals("vheartbeat")) { - event.put(VIDEO_START_ID_KEY, uuid); - } - - if (action.equals("pageview") || action.equals("heartbeat")) { - event.put(PAGE_VIEW_ID_KEY, uuid); - } - - return event; - } - -} diff --git a/parsely/src/main/java/com/parsely/parselyandroid/EventsBuilder.kt b/parsely/src/main/java/com/parsely/parselyandroid/EventsBuilder.kt new file mode 100644 index 00000000..945c8b33 --- /dev/null +++ b/parsely/src/main/java/com/parsely/parselyandroid/EventsBuilder.kt @@ -0,0 +1,63 @@ +package com.parsely.parselyandroid + +import com.parsely.parselyandroid.Logging.log +import java.util.Calendar +import java.util.TimeZone + +internal class EventsBuilder( + private val deviceInfoRepository: DeviceInfoRepository, + private val siteId: String, + private val clock: Clock, +) { + /** + * Create an event Map + * + * @param url The URL identifying the pageview/heartbeat + * @param action Action to use (e.g. pageview, heartbeat, videostart, vheartbeat) + * @param metadata Metadata to attach to the event. + * @param extraData A Map of additional information to send with the event. + * @return A Map object representing the event to be sent to Parse.ly. + */ + fun buildEvent( + url: String, + urlRef: String, + action: String, + metadata: ParselyMetadata?, + extraData: Map?, + uuid: String + ): Map { + log("buildEvent called for %s/%s", action, url) + + // Main event info + val event: MutableMap = HashMap() + event["url"] = url + event["urlref"] = urlRef + event["idsite"] = siteId + event["action"] = action + + // Make a copy of extraData and add some things. + val data: MutableMap = HashMap() + if (extraData != null) { + data.putAll(extraData) + } + val deviceInfo = deviceInfoRepository.collectDeviceInfo() + data["ts"] = clock.now.inWholeMilliseconds + data.putAll(deviceInfo) + event["data"] = data + metadata?.let { + event["metadata"] = it.toMap() + } + if (action == "videostart" || action == "vheartbeat") { + event[VIDEO_START_ID_KEY] = uuid + } + if (action == "pageview" || action == "heartbeat") { + event[PAGE_VIEW_ID_KEY] = uuid + } + return event + } + + companion object { + private const val VIDEO_START_ID_KEY = "vsid" + private const val PAGE_VIEW_ID_KEY = "pvid" + } +} diff --git a/parsely/src/main/java/com/parsely/parselyandroid/Logging.kt b/parsely/src/main/java/com/parsely/parselyandroid/Logging.kt index 4ded5788..cdee406a 100644 --- a/parsely/src/main/java/com/parsely/parselyandroid/Logging.kt +++ b/parsely/src/main/java/com/parsely/parselyandroid/Logging.kt @@ -2,7 +2,7 @@ package com.parsely.parselyandroid import java.util.Formatter -object Logging { +internal object Logging { /** * Log a message to the console. diff --git a/parsely/src/main/java/com/parsely/parselyandroid/ParselyMetadata.kt b/parsely/src/main/java/com/parsely/parselyandroid/ParselyMetadata.kt index 201b4eb5..8b551587 100644 --- a/parsely/src/main/java/com/parsely/parselyandroid/ParselyMetadata.kt +++ b/parsely/src/main/java/com/parsely/parselyandroid/ParselyMetadata.kt @@ -36,7 +36,7 @@ open class ParselyMetadata * * @return a Map object representing the metadata. */ - open fun toMap(): Map? { + open fun toMap(): Map { val output: MutableMap = HashMap() if (authors != null) { output["authors"] = authors diff --git a/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java b/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java index 5a741d8d..17682e90 100644 --- a/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java +++ b/parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.java @@ -66,11 +66,12 @@ public class ParselyTracker { */ protected ParselyTracker(String siteId, int flushInterval, Context c) { Context context = c.getApplicationContext(); + clock = new Clock(); eventsBuilder = new EventsBuilder( new AndroidDeviceInfoRepository( new AdvertisementIdProvider(context, ParselyCoroutineScopeKt.getSdkScope()), new AndroidIdProvider(context) - ), siteId); + ), siteId, clock); LocalStorageRepository localStorageRepository = new LocalStorageRepository(context); flushManager = new ParselyFlushManager(new Function0() { @Override @@ -88,7 +89,6 @@ public Unit invoke() { return Unit.INSTANCE; }); flushQueue = new FlushQueue(flushManager, localStorageRepository, new ParselyAPIConnection(ROOT_URL + "mobileproxy"), ParselyCoroutineScopeKt.getSdkScope(), new AndroidConnectivityStatusProvider(context)); - clock = new Clock(); intervalCalculator = new HeartbeatIntervalCalculator(clock); // get the adkey straight away on instantiation @@ -266,6 +266,10 @@ public void startEngagement( log("url cannot be empty"); return; } + if (lastPageviewUuid == null) { + log("engagement session cannot start without calling trackPageview first"); + return; + } // Blank urlref is better than null if (urlRef == null) { diff --git a/parsely/src/main/java/com/parsely/parselyandroid/ParselyVideoMetadata.kt b/parsely/src/main/java/com/parsely/parselyandroid/ParselyVideoMetadata.kt index 4a877e31..36c6381a 100644 --- a/parsely/src/main/java/com/parsely/parselyandroid/ParselyVideoMetadata.kt +++ b/parsely/src/main/java/com/parsely/parselyandroid/ParselyVideoMetadata.kt @@ -32,9 +32,9 @@ class ParselyVideoMetadata * * @return a Map object representing the metadata. */ - override fun toMap(): Map? { - val output = super.toMap()?.toMutableMap() - output?.put("duration", durationSeconds) + override fun toMap(): Map { + val output = super.toMap().toMutableMap() + output["duration"] = durationSeconds return output } } diff --git a/parsely/src/test/java/com/parsely/parselyandroid/EventsBuilderTest.kt b/parsely/src/test/java/com/parsely/parselyandroid/EventsBuilderTest.kt index 8f57335b..05af5efb 100644 --- a/parsely/src/test/java/com/parsely/parselyandroid/EventsBuilderTest.kt +++ b/parsely/src/test/java/com/parsely/parselyandroid/EventsBuilderTest.kt @@ -1,5 +1,7 @@ package com.parsely.parselyandroid +import kotlin.time.Duration +import kotlin.time.Duration.Companion.hours import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.MapAssert import org.junit.Before @@ -7,12 +9,14 @@ import org.junit.Test internal class EventsBuilderTest { private lateinit var sut: EventsBuilder + private val clock = FakeClock() @Before fun setUp() { sut = EventsBuilder( FakeDeviceInfoRepository(), TEST_SITE_ID, + clock ) } @@ -187,9 +191,6 @@ internal class EventsBuilderTest { assertThat(it) .hasSize(2) .containsAllEntriesOf(FAKE_DEVICE_INFO) - .hasEntrySatisfying("ts") { timestamp -> - assertThat(timestamp as Long).isBetween(1111111111111, 9999999999999) - } } companion object { @@ -197,10 +198,15 @@ internal class EventsBuilderTest { const val TEST_URL = "http://example.com/some-old/article.html" const val TEST_UUID = "123e4567-e89b-12d3-a456-426614174000" - val FAKE_DEVICE_INFO = mapOf("device" to "info") + val FAKE_NOW = 15.hours + val FAKE_DEVICE_INFO = mapOf("device" to "info", "ts" to FAKE_NOW.inWholeMilliseconds.toString()) } class FakeDeviceInfoRepository: DeviceInfoRepository { override fun collectDeviceInfo(): Map = FAKE_DEVICE_INFO } + + class FakeClock() : Clock() { + override val now: Duration = FAKE_NOW + } }