Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

Revert "App Update (EXPOSUREAPP-8905)" #4194

Merged
merged 9 commits into from
Oct 12, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@ import android.net.Uri
import androidx.test.core.app.launchActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.google.android.play.core.appupdate.AppUpdateInfo
import com.google.android.play.core.appupdate.AppUpdateManager
import com.google.android.play.core.install.model.UpdateAvailability
import dagger.Module
import dagger.android.ContributesAndroidInjector
import de.rki.coronawarnapp.main.CWASettings
import de.rki.coronawarnapp.rootdetection.RootDetectionCheck
import de.rki.coronawarnapp.storage.OnboardingSettings
import de.rki.coronawarnapp.update.getUpdateInfo
import de.rki.coronawarnapp.update.UpdateChecker
import de.rki.coronawarnapp.util.ui.SingleLiveEvent
import io.mockk.MockKAnnotations
import io.mockk.Runs
Expand All @@ -22,7 +19,6 @@ import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.spyk
import org.junit.After
import org.junit.Before
Expand All @@ -36,7 +32,7 @@ import testhelpers.takeScreenshot
@RunWith(AndroidJUnit4::class)
class LauncherActivityTest : BaseUITest() {

@MockK lateinit var appUpdateManager: AppUpdateManager
@MockK lateinit var updateChecker: UpdateChecker
@MockK lateinit var cwaSettings: CWASettings
@MockK lateinit var onboardingSettings: OnboardingSettings
@MockK lateinit var rootDetectionCheck: RootDetectionCheck
Expand All @@ -45,15 +41,10 @@ class LauncherActivityTest : BaseUITest() {
@Before
fun setup() {
MockKAnnotations.init(this)
mockkStatic("de.rki.coronawarnapp.update.InAppUpdateKt")

coEvery { appUpdateManager.getUpdateInfo() } returns
mockk<AppUpdateInfo>().apply {
every { updateAvailability() } returns UpdateAvailability.UPDATE_NOT_AVAILABLE
}

coEvery { rootDetectionCheck.isRooted() } returns false

coEvery { updateChecker.checkForUpdate() } returns UpdateChecker.Result(isUpdateNeeded = false)
every { onboardingSettings.isOnboarded } returns false
viewModel = launcherActivityViewModel()
setupMockViewModel(
Expand Down Expand Up @@ -114,7 +105,7 @@ class LauncherActivityTest : BaseUITest() {

private fun launcherActivityViewModel() = spyk(
LauncherActivityViewModel(
appUpdateManager,
updateChecker,
TestDispatcherProvider(),
cwaSettings,
onboardingSettings,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package de.rki.coronawarnapp.ui.launcher
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.rootdetection.showRootDetectionDialog
Expand Down Expand Up @@ -38,24 +39,23 @@ class LauncherActivity : AppCompatActivity() {
this.overridePendingTransition(0, 0)
finish()
}
is LauncherEvent.ForceUpdate -> it.forceUpdate(this)
LauncherEvent.ShowUpdateDialog -> showUpdateNeededDialog()
is LauncherEvent.ShowUpdateDialog -> {
showUpdateNeededDialog(it.updateIntent)
}

LauncherEvent.ShowRootedDialog -> showRootDetectionDialog { viewModel.onRootedDialogDismiss() }
}
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
viewModel.onResult(requestCode, resultCode)
}

private fun showUpdateNeededDialog() {
private fun showUpdateNeededDialog(intent: Intent) {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.update_dialog_title)
.setMessage(R.string.update_dialog_message)
.setCancelable(false)
.setPositiveButton(R.string.update_dialog_button) { _, _ -> viewModel.requestUpdate() }
.setPositiveButton(R.string.update_dialog_button) { _, _ ->
ContextCompat.startActivity(this, intent, null)
}
.show()
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
package de.rki.coronawarnapp.ui.launcher

import android.app.Activity.RESULT_OK
import com.google.android.play.core.appupdate.AppUpdateInfo
import com.google.android.play.core.appupdate.AppUpdateManager
import com.google.android.play.core.install.model.AppUpdateType.IMMEDIATE
import com.google.android.play.core.install.model.UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
import com.google.android.play.core.install.model.UpdateAvailability.UPDATE_AVAILABLE
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import de.rki.coronawarnapp.environment.BuildConfigWrap
import de.rki.coronawarnapp.main.CWASettings
import de.rki.coronawarnapp.rootdetection.RootDetectionCheck
import de.rki.coronawarnapp.storage.OnboardingSettings
import de.rki.coronawarnapp.tag
import de.rki.coronawarnapp.update.getUpdateInfo
import de.rki.coronawarnapp.update.UpdateChecker
import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
import de.rki.coronawarnapp.util.ui.SingleLiveEvent
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
import timber.log.Timber

class LauncherActivityViewModel @AssistedInject constructor(
private val appUpdateManager: AppUpdateManager,
private val updateChecker: UpdateChecker,
dispatcherProvider: DispatcherProvider,
private val cwaSettings: CWASettings,
private val onboardingSettings: OnboardingSettings,
Expand All @@ -45,10 +39,9 @@ class LauncherActivityViewModel @AssistedInject constructor(

private fun checkForUpdate() = launch {
Timber.tag(TAG).d("checkForUpdate()")
val appUpdateInfo = appUpdateManager.getUpdateInfo()
Timber.tag(TAG).d("appUpdateInfo=%s", appUpdateInfo?.updateAvailability())
val updateResult = updateChecker.checkForUpdate()
when {
appUpdateInfo?.updateAvailability() == UPDATE_AVAILABLE -> forceUpdateEvent(appUpdateInfo)
updateResult.isUpdateNeeded -> LauncherEvent.ShowUpdateDialog(updateResult.updateIntent?.invoke()!!)
isJustInstalledOrUpdated() -> LauncherEvent.GoToOnboarding
else -> LauncherEvent.GoToMainActivity
}.let { events.postValue(it) }
Expand All @@ -59,44 +52,6 @@ class LauncherActivityViewModel @AssistedInject constructor(
checkForUpdate()
}

fun onResume() = launch {
Timber.tag(TAG).d("onResume()")
val appUpdateInfo = appUpdateManager.getUpdateInfo()
Timber.tag(TAG).d("onResume - appUpdateInfo=%s", appUpdateInfo?.updateAvailability())
if (appUpdateInfo?.updateAvailability() == DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
events.postValue(forceUpdateEvent(appUpdateInfo))
}
}

fun onResult(requestCode: Int, resultCode: Int) {
Timber.tag(TAG).d("onResult(requestCode=$requestCode, resultCode=$resultCode)")
if (requestCode == UPDATE_CODE && resultCode != RESULT_OK) {
Timber.tag(TAG).d("Update flow failed! Result code: $resultCode")
// If the update is cancelled or fails, request to start the update again.
events.postValue(LauncherEvent.ShowUpdateDialog)
}
}

fun requestUpdate() = launch {
Timber.tag(TAG).d("requestUpdate()")
val appUpdateInfo = appUpdateManager.getUpdateInfo()
Timber.tag(TAG).d("requestUpdate - appUpdateInfo=%s", appUpdateInfo?.updateAvailability())
if (appUpdateInfo?.updateAvailability() == UPDATE_AVAILABLE) {
events.postValue(forceUpdateEvent(appUpdateInfo))
}
}

private fun forceUpdateEvent(appUpdateInfo: AppUpdateInfo) =
LauncherEvent.ForceUpdate { activity ->
try {
appUpdateManager.startUpdateFlowForResult(appUpdateInfo, IMMEDIATE, activity, UPDATE_CODE)
} catch (e: Exception) {
Timber.tag(TAG).d("startUpdateFlowForResult failed for appUpdateInfo=$appUpdateInfo")
Timber.tag(TAG).d("startUpdateFlowForResult - Ask user to try again")
events.postValue(LauncherEvent.ShowUpdateDialog)
}
}

private fun isJustInstalledOrUpdated() =
!onboardingSettings.isOnboarded || !cwaSettings.wasInteroperabilityShownAtLeastOnce ||
cwaSettings.lastChangelogVersion.value < BuildConfigWrap.VERSION_CODE
Expand All @@ -105,7 +60,6 @@ class LauncherActivityViewModel @AssistedInject constructor(
interface Factory : SimpleCWAViewModelFactory<LauncherActivityViewModel>

companion object {
const val UPDATE_CODE = 90000
private val TAG = tag<LauncherActivityViewModel>()
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package de.rki.coronawarnapp.ui.launcher

import android.app.Activity
import android.content.Intent

sealed class LauncherEvent {
object GoToOnboarding : LauncherEvent()
object GoToMainActivity : LauncherEvent()
data class ForceUpdate(
val forceUpdate: (Activity) -> Unit
data class ShowUpdateDialog(
val updateIntent: Intent
) : LauncherEvent()

object ShowUpdateDialog : LauncherEvent()
object ShowRootedDialog : LauncherEvent()
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package de.rki.coronawarnapp.update

import android.content.Intent
import android.net.Uri
import dagger.Reusable
import de.rki.coronawarnapp.BuildConfig
import de.rki.coronawarnapp.appconfig.AppConfigProvider
import de.rki.coronawarnapp.appconfig.CWAConfig
import de.rki.coronawarnapp.appconfig.internal.ApplicationConfigurationCorruptException
import de.rki.coronawarnapp.environment.BuildConfigWrap
import kotlinx.coroutines.flow.first
import timber.log.Timber
import javax.inject.Inject

@Reusable
class UpdateChecker @Inject constructor(
private val appConfigProvider: AppConfigProvider
) {

suspend fun checkForUpdate(): Result = try {
if (isUpdateNeeded()) {
Result(isUpdateNeeded = true, updateIntent = createUpdateAction())
} else {
Result(isUpdateNeeded = false)
}
} catch (exception: ApplicationConfigurationCorruptException) {
Timber.e(
"ApplicationConfigurationCorruptException caught:%s",
exception.localizedMessage
)

Result(isUpdateNeeded = true, updateIntent = createUpdateAction())
} catch (e: Exception) {
Timber.tag(TAG).e(e, "Update check failed, network connection?")
Result(isUpdateNeeded = false)
}

private suspend fun isUpdateNeeded(): Boolean {
val cwaAppConfig: CWAConfig = appConfigProvider.currentConfig.first()

val minVersionFromServer = cwaAppConfig.minVersionCode

val currentVersion = BuildConfigWrap.VERSION_CODE

Timber.tag(TAG).d("minVersionFromServer:%s", minVersionFromServer)
Timber.tag(TAG).d("Current app version:%s", currentVersion)
val needsImmediateUpdate = VersionComparator.isVersionOlder(
currentVersion,
minVersionFromServer
)
Timber.tag(TAG).e("needs update:$needsImmediateUpdate")
return needsImmediateUpdate
}

private fun createUpdateAction(): () -> Intent = {
val uriStringInPlayStore = STORE_PREFIX + BuildConfig.APPLICATION_ID
Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(uriStringInPlayStore)
setPackage(COM_ANDROID_VENDING)
}
}

data class Result(
val isUpdateNeeded: Boolean,
val updateIntent: (() -> Intent)? = null
)

companion object {
private const val TAG: String = "UpdateChecker"
private const val STORE_PREFIX = "https://play.google.com/store/apps/details?id="
private const val COM_ANDROID_VENDING = "com.android.vending"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package de.rki.coronawarnapp.update

/**
* Helper to compare 2 version strings
*/
object VersionComparator {

/**
* Checks if currentVersion is older than versionToCompareTo
*
* Expected input format: <major>.<minor>.<patch>
* major, minor and patch are Int
*
* @param currentVersion
* @param versionToCompareTo
* @return true if currentVersion is older than versionToCompareTo, else false
*/
fun isVersionOlder(currentVersion: Long, versionToCompareTo: Long): Boolean {
return currentVersion < versionToCompareTo
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import de.rki.coronawarnapp.task.TaskController
import de.rki.coronawarnapp.task.internal.TaskModule
import de.rki.coronawarnapp.test.DeviceForTestersModule
import de.rki.coronawarnapp.ui.ActivityBinder
import de.rki.coronawarnapp.update.InAppUpdateModule
import de.rki.coronawarnapp.util.coil.CoilModule
import de.rki.coronawarnapp.util.coroutine.AppCoroutineScope
import de.rki.coronawarnapp.util.coroutine.AppScope
Expand Down Expand Up @@ -87,7 +86,6 @@ import javax.inject.Singleton
CoronaTestModule::class,
DigitalCovidCertificateModule::class,
QrCodeScannerModule::class,
InAppUpdateModule::class,
RootDetectionModule::class,
]
)
Expand Down
Loading