diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ea6dde3..e573b28 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -204,5 +204,10 @@ + + diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/AppModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/AppModule.kt index 2a7b48c..246b7fa 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/AppModule.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/AppModule.kt @@ -2,6 +2,8 @@ package com.zaneschepke.wireguardautotunnel.module import android.content.Context import com.zaneschepke.logcatter.LogReader +import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService +import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification import com.zaneschepke.logcatter.LogcatReader import dagger.Module import dagger.Provides @@ -27,4 +29,10 @@ class AppModule { fun provideLogCollect(@ApplicationContext context: Context): LogReader { return LogcatReader.init(storageDir = context.filesDir.absolutePath) } + + @Singleton + @Provides + fun provideNotificationService(@ApplicationContext context: Context): NotificationService { + return WireGuardNotification(context) + } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt index 3810bb2..0dfb304 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/ServiceModule.kt @@ -4,8 +4,6 @@ import com.zaneschepke.wireguardautotunnel.service.network.EthernetService import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService import com.zaneschepke.wireguardautotunnel.service.network.NetworkService import com.zaneschepke.wireguardautotunnel.service.network.WifiService -import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService -import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -15,10 +13,6 @@ import dagger.hilt.android.scopes.ServiceScoped @Module @InstallIn(ServiceComponent::class) abstract class ServiceModule { - @Binds - @ServiceScoped - abstract fun provideNotificationService(wireGuardNotification: WireGuardNotification): NotificationService - @Binds @ServiceScoped abstract fun provideWifiService(wifiService: WifiService): NetworkService diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt index 5c9ceff..42f5a3d 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/module/TunnelModule.kt @@ -10,6 +10,7 @@ import com.wireguard.android.util.ToolsInstaller import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager +import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService import com.zaneschepke.wireguardautotunnel.service.tunnel.WireGuardTunnel import dagger.Module @@ -76,6 +77,7 @@ class TunnelModule { @ApplicationScope applicationScope: CoroutineScope, @IoDispatcher ioDispatcher: CoroutineDispatcher, serviceManager: ServiceManager, + notificationService: NotificationService, ): TunnelService { return WireGuardTunnel( amneziaBackend, @@ -85,12 +87,17 @@ class TunnelModule { applicationScope, ioDispatcher, serviceManager, + notificationService, ) } @Singleton @Provides - fun provideServiceManager(@ApplicationContext context: Context): ServiceManager { - return ServiceManager.getInstance(context) + fun provideServiceManager( + @ApplicationContext context: Context, + @IoDispatcher ioDispatcher: CoroutineDispatcher, + appDataRepository: AppDataRepository, + ): ServiceManager { + return ServiceManager(context, ioDispatcher, appDataRepository) } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/KernelReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/KernelReceiver.kt index 980b62e..c43d198 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/KernelReceiver.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/KernelReceiver.kt @@ -5,8 +5,8 @@ import android.content.Context import android.content.Intent import com.zaneschepke.wireguardautotunnel.data.repository.TunnelConfigRepository import com.zaneschepke.wireguardautotunnel.module.ApplicationScope +import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService -import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -26,6 +26,9 @@ class KernelReceiver : BroadcastReceiver() { @Inject lateinit var tunnelConfigRepository: TunnelConfigRepository + @Inject + lateinit var serviceManager: ServiceManager + override fun onReceive(context: Context, intent: Intent) { val action = intent.action ?: return applicationScope.launch { @@ -37,7 +40,7 @@ class KernelReceiver : BroadcastReceiver() { tunnelConfigRepository.save(it.copy(isActive = true)) } } - context.requestTunnelTileServiceStateUpdate() + serviceManager.updateTunnelTile() } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt new file mode 100644 index 0000000..9ae409b --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt @@ -0,0 +1,36 @@ +package com.zaneschepke.wireguardautotunnel.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.zaneschepke.wireguardautotunnel.module.ApplicationScope +import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager +import com.zaneschepke.wireguardautotunnel.service.notification.NotificationAction +import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import javax.inject.Inject + +@AndroidEntryPoint +class NotificationActionReceiver : BroadcastReceiver() { + + @Inject + lateinit var serviceManager: ServiceManager + + @Inject + lateinit var tunnelService: TunnelService + + @Inject + @ApplicationScope + lateinit var applicationScope: CoroutineScope + + override fun onReceive(context: Context, intent: Intent) { + applicationScope.launch { + when (intent.action) { + NotificationAction.AUTO_TUNNEL_OFF.name -> serviceManager.stopAutoTunnel() + NotificationAction.TUNNEL_OFF.name -> tunnelService.stopTunnel() + } + } + } +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceManager.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceManager.kt index e1b51b9..4fd3089 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceManager.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceManager.kt @@ -3,20 +3,25 @@ package com.zaneschepke.wireguardautotunnel.service.foreground import android.app.Service import android.content.Context import android.content.Intent +import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.service.foreground.autotunnel.AutoTunnelService -import com.zaneschepke.wireguardautotunnel.util.SingletonHolder +import com.zaneschepke.wireguardautotunnel.service.tile.AutoTunnelControlTile +import com.zaneschepke.wireguardautotunnel.service.tile.TunnelControlTile +import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate import jakarta.inject.Inject import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.withContext import timber.log.Timber @OptIn(ExperimentalCoroutinesApi::class) class ServiceManager -@Inject constructor(private val context: Context) { +@Inject constructor(private val context: Context, private val ioDispatcher: CoroutineDispatcher, private val appDataRepository: AppDataRepository) { private val _autoTunnelActive = MutableStateFlow(false) @@ -24,8 +29,8 @@ class ServiceManager var autoTunnelService = CompletableDeferred() var backgroundService = CompletableDeferred() - - companion object : SingletonHolder(::ServiceManager) + var autoTunnelTile = CompletableDeferred() + var tunnelControlTile = CompletableDeferred() private fun startService(cls: Class, background: Boolean) { runCatching { @@ -39,12 +44,15 @@ class ServiceManager } suspend fun startAutoTunnel(background: Boolean) { + val settings = appDataRepository.settings.getSettings() + appDataRepository.settings.save(settings.copy(isAutoTunnelEnabled = true)) if (autoTunnelService.isCompleted) return _autoTunnelActive.update { true } kotlin.runCatching { startService(AutoTunnelService::class.java, background) autoTunnelService.await() autoTunnelService.getCompleted().start() _autoTunnelActive.update { true } + updateAutoTunnelTile() }.onFailure { Timber.e(it) } @@ -70,17 +78,41 @@ class ServiceManager } } - fun stopAutoTunnel() { - if (!autoTunnelService.isCompleted) return - runCatching { - autoTunnelService.getCompleted().stop() - _autoTunnelActive.update { false } - }.onFailure { - Timber.e(it) + suspend fun toggleAutoTunnel(background: Boolean) { + withContext(ioDispatcher) { + if (_autoTunnelActive.value) return@withContext stopAutoTunnel() + startAutoTunnel(background) } } - fun requestTunnelTileUpdate() { - context.requestTunnelTileServiceStateUpdate() + fun updateAutoTunnelTile() { + if (autoTunnelTile.isCompleted) { + autoTunnelTile.getCompleted().updateTileState() + } else { + context.requestAutoTunnelTileServiceUpdate() + } + } + + fun updateTunnelTile() { + if (tunnelControlTile.isCompleted) { + tunnelControlTile.getCompleted().updateTileState() + } else { + context.requestTunnelTileServiceStateUpdate() + } + } + + suspend fun stopAutoTunnel() { + withContext(ioDispatcher) { + val settings = appDataRepository.settings.getSettings() + appDataRepository.settings.save(settings.copy(isAutoTunnelEnabled = false)) + if (!autoTunnelService.isCompleted) return@withContext + runCatching { + autoTunnelService.getCompleted().stop() + _autoTunnelActive.update { false } + updateAutoTunnelTile() + }.onFailure { + Timber.e(it) + } + } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/TunnelBackgroundService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/TunnelBackgroundService.kt index 7bec3d5..02e6dbe 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/TunnelBackgroundService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/TunnelBackgroundService.kt @@ -7,6 +7,7 @@ import androidx.core.app.ServiceCompat import androidx.lifecycle.LifecycleService import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService +import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification import com.zaneschepke.wireguardautotunnel.util.Constants import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CompletableDeferred @@ -21,8 +22,6 @@ class TunnelBackgroundService : LifecycleService() { @Inject lateinit var serviceManager: ServiceManager - private val foregroundId = 123 - override fun onCreate() { super.onCreate() start() @@ -42,7 +41,7 @@ class TunnelBackgroundService : LifecycleService() { fun start() { ServiceCompat.startForeground( this, - foregroundId, + NotificationService.KERNEL_SERVICE_NOTIFICATION_ID, createNotification(), Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID, ) @@ -60,8 +59,7 @@ class TunnelBackgroundService : LifecycleService() { private fun createNotification(): Notification { return notificationService.createNotification( - getString(R.string.vpn_channel_id), - getString(R.string.vpn_channel_name), + WireGuardNotification.NotificationChannels.VPN, getString(R.string.tunnel_running), description = "", ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt index df8b9ef..d8c1b1b 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/autotunnel/AutoTunnelService.kt @@ -24,7 +24,9 @@ import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService import com.zaneschepke.wireguardautotunnel.service.network.NetworkService import com.zaneschepke.wireguardautotunnel.service.network.NetworkStatus import com.zaneschepke.wireguardautotunnel.service.network.WifiService +import com.zaneschepke.wireguardautotunnel.service.notification.NotificationAction import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService +import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.extensions.TunnelConfigs @@ -53,7 +55,6 @@ import javax.inject.Provider @AndroidEntryPoint class AutoTunnelService : LifecycleService() { - private val foregroundId = 122 @Inject @AppShell @@ -147,14 +148,16 @@ class AutoTunnelService : LifecycleService() { private fun launchWatcherNotification(description: String = getString(R.string.monitoring_state_changes)) { val notification = notificationService.createNotification( - channelId = getString(R.string.watcher_channel_id), - channelName = getString(R.string.watcher_channel_name), + WireGuardNotification.NotificationChannels.AUTO_TUNNEL, title = getString(R.string.auto_tunnel_title), description = description, + actions = listOf( + notificationService.createNotificationAction(NotificationAction.AUTO_TUNNEL_OFF), + ), ) ServiceCompat.startForeground( this, - foregroundId, + NotificationService.AUTO_TUNNEL_NOTIFICATION_ID, notification, Constants.SYSTEM_EXEMPT_SERVICE_TYPE_ID, ) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationAction.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationAction.kt new file mode 100644 index 0000000..5ff2e8c --- /dev/null +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationAction.kt @@ -0,0 +1,17 @@ +package com.zaneschepke.wireguardautotunnel.service.notification + +import android.content.Context +import com.zaneschepke.wireguardautotunnel.R + +enum class NotificationAction { + TUNNEL_OFF, + AUTO_TUNNEL_OFF, + ; + + fun title(context: Context): String { + return when (this) { + TUNNEL_OFF -> context.getString(R.string.stop) + AUTO_TUNNEL_OFF -> context.getString(R.string.stop) + } + } +} diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt index 0cc604d..9fd7c00 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/NotificationService.kt @@ -2,21 +2,32 @@ package com.zaneschepke.wireguardautotunnel.service.notification import android.app.Notification import android.app.NotificationManager -import android.app.PendingIntent +import android.content.Context +import androidx.core.app.NotificationCompat +import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification.NotificationChannels interface NotificationService { + val context: Context fun createNotification( - channelId: String, - channelName: String, + channel: NotificationChannels, title: String = "", - action: PendingIntent? = null, - actionText: String? = null, - description: String, + actions: Collection = emptyList(), + description: String = "", showTimestamp: Boolean = false, importance: Int = NotificationManager.IMPORTANCE_HIGH, - vibration: Boolean = false, onGoing: Boolean = true, - lights: Boolean = true, onlyAlertOnce: Boolean = true, ): Notification + + fun createNotificationAction(action: NotificationAction): NotificationCompat.Action + + fun remove(notificationId: Int) + + fun show(notificationId: Int, notification: Notification) + + companion object { + const val KERNEL_SERVICE_NOTIFICATION_ID = 123 + const val AUTO_TUNNEL_NOTIFICATION_ID = 122 + const val VPN_NOTIFICATION_ID = 100 + } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt index 1409697..2b431c2 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/notification/WireGuardNotification.kt @@ -1,105 +1,134 @@ package com.zaneschepke.wireguardautotunnel.service.notification +import android.Manifest import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.graphics.Color +import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat import com.zaneschepke.wireguardautotunnel.R -import com.zaneschepke.wireguardautotunnel.ui.MainActivity +import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject class WireGuardNotification @Inject constructor( - @ApplicationContext private val context: Context, -) : - NotificationService { - private val notificationManager = - context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + @ApplicationContext override val context: Context, +) : NotificationService { - private val watcherBuilder: NotificationCompat.Builder = - NotificationCompat.Builder( - context, - context.getString(R.string.watcher_channel_id), - ) - private val tunnelBuilder: NotificationCompat.Builder = - NotificationCompat.Builder( - context, - context.getString(R.string.vpn_channel_id), - ) + enum class NotificationChannels { + VPN, + AUTO_TUNNEL, + } + + private val notificationManager = NotificationManagerCompat.from(context) override fun createNotification( - channelId: String, - channelName: String, + channel: NotificationChannels, title: String, - action: PendingIntent?, - actionText: String?, + actions: Collection, description: String, showTimestamp: Boolean, importance: Int, - vibration: Boolean, onGoing: Boolean, - lights: Boolean, onlyAlertOnce: Boolean, ): Notification { - val channel = - NotificationChannel( - channelId, - channelName, - importance, - ) - .let { - it.description = title - it.enableLights(lights) - it.lightColor = Color.RED - it.enableVibration(vibration) - it.vibrationPattern = longArrayOf(100, 200, 300) - it - } - notificationManager.createNotificationChannel(channel) - val pendingIntent: PendingIntent = - Intent(context, MainActivity::class.java).let { notificationIntent -> - PendingIntent.getActivity( + notificationManager.createNotificationChannel(channel.asChannel()) + return channel.asBuilder().apply { + actions.forEach { + addAction(it) + } + setContentTitle(title) + setContentText(description) + setOnlyAlertOnce(onlyAlertOnce) + setOngoing(onGoing) + setPriority(NotificationCompat.PRIORITY_HIGH) + setShowWhen(showTimestamp) + setSmallIcon(R.drawable.ic_launcher) + }.build() + } + + override fun createNotificationAction(notificationAction: NotificationAction): NotificationCompat.Action { + val pendingIntent = PendingIntent.getBroadcast( + context, + 0, + Intent(context, NotificationActionReceiver::class.java).apply { + action = notificationAction.name + }, + PendingIntent.FLAG_IMMUTABLE, + ) + return NotificationCompat.Action.Builder( + R.drawable.ic_launcher, + notificationAction.title(context).uppercase(), + pendingIntent, + ).build() + } + + override fun remove(notificationId: Int) { + notificationManager.cancel(notificationId) + } + + override fun show(notificationId: Int, notification: Notification) { + with(notificationManager) { + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + return + } + notify(notificationId, notification) + } + } + + fun NotificationChannels.asBuilder(): NotificationCompat.Builder { + return when (this) { + NotificationChannels.VPN -> { + NotificationCompat.Builder( context, - 0, - notificationIntent, - PendingIntent.FLAG_IMMUTABLE, + context.getString(R.string.auto_tunnel_channel_id), ) } + NotificationChannels.AUTO_TUNNEL -> { + NotificationCompat.Builder( + context, + context.getString(R.string.vpn_channel_id), + ) + } + } + } - val builder = - when (channelId) { - context.getString(R.string.watcher_channel_id) -> watcherBuilder - context.getString(R.string.vpn_channel_id) -> tunnelBuilder - else -> { - NotificationCompat.Builder( - context, - channelId, - ) + fun NotificationChannels.asChannel(): NotificationChannel { + return when (this) { + NotificationChannels.VPN -> { + NotificationChannel( + context.getString(R.string.vpn_channel_id), + context.getString(R.string.vpn_channel_name), + NotificationManager.IMPORTANCE_HIGH, + ).apply { + description = context.getString(R.string.vpn_channel_description) + enableLights(true) + lightColor = Color.WHITE + enableVibration(false) + vibrationPattern = longArrayOf(100, 200, 300) } } - - return builder.let { - if (action != null && actionText != null) { - it.addAction( - NotificationCompat.Action.Builder(0, actionText, action).build(), - ) - it.setAutoCancel(true) + NotificationChannels.AUTO_TUNNEL -> { + NotificationChannel( + context.getString(R.string.auto_tunnel_channel_id), + context.getString(R.string.auto_tunnel_channel_name), + NotificationManager.IMPORTANCE_HIGH, + ).apply { + description = context.getString(R.string.auto_tunnel_channel_description) + enableLights(true) + lightColor = Color.WHITE + enableVibration(false) + vibrationPattern = longArrayOf(100, 200, 300) + } } - it.setContentTitle(title) - .setContentText(description) - .setOnlyAlertOnce(onlyAlertOnce) - .setContentIntent(pendingIntent) - .setOngoing(onGoing) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setShowWhen(showTimestamp) - .setSmallIcon(R.drawable.ic_launcher) - .build() } } } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/AutoTunnelControlTile.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/AutoTunnelControlTile.kt index 0a445e3..20dc8cc 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/AutoTunnelControlTile.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/AutoTunnelControlTile.kt @@ -1,24 +1,18 @@ package com.zaneschepke.wireguardautotunnel.service.tile -import android.content.Intent -import android.os.IBinder import android.service.quicksettings.Tile import android.service.quicksettings.TileService -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LifecycleRegistry -import androidx.lifecycle.lifecycleScope import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.module.ApplicationScope import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint -class AutoTunnelControlTile : TileService(), LifecycleOwner { +class AutoTunnelControlTile : TileService() { @Inject lateinit var appDataRepository: AppDataRepository @@ -29,32 +23,26 @@ class AutoTunnelControlTile : TileService(), LifecycleOwner { @ApplicationScope lateinit var applicationScope: CoroutineScope - private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this) - override fun onCreate() { super.onCreate() - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) - } - - override fun onStopListening() { - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP) + serviceManager.autoTunnelTile.complete(this) } override fun onDestroy() { super.onDestroy() - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) + serviceManager.autoTunnelTile = CompletableDeferred() } override fun onStartListening() { super.onStartListening() - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START) - lifecycleScope.launch { + serviceManager.autoTunnelTile.complete(this) + applicationScope.launch { if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable() updateTileState() } } - private fun updateTileState() { + fun updateTileState() { serviceManager.autoTunnelActive.value.let { if (it) setActive() else setInactive() } @@ -63,7 +51,7 @@ class AutoTunnelControlTile : TileService(), LifecycleOwner { override fun onClick() { super.onClick() unlockAndRun { - lifecycleScope.launch { + applicationScope.launch { if (serviceManager.autoTunnelActive.value) { serviceManager.stopAutoTunnel() setInactive() @@ -95,18 +83,4 @@ class AutoTunnelControlTile : TileService(), LifecycleOwner { qsTile.updateTile() } } - - /* This works around an annoying unsolved frameworks bug some people are hitting. */ - override fun onBind(intent: Intent): IBinder? { - var ret: IBinder? = null - try { - ret = super.onBind(intent) - } catch (_: Throwable) { - Timber.e("Failed to bind to TunnelControlTile") - } - return ret - } - - override val lifecycle: Lifecycle - get() = lifecycleRegistry } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt index e672f9e..ad7cb02 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt @@ -1,27 +1,22 @@ package com.zaneschepke.wireguardautotunnel.service.tile -import android.content.Intent import android.os.Build -import android.os.IBinder import android.service.quicksettings.Tile import android.service.quicksettings.TileService -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LifecycleRegistry -import androidx.lifecycle.lifecycleScope import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository import com.zaneschepke.wireguardautotunnel.module.ApplicationScope +import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import timber.log.Timber import javax.inject.Inject import javax.inject.Provider @AndroidEntryPoint -class TunnelControlTile : TileService(), LifecycleOwner { +class TunnelControlTile : TileService() { @Inject lateinit var appDataRepository: AppDataRepository @@ -32,33 +27,29 @@ class TunnelControlTile : TileService(), LifecycleOwner { @ApplicationScope lateinit var applicationScope: CoroutineScope - private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this) + @Inject + lateinit var serviceManager: ServiceManager override fun onCreate() { super.onCreate() - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) - } - - override fun onStopListening() { - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP) + serviceManager.tunnelControlTile.complete(this) } override fun onDestroy() { super.onDestroy() - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) + serviceManager.tunnelControlTile = CompletableDeferred() } override fun onStartListening() { super.onStartListening() - lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START) - Timber.d("Updating tile!") - lifecycleScope.launch { + serviceManager.tunnelControlTile.complete(this) + applicationScope.launch { if (appDataRepository.tunnels.getAll().isEmpty()) return@launch setUnavailable() updateTileState() } } - private suspend fun updateTileState() { + fun updateTileState() = applicationScope.launch { val lastActive = appDataRepository.getStartTunnelConfig() lastActive?.let { updateTile(it) @@ -68,7 +59,7 @@ class TunnelControlTile : TileService(), LifecycleOwner { override fun onClick() { super.onClick() unlockAndRun { - lifecycleScope.launch { + applicationScope.launch { val lastActive = appDataRepository.getStartTunnelConfig() lastActive?.let { tunnel -> if (tunnel.isActive) { @@ -125,18 +116,4 @@ class TunnelControlTile : TileService(), LifecycleOwner { } } } - - /* This works around an annoying unsolved frameworks bug some people are hitting. */ - override fun onBind(intent: Intent): IBinder? { - var ret: IBinder? = null - try { - ret = super.onBind(intent) - } catch (_: Throwable) { - Timber.e("Failed to bind to TunnelControlTile") - } - return ret - } - - override val lifecycle: Lifecycle - get() = lifecycleRegistry } diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt index dd10821..4c81940 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tunnel/WireGuardTunnel.kt @@ -9,6 +9,8 @@ import com.zaneschepke.wireguardautotunnel.module.ApplicationScope import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.module.Kernel import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager +import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService +import com.zaneschepke.wireguardautotunnel.service.notification.WireGuardNotification import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics @@ -27,6 +29,9 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.amnezia.awg.backend.Tunnel +import com.zaneschepke.wireguardautotunnel.R +import com.zaneschepke.wireguardautotunnel.service.notification.NotificationAction +import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService.Companion.VPN_NOTIFICATION_ID import timber.log.Timber import javax.inject.Inject import javax.inject.Provider @@ -41,6 +46,7 @@ constructor( @ApplicationScope private val applicationScope: CoroutineScope, @IoDispatcher private val ioDispatcher: CoroutineDispatcher, private val serviceManager: ServiceManager, + private val notificationService: NotificationService, ) : TunnelService { private val _vpnState = MutableStateFlow(VpnState()) @@ -116,6 +122,16 @@ constructor( setState(tunnelConfig, TunnelState.UP).onSuccess { startActiveTunnelJobs() if (it.isUp()) appDataRepository.tunnels.save(tunnelConfig.copy(isActive = true)) + with(notificationService) { + val notification = createNotification( + WireGuardNotification.NotificationChannels.VPN, + title = "${context.getString(R.string.tunnel_running)} - ${tunnelConfig.name}", + actions = listOf( + notificationService.createNotificationAction(NotificationAction.TUNNEL_OFF), + ), + ) + show(VPN_NOTIFICATION_ID, notification) + } updateTunnelState(it, tunnelConfig) } }.onFailure { @@ -134,6 +150,7 @@ constructor( setState(tunnelConfig, TunnelState.DOWN).onSuccess { updateTunnelState(it, null) onStop(tunnelConfig) + notificationService.remove(VPN_NOTIFICATION_ID) stopBackgroundService() }.onFailure { Timber.e(it) @@ -161,9 +178,7 @@ constructor( } callback() } - is Backend -> { - callback() - } + is Backend -> callback() } } @@ -181,9 +196,7 @@ constructor( is org.amnezia.awg.backend.Backend -> { backend.backendState.asBackendState() } - is Backend -> { - BackendState.SERVICE_ACTIVE - } + is Backend -> BackendState.SERVICE_ACTIVE else -> BackendState.INACTIVE } } @@ -213,12 +226,12 @@ constructor( private suspend fun startBackgroundService() { serviceManager.startBackgroundService() - serviceManager.requestTunnelTileUpdate() + serviceManager.updateTunnelTile() } private fun stopBackgroundService() { serviceManager.stopBackgroundService() - serviceManager.requestTunnelTileUpdate() + serviceManager.updateTunnelTile() } private suspend fun onBeforeStart(background: Boolean) { @@ -320,14 +333,14 @@ constructor( _vpnState.update { it.copy(status = TunnelState.from(newState)) } - serviceManager.requestTunnelTileUpdate() + serviceManager.updateTunnelTile() } override fun onStateChange(state: State) { _vpnState.update { it.copy(status = TunnelState.from(state)) } - serviceManager.requestTunnelTileUpdate() + serviceManager.updateTunnelTile() } companion object { diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt index d5d0bc3..c96912d 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/main/MainViewModel.kt @@ -159,18 +159,7 @@ constructor( } fun onToggleAutoTunnel() = viewModelScope.launch { - val settings = appDataRepository.settings.getSettings() - val toggled = !settings.isAutoTunnelEnabled - if (toggled) { - serviceManager.startAutoTunnel(false) - } else { - serviceManager.stopAutoTunnel() - } - appDataRepository.settings.save( - settings.copy( - isAutoTunnelEnabled = toggled, - ), - ) + serviceManager.toggleAutoTunnel(false) } private suspend fun saveTunnelsFromZipUri(uri: Uri, context: Context) { diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1c8c1bb..bbd33fa 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -81,7 +81,6 @@ Version Einstellungen Unterstützung - Wächterkanal Authentifizierung fehlgeschlagen Konfigurationen exportieren Unbekannter Fehler aufgetreten @@ -99,7 +98,6 @@ Als Primären Tunnel setzen VPN Kanal VPN Benachrichtigungskanal - Wächterbenachrichtigungskanal Aktion erfordert deaktivierten Tunnel Kernel Kernelmodul verwenden @@ -171,4 +169,4 @@ Wildcards für Namen verwenden Auto-Tunnel stoppen Überwache Statusänderungen - \ No newline at end of file + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index f728098..fadb178 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -101,10 +101,8 @@ WG Tunnel Canal VPN Canal de notificación VPN - Canal del obvervador - Canal de notificación del obvervador - La monitorización SSID Wi-Fi necesita de permiso de ubicación en segundo plano incluso si la app está cerrada. Mira el enlace a la Política de Privacidad en la pantalla de ayuda para más detalles. + La monitorización SSID Wi-Fi necesita de permiso de ubicación en segundo plano incluso si la app está cerrada. Mira el enlace a la Política de Privacidad en la pantalla de ayuda para más detalles. Recuento de paquetes basura Tamaño mínimo del paquete basura Agregar desde el portapapeles - \ No newline at end of file + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 94df80e..25fcb63 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,8 +1,6 @@ Canal VPN - Canal de surveillance - Canal de notification de surveillance Cette action nécessite la désactivation du tunnel Aucun tunnel n\'a été ajouté pour le moment ! Tunnels @@ -171,4 +169,4 @@ Noyau non supporté Démarrer l\'auto-tunnel Cette modification nécessite un redémarrage de l\'application. Voulez-vous continuer ? - \ No newline at end of file + diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 3b07fb2..281b9b8 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -6,7 +6,6 @@ Kernel Ukuran sampah paket init WG Tunnel - Notifikasi Saluran Pengamat File bukan .conf atau .zip Aksi memerlukan tunnel mati Belum ada tunnel yang ditambahkan! @@ -99,7 +98,6 @@ handshake Saluran VPN Notifikasi Saluran VPN - Saluran Pengamat Fitur ini memerlukan izin lokasi latar belakang untuk mengaktifkan pemantauan SSID Wi-Fi bahkan saat aplikasi ditutup. Untuk detail lebih lanjut, silakan lihat Kebijakan Privasi yang ditautkan di layar Dukungan. Salin kunci publik kunci base64 @@ -117,4 +115,4 @@ Buat pin Ditetapkan sebagai tunnel data seluler Ditetapkan sebagai tunnel utama - \ No newline at end of file + diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 059c32f..d232539 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -15,8 +15,6 @@ Icoon Meenemen Adres - Watcher Kanaal - Watcher Notificatiekanaal Peer (extern systeem) Allowed IPs Altijd-aan VPN toestaan @@ -138,4 +136,4 @@ De app kan geen ingeschakelde locatieservices op je apparaat vinden. Afhankelijk van het type apparaat kan dit leiden tot een niet functionerende herkenning van het verbonden WiFi netwerk. De niet-vertrouwde WiFi functionaliteit werkt daardoor mogelijk niet. Toch doorgaan? Permanente achtergrondtoegang tot exacte locatie is vereist voor deze functie. Bekijk aub de Transport packet magic header - \ No newline at end of file + diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 63561b2..800967c 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -99,8 +99,6 @@ WG Tunnel Canal de VPN Canal de notificações VPN - Canal de vigia - Canal de notificações de vigia Este recurso precisa de permissões de localização em segundo plano para ativar o monitoramento do SSID da rede Wi-Fi mesmo quando a aplicação está fechado. Para mais pormenores, por favor veja a Política de Privacidade no ecrã de Suporte. Envie o SSID Adicionar a partir de ficheiro ou zip @@ -132,4 +130,4 @@ guia de início rápido para ter certeza que VPN Sempre-ligada é desligada para todas as outras aplicações e tente novamente Reiniciar no arranque - \ No newline at end of file + diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index af028ba..14e8909 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -110,8 +110,6 @@ Formato de configuração inválido Canal de VPN Canal de notificações VPN - Canal de vigia - Canal de notificações de vigia wg-tunnel-db Definir ip ping personalizado Permissão negada @@ -130,4 +128,4 @@ handshake Permitir que toda a permissão de localização do tempo e/ou localização precisa é necessária para este recurso. Por favor, veja seg - \ No newline at end of file + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a8a6f89..481ea04 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -63,8 +63,6 @@ WG Tunnel Канал VPN Канал уведомлений VPN - Канал наблюдателя - Канал уведомлений наблюдателя Туннели Хорошо Фоновая передача местоположения @@ -171,4 +169,4 @@ Отслеживание изменений состояния Включить ведение журнала Добавить из буфера обмена - \ No newline at end of file + diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index f305881..e0f2421 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -3,8 +3,6 @@ WG Tunnel VPN Kanalı VPN Bildirim Kanalı - İzleyici Kanalı - İzleyici Bildirim Kanalı https://github.com/zaneschepke/wgtunnel/issues https://zaneschepke.com/wgtunnel-docs/overview.html https://zaneschepke.com/wgtunnel-docs/privacypolicy.html @@ -124,4 +122,4 @@ başlangıç kılavuzu Geçersiz tünel yapılandırma formatı Önyüklemede yeniden başlat - \ No newline at end of file + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index fc7db67..ff7f4f8 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -97,12 +97,10 @@ 没有安装浏览器 密码不正确 自定义 Ping 的目标 ip - 守护者通知频道 VPN 频道 无效包计数 WG Tunnel VPN 通知频道 - 守护者频道 查看问题 查看日志 (自动) @@ -171,4 +169,4 @@ 开启本地日志 配置更改 此更改需要重新启动应用程序。您是否要继续? - \ No newline at end of file + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fafc672..7117deb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,9 +2,7 @@ WG Tunnel VPN Channel VPN Notification Channel - Watcher Channel - Watcher Notification Channel - https://github.com/zaneschepke/wgtunnel/issues + https://github.com/zaneschepke/wgtunnel/issues https://zaneschepke.com/wgtunnel-docs/overview.html https://zaneschepke.com/wgtunnel-docs/privacypolicy.html https://zaneschepke.com/wgtunnel-docs/features.html#wildcard-wi-fi-name-support @@ -82,7 +80,7 @@ No file explorer installed Invalid QR code The app is not detecting any location services enabled on your device. Depending on the device, this could cause the untrusted wifi feature to fail to read the wifi name. Would you like to continue anyways? - Auto-tunnel Service + Auto-tunnel service Delete tunnel Are you sure you would like to delete this tunnel? Yes @@ -187,4 +185,9 @@ Kill switch options Allow LAN traffic Bypass LAN for kill switch + A channel for VPN state notifications + Auto-tunnel Channel + Auto-tunnel Notification Channel + A channel for auto-tunnel state notifications + stop