Skip to content

Commit

Permalink
Added support for latestTestVersion (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
ssawchenko authored Oct 5, 2021
1 parent e444dd7 commit f492307
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 19 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class App: Application() {
VersionCheckConfig(
appVersionName = BuildConfig.VERSION_NAME,
appVersionCode = BuildConfig.VERSION_CODE,
url = "https://myservice.com/api/version" // <-- Change this URL
url = "https://myservice.com/api/version", // <-- Change this URL
packageDetails = DefaultPackageDetails(packageManager, packageName) // Optional, but required for latestTestVersion support
)
)
Expand Down
13 changes: 8 additions & 5 deletions app/src/main/java/com/steamclock/versioncheckkotlinsample/App.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.steamclock.versioncheckkotlinsample

import android.app.Application
import android.content.pm.PackageManager
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.ProcessLifecycleOwner
import com.steamclock.versioncheckkotlin.VersionCheck
import com.steamclock.versioncheckkotlin.VersionCheckConfig
import com.steamclock.versioncheckkotlin.interfaces.DefaultPackageDetails
import com.steamclock.versioncheckkotlin.interfaces.DefaultUpgradeDialog
import com.steamclock.versioncheckkotlin.interfaces.URLFetcher
import kotlinx.coroutines.*
Expand All @@ -21,11 +23,12 @@ class App: Application() {
private fun setupVersionCheck() {
val versionChecker = VersionCheck(
VersionCheckConfig(
appVersionName = BuildConfig.VERSION_NAME,
appVersionCode = BuildConfig.VERSION_CODE,
url = "https://doesn't_matter",
urlFetcher = MockURLFetcher
)
appVersionName = BuildConfig.VERSION_NAME,
appVersionCode = BuildConfig.VERSION_CODE,
url = "https://doesn't_matter",
urlFetcher = MockURLFetcher,
packageDetails = DefaultPackageDetails(packageManager, packageName)
)
)
val upgradeDialog = DefaultUpgradeDialog(versionChecker.displayStateFlow)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ class VersionCheck(private val config: VersionCheckConfig):

private val coroutineScope = MainScope()

/**
* For now assume that all side-loaded apps may be able to upgrade to test build versions
*/
private var isTesterMode = config.packageDetails?.wasSideLoaded() ?: false

//--------------------------------------------------
// App LifecycleObserver Hooks - requires lifecycle annotations
//--------------------------------------------------
Expand Down Expand Up @@ -88,12 +93,10 @@ class VersionCheck(private val config: VersionCheckConfig):
setFailure()
return
}

}

private fun validateAppVersion(serverVersionData: VersionData, appVersion: Version) {
// todo 2021-09-27 Add support for latestTestVersion
val androidVersionData = serverVersionData?.android
val androidVersionData = serverVersionData.android
if (androidVersionData == null) {
// Failed to parse android data
setFailure()
Expand All @@ -108,6 +111,10 @@ class VersionCheck(private val config: VersionCheckConfig):
}

when {
serverVersionData.serverForceVersionFailure == true -> {
// Server has been set to force update no matter the app version
setDisallowed()
}
appVersion < serverMinVersion -> {
// App version now below the server minimum
setDisallowed()
Expand All @@ -116,6 +123,14 @@ class VersionCheck(private val config: VersionCheckConfig):
// Current app version is now blocked
setDisallowed()
}
serverVersionData.serverMaintenance == true -> {
// Server is currently undergoing maintenance
setMaintenance()
}
isTesterMode && androidVersionData.shouldUpdateForTesting(appVersion) -> {
// If a newer test build is available
setTestBuild()
}
else -> {
// If we get all the way down here, the version is allowed!
setAllowed()
Expand All @@ -125,8 +140,6 @@ class VersionCheck(private val config: VersionCheckConfig):

private fun setDisallowed() {
mutableStatusFlow.value = Status.VersionDisallowed
// todo Currently always ForceUpdate, add logic to determine when shouldUpdate is required
// If dialog handler set, have it handle the state immediately
mutableDisplayStateFlow.value = DisplayState.ForceUpdate
}

Expand All @@ -139,4 +152,14 @@ class VersionCheck(private val config: VersionCheckConfig):
mutableStatusFlow.value = Status.FetchFailure
mutableDisplayStateFlow.value = DisplayState.Clear
}

private fun setMaintenance() {
mutableStatusFlow.value = Status.VersionAllowed
mutableDisplayStateFlow.value = DisplayState.DownForMaintenance
}

private fun setTestBuild() {
mutableStatusFlow.value = Status.VersionAllowed
mutableDisplayStateFlow.value = DisplayState.SuggestUpdate
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package com.steamclock.versioncheckkotlin

import com.steamclock.versioncheckkotlin.interfaces.DefaultVersionDataConverter
import com.steamclock.versioncheckkotlin.interfaces.NetworkURLFetcher
import com.steamclock.versioncheckkotlin.interfaces.URLFetcher
import com.steamclock.versioncheckkotlin.interfaces.VersionDataConverter
import com.steamclock.versioncheckkotlin.interfaces.*

data class VersionCheckConfig(
val appVersionName: String,
val appVersionCode: Int,
val url: String,
val urlFetcher: URLFetcher = NetworkURLFetcher,
val versionDataConverter: VersionDataConverter = DefaultVersionDataConverter
val versionDataConverter: VersionDataConverter = DefaultVersionDataConverter,
val packageDetails: PackageDetails? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.steamclock.versioncheckkotlin.interfaces

import android.content.pm.PackageManager
import android.os.Build

interface PackageDetails {
fun wasSideLoaded(): Boolean
}

@Suppress("DEPRECATION")
class DefaultPackageDetails(private val pm: PackageManager, private val name: String): PackageDetails {
override fun wasSideLoaded(): Boolean {
return try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val info = pm.getInstallSourceInfo(name)
info.installingPackageName == null
} else {
val installer = pm.getInstallerPackageName(name)
installer == null
}
} catch (e: Exception) {
false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ data class PlatformVersionData(
}
}
}

fun shouldUpdateForTesting(other: Version): Boolean {
return if (latestTestVersion == null) {
false
} else {
other < latestTestVersion
}
}
}

data class VersionData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ class PlatformVersionDataTest {
blockedVersions = setOf(Version("@400"), Version("2.2.1@300"), Version("2.3@900")),
latestTestVersion = null)

private val latestTestVersion = PlatformVersionData(
minimumVersion = Version("1.2.0"),
blockedVersions = setOf(),
latestTestVersion = Version("1.4"))

@Test
fun testContainsBlockedVersion() {
val versionNotBlocked = Version("1.2.3@900")
Expand Down Expand Up @@ -53,4 +58,12 @@ class PlatformVersionDataTest {
shouldBeTrue.forEach { assertTrue(it.first.containsBlockedVersion(it.second)) }
shouldNotFalse.forEach { assertFalse(it.first.containsBlockedVersion(it.second)) }
}

@Test
fun testShouldUpdateForTesting() {
assertTrue(latestTestVersion.shouldUpdateForTesting(Version("1.2.2@200")))
assertTrue(latestTestVersion.shouldUpdateForTesting(Version("1.3@400")))
assertFalse(latestTestVersion.shouldUpdateForTesting(Version("1.4.0@600")))
assertFalse(latestTestVersion.shouldUpdateForTesting(Version("2.4.0@800")))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class VersionCheckTest {
* On version OK
*/
@Test
fun `Status set to Allowed when app version is ok`() = runBlocking {
fun `Status set to VersionAllowed when app version is ok`() = runBlocking {
versionCheck = VersionCheck(TestConstants.VersionCheckConfig.validApp)
versionCheck.statusFlow.test {
assertEquals(Status.Unknown, awaitItem())
Expand All @@ -68,7 +68,7 @@ class VersionCheckTest {
* When app version too old
*/
@Test
fun `Status set to Disallowed when app version older`() = runBlocking {
fun `Status set to VersionDisallowed when app version older`() = runBlocking {
versionCheck = VersionCheck(TestConstants.VersionCheckConfig.appOldVersion)
versionCheck.statusFlow.test {
assertEquals(Status.Unknown, awaitItem())
Expand All @@ -93,7 +93,7 @@ class VersionCheckTest {
* When app version is blocked
*/
@Test
fun `Status set to Disallowed when app version is blocked`() = runBlocking {
fun `Status set to VersionDisallowed when app version is blocked`() = runBlocking {
versionCheck = VersionCheck(TestConstants.VersionCheckConfig.appVersionBlocked)
versionCheck.statusFlow.test {
assertEquals(Status.Unknown, awaitItem())
Expand Down Expand Up @@ -163,4 +163,57 @@ class VersionCheckTest {
confirmLastEmit()
}
}

/**
* LatestTest Available
*/
@Test
fun `Status set to VersionAllowed when latestTestVersion available and app side loaded`() = runBlocking {
versionCheck = VersionCheck(TestConstants.VersionCheckConfig.latestTestVersionAvailable)
versionCheck.statusFlow.test {
assertEquals(Status.Unknown, awaitItem())
versionCheck.runVersionCheck()
assertEquals(Status.VersionAllowed, awaitItem())
confirmLastEmit()
}
}

// todo need to flip assertEquals prop order

@Test
fun `DisplayState set to SuggestUpdate when latestTestVersion available and app side loaded`() = runBlocking {
versionCheck = VersionCheck(TestConstants.VersionCheckConfig.latestTestVersionAvailable)
versionCheck.displayStateFlow.test {
assertEquals(awaitItem(),DisplayState.Clear)
versionCheck.runVersionCheck()
assertEquals(awaitItem(),DisplayState.SuggestUpdate)
confirmLastEmit()
}
}

/**
* LatestTest Not Applicable
*/
@Test
fun `Status set to VersionAllowed when latestTestVersion available but not applicable`() = runBlocking {
versionCheck = VersionCheck(TestConstants.VersionCheckConfig.latestTestVersionNotApplicable)
versionCheck.statusFlow.test {
assertEquals(Status.Unknown, awaitItem())
versionCheck.runVersionCheck()
assertEquals(Status.VersionAllowed, awaitItem())
confirmLastEmit()
}
}

@Test
fun `DisplayState set to Clear (only once) when latestTestVersion available but not applicable`() = runBlocking {
versionCheck = VersionCheck(TestConstants.VersionCheckConfig.latestTestVersionNotApplicable)
versionCheck.displayStateFlow.test {
assertEquals(awaitItem(),DisplayState.Clear)
versionCheck.runVersionCheck()
// DisplayState should not get updated/emit
confirmLastEmit()
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.steamclock.versioncheckkotlin.utils

import com.steamclock.versioncheckkotlin.interfaces.PackageDetails

object MockPackageDetails {
object WasSideLoaded: PackageDetails {
override fun wasSideLoaded() = true
}

object WasNotSideLoaded: PackageDetails {
override fun wasSideLoaded() = false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,21 @@ object TestConstants {
url = "https://this-doesnt-matter",
urlFetcher = MockURLFetcher(MockJson.invalidVersionDataJson)
)

val latestTestVersionAvailable = VersionCheckConfig(
appVersionName = "1.3",
appVersionCode = 400,
url = "https://this-doesnt-matter",
urlFetcher = MockURLFetcher(MockJson.validVersionDataJson),
packageDetails = MockPackageDetails.WasSideLoaded
)

val latestTestVersionNotApplicable = VersionCheckConfig(
appVersionName = "1.3",
appVersionCode = 400,
url = "https://this-doesnt-matter",
urlFetcher = MockURLFetcher(MockJson.validVersionDataJson),
packageDetails = MockPackageDetails.WasNotSideLoaded
)
}
}

0 comments on commit f492307

Please sign in to comment.