diff --git a/app-android/app/src/main/kotlin/com/algoritmico/passepartout/Globals.kt b/app-android/app/src/main/kotlin/com/algoritmico/passepartout/Globals.kt index ff5a6470d..76ba259d1 100644 --- a/app-android/app/src/main/kotlin/com/algoritmico/passepartout/Globals.kt +++ b/app-android/app/src/main/kotlin/com/algoritmico/passepartout/Globals.kt @@ -15,8 +15,13 @@ object Globals { const val logTag = "Passepartout" const val serviceLogTag = "PassepartoutVpnService" - const val EVENT_BUFFER_CAPACITY = 64 + // FIXME: Build bundle dynamically + const val BUNDLE_FILENAME = "bundle.json" + const val CONSTANTS_FILENAME = "constants.json" + const val PROFILES_DIRECTORY = "profiles-v1" + const val PROFILE_LAST_PATH = "tunnel_profile.json" + const val EVENT_BUFFER_CAPACITY = 64 const val EVENT_REPLAY = 64 } diff --git a/app-android/app/src/main/kotlin/com/algoritmico/passepartout/PassepartoutVpnService.kt b/app-android/app/src/main/kotlin/com/algoritmico/passepartout/PassepartoutVpnService.kt index 8e168d729..f5adf3674 100644 --- a/app-android/app/src/main/kotlin/com/algoritmico/passepartout/PassepartoutVpnService.kt +++ b/app-android/app/src/main/kotlin/com/algoritmico/passepartout/PassepartoutVpnService.kt @@ -1,102 +1,40 @@ package com.algoritmico.passepartout -import android.app.Notification import android.content.Intent import android.net.VpnService import android.os.IBinder -import androidx.core.app.NotificationChannelCompat -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat +import android.util.AtomicFile import com.algoritmico.passepartout.abi.PassepartoutWrapper import io.partout.PartoutVpnServiceRuntime import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import java.io.File class PassepartoutVpnService: VpnService() { private val runtime by lazy { PartoutVpnServiceRuntime( logTag = Globals.serviceLogTag, service = this, - engine = VpnEngine( - library = PassepartoutWrapper(), - bundleProvider = { - readAsset(BUNDLE_FILENAME) - }, - constantsProvider = { - readAsset(CONSTANTS_FILENAME) - }, - cachePathProvider = { - cacheDir.absolutePath - } - ), - stopService = { - stopSelf() - } + engine = engine ) } - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - return runtime.onStartCommand(intent, flags, startId) - } - - override fun onDestroy() { - runtime.onDestroy() - stopForeground(STOP_FOREGROUND_REMOVE) - super.onDestroy() - } - - override fun onRevoke() { - runtime.onRevoke() - } - - override fun onBind(intent: Intent?): IBinder? { - if (intent?.action == SERVICE_INTERFACE) { - return super.onBind(intent) - } - return runtime.onBind(intent) - } - - private fun createNotification(): Notification { - val channelId = NOTIFICATION_CHANNEL_ID - - // Create a notification channel (required on Android 8.0+) - val channel = NotificationChannelCompat.Builder( - channelId, - NotificationManagerCompat.IMPORTANCE_LOW // low importance to avoid sound - ) - .setName("Passepartout VPN") - .setDescription("Notification for the VPN foreground service") - .build() - - NotificationManagerCompat - .from(this) - .createNotificationChannel(channel) + private val engine = object : PartoutVpnServiceRuntime.Engine { + private val library = PassepartoutWrapper() + private val lastProfileFile: File + get() = File(noBackupFilesDir, Globals.PROFILE_LAST_PATH) - // Build the notification - return NotificationCompat.Builder(this, channelId) - .setContentTitle("Passepartout Active") - .setContentText("VPN is running") - .setOngoing(true) - .build() - } - - private class VpnEngine( - private val library: PassepartoutWrapper, - private val bundleProvider: suspend () -> String, - private val constantsProvider: suspend () -> String, - private val cachePathProvider: () -> String - ) : PartoutVpnServiceRuntime.Engine { override suspend fun start( runtime: PartoutVpnServiceRuntime, profileJSON: String ): PartoutVpnServiceRuntime.Result = withContext(Dispatchers.IO) { PartoutVpnServiceRuntime.Result( library.tunnelStart( - bundleProvider(), - constantsProvider(), + readAsset(Globals.BUNDLE_FILENAME), + readAsset(Globals.CONSTANTS_FILENAME), profileJSON, - cachePathProvider(), + cacheDir.absolutePath, runtime ), null @@ -110,15 +48,48 @@ class PassepartoutVpnService: VpnService() { } result.await() } + + override suspend fun readLastProfile(): String { + return withContext(Dispatchers.IO) { + AtomicFile(lastProfileFile).openRead().bufferedReader(Charsets.UTF_8).use { + it.readText() + } + } + } + + override suspend fun writeLastProfile(json: String) { + withContext(Dispatchers.IO) { + val file = AtomicFile(lastProfileFile) + val stream = file.startWrite() + try { + stream.write(json.toByteArray(Charsets.UTF_8)) + file.finishWrite(stream) + } catch (e: Exception) { + file.failWrite(stream) + throw e + } + } + } } - companion object { - private const val BUNDLE_FILENAME = "bundle.json" + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + return runtime.onStartCommand(intent, flags, startId) + } - private const val CONSTANTS_FILENAME = "constants.json" + override fun onDestroy() { + runtime.onDestroy() + stopForeground(STOP_FOREGROUND_REMOVE) + super.onDestroy() + } - private const val NOTIFICATION_ID = 1 + override fun onRevoke() { + runtime.onRevoke() + } - private const val NOTIFICATION_CHANNEL_ID = "vpn_service_channel" + override fun onBind(intent: Intent?): IBinder? { + if (intent?.action == SERVICE_INTERFACE) { + return super.onBind(intent) + } + return runtime.onBind(intent) } -} \ No newline at end of file +} diff --git a/app-android/app/src/main/kotlin/com/algoritmico/passepartout/ui/AppContext.kt b/app-android/app/src/main/kotlin/com/algoritmico/passepartout/ui/AppContext.kt index 370af5a78..c879a6750 100644 --- a/app-android/app/src/main/kotlin/com/algoritmico/passepartout/ui/AppContext.kt +++ b/app-android/app/src/main/kotlin/com/algoritmico/passepartout/ui/AppContext.kt @@ -8,12 +8,12 @@ import android.content.Context import android.content.Intent import android.util.Log import com.algoritmico.passepartout.Globals +import com.algoritmico.passepartout.PassepartoutVpnService import com.algoritmico.passepartout.abi.AppABIProfile import com.algoritmico.passepartout.abi.PassepartoutWrapper import com.algoritmico.passepartout.abi.helpers.ABIEventDispatcher import com.algoritmico.passepartout.abi.models.Event import com.algoritmico.passepartout.readAsset -import com.algoritmico.passepartout.PassepartoutVpnService import io.partout.PartoutTunnel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow @@ -46,9 +46,9 @@ class AppContext( Log.i(Globals.logTag, ">>> Partout $partoutVersion") Log.e(Globals.logTag, ">>> Started app") - val bundle = applicationContext.readAsset(BUNDLE_FILENAME) - val constants = applicationContext.readAsset(CONSTANTS_FILENAME) - val profilesDirectory = File(applicationContext.noBackupFilesDir, PROFILES_DIRECTORY) + val bundle = applicationContext.readAsset(Globals.BUNDLE_FILENAME) + val constants = applicationContext.readAsset(Globals.CONSTANTS_FILENAME) + val profilesDirectory = File(applicationContext.noBackupFilesDir, Globals.PROFILES_DIRECTORY) .apply { mkdirs() } @@ -101,12 +101,4 @@ class AppContext( tunnelObservable.close() library.appDeinit { _, _ -> } } - - companion object { - private const val BUNDLE_FILENAME = "bundle.json" - - private const val CONSTANTS_FILENAME = "constants.json" - - private const val PROFILES_DIRECTORY = "profiles-v1" - } } diff --git a/app-cross/partout b/app-cross/partout index fcafa391f..554eafa33 160000 --- a/app-cross/partout +++ b/app-cross/partout @@ -1 +1 @@ -Subproject commit fcafa391f637c41fe003e7ba494e628a6852b0e5 +Subproject commit 554eafa334b7a8dd78c8dcbe8f930a5110463437