Skip to content
This repository has been archived by the owner on Jul 11, 2024. It is now read-only.

Commit

Permalink
fix: country caching
Browse files Browse the repository at this point in the history
Add hilt performance improvements

Fixes issue where countries were not caching properly on app startup.
  • Loading branch information
zaneschepke committed Apr 26, 2024
1 parent ee4cb2d commit 5ebc19f
Show file tree
Hide file tree
Showing 18 changed files with 203 additions and 167 deletions.
11 changes: 11 additions & 0 deletions app/src/main/java/net/nymtech/nymvpn/NymVpn.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import android.service.quicksettings.TileService
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import net.nymtech.nymvpn.service.tile.VpnQuickTile
import net.nymtech.nymvpn.util.actionBarSize
import net.nymtech.nymvpn.util.log.DebugTree
Expand All @@ -16,6 +18,7 @@ import timber.log.Timber

@HiltAndroidApp
class NymVpn : Application() {

override fun onCreate() {
super.onCreate()
instance = this
Expand All @@ -27,7 +30,15 @@ class NymVpn : Application() {
}
}

override fun onLowMemory() {
super.onLowMemory()
applicationScope.cancel("onLowMemory() called by system")
applicationScope = MainScope()
}

companion object {

var applicationScope = MainScope()
lateinit var instance: NymVpn
private set

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import net.nymtech.vpn.model.Country

interface GatewayRepository {

suspend fun getLowLatencyCountry(): Country
suspend fun getLowLatencyCountry(): Country?

suspend fun setLowLatencyCountry(country: Country)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class DataStoreGatewayRepository(private val dataStoreManager: DataStoreManager)
val EXIT_COUNTRIES = stringPreferencesKey("EXIT_COUNTRIES")
}

override suspend fun getLowLatencyCountry(): Country {
override suspend fun getLowLatencyCountry(): Country? {
val country = dataStoreManager.getFromStore(LOW_LATENCY_COUNTRY)
return Country.from(country)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import timber.log.Timber
class DataStoreSettingsRepository(private val dataStoreManager: DataStoreManager) :
SettingsRepository {

private val default = Country(isDefault = true)
companion object {
val FIRST_HOP_COUNTRY = stringPreferencesKey("FIRST_HOP_COUNTRY")
val LAST_HOP_COUNTRY = stringPreferencesKey("LAST_HOP_COUNTRY")
Expand Down Expand Up @@ -60,7 +61,7 @@ class DataStoreSettingsRepository(private val dataStoreManager: DataStoreManager

override suspend fun getFirstHopCountry(): Country {
val country = dataStoreManager.getFromStore(FIRST_HOP_COUNTRY)
return Country.from(country)
return Country.from(country) ?: default
}

override suspend fun setFirstHopCountry(country: Country) {
Expand All @@ -73,7 +74,7 @@ class DataStoreSettingsRepository(private val dataStoreManager: DataStoreManager

override suspend fun getLastHopCountry(): Country {
val country = dataStoreManager.getFromStore(LAST_HOP_COUNTRY)
return Country.from(country)
return Country.from(country) ?: default
}

override suspend fun setLastHopCountry(country: Country) {
Expand Down Expand Up @@ -146,8 +147,8 @@ class DataStoreSettingsRepository(private val dataStoreManager: DataStoreManager
pref[FIRST_HOP_SELECTION]
?: Settings.FIRST_HOP_SELECTION_DEFAULT,
isAnalyticsShown = pref[ANALYTICS_SHOWN] ?: Settings.ANALYTICS_SHOWN_DEFAULT,
firstHopCountry = Country.from(pref[FIRST_HOP_COUNTRY]),
lastHopCountry = Country.from(pref[LAST_HOP_COUNTRY]),
firstHopCountry = Country.from(pref[FIRST_HOP_COUNTRY]) ?: default,
lastHopCountry = Country.from(pref[LAST_HOP_COUNTRY]) ?: default,
)
} catch (e: IllegalArgumentException) {
Timber.e(e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package net.nymtech.nymvpn.data.model
import net.nymtech.vpn.model.Country

data class Gateways(
val lowLatencyCountry: Country = Country(),
val lowLatencyCountry: Country? = null,
val entryCountries: Set<Country> = emptySet(),
val exitCountries: Set<Country> = emptySet(),
)
9 changes: 5 additions & 4 deletions app/src/main/java/net/nymtech/nymvpn/receiver/BootReceiver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import net.nymtech.vpn.VpnClient
import net.nymtech.vpn.util.InvalidCredentialException
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Provider

@AndroidEntryPoint
class BootReceiver : BroadcastReceiver() {
Expand All @@ -20,24 +21,24 @@ class BootReceiver : BroadcastReceiver() {
lateinit var settingsRepository: SettingsRepository

@Inject
lateinit var secretsRepository: SecretsRepository
lateinit var secretsRepository: Provider<SecretsRepository>

@Inject
lateinit var vpnClient: VpnClient
lateinit var vpnClient: Provider<VpnClient>

override fun onReceive(context: Context?, intent: Intent?) = goAsync {
if (Intent.ACTION_BOOT_COMPLETED != intent?.action) return@goAsync
if (settingsRepository.isAutoStartEnabled()) {
val entryCountry = settingsRepository.getFirstHopCountry()
val exitCountry = settingsRepository.getLastHopCountry()
val credential = secretsRepository.getCredential()
val credential = secretsRepository.get().getCredential()
val mode = settingsRepository.getVpnMode()
if (credential != null) {
context?.let { context ->
val entry = entryCountry.toEntryPoint()
val exit = exitCountry.toExitPoint()
try {
vpnClient.apply {
vpnClient.get().apply {
this.mode = mode
this.exitPoint = exit
this.entryPoint = entry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.os.IBinder
import androidx.lifecycle.LifecycleService
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import net.nymtech.nymvpn.NymVpn
import net.nymtech.nymvpn.data.SecretsRepository
Expand All @@ -13,6 +14,7 @@ import net.nymtech.vpn.VpnClient
import net.nymtech.vpn.util.InvalidCredentialException
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Provider

@AndroidEntryPoint
class AlwaysOnVpnService : LifecycleService() {
Expand All @@ -21,10 +23,10 @@ class AlwaysOnVpnService : LifecycleService() {
lateinit var settingsRepository: SettingsRepository

@Inject
lateinit var secretsRepository: SecretsRepository
lateinit var secretsRepository: Provider<SecretsRepository>

@Inject
lateinit var vpnClient: VpnClient
lateinit var vpnClient: Provider<VpnClient>

override fun onBind(intent: Intent): IBinder? {
super.onBind(intent)
Expand All @@ -35,16 +37,16 @@ class AlwaysOnVpnService : LifecycleService() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent == null || intent.component == null || intent.component!!.packageName != packageName) {
Timber.i("Always-on VPN requested start")
lifecycleScope.launch {
val credential = secretsRepository.getCredential()
lifecycleScope.launch(Dispatchers.IO) {
val credential = secretsRepository.get().getCredential()
if (credential != null) {
val entryCountry = settingsRepository.getFirstHopCountry()
val exitCountry = settingsRepository.getLastHopCountry()
val mode = settingsRepository.getVpnMode()
val entry = entryCountry.toEntryPoint()
val exit = exitCountry.toExitPoint()
try {
vpnClient.apply {
vpnClient.get().apply {
this.mode = mode
this.entryPoint = entry
this.exitPoint = exit
Expand Down
18 changes: 11 additions & 7 deletions app/src/main/java/net/nymtech/nymvpn/service/tile/VpnQuickTile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.nymtech.nymvpn.R
import net.nymtech.nymvpn.data.SecretsRepository
import net.nymtech.nymvpn.data.SettingsRepository
Expand All @@ -17,17 +18,18 @@ import net.nymtech.vpn.model.VpnState
import net.nymtech.vpn.util.InvalidCredentialException
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Provider

@AndroidEntryPoint
class VpnQuickTile : TileService() {
@Inject
lateinit var secretsRepository: SecretsRepository
lateinit var secretsRepository: Provider<SecretsRepository>

@Inject
lateinit var settingsRepository: SettingsRepository

@Inject
lateinit var vpnClient: VpnClient
lateinit var vpnClient: Provider<VpnClient>

private val scope = CoroutineScope(Dispatchers.Main)

Expand All @@ -36,7 +38,7 @@ class VpnQuickTile : TileService() {
Timber.d("Quick tile listening called")
setTileText()
scope.launch {
vpnClient.stateFlow.collect {
vpnClient.get().stateFlow.collect {
when (it.vpnState) {
VpnState.Up -> {
setActive()
Expand Down Expand Up @@ -79,19 +81,21 @@ class VpnQuickTile : TileService() {
super.onClick()
setTileText()
unlockAndRun {
when (vpnClient.getState().vpnState) {
when (vpnClient.get().getState().vpnState) {
VpnState.Up -> {
scope.launch {
setTileDescription(this@VpnQuickTile.getString(R.string.disconnecting))
vpnClient.stop(this@VpnQuickTile, true)
vpnClient.get().stop(this@VpnQuickTile, true)
}
}
VpnState.Down -> {
scope.launch {
val credential = secretsRepository.getCredential()
val credential = withContext(Dispatchers.IO) {
secretsRepository.get().getCredential()
}
if (credential != null) {
try {
vpnClient.apply {
vpnClient.get().apply {
this.mode = settingsRepository.getVpnMode()
this.exitPoint = settingsRepository.getLastHopCountry().toExitPoint()
this.entryPoint = settingsRepository.getFirstHopCountry().toEntryPoint()
Expand Down
12 changes: 8 additions & 4 deletions app/src/main/java/net/nymtech/nymvpn/ui/AppViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,26 @@ import net.nymtech.logcathelper.model.LogMessage
import net.nymtech.nymvpn.R
import net.nymtech.nymvpn.data.GatewayRepository
import net.nymtech.nymvpn.data.SettingsRepository
import net.nymtech.nymvpn.service.country.CountryCacheService
import net.nymtech.nymvpn.util.Constants
import net.nymtech.nymvpn.util.FileUtils
import net.nymtech.nymvpn.util.log.NymLibException
import net.nymtech.vpn.NymApi
import net.nymtech.vpn.VpnClient
import net.nymtech.vpn.model.Country
import timber.log.Timber
import java.time.Instant
import javax.inject.Inject
import javax.inject.Provider

@HiltViewModel
class AppViewModel
@Inject
constructor(
private val settingsRepository: SettingsRepository,
private val gatewayRepository: GatewayRepository,
private val vpnClient: VpnClient,
private val countryCacheService: CountryCacheService,
private val vpnClient: Provider<VpnClient>,
private val nymApi: NymApi,
) : ViewModel() {

Expand All @@ -52,7 +56,7 @@ constructor(
private val logsBuffer = mutableListOf<LogMessage>()

val uiState =
combine(_uiState, settingsRepository.settingsFlow, vpnClient.stateFlow) { state, settings, vpnState ->
combine(_uiState, settingsRepository.settingsFlow, vpnClient.get().stateFlow) { state, settings, vpnState ->
AppUiState(
false,
state.snackbarMessage,
Expand Down Expand Up @@ -112,7 +116,7 @@ constructor(
settingsRepository.setAnalyticsShown(true)
}

fun onEntryLocationSelected(selected: Boolean) = viewModelScope.launch {
fun onEntryLocationSelected(selected: Boolean) = viewModelScope.launch(Dispatchers.IO) {
settingsRepository.setFirstHopSelection(selected)
setFirstHopToLowLatency()
}
Expand All @@ -131,7 +135,7 @@ constructor(
}.onFailure {
Timber.e(it)
}.onSuccess {
settingsRepository.setFirstHopCountry(it)
settingsRepository.setFirstHopCountry(it ?: Country(isDefault = true))
}
}

Expand Down
44 changes: 26 additions & 18 deletions app/src/main/java/net/nymtech/nymvpn/ui/SplashActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,54 +12,62 @@ import androidx.lifecycle.repeatOnLifecycle
import dagger.hilt.android.AndroidEntryPoint
import io.sentry.android.core.SentryAndroid
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch
import net.nymtech.nymvpn.BuildConfig
import net.nymtech.nymvpn.data.GatewayRepository
import net.nymtech.nymvpn.NymVpn
import net.nymtech.nymvpn.data.SettingsRepository
import net.nymtech.nymvpn.service.country.CountryCacheService
import net.nymtech.nymvpn.util.Constants
import net.nymtech.vpn.NymApi
import timber.log.Timber
import javax.inject.Inject

@SuppressLint("CustomSplashScreen")
@AndroidEntryPoint
class SplashActivity : ComponentActivity() {

@Inject
lateinit var gatewayRepository: GatewayRepository

@Inject
lateinit var countryCacheService: CountryCacheService

@Inject
lateinit var nymApi: NymApi

@Inject
lateinit var settingsRepository: SettingsRepository

override fun onCreate(savedInstanceState: Bundle?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val splashScreen = installSplashScreen()
splashScreen.setKeepOnScreenCondition { true }
}
super.onCreate(savedInstanceState)
lifecycleScope.launch {
lifecycleScope.launch(Dispatchers.IO) {
repeatOnLifecycle(Lifecycle.State.CREATED) {
// init data
settingsRepository.init()

NymVpn.applicationScope.launch(Dispatchers.IO) {
listOf(
async {
Timber.d("Updating exit country cache")
countryCacheService.updateExitCountriesCache()
Timber.d("Exit countries updated")
},
async {
Timber.d("Updating entry country cache")
countryCacheService.updateEntryCountriesCache()
Timber.d("Entry countries updated")
},
async {
Timber.d("Updating low latency country cache")
countryCacheService.updateLowLatencyEntryCountryCache()
Timber.d("Low latency country updated")
},
).awaitAll()
}

configureSentry()

val isAnalyticsShown = settingsRepository.isAnalyticsShown()

launch(Dispatchers.IO) {
countryCacheService.updateEntryCountriesCache()
countryCacheService.updateExitCountriesCache()
}

launch(Dispatchers.IO) {
countryCacheService.updateLowLatencyEntryCountryCache()
}

val intent = Intent(this@SplashActivity, MainActivity::class.java).apply {
putExtra(IS_ANALYTICS_SHOWN_INTENT_KEY, isAnalyticsShown)
}
Expand Down
Loading

0 comments on commit 5ebc19f

Please sign in to comment.