From f492307b484be6a56667bd372152ee9d09d98506 Mon Sep 17 00:00:00 2001 From: Shayla Sawchenko Date: Tue, 5 Oct 2021 09:47:40 -0700 Subject: [PATCH] Added support for latestTestVersion (#10) --- README.md | 3 +- .../versioncheckkotlinsample/App.kt | 13 ++-- .../versioncheckkotlin/VersionCheck.kt | 33 +++++++++-- .../versioncheckkotlin/VersionCheckConfig.kt | 8 +-- .../interfaces/PackageDetails.kt | 25 ++++++++ .../versioncheckkotlin/models/VersionData.kt | 8 +++ .../PlatformVersionDataTest.kt | 13 ++++ .../versioncheckkotlin/VersionCheckTest.kt | 59 ++++++++++++++++++- .../utils/MockPackageDetails.kt | 13 ++++ .../versioncheckkotlin/utils/TestConstants.kt | 16 +++++ 10 files changed, 172 insertions(+), 19 deletions(-) create mode 100644 versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/interfaces/PackageDetails.kt create mode 100644 versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/utils/MockPackageDetails.kt diff --git a/README.md b/README.md index 979a47c..edaa853 100644 --- a/README.md +++ b/README.md @@ -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 ) ) diff --git a/app/src/main/java/com/steamclock/versioncheckkotlinsample/App.kt b/app/src/main/java/com/steamclock/versioncheckkotlinsample/App.kt index 8940c92..10baaa8 100644 --- a/app/src/main/java/com/steamclock/versioncheckkotlinsample/App.kt +++ b/app/src/main/java/com/steamclock/versioncheckkotlinsample/App.kt @@ -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.* @@ -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) diff --git a/versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/VersionCheck.kt b/versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/VersionCheck.kt index 6f75c58..c8a3166 100644 --- a/versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/VersionCheck.kt +++ b/versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/VersionCheck.kt @@ -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 //-------------------------------------------------- @@ -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() @@ -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() @@ -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() @@ -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 } @@ -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 + } } \ No newline at end of file diff --git a/versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/VersionCheckConfig.kt b/versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/VersionCheckConfig.kt index 65aa36b..c69b321 100644 --- a/versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/VersionCheckConfig.kt +++ b/versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/VersionCheckConfig.kt @@ -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 ) \ No newline at end of file diff --git a/versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/interfaces/PackageDetails.kt b/versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/interfaces/PackageDetails.kt new file mode 100644 index 0000000..85b30ed --- /dev/null +++ b/versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/interfaces/PackageDetails.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/models/VersionData.kt b/versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/models/VersionData.kt index 2d6fcbf..cc524a0 100644 --- a/versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/models/VersionData.kt +++ b/versioncheckkotlin/src/main/java/com/steamclock/versioncheckkotlin/models/VersionData.kt @@ -33,6 +33,14 @@ data class PlatformVersionData( } } } + + fun shouldUpdateForTesting(other: Version): Boolean { + return if (latestTestVersion == null) { + false + } else { + other < latestTestVersion + } + } } data class VersionData( diff --git a/versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/PlatformVersionDataTest.kt b/versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/PlatformVersionDataTest.kt index bc68d2d..f89b9ed 100644 --- a/versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/PlatformVersionDataTest.kt +++ b/versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/PlatformVersionDataTest.kt @@ -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") @@ -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"))) + } } \ No newline at end of file diff --git a/versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/VersionCheckTest.kt b/versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/VersionCheckTest.kt index ee25b8b..0405533 100644 --- a/versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/VersionCheckTest.kt +++ b/versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/VersionCheckTest.kt @@ -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()) @@ -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()) @@ -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()) @@ -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() + } + } + } \ No newline at end of file diff --git a/versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/utils/MockPackageDetails.kt b/versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/utils/MockPackageDetails.kt new file mode 100644 index 0000000..629e34d --- /dev/null +++ b/versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/utils/MockPackageDetails.kt @@ -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 + } +} \ No newline at end of file diff --git a/versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/utils/TestConstants.kt b/versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/utils/TestConstants.kt index 6714273..016d5ef 100644 --- a/versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/utils/TestConstants.kt +++ b/versioncheckkotlin/src/test/java/com/steamclock/versioncheckkotlin/utils/TestConstants.kt @@ -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 + ) } } \ No newline at end of file