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

Lp/image preloading executors #505

Merged
merged 13 commits into from
Nov 21, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@ fun JSONArray.toList(): List<JSONObject> {
return jsonObjectList
}

fun JSONArray.iterator(foreach: (jsonObject: JSONObject) -> Unit) {
for (index in 0 until length()) {
foreach(getJSONObject(index))
}
}

fun JSONObject.safeGetJSONArray(key: String): Pair<Boolean, JSONArray?> {
val has = has(key)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ public void writeToParcel(Parcel dest, int flags) {
}

void didDismiss(InAppResourceProvider resourceProvider) {
removeImageOrGif(resourceProvider);
//removeImageOrGif(resourceProvider); // todo add coorect removal
}

String getBackgroundColor() {
Expand Down Expand Up @@ -443,7 +443,7 @@ void prepareForDisplay(InAppResourceProvider inAppResourceProvider) {
}
} else if (media.isImage()) {

Bitmap bitmap = inAppResourceProvider.fetchInAppImage(media.getMediaUrl(), Bitmap.class);
Bitmap bitmap = inAppResourceProvider.fetchInAppImage(media.getMediaUrl());
if (bitmap != null) {
listener.notificationReady(this);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public CTInAppNotificationMedia[] newArray(int size) {

private String mediaUrl;

CTInAppNotificationMedia() {
public CTInAppNotificationMedia() {
}

private CTInAppNotificationMedia(Parcel in) {
Expand Down Expand Up @@ -65,7 +65,7 @@ String getContentType() {
return contentType;
}

String getMediaUrl() {
public String getMediaUrl() {
return mediaUrl;
}

Expand All @@ -74,7 +74,7 @@ void setMediaUrl(String mediaUrl) {
this.mediaUrl = mediaUrl;
}

CTInAppNotificationMedia initWithJSON(JSONObject mediaObject, int orientation) {
public CTInAppNotificationMedia initWithJSON(JSONObject mediaObject, int orientation) {
this.orientation = orientation;
try {
this.contentType = mediaObject.has(Constants.KEY_CONTENT_TYPE) ? mediaObject
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.clevertap.android.sdk.inapp.data

import android.content.res.Configuration
import com.clevertap.android.sdk.Constants
import com.clevertap.android.sdk.inapp.CTInAppNotificationMedia
import com.clevertap.android.sdk.inapp.evaluation.LimitAdapter
import com.clevertap.android.sdk.inapp.evaluation.TriggerAdapter
import com.clevertap.android.sdk.iterator
import com.clevertap.android.sdk.orEmptyArray
import com.clevertap.android.sdk.safeGetJSONArray
import com.clevertap.android.sdk.toList
Expand Down Expand Up @@ -34,7 +37,65 @@ class InAppResponseAdapter(
}
}

val preloadImage: List<String> = emptyList() // todo provide images
val preloadImage: List<String>

val legacyInApps: Pair<Boolean, JSONArray?> = responseJson.safeGetJSONArray(Constants.INAPP_JSON_RESPONSE_KEY)

val clientSideInApps: Pair<Boolean, JSONArray?> = responseJson.safeGetJSONArray(Constants.INAPP_NOTIFS_KEY_CS)

val serverSideInApps: Pair<Boolean, JSONArray?> = responseJson.safeGetJSONArray(Constants.INAPP_NOTIFS_KEY_SS)

init {
val list = mutableListOf<String>()

// do legacy inapps stuff
if (legacyInApps.first) {
legacyInApps.second?.iterator { jsonObject ->
val portrait = jsonObject.optJSONObject(Constants.KEY_MEDIA)

if (portrait != null) {
val portraitMedia = CTInAppNotificationMedia()
.initWithJSON(portrait, Configuration.ORIENTATION_PORTRAIT)

list.add(portraitMedia.mediaUrl)
}
val landscape = jsonObject.optJSONObject(Constants.KEY_MEDIA_LANDSCAPE)
if (landscape != null) {
val landscapeMedia = CTInAppNotificationMedia()
.initWithJSON(landscape, Configuration.ORIENTATION_LANDSCAPE)

list.add(landscapeMedia.mediaUrl)
}
}
}

// do cs inapps stuff
if (clientSideInApps.first) {
clientSideInApps.second?.iterator { jsonObject ->
val portrait = jsonObject.optJSONObject(Constants.KEY_MEDIA)

if (portrait != null) {
val portraitMedia = CTInAppNotificationMedia()
.initWithJSON(portrait, Configuration.ORIENTATION_PORTRAIT)

if (portraitMedia != null && portraitMedia.mediaUrl != null) {
list.add(portraitMedia.mediaUrl)
}
}
val landscape = jsonObject.optJSONObject(Constants.KEY_MEDIA_LANDSCAPE)
if (landscape != null) {
val landscapeMedia = CTInAppNotificationMedia()
.initWithJSON(landscape, Configuration.ORIENTATION_LANDSCAPE)

if (landscapeMedia != null && landscapeMedia.mediaUrl != null) {
list.add(landscapeMedia.mediaUrl)
}
}
}
}

preloadImage = list
}

val inAppsPerSession: Int = responseJson.optInt(IN_APP_SESSION_KEY, IN_APP_DEFAULT_SESSION)

Expand All @@ -44,14 +105,8 @@ class InAppResponseAdapter(

val staleInApps: Pair<Boolean, JSONArray?> = responseJson.safeGetJSONArray(Constants.INAPP_NOTIFS_STALE_KEY)

val legacyInApps: Pair<Boolean, JSONArray?> = responseJson.safeGetJSONArray(Constants.INAPP_JSON_RESPONSE_KEY)

val appLaunchServerSideInApps: Pair<Boolean, JSONArray?> =
responseJson.safeGetJSONArray(Constants.INAPP_NOTIFS_APP_LAUNCHED_KEY)

val clientSideInApps: Pair<Boolean, JSONArray?> = responseJson.safeGetJSONArray(Constants.INAPP_NOTIFS_KEY_CS)

val serverSideInApps: Pair<Boolean, JSONArray?> = responseJson.safeGetJSONArray(Constants.INAPP_NOTIFS_KEY_SS)
}

// Define a common interface for the properties that are common to both data classes
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ internal class InAppResourceProvider constructor(
return fileToBytes(gifDiskCache.get(cacheKey))
}

fun fetchInAppImage(url: String): Bitmap? {
return fetchInAppImage(url = url, clazz = Bitmap::class.java)
}

/**
* Function that would fetch and cache bitmap image into Memory and File cache and return it.
* If image is found in cache, the cached image is returned.
Expand All @@ -134,15 +138,13 @@ internal class InAppResourceProvider constructor(
val cachedImage: Bitmap? = cachedImage(url)

if (cachedImage != null) {
return if (clazz.isAssignableFrom(Bitmap::class.java)) {
cachedImage as? T
if (clazz.isAssignableFrom(Bitmap::class.java)) {
return cachedImage as? T
} else if (clazz.isAssignableFrom(ByteArray::class.java)) {
val stream = ByteArrayOutputStream()
cachedImage.compress(Bitmap.CompressFormat.PNG, 100, stream)
val byteArray = stream.toByteArray()
byteArray as? T
} else {
null
return byteArray as? T
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.clevertap.android.sdk.inapp.images.preload

internal data class InAppImagePreloadConfig(
val parallelDownloads: Int,
) {
companion object {
private const val DEFAULT_PARALLEL_DOWNLOAD = 4

fun default() : InAppImagePreloadConfig = InAppImagePreloadConfig(
parallelDownloads = DEFAULT_PARALLEL_DOWNLOAD
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.clevertap.android.sdk.inapp.images.preload

import com.clevertap.android.sdk.ILogger
import com.clevertap.android.sdk.inapp.images.InAppResourceProvider
import com.clevertap.android.sdk.utils.CtDefaultDispatchers
import com.clevertap.android.sdk.utils.DispatcherProvider
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlin.system.measureTimeMillis

internal class InAppImagePreloaderCoroutine @JvmOverloads constructor(
override val inAppImageProvider: InAppResourceProvider,
override val logger: ILogger? = null,
private val dispatchers: DispatcherProvider = CtDefaultDispatchers(),
override val config: InAppImagePreloadConfig = InAppImagePreloadConfig.default()
) : InAppImagePreloaderStrategy {

private val jobs: MutableList<Job> = mutableListOf()

@OptIn(ExperimentalCoroutinesApi::class)
override fun preloadImages(urls: List<String>) {

val handler = CoroutineExceptionHandler { _, throwable ->
logger?.verbose("Cancelled image pre fetch \n ${throwable.stackTrace}")
}
val scope = CoroutineScope(dispatchers.io().limitedParallelism(config.parallelDownloads))

urls.forEach { url ->
val job = scope.launch(handler) {
logger?.verbose("started image url fetch $url")

val mils = measureTimeMillis {
inAppImageProvider.fetchInAppImage(url)
}

logger?.verbose("finished image url fetch $url in $mils ms")
}
jobs.add(job)
}
}

override fun cleanup() {
jobs.map { job -> job.cancel() }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.clevertap.android.sdk.inapp.images.preload

import com.clevertap.android.sdk.ILogger
import com.clevertap.android.sdk.inapp.images.InAppResourceProvider
import com.clevertap.android.sdk.task.CTExecutors

internal class InAppImagePreloaderExecutors @JvmOverloads constructor(
private val executor: CTExecutors,
override val inAppImageProvider: InAppResourceProvider,
override val logger: ILogger? = null,
override val config: InAppImagePreloadConfig = InAppImagePreloadConfig.default()
) : InAppImagePreloaderStrategy {

override fun preloadImages(urls: List<String>) {

for (url in urls) {
val task = executor.ioTaskNonUi<Void>()

task.execute("tag") {
inAppImageProvider.fetchInAppImage(url)
null
}
}
}

override fun cleanup() {
//executor?.shutdown
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.clevertap.android.sdk.inapp.images.preload

import com.clevertap.android.sdk.ILogger
import com.clevertap.android.sdk.inapp.images.InAppResourceProvider

internal interface InAppImagePreloaderStrategy {

val inAppImageProvider: InAppResourceProvider

val logger: ILogger?

val config: InAppImagePreloadConfig

fun preloadImages(urls: List<String>)
fun cleanup()
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
import com.clevertap.android.sdk.ControllerManager;
import com.clevertap.android.sdk.Logger;
import com.clevertap.android.sdk.inapp.data.InAppResponseAdapter;
import com.clevertap.android.sdk.inapp.images.InAppImagePreloader;
import com.clevertap.android.sdk.inapp.images.preload.InAppImagePreloaderCoroutine;
import com.clevertap.android.sdk.inapp.images.InAppResourceProvider;
import com.clevertap.android.sdk.inapp.images.preload.InAppImagePreloaderStrategy;
import com.clevertap.android.sdk.inapp.store.preference.ImpressionStore;
import com.clevertap.android.sdk.inapp.store.preference.InAppStore;
import com.clevertap.android.sdk.inapp.store.preference.StoreRegistry;
import com.clevertap.android.sdk.task.CTExecutorFactory;
import com.clevertap.android.sdk.task.CTExecutors;
import com.clevertap.android.sdk.task.Task;
import java.util.concurrent.Callable;
import kotlin.Pair;
Expand Down Expand Up @@ -120,7 +122,10 @@ public void processResponse(
}

InAppResourceProvider inAppResourceProvider = new InAppResourceProvider(context, logger);
InAppImagePreloader preloader = new InAppImagePreloader(inAppResourceProvider, logger);

CTExecutors executor = CTExecutorFactory.executorResourceDownloader();
InAppImagePreloaderStrategy preloader = new InAppImagePreloaderCoroutine(inAppResourceProvider, logger);
//InAppImagePreloaderStrategy preloader = new InAppImagePreloaderExecutors(executor, inAppResourceProvider, logger);

preloader.preloadImages(res.getPreloadImage());

Expand Down
Loading