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
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

@RequiresApi(Build.VERSION_CODES.N)
class VpnTileService : TileService() {
Expand Down Expand Up @@ -49,22 +51,41 @@ class VpnTileService : TileService() {
return
}

tileScope.launch(Dispatchers.IO) {
val selectedProfile = AppDatabase.getInstance(this@VpnTileService)
.profileDao()
.getSelectedProfile()
tileScope.launch {
// Read the selected profile on a background thread — Room
// call must not block the system's main thread or ANR the
// quick-settings shade.
val selectedProfile = withContext(Dispatchers.IO) {
AppDatabase.getInstance(this@VpnTileService)
.profileDao()
.getSelectedProfile()
}

// If the tile service was torn down while we were reading
// the DB (user dismissed the shade, or Android killed the
// service), abandon the connect — tileScope is cancelled
// in onDestroy, but cancellation is cooperative.
if (!isActive) return@launch

launch(Dispatchers.Main) {
if (selectedProfile != null) {
VpnManager.connect(this@VpnTileService, selectedProfile)
updateTile()
} else {
openApp()
}
// Back on Dispatchers.Main (the scope's default dispatcher).
if (selectedProfile != null) {
VpnManager.connect(this@VpnTileService, selectedProfile)
updateTile()
} else {
openApp()
}
}
}

/**
* Test-only: returns the action the tile should take given the current
* selected profile. Pure function — does not touch Android framework.
*/
internal enum class TileAction { CONNECT, OPEN_APP }

internal fun tileActionForSelectedProfile(selectedProfile: com.gooserelay.gooserelayvpn.data.local.ProfileEntity?): TileAction =
if (selectedProfile != null) TileAction.CONNECT else TileAction.OPEN_APP

private fun openApp() {
val intent = Intent(this, MainActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.gooserelay.gooserelayvpn

import com.google.common.truth.Truth.assertThat
import com.gooserelay.gooserelayvpn.data.local.ProfileEntity
import com.gooserelay.gooserelayvpn.service.VpnTileService
import org.junit.Test

class VpnTileServiceConnectLogicTest {

@Test
fun `null profile returns OPEN_APP`() {
val action = VpnTileService().tileActionForSelectedProfile(null)
assertThat(action).isEqualTo(VpnTileService.TileAction.OPEN_APP)
}

@Test
fun `non-null profile returns CONNECT`() {
val profile = ProfileEntity(name = "test", tunnelKey = "x")
val action = VpnTileService().tileActionForSelectedProfile(profile)
assertThat(action).isEqualTo(VpnTileService.TileAction.CONNECT)
}
}
Loading