Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions sdk/src/main/java/cloud/mindbox/mobile_sdk/MindboxConfiguration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class MindboxConfiguration private constructor(
internal val versionCode: String,
internal val subscribeCustomerIfCreated: Boolean,
internal val shouldCreateCustomer: Boolean,
internal val shouldIncludeVersionCode: Boolean,
internal val uuidDebugEnabled: Boolean,
internal val operationsDomain: String? = null,
) {
Expand All @@ -35,6 +36,7 @@ public class MindboxConfiguration private constructor(
versionCode = builder.versionCode,
subscribeCustomerIfCreated = builder.subscribeCustomerIfCreated,
shouldCreateCustomer = builder.shouldCreateCustomer,
shouldIncludeVersionCode = builder.shouldIncludeVersionCode,
uuidDebugEnabled = builder.uuidDebugEnabled,
operationsDomain = builder.operationsDomain,
)
Expand All @@ -49,6 +51,7 @@ public class MindboxConfiguration private constructor(
versionCode: String = this.versionCode,
subscribeCustomerIfCreated: Boolean = this.subscribeCustomerIfCreated,
shouldCreateCustomer: Boolean = this.shouldCreateCustomer,
shouldIncludeVersionCode: Boolean = this.shouldIncludeVersionCode,
uuidDebugEnabled: Boolean = this.uuidDebugEnabled,
operationsDomain: String? = this.operationsDomain,
) = MindboxConfiguration(
Expand All @@ -61,6 +64,7 @@ public class MindboxConfiguration private constructor(
versionCode = versionCode,
subscribeCustomerIfCreated = subscribeCustomerIfCreated,
shouldCreateCustomer = shouldCreateCustomer,
shouldIncludeVersionCode = shouldIncludeVersionCode,
uuidDebugEnabled = uuidDebugEnabled,
operationsDomain = operationsDomain,
)
Expand All @@ -75,6 +79,7 @@ public class MindboxConfiguration private constructor(
"versionCode = $versionCode, " +
"subscribeCustomerIfCreated = $subscribeCustomerIfCreated, " +
"shouldCreateCustomer = $shouldCreateCustomer, " +
"shouldIncludeVersionCode = $shouldIncludeVersionCode, " +
"uuidDebugEnabled = $uuidDebugEnabled, " +
"operationsDomain = $operationsDomain)"
}
Expand All @@ -89,6 +94,7 @@ public class MindboxConfiguration private constructor(
private const val PLACEHOLDER_APP_PACKAGE_NAME = "Unknown package name"
private const val PLACEHOLDER_APP_VERSION_NAME = "Unknown version"
private const val PLACEHOLDER_APP_VERSION_CODE = "?"
private const val EMPTY_APP_VERSION_CODE = ""
}

internal var previousInstallationId: String = ""
Expand All @@ -98,6 +104,7 @@ public class MindboxConfiguration private constructor(
internal var versionName: String = PLACEHOLDER_APP_VERSION_NAME
internal var versionCode: String = PLACEHOLDER_APP_VERSION_CODE
internal var shouldCreateCustomer: Boolean = true
internal var shouldIncludeVersionCode: Boolean = true
internal var uuidDebugEnabled: Boolean = true
internal var operationsDomain: String? = null

Expand Down Expand Up @@ -142,6 +149,21 @@ public class MindboxConfiguration private constructor(
return this
}

/**
* Specifies whether the app versionCode is included in the app version reported
* to Mindbox (the User-Agent header). When false, only versionName is reported.
*
* Note: starting from SDK version 3.0.0 the default value will change to false,
* and versionCode will not be reported unless explicitly enabled.
*
* @param shouldIncludeVersionCode - flag which determines if versionCode is reported.
* Default value is true.
*/
public fun shouldIncludeVersionCode(shouldIncludeVersionCode: Boolean): Builder {
this.shouldIncludeVersionCode = shouldIncludeVersionCode
return this
}

/**
* Specifies if Mindbox UUID copy to clipboard functionality is enabled. If enabled - UUID
* can be copied to clipboard by minimizing and maximizing your app 5 times in 10 seconds
Expand Down Expand Up @@ -181,18 +203,25 @@ public class MindboxConfiguration private constructor(
val packageInfo = packageManager.getPackageInfoCompat(context, 0)
packageName = packageInfo.packageName.trim()
this.versionName = packageInfo.versionName?.trim()
?: PLACEHOLDER_APP_PACKAGE_NAME
?: PLACEHOLDER_APP_VERSION_NAME
this.versionCode =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
packageInfo.longVersionCode.toString().trim()
if (shouldIncludeVersionCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
packageInfo.longVersionCode.toString().trim()
} else {
PackageInfoCompat.getLongVersionCode(packageInfo).toString().trim()
}
} else {
PackageInfoCompat.getLongVersionCode(packageInfo).toString().trim()
EMPTY_APP_VERSION_CODE
}

// need for scheduling and stopping one-time background service
SharedPreferencesManager.with(context)
MindboxPreferences.hostAppName = packageName
} catch (_: Exception) {
if (!shouldIncludeVersionCode) {
versionCode = EMPTY_APP_VERSION_CODE
}
MindboxLoggerImpl.e(
this,
"Getting app info failed. Identified as an unknown application",
Expand Down
13 changes: 10 additions & 3 deletions sdk/src/main/java/cloud/mindbox/mobile_sdk/models/Configuration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal data class Configuration(
val subscribeCustomerIfCreated: Boolean,
val shouldCreateCustomer: Boolean,
val operationsDomain: String? = null,
val shouldIncludeVersionCode: Boolean = true,
) {

internal constructor(mindboxConfiguration: MindboxConfiguration) : this(
Expand All @@ -33,7 +34,14 @@ internal data class Configuration(
subscribeCustomerIfCreated = mindboxConfiguration.subscribeCustomerIfCreated,
shouldCreateCustomer = mindboxConfiguration.shouldCreateCustomer,
operationsDomain = mindboxConfiguration.operationsDomain,
shouldIncludeVersionCode = mindboxConfiguration.shouldIncludeVersionCode,
)

internal val hostAppVersion: String
get() = if (versionCode.isBlank()) versionName else "$versionName($versionCode)"

internal val shortHostAppVersion: String
get() = if (versionCode.isBlank()) versionName else "$versionName-$versionCode"
}

internal fun Configuration.getUserAgent(): String {
Expand All @@ -44,11 +52,10 @@ internal fun Configuration.getUserAgent(): String {
Build.MANUFACTURER,
Build.MODEL,
packageName,
versionName,
versionCode,
hostAppVersion,
)
}

internal fun Configuration.getShortUserAgent(): String {
return "$packageName/$versionName-$versionCode mindbox.sdk/${BuildConfig.VERSION_NAME} "
return "$packageName/$shortHostAppVersion mindbox.sdk/${BuildConfig.VERSION_NAME}"
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ internal data class MindboxRequest(

private const val VALUE_CONTENT_TYPE = "application/json; charset=utf-8"
internal const val VALUE_USER_AGENT =
"mindbox.sdk/%1$1s (Android %2$1s; %3$1s; %4$1s) %5$1s/%6$1s(%7$1s)" // format: mindbox.sdk/{sdk.version} (Android {os_version}; {vendor}; {model}) {host_app_name}/{host_app_version}
"mindbox.sdk/%1$1s (Android %2$1s; %3$1s; %4$1s) %5$1s/%6$1s" // format: mindbox.sdk/{sdk.version} (Android {os_version}; {vendor}; {model}) {host_app_name}/{host_app_version}
private const val VALUE_INTEGRATION = "Android-SDK"
private const val VALUE_ACCEPT = "application/json"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import androidx.sqlite.db.SupportSQLiteDatabase
import cloud.mindbox.mobile_sdk.converters.MindboxRoomConverter
import cloud.mindbox.mobile_sdk.data.ConfigurationsDao
import cloud.mindbox.mobile_sdk.data.EventsDao
import cloud.mindbox.mobile_sdk.logger.mindboxLogI
import cloud.mindbox.mobile_sdk.managers.DbManager.CONFIGURATION_TABLE_NAME
import cloud.mindbox.mobile_sdk.models.Configuration
import cloud.mindbox.mobile_sdk.models.Event

@Database(entities = [Configuration::class, Event::class], exportSchema = false, version = 3)
@Database(entities = [Configuration::class, Event::class], exportSchema = false, version = 4)
@TypeConverters(MindboxRoomConverter::class)
internal abstract class MindboxDatabase : RoomDatabase() {

Expand All @@ -40,6 +41,18 @@ internal abstract class MindboxDatabase : RoomDatabase() {
}
}

private val MIGRATION_3_4 = object : Migration(3, 4) {

override fun migrate(db: SupportSQLiteDatabase) {
mindboxLogI("DB migration 3->4 started")
db.execSQL(
"ALTER TABLE $CONFIGURATION_TABLE_NAME " +
"ADD COLUMN shouldIncludeVersionCode INTEGER NOT NULL DEFAULT 1"
)
mindboxLogI("DB migration 3->4 finished")
}
}

internal var isTestMode = false

internal fun getInstance(context: Context) = if (!isTestMode) {
Expand All @@ -50,7 +63,8 @@ internal abstract class MindboxDatabase : RoomDatabase() {
DATABASE_NAME,
).addMigrations(
MIGRATION_1_2,
MIGRATION_2_3
MIGRATION_2_3,
MIGRATION_3_4
)
.build()
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package cloud.mindbox.mobile_sdk

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import io.mockk.every
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf

@RunWith(RobolectricTestRunner::class)
internal class MindboxConfigurationTest {

companion object {
private const val DOMAIN = "api.mindbox.ru"
private const val ENDPOINT = "test-endpoint"
private const val VERSION_NAME = "1.2.3"
private const val VERSION_CODE = 123
}

private val context: Context = ApplicationProvider.getApplicationContext()

@Before
fun setUp() {
val packageInfo = shadowOf(context.packageManager)
.getInternalMutablePackageInfo(context.packageName)
packageInfo.versionName = VERSION_NAME
@Suppress("DEPRECATION")
packageInfo.versionCode = VERSION_CODE
}

@Test
fun `versionCode is read from PackageInfo by default`() {
val configuration = MindboxConfiguration.Builder(context, DOMAIN, ENDPOINT).build()

assertEquals(VERSION_NAME, configuration.versionName)
assertEquals(VERSION_CODE.toString(), configuration.versionCode)
assertTrue(configuration.shouldIncludeVersionCode)
}

@Test
fun `versionCode is read from PackageInfo when shouldIncludeVersionCode is true`() {
val configuration = MindboxConfiguration.Builder(context, DOMAIN, ENDPOINT)
.shouldIncludeVersionCode(true)
.build()

assertEquals(VERSION_CODE.toString(), configuration.versionCode)
}

@Test
fun `versionCode is empty when shouldIncludeVersionCode is false`() {
val configuration = MindboxConfiguration.Builder(context, DOMAIN, ENDPOINT)
.shouldIncludeVersionCode(false)
.build()

assertEquals(VERSION_NAME, configuration.versionName)
assertEquals("", configuration.versionCode)
assertFalse(configuration.shouldIncludeVersionCode)
}

@Test
fun `versionCode is placeholder when app info is unavailable`() {
val brokenContext = mockk<Context> {
every { packageManager } throws RuntimeException("no package manager")
}

val configuration = MindboxConfiguration.Builder(brokenContext, DOMAIN, ENDPOINT).build()

assertEquals("?", configuration.versionCode)
}

@Test
fun `versionCode is empty when shouldIncludeVersionCode is false and app info is unavailable`() {
val brokenContext = mockk<Context> {
every { packageManager } throws RuntimeException("no package manager")
}

val configuration = MindboxConfiguration.Builder(brokenContext, DOMAIN, ENDPOINT)
.shouldIncludeVersionCode(false)
.build()

assertEquals("", configuration.versionCode)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package cloud.mindbox.mobile_sdk.models

import cloud.mindbox.mobile_sdk.BuildConfig
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
internal class ConfigurationTest {

private fun configuration(versionCode: String) = Configuration(
previousInstallationId = "",
previousDeviceUUID = "",
endpointId = "test-endpoint",
domain = "api.mindbox.ru",
packageName = "com.example.app",
versionName = "1.2.3",
versionCode = versionCode,
subscribeCustomerIfCreated = false,
shouldCreateCustomer = true,
)

@Test
fun `hostAppVersion contains versionCode in brackets when it is not blank`() {
assertEquals("1.2.3(123)", configuration(versionCode = "123").hostAppVersion)
}

@Test
fun `hostAppVersion is versionName only when versionCode is blank`() {
assertEquals("1.2.3", configuration(versionCode = "").hostAppVersion)
}

@Test
fun `user agent contains app version with versionCode when it is not blank`() {
val userAgent = configuration(versionCode = "123").getUserAgent()

assertTrue(
"Unexpected User-Agent: $userAgent",
userAgent.endsWith(" com.example.app/1.2.3(123)"),
)
}

@Test
fun `user agent contains app version without versionCode when it is blank`() {
val userAgent = configuration(versionCode = "").getUserAgent()

assertTrue(
"Unexpected User-Agent: $userAgent",
userAgent.endsWith(" com.example.app/1.2.3"),
)
}

@Test
fun `short user agent contains versionCode when it is not blank`() {
assertEquals(
"com.example.app/1.2.3-123 mindbox.sdk/${BuildConfig.VERSION_NAME}",
configuration(versionCode = "123").getShortUserAgent(),
)
}

@Test
fun `short user agent contains versionName only when versionCode is blank`() {
assertEquals(
"com.example.app/1.2.3 mindbox.sdk/${BuildConfig.VERSION_NAME}",
configuration(versionCode = "").getShortUserAgent(),
)
}
}