Skip to content

Commit e35e77b

Browse files
Merge pull request #363 from mindbox-cloud/release-2.7.0-rc
Release 2.7.0 rc
2 parents e06a534 + 48936b8 commit e35e77b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2140
-194
lines changed

.github/workflows/build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
lint:
1313
runs-on: ubuntu-latest
1414
steps:
15-
- uses: actions/checkout@v2
15+
- uses: actions/checkout@v3
1616

1717
- name: Set up JDK 17
1818
uses: actions/setup-java@v3
@@ -30,7 +30,7 @@ jobs:
3030
unit:
3131
runs-on: ubuntu-latest
3232
steps:
33-
- uses: actions/checkout@v2
33+
- uses: actions/checkout@v3
3434

3535
- name: Set up JDK 17
3636
uses: actions/setup-java@v3
@@ -52,7 +52,7 @@ jobs:
5252
build:
5353
runs-on: ubuntu-latest
5454
steps:
55-
- uses: actions/checkout@v2
55+
- uses: actions/checkout@v3
5656

5757
- name: Set up JDK 17
5858
uses: actions/setup-java@v3

.github/workflows/publish.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
lint:
1414
runs-on: ubuntu-latest
1515
steps:
16-
- uses: actions/checkout@v2
16+
- uses: actions/checkout@v3
1717

1818
- name: Set up JDK 17
1919
uses: actions/setup-java@v3
@@ -31,7 +31,7 @@ jobs:
3131
unit:
3232
runs-on: ubuntu-latest
3333
steps:
34-
- uses: actions/checkout@v2
34+
- uses: actions/checkout@v3
3535

3636
- name: Set up JDK 17
3737
uses: actions/setup-java@v3
@@ -50,7 +50,7 @@ jobs:
5050
needs: [unit, lint]
5151
runs-on: ubuntu-latest
5252
steps:
53-
- uses: actions/checkout@v2
53+
- uses: actions/checkout@v3
5454

5555
- name: Set up JDK 17
5656
uses: actions/setup-java@v3

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ android.enableJetifier=true
2121
kotlin.code.style=official
2222

2323
# SDK version property
24-
SDK_VERSION_NAME=2.6.1
24+
SDK_VERSION_NAME=2.7.0-rc

proguard/proguard-gson.pro

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,48 @@
1-
# Gson
1+
# Keep generic signatures; needed for correct type resolution
22
-keepattributes Signature
33

4-
# For using GSON @Expose annotation
4+
# Keep Gson annotations
5+
# Note: Cannot perform finer selection here to only cover Gson annotations, see also https://stackoverflow.com/q/47515093
56
-keepattributes *Annotation*
67

7-
# Gson specific classes
8-
-dontwarn sun.misc.**
98

10-
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
11-
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
12-
-keep class * extends com.google.gson.TypeAdapter
13-
-keep class * implements com.google.gson.TypeAdapterFactory
14-
-keep class * implements com.google.gson.JsonSerializer
15-
-keep class * implements com.google.gson.JsonDeserializer
9+
### The following rules are needed for R8 in "full mode" which only adheres to `-keepattribtues` if
10+
### the corresponding class or field is matches by a `-keep` rule as well, see
11+
### https://r8.googlesource.com/r8/+/refs/heads/master/compatibility-faq.md#r8-full-mode
1612

17-
# Prevent R8 from leaving Data object members always null
13+
# Keep class TypeToken (respectively its generic signature)
14+
-keep class com.google.gson.reflect.TypeToken { *; }
15+
16+
# Keep any (anonymous) classes extending TypeToken
17+
-keep class * extends com.google.gson.reflect.TypeToken
18+
19+
# Keep classes with @JsonAdapter annotation
20+
-keep @com.google.gson.annotations.JsonAdapter class *
21+
22+
# Keep fields with @SerializedName annotation, but allow obfuscation of their names
1823
-keepclassmembers,allowobfuscation class * {
1924
@com.google.gson.annotations.SerializedName <fields>;
20-
}
25+
}
26+
27+
# Keep fields with any other Gson annotation
28+
-keepclassmembers class * {
29+
@com.google.gson.annotations.Expose <fields>;
30+
@com.google.gson.annotations.JsonAdapter <fields>;
31+
@com.google.gson.annotations.Since <fields>;
32+
@com.google.gson.annotations.Until <fields>;
33+
}
34+
35+
# Keep no-args constructor of classes which can be used with @JsonAdapter
36+
# By default their no-args constructor is invoked to create an adapter instance
37+
-keep class * extends com.google.gson.TypeAdapter {
38+
<init>();
39+
}
40+
-keep class * implements com.google.gson.TypeAdapterFactory {
41+
<init>();
42+
}
43+
-keep class * implements com.google.gson.JsonSerializer {
44+
<init>();
45+
}
46+
-keep class * implements com.google.gson.JsonDeserializer {
47+
<init>();
48+
}

sdk/src/main/java/cloud/mindbox/mobile_sdk/Extensions.kt

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package cloud.mindbox.mobile_sdk
22

3+
import android.app.ActivityManager
4+
import android.app.Application
5+
import android.content.Context
6+
import android.os.Build
7+
import android.os.Process
8+
import android.view.View
39
import cloud.mindbox.mobile_sdk.logger.MindboxLoggerImpl
410
import cloud.mindbox.mobile_sdk.utils.LoggingExceptionHandler
511
import java.time.Instant
@@ -8,6 +14,7 @@ import java.time.ZoneOffset
814
import java.time.ZonedDateTime
915
import java.time.format.DateTimeFormatter
1016

17+
1118
internal fun Map<String, String>.toUrlQueryString() = LoggingExceptionHandler.runCatching(
1219
defaultValue = ""
1320
) {
@@ -60,7 +67,36 @@ internal inline fun <reified T : Enum<T>> String?.enumValue(default: T? = null):
6067
.replace("_", "")
6168
.equals(
6269
this.replace("_", "").trim(),
63-
ignoreCase = true)
70+
ignoreCase = true
71+
)
6472
}
6573
} ?: default ?: throw IllegalArgumentException("Value for $this could not be found")
74+
}
75+
76+
internal fun Context.isMainProcess(processName: String?): Boolean {
77+
val mainProcessName = getString(R.string.mindbox_android_process).ifBlank { packageName }
78+
return processName?.equalsAny(
79+
mainProcessName,
80+
"$packageName:$mainProcessName",
81+
"$packageName$mainProcessName",
82+
) ?: false
83+
}
84+
85+
internal fun Context.getCurrentProcessName(): String? {
86+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
87+
return Application.getProcessName()
88+
}
89+
90+
val mypid = Process.myPid()
91+
val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
92+
val processes = manager.runningAppProcesses
93+
94+
return processes.firstOrNull { info -> info.pid == mypid }?.processName
95+
}
96+
97+
internal fun View.setSingleClickListener(listener: View.OnClickListener) {
98+
setOnClickListener {
99+
setOnClickListener(null)
100+
listener.onClick(it)
101+
}
66102
}

sdk/src/main/java/cloud/mindbox/mobile_sdk/Mindbox.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import cloud.mindbox.mobile_sdk.inapp.presentation.InAppCallback
1414
import cloud.mindbox.mobile_sdk.inapp.presentation.InAppMessageManager
1515
import cloud.mindbox.mobile_sdk.logger.Level
1616
import cloud.mindbox.mobile_sdk.logger.MindboxLoggerImpl
17+
import cloud.mindbox.mobile_sdk.logger.mindboxLogD
18+
import cloud.mindbox.mobile_sdk.logger.mindboxLogW
1719
import cloud.mindbox.mobile_sdk.managers.*
1820
import cloud.mindbox.mobile_sdk.models.*
1921
import cloud.mindbox.mobile_sdk.models.operation.OperationBody
@@ -26,6 +28,7 @@ import cloud.mindbox.mobile_sdk.pushes.handler.image.MindboxImageFailureHandler
2628
import cloud.mindbox.mobile_sdk.pushes.handler.image.MindboxImageLoader
2729
import cloud.mindbox.mobile_sdk.repository.MindboxPreferences
2830
import cloud.mindbox.mobile_sdk.services.BackgroundWorkManager
31+
import cloud.mindbox.mobile_sdk.utils.Constants
2932
import cloud.mindbox.mobile_sdk.utils.LoggingExceptionHandler
3033
import kotlinx.coroutines.*
3134
import kotlinx.coroutines.Dispatchers.Default
@@ -382,8 +385,15 @@ object Mindbox {
382385
pushServices: List<MindboxPushService>,
383386
) {
384387
LoggingExceptionHandler.runCatching {
388+
389+
val currentProcessName = context.getCurrentProcessName()
390+
if (!context.isMainProcess(currentProcessName)) {
391+
this@Mindbox.mindboxLogW("Skip Mindbox init not in main process! Current process $currentProcessName")
392+
return@runCatching
393+
}
394+
385395
initComponents(context.applicationContext, pushServices)
386-
MindboxLoggerImpl.d(this, "init. firstInitCall: $firstInitCall, " +
396+
this@Mindbox.mindboxLogD("init in $currentProcessName. firstInitCall: $firstInitCall, " +
387397
"configuration: $configuration, pushServices: " +
388398
pushServices.joinToString(", ") { it.javaClass.simpleName } + ", SdkVersion:${getSdkVersion()}")
389399
initScope.launch {
@@ -1090,6 +1100,7 @@ object Mindbox {
10901100
endpointId = endpointId,
10911101
source = source,
10921102
requestUrl = requestUrl,
1103+
sdkVersionNumeric = Constants.SDK_VERSION_NUMERIC
10931104
)
10941105

10951106
MindboxEventManager.appStarted(applicationContext, trackVisitData)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package cloud.mindbox.mobile_sdk.abtests
2+
3+
interface CustomerAbMixer {
4+
fun stringModulusHash(identifier: String, salt: String): Int
5+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package cloud.mindbox.mobile_sdk.abtests
2+
3+
import java.security.MessageDigest
4+
5+
internal class CustomerAbMixerImpl: CustomerAbMixer {
6+
7+
private val sha256 by lazy {
8+
MessageDigest.getInstance("SHA-256").apply { reset() }
9+
}
10+
11+
@OptIn(ExperimentalUnsignedTypes::class)
12+
override fun stringModulusHash(identifier: String, salt: String): Int {
13+
val saltedId = identifier.uppercase() + salt.uppercase()
14+
15+
val bytes = saltedId.toByteArray(Charsets.UTF_8)
16+
val hash = sha256.digest(bytes).toUByteArray()
17+
18+
val bigEndianLastBytesAsInt: Int =
19+
(hash[28] shl 24) or (hash[29] shl 16) or (hash[30] shl 8) or hash[31].toInt()
20+
val unsigned = bigEndianLastBytesAsInt.toUInt()
21+
22+
return (unsigned % 100u).toInt()
23+
}
24+
25+
private infix fun UByte.shl(that: Int): Int = this.toInt().shl(that)
26+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package cloud.mindbox.mobile_sdk.abtests
2+
3+
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.MobileConfigRepository
4+
import cloud.mindbox.mobile_sdk.inapp.domain.models.ABTest
5+
import cloud.mindbox.mobile_sdk.logger.MindboxLog
6+
import cloud.mindbox.mobile_sdk.repository.MindboxPreferences
7+
8+
internal class InAppABTestLogic(
9+
private val mixer: CustomerAbMixer,
10+
private val repository: MobileConfigRepository,
11+
): MindboxLog {
12+
13+
suspend fun getInAppsPool(allInApps: List<String>): Set<String> {
14+
val abtests = repository.getABTests()
15+
val uuid = MindboxPreferences.deviceUuid
16+
17+
if (abtests.isEmpty()) {
18+
logI("Abtests is empty. Use all inApps.")
19+
return allInApps.toSet()
20+
}
21+
if (allInApps.isEmpty()) {
22+
return emptySet()
23+
}
24+
25+
val inAppsForAbtest: List<Set<String>> = abtests.map { abtest ->
26+
27+
val hash = mixer.stringModulusHash(
28+
identifier = uuid,
29+
salt = abtest.salt.uppercase(),
30+
)
31+
32+
logI("Mixer calculate hash $hash for abtest ${abtest.id} with salt ${abtest.salt} and deviceUuid $uuid")
33+
34+
val targetVariantInApps: MutableSet<String> = mutableSetOf()
35+
val otherVariantInApps: MutableSet<String> = mutableSetOf()
36+
37+
abtest.variants.onEach { variant ->
38+
val inApps: List<String> = when (variant.kind) {
39+
ABTest.Variant.VariantKind.ALL -> allInApps
40+
ABTest.Variant.VariantKind.CONCRETE -> variant.inapps
41+
}
42+
if ((variant.lower until variant.upper).contains(hash)) {
43+
targetVariantInApps.addAll(inApps)
44+
logI("Selected variant $variant for ${abtest.id}")
45+
} else {
46+
otherVariantInApps.addAll(inApps)
47+
}
48+
}
49+
50+
(targetVariantInApps + (allInApps - otherVariantInApps)).also { inappsSet ->
51+
logI("For abtest ${abtest.id} determined $inappsSet")
52+
}
53+
}
54+
55+
if (inAppsForAbtest.isEmpty()) {
56+
logW("No inApps after calculation abtests logic. InApp will not be shown.")
57+
return setOf()
58+
}
59+
60+
return if (inAppsForAbtest.size == 1) {
61+
inAppsForAbtest.first()
62+
} else {
63+
getIntersectionForAllABTests(inAppsForAbtest)
64+
}
65+
}
66+
67+
private fun getIntersectionForAllABTests(inAppsForAbtest: List<Set<String>>): Set<String> =
68+
inAppsForAbtest.reduce { acc, list -> acc.intersect(list) }
69+
70+
}

sdk/src/main/java/cloud/mindbox/mobile_sdk/di/modules/DataModule.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ import cloud.mindbox.mobile_sdk.inapp.data.repositories.InAppGeoRepositoryImpl
99
import cloud.mindbox.mobile_sdk.inapp.data.repositories.InAppRepositoryImpl
1010
import cloud.mindbox.mobile_sdk.inapp.data.repositories.InAppSegmentationRepositoryImpl
1111
import cloud.mindbox.mobile_sdk.inapp.data.repositories.MobileConfigRepositoryImpl
12-
import cloud.mindbox.mobile_sdk.inapp.data.validators.InAppValidatorImpl
13-
import cloud.mindbox.mobile_sdk.inapp.data.validators.OperationNameValidator
14-
import cloud.mindbox.mobile_sdk.inapp.data.validators.OperationValidator
12+
import cloud.mindbox.mobile_sdk.inapp.data.validators.*
1513
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.GeoSerializationManager
1614
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.InAppSerializationManager
1715
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.managers.MobileConfigSerializationManager
@@ -42,6 +40,7 @@ internal fun DataModule(
4240
inAppMapper = inAppMapper,
4341
mobileConfigSerializationManager = mobileConfigSerializationManager,
4442
inAppValidator = inAppValidator,
43+
abTestValidator = abTestValidator,
4544
monitoringValidator = monitoringValidator,
4645
operationNameValidator = operationNameValidator,
4746
operationValidator = operationValidator,
@@ -86,7 +85,11 @@ internal fun DataModule(
8685

8786
override val monitoringValidator: MonitoringValidator by lazy { MonitoringValidator() }
8887

89-
override val inAppValidator: InAppValidator by lazy { InAppValidatorImpl() }
88+
override val inAppValidator: InAppValidator by lazy { InAppValidatorImpl(sdkVersionValidator) }
89+
90+
override val abTestValidator: ABTestValidator by lazy { ABTestValidator(sdkVersionValidator) }
91+
92+
override val sdkVersionValidator: SdkVersionValidator by lazy { SdkVersionValidator() }
9093

9194
override val operationNameValidator: OperationNameValidator
9295
get() = OperationNameValidator()

0 commit comments

Comments
 (0)