diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 9c2f84f..fda21cf 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -16,8 +16,8 @@ android {
compileSdk = 34
val versionMajor = 2
- val versionMinor = 3
- val versionPatch = 7
+ val versionMinor = 4
+ val versionPatch = 1
val versionBuild = 0
defaultConfig {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 716f987..f674159 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -58,6 +58,10 @@
+
@@ -82,6 +86,7 @@
android:name=".service.foreground.WireGuardTunnelService"
android:permission="android.permission.BIND_VPN_SERVICE"
android:enabled="true"
+ android:persistent="true"
android:foregroundServiceType="remoteMessaging"
android:exported="false">
@@ -94,6 +99,7 @@
android:name=".service.foreground.WireGuardConnectivityWatcherService"
android:enabled="true"
android:stopWithTask="false"
+ android:persistent="true"
android:foregroundServiceType="location"
android:permission=""
android:exported="false">
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt
index db1c71b..03b8e9f 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/BootReceiver.kt
@@ -3,11 +3,8 @@ package com.zaneschepke.wireguardautotunnel.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.Repository
-import com.zaneschepke.wireguardautotunnel.service.foreground.Action
-import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceTracker
-import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService
+import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
@@ -30,12 +27,7 @@ class BootReceiver : BroadcastReceiver() {
if (!settings.isNullOrEmpty()) {
val setting = settings.first()
if (setting.isAutoTunnelEnabled && setting.defaultTunnel != null) {
- ServiceTracker.actionOnService(
- Action.START, context,
- WireGuardConnectivityWatcherService::class.java,
- mapOf(context.resources.getString(R.string.tunnel_extras_key) to
- setting.defaultTunnel!!)
- )
+ ServiceManager.startWatcherService(context, setting.defaultTunnel!!)
}
}
} finally {
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt
index d8cdf9a..93a10ae 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/receiver/NotificationActionReceiver.kt
@@ -3,11 +3,8 @@ package com.zaneschepke.wireguardautotunnel.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.Repository
-import com.zaneschepke.wireguardautotunnel.service.foreground.Action
-import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceTracker
-import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
+import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
@@ -29,23 +26,9 @@ class NotificationActionReceiver : BroadcastReceiver() {
if (!settings.isNullOrEmpty()) {
val setting = settings.first()
if (setting.defaultTunnel != null) {
- ServiceTracker.actionOnService(
- Action.STOP, context,
- WireGuardTunnelService::class.java,
- mapOf(
- context.resources.getString(R.string.tunnel_extras_key) to
- setting.defaultTunnel!!
- )
- )
+ ServiceManager.stopVpnService(context)
delay(1000)
- ServiceTracker.actionOnService(
- Action.START, context,
- WireGuardTunnelService::class.java,
- mapOf(
- context.resources.getString(R.string.tunnel_extras_key) to
- setting.defaultTunnel!!
- )
- )
+ ServiceManager.startVpnService(context, setting.defaultTunnel.toString())
}
}
} finally {
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
new file mode 100644
index 0000000..960f0e8
--- /dev/null
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceManager.kt
@@ -0,0 +1,82 @@
+package com.zaneschepke.wireguardautotunnel.service.foreground
+
+import android.app.ActivityManager
+import android.app.Application
+import android.app.Service
+import android.content.Context
+import android.content.Context.ACTIVITY_SERVICE
+import android.content.Intent
+import com.google.firebase.crashlytics.ktx.crashlytics
+import com.google.firebase.ktx.Firebase
+import com.zaneschepke.wireguardautotunnel.R
+
+object ServiceManager {
+ @Suppress("DEPRECATION")
+ private // Deprecated for third party Services.
+ fun Context.isServiceRunning(service: Class) =
+ (getSystemService(ACTIVITY_SERVICE) as ActivityManager)
+ .getRunningServices(Integer.MAX_VALUE)
+ .any { it.service.className == service.name }
+
+ fun getServiceState(context: Context, cls : Class): ServiceState {
+ val isServiceRunning = context.isServiceRunning(cls)
+ return if(isServiceRunning) ServiceState.STARTED else ServiceState.STOPPED
+ }
+
+ private fun actionOnService(action: Action, context: Context, cls : Class, extras : Map? = null) {
+ if (getServiceState(context, cls) == ServiceState.STOPPED && action == Action.STOP) return
+ if (getServiceState(context, cls) == ServiceState.STARTED && action == Action.START) return
+ val intent = Intent(context, cls).also {
+ it.action = action.name
+ extras?.forEach {(k, v) ->
+ it.putExtra(k, v)
+ }
+ }
+ intent.component?.javaClass
+ try {
+ when(action) {
+ Action.START -> context.startForegroundService(intent)
+ Action.STOP -> context.startService(intent)
+ }
+ } catch (e : Exception) {
+ e.message?.let { Firebase.crashlytics.log(it) }
+ }
+ }
+
+ fun startVpnService(context : Context, tunnelConfig : String) {
+ actionOnService(
+ Action.START,
+ context,
+ WireGuardTunnelService::class.java,
+ mapOf(context.getString(R.string.tunnel_extras_key) to tunnelConfig))
+ }
+ fun stopVpnService(context : Context) {
+ actionOnService(
+ Action.STOP,
+ context,
+ WireGuardTunnelService::class.java
+ )
+ }
+
+ fun startWatcherService(context : Context, tunnelConfig : String) {
+ actionOnService(
+ Action.START, context,
+ WireGuardConnectivityWatcherService::class.java, mapOf(context.
+ getString(R.string.tunnel_extras_key) to
+ tunnelConfig))
+ }
+
+ fun stopWatcherService(context : Context) {
+ actionOnService(
+ Action.STOP, context,
+ WireGuardConnectivityWatcherService::class.java)
+ }
+
+ fun toggleWatcherService(context: Context, tunnelConfig : String) {
+ when(getServiceState( context,
+ WireGuardConnectivityWatcherService::class.java,)) {
+ ServiceState.STARTED -> stopWatcherService(context)
+ ServiceState.STOPPED -> startWatcherService(context, tunnelConfig)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceTracker.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceTracker.kt
deleted file mode 100644
index 64e5ceb..0000000
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/ServiceTracker.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.zaneschepke.wireguardautotunnel.service.foreground
-
-import android.app.ActivityManager
-import android.app.Application
-import android.app.Service
-import android.content.Context
-import android.content.Context.ACTIVITY_SERVICE
-import android.content.Intent
-import com.google.firebase.crashlytics.ktx.crashlytics
-import com.google.firebase.ktx.Firebase
-
-object ServiceTracker {
- @Suppress("DEPRECATION")
- private // Deprecated for third party Services.
- fun Context.isServiceRunning(service: Class) =
- (getSystemService(ACTIVITY_SERVICE) as ActivityManager)
- .getRunningServices(Integer.MAX_VALUE)
- .any { it.service.className == service.name }
-
- fun getServiceState(context: Context, cls : Class): ServiceState {
- val isServiceRunning = context.isServiceRunning(cls)
- return if(isServiceRunning) ServiceState.STARTED else ServiceState.STOPPED
- }
-
- fun actionOnService(action: Action, application: Application, cls : Class, extras : Map? = null) {
- if (getServiceState(application, cls) == ServiceState.STOPPED && action == Action.STOP) return
- if (getServiceState(application, cls) == ServiceState.STARTED && action == Action.START) return
- val intent = Intent(application, cls).also {
- it.action = action.name
- extras?.forEach {(k, v) ->
- it.putExtra(k, v)
- }
- }
- intent.component?.javaClass
- try {
- application.startService(intent)
- } catch (e : Exception) {
- e.message?.let { Firebase.crashlytics.log(it) }
- }
- }
-
- fun actionOnService(action: Action, context: Context, cls : Class, extras : Map? = null) {
- if (getServiceState(context, cls) == ServiceState.STOPPED && action == Action.STOP) return
- if (getServiceState(context, cls) == ServiceState.STARTED && action == Action.START) return
- val intent = Intent(context, cls).also {
- it.action = action.name
- extras?.forEach {(k, v) ->
- it.putExtra(k, v)
- }
- }
- intent.component?.javaClass
- try {
- context.startService(intent)
- } catch (e : Exception) {
- e.message?.let { Firebase.crashlytics.log(it) }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt
index 30354b2..4dac1a2 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/foreground/WireGuardConnectivityWatcherService.kt
@@ -1,7 +1,6 @@
package com.zaneschepke.wireguardautotunnel.service.foreground
import android.app.AlarmManager
-import android.app.Application
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
@@ -54,7 +53,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
private lateinit var watcherJob : Job;
private lateinit var setting : Settings
- private lateinit var tunnelId: String
+ private lateinit var tunnelConfig: String
private var wakeLock: PowerManager.WakeLock? = null
private val tag = this.javaClass.name;
@@ -64,13 +63,13 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
super.startService(extras)
val tunnelId = extras?.getString(getString(R.string.tunnel_extras_key))
if (tunnelId != null) {
- this.tunnelId = tunnelId
+ this.tunnelConfig = tunnelId
}
// we need this lock so our service gets not affected by Doze Mode
initWakeLock()
cancelWatcherJob()
launchWatcherNotification()
- if(this::tunnelId.isInitialized) {
+ if(this::tunnelConfig.isInitialized) {
startWatcherJob()
} else {
stopService(extras)
@@ -187,36 +186,21 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
!isWifiConnected &&
isMobileDataConnected
&& vpnService.getState() == Tunnel.State.DOWN) {
- startVPN()
+ ServiceManager.startVpnService(this, tunnelConfig)
} else if(!setting.isTunnelOnMobileDataEnabled &&
!isWifiConnected &&
vpnService.getState() == Tunnel.State.UP) {
- stopVPN()
+ ServiceManager.stopVpnService(this)
} else if(isWifiConnected &&
!setting.trustedNetworkSSIDs.contains(currentNetworkSSID) &&
(vpnService.getState() != Tunnel.State.UP)) {
- startVPN()
+ ServiceManager.startVpnService(this, tunnelConfig)
} else if((isWifiConnected &&
setting.trustedNetworkSSIDs.contains(currentNetworkSSID)) &&
(vpnService.getState() == Tunnel.State.UP)) {
- stopVPN()
+ ServiceManager.stopVpnService(this)
}
delay(Constants.VPN_CONNECTIVITY_CHECK_INTERVAL)
}
}
-
- private fun startVPN() {
- ServiceTracker.actionOnService(
- Action.START,
- this.applicationContext as Application,
- WireGuardTunnelService::class.java,
- mapOf(getString(R.string.tunnel_extras_key) to tunnelId))
- }
- private fun stopVPN() {
- ServiceTracker.actionOnService(
- Action.STOP,
- this.applicationContext as Application,
- WireGuardTunnelService::class.java
- )
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsActivity.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsActivity.kt
new file mode 100644
index 0000000..91c0940
--- /dev/null
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsActivity.kt
@@ -0,0 +1,28 @@
+package com.zaneschepke.wireguardautotunnel.service.shortcut
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import com.zaneschepke.wireguardautotunnel.R
+import com.zaneschepke.wireguardautotunnel.service.foreground.Action
+import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
+import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class ShortcutsActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if(intent.getStringExtra(ShortcutsManager.CLASS_NAME_EXTRA_KEY)
+ .equals(WireGuardTunnelService::class.java.name)) {
+ intent.getStringExtra(getString(R.string.tunnel_extras_key))?.let {
+ ServiceManager.toggleWatcherService(this, it)
+ }
+ when(intent.action){
+ Action.STOP.name -> ServiceManager.stopVpnService(this)
+ Action.START.name -> intent.getStringExtra(getString(R.string.tunnel_extras_key))
+ ?.let { ServiceManager.startVpnService(this, it) }
+ }
+ }
+ finish()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsManager.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsManager.kt
new file mode 100644
index 0000000..04c0e9d
--- /dev/null
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/shortcut/ShortcutsManager.kt
@@ -0,0 +1,73 @@
+package com.zaneschepke.wireguardautotunnel.service.shortcut
+
+import android.content.Context
+import android.content.Intent
+import androidx.core.content.pm.ShortcutInfoCompat
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.graphics.drawable.IconCompat
+import com.zaneschepke.wireguardautotunnel.R
+import com.zaneschepke.wireguardautotunnel.service.foreground.Action
+import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
+import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
+
+object ShortcutsManager {
+
+ private const val SHORT_LABEL_MAX_SIZE = 10;
+ private const val LONG_LABEL_MAX_SIZE = 25;
+ private const val APPEND_ON = " On";
+ private const val APPEND_OFF = " Off"
+ const val CLASS_NAME_EXTRA_KEY = "className"
+
+ private fun createAndPushShortcut(context : Context, intent : Intent, id : String, shortLabel : String,
+ longLabel : String, drawable : Int ) {
+ val shortcut = ShortcutInfoCompat.Builder(context, id)
+ .setShortLabel(shortLabel)
+ .setLongLabel(longLabel)
+ .setIcon(IconCompat.createWithResource(context, drawable))
+ .setIntent(intent)
+ .build()
+ ShortcutManagerCompat.pushDynamicShortcut(context, shortcut)
+ }
+
+ fun createTunnelShortcuts(context : Context, tunnelConfig : TunnelConfig) {
+ createAndPushShortcut(context,
+ createTunnelOnIntent(context, mapOf(context.getString(R.string.tunnel_extras_key) to tunnelConfig.toString())),
+ tunnelConfig.id.toString() + APPEND_ON,
+ tunnelConfig.name.take((SHORT_LABEL_MAX_SIZE - APPEND_ON.length)) + APPEND_ON,
+ tunnelConfig.name.take((LONG_LABEL_MAX_SIZE - APPEND_ON.length)) + APPEND_ON,
+ R.drawable.vpn_on
+ )
+ createAndPushShortcut(context,
+ createTunnelOffIntent(context, mapOf(context.getString(R.string.tunnel_extras_key) to tunnelConfig.toString())),
+ tunnelConfig.id.toString() + APPEND_OFF,
+ tunnelConfig.name.take((SHORT_LABEL_MAX_SIZE - APPEND_OFF.length)) + APPEND_OFF,
+ tunnelConfig.name.take((LONG_LABEL_MAX_SIZE - APPEND_OFF.length)) + APPEND_OFF,
+ R.drawable.vpn_off
+ )
+ }
+
+ fun removeTunnelShortcuts(context : Context, tunnelConfig : TunnelConfig) {
+ ShortcutManagerCompat.removeDynamicShortcuts(context, listOf(tunnelConfig.id.toString() + APPEND_ON,
+ tunnelConfig.id.toString() + APPEND_OFF ))
+ }
+
+ private fun createTunnelOnIntent(context: Context, extras : Map) : Intent {
+ return Intent(context, ShortcutsActivity::class.java).also {
+ it.action = Action.START.name
+ it.putExtra(CLASS_NAME_EXTRA_KEY, WireGuardTunnelService::class.java.name)
+ extras.forEach {(k, v) ->
+ it.putExtra(k, v)
+ }
+ }
+ }
+
+ private fun createTunnelOffIntent(context : Context, extras : Map) : Intent {
+ return Intent(context, ShortcutsActivity::class.java).also {
+ it.action = Action.STOP.name
+ it.putExtra(CLASS_NAME_EXTRA_KEY, WireGuardTunnelService::class.java.name)
+ extras.forEach {(k, v) ->
+ it.putExtra(k, v)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/TunnelControlTile.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt
similarity index 69%
rename from app/src/main/java/com/zaneschepke/wireguardautotunnel/service/TunnelControlTile.kt
rename to app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt
index d77fdd4..aa211de 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/TunnelControlTile.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/service/tile/TunnelControlTile.kt
@@ -1,4 +1,4 @@
-package com.zaneschepke.wireguardautotunnel.service
+package com.zaneschepke.wireguardautotunnel.service.tile
import android.os.Build
import android.service.quicksettings.Tile
@@ -6,11 +6,7 @@ import android.service.quicksettings.TileService
import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.Repository
-import com.zaneschepke.wireguardautotunnel.service.foreground.Action
-import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceState
-import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceTracker
-import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService
-import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
+import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
@@ -68,9 +64,9 @@ class TunnelControlTile : TileService() {
if(tunnel != null) {
attemptWatcherServiceToggle(tunnel.toString())
if(vpnService.getState() == Tunnel.State.UP) {
- stopTunnelService();
+ ServiceManager.stopVpnService(this@TunnelControlTile)
} else {
- startTunnelService(tunnel.toString())
+ ServiceManager.startVpnService(this@TunnelControlTile, tunnel.toString())
}
}
} catch (e : Exception) {
@@ -97,34 +93,6 @@ class TunnelControlTile : TileService() {
return tunnelConfig;
}
- private fun stopTunnelService() {
- ServiceTracker.actionOnService(
- Action.STOP, this.applicationContext,
- WireGuardTunnelService::class.java)
- }
-
- private fun startTunnelService(tunnelConfig : String) {
- ServiceTracker.actionOnService(
- Action.START, this.applicationContext,
- WireGuardTunnelService::class.java,
- mapOf(this.applicationContext.resources.
- getString(R.string.tunnel_extras_key) to
- tunnelConfig))
- }
-
- private fun startWatcherService(tunnelConfig : String) {
- ServiceTracker.actionOnService(
- Action.START, this,
- WireGuardConnectivityWatcherService::class.java, mapOf(this.resources.
- getString(R.string.tunnel_extras_key) to
- tunnelConfig))
- }
-
- private fun stopWatcherService() {
- ServiceTracker.actionOnService(
- Action.STOP, this,
- WireGuardConnectivityWatcherService::class.java)
- }
private fun attemptWatcherServiceToggle(tunnelConfig : String) {
scope.launch {
@@ -132,11 +100,7 @@ class TunnelControlTile : TileService() {
if (!settings.isNullOrEmpty()) {
val setting = settings.first()
if(setting.isAutoTunnelEnabled) {
- when(ServiceTracker.getServiceState( this@TunnelControlTile,
- WireGuardConnectivityWatcherService::class.java,)) {
- ServiceState.STARTED -> stopWatcherService()
- ServiceState.STOPPED -> startWatcherService(tunnelConfig)
- }
+ ServiceManager.toggleWatcherService(this@TunnelControlTile, tunnelConfig)
}
}
}
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/SearchBar.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/SearchBar.kt
new file mode 100644
index 0000000..7deea5a
--- /dev/null
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/common/SearchBar.kt
@@ -0,0 +1,80 @@
+package com.zaneschepke.wireguardautotunnel.ui.common
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.rounded.Clear
+import androidx.compose.material.icons.rounded.Search
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.material3.TextFieldDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.input.KeyboardType
+import com.zaneschepke.wireguardautotunnel.R
+
+@Composable
+fun SearchBar(
+ onQuery : (queryString : String) -> Unit
+) {
+ // Immediately update and keep track of query from text field changes.
+ var query: String by rememberSaveable { mutableStateOf("") }
+ var showClearIcon by rememberSaveable { mutableStateOf(false) }
+
+ if (query.isEmpty()) {
+ showClearIcon = false
+ } else if (query.isNotEmpty()) {
+ showClearIcon = true
+ }
+
+ TextField(
+ value = query,
+ onValueChange = { onQueryChanged ->
+ // If user makes changes to text, immediately updated it.
+ query = onQueryChanged
+ onQuery(onQueryChanged)
+ },
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Rounded.Search,
+ tint = MaterialTheme.colorScheme.onBackground,
+ contentDescription = stringResource(id = R.string.search_icon)
+ )
+ },
+ trailingIcon = {
+ if (showClearIcon) {
+ IconButton(onClick = { query = "" }) {
+ Icon(
+ imageVector = Icons.Rounded.Clear,
+ tint = MaterialTheme.colorScheme.onBackground,
+ contentDescription = stringResource(id = R.string.clear_icon)
+ )
+ }
+ }
+ },
+ maxLines = 1,
+ colors = TextFieldDefaults.colors(
+ focusedContainerColor = Color.Transparent,
+ unfocusedContainerColor = Color.Transparent,
+ disabledContainerColor = Color.Transparent,
+ ),
+ placeholder = { Text(text = stringResource(R.string.hint_search_packages)) },
+ textStyle = MaterialTheme.typography.bodySmall,
+ singleLine = true,
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(color = MaterialTheme.colorScheme.background, shape = RectangleShape)
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt
index 985386e..bc38ed4 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigScreen.kt
@@ -1,6 +1,5 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.config
-import android.content.pm.PackageManager
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
@@ -25,7 +24,6 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
@@ -45,6 +43,7 @@ import androidx.navigation.NavController
import com.google.accompanist.drawablepainter.DrawablePainter
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.ui.Routes
+import com.zaneschepke.wireguardautotunnel.ui.common.SearchBar
import kotlinx.coroutines.launch
@OptIn(ExperimentalComposeUiApi::class)
@@ -71,7 +70,7 @@ fun ConfigScreen(
LaunchedEffect(Unit) {
viewModel.getTunnelById(id)
- viewModel.emitAllInternetCapablePackages()
+ viewModel.emitQueriedPackages("")
viewModel.emitCurrentPackageConfigurations(id)
}
@@ -165,6 +164,16 @@ fun ConfigScreen(
}
}
}
+ item {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 20.dp, vertical = 7.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween) {
+ SearchBar(viewModel::emitQueriedPackages);
+ }
+ }
items(packages) { pack ->
Row(
verticalAlignment = Alignment.CenterVertically,
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt
index 8d57350..ce57f4b 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/config/ConfigViewModel.kt
@@ -8,12 +8,14 @@ import android.os.Build
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.toMutableStateList
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@@ -96,8 +98,12 @@ class ConfigViewModel @Inject constructor(private val application : Application,
}
}
- suspend fun emitAllInternetCapablePackages() {
- _packages.emit(getAllInternetCapablePackages())
+ fun emitQueriedPackages(query : String) {
+ viewModelScope.launch {
+ _packages.emit(getAllInternetCapablePackages().filter {
+ it.packageName.contains(query)
+ })
+ }
}
private fun getAllInternetCapablePackages() : List {
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 6897cff..8caed38 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
@@ -12,11 +12,10 @@ import com.wireguard.config.Config
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.Repository
import com.zaneschepke.wireguardautotunnel.service.barcode.CodeScanner
-import com.zaneschepke.wireguardautotunnel.service.foreground.Action
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceState
-import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceTracker
+import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService
-import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardTunnelService
+import com.zaneschepke.wireguardautotunnel.service.shortcut.ShortcutsManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
@@ -64,23 +63,17 @@ class MainViewModel @Inject constructor(private val application : Application,
}
private fun validateWatcherServiceState(settings: Settings) {
- val watcherState = ServiceTracker.getServiceState(application, WireGuardConnectivityWatcherService::class.java)
+ val watcherState = ServiceManager.getServiceState(application.applicationContext, WireGuardConnectivityWatcherService::class.java)
if(settings.isAutoTunnelEnabled && watcherState == ServiceState.STOPPED && settings.defaultTunnel != null) {
- startWatcherService(settings.defaultTunnel!!)
+ ServiceManager.startWatcherService(application.applicationContext, settings.defaultTunnel!!)
}
}
- private fun startWatcherService(tunnel : String) {
- ServiceTracker.actionOnService(
- Action.START, application,
- WireGuardConnectivityWatcherService::class.java,
- mapOf(application.resources.getString(R.string.tunnel_extras_key) to tunnel))
- }
fun onDelete(tunnel : TunnelConfig) {
viewModelScope.launch {
if(tunnelRepo.count() == 1L) {
- ServiceTracker.actionOnService( Action.STOP, application, WireGuardConnectivityWatcherService::class.java)
+ ServiceManager.stopWatcherService(application.applicationContext)
val settings = settingsRepo.getAll()
if(!settings.isNullOrEmpty()) {
val setting = settings[0]
@@ -91,22 +84,23 @@ class MainViewModel @Inject constructor(private val application : Application,
}
}
tunnelRepo.delete(tunnel)
+ ShortcutsManager.removeTunnelShortcuts(application.applicationContext, tunnel)
}
}
fun onTunnelStart(tunnelConfig : TunnelConfig) = viewModelScope.launch {
- ServiceTracker.actionOnService( Action.START, application, WireGuardTunnelService::class.java,
- mapOf(application.resources.getString(R.string.tunnel_extras_key) to tunnelConfig.toString()))
+ ServiceManager.startVpnService(application.applicationContext, tunnelConfig.toString())
}
fun onTunnelStop() {
- ServiceTracker.actionOnService( Action.STOP, application, WireGuardTunnelService::class.java)
+ ServiceManager.stopVpnService(application.applicationContext)
}
suspend fun onTunnelQRSelected() {
codeScanner.scan().collect {
if(!it.isNullOrEmpty() && it.contains(application.resources.getString(R.string.config_validation))) {
- tunnelRepo.save(TunnelConfig(name = defaultConfigName(), wgQuick = it))
+ val tunnelConfig = TunnelConfig(name = defaultConfigName(), wgQuick = it)
+ saveTunnel(tunnelConfig)
} else if(!it.isNullOrEmpty() && it.contains(application.resources.getString(R.string.barcode_downloading))) {
showSnackBarMessage(application.resources.getString(R.string.barcode_downloading_message))
} else {
@@ -130,9 +124,7 @@ class MainViewModel @Inject constructor(private val application : Application,
val bufferReader = stream.bufferedReader(charset = Charsets.UTF_8)
val config = Config.parse(bufferReader)
val tunnelName = getNameFromFileName(fileName)
- viewModelScope.launch {
- tunnelRepo.save(TunnelConfig(name = tunnelName, wgQuick = config.toWgQuickString()))
- }
+ saveTunnel(TunnelConfig(name = tunnelName, wgQuick = config.toWgQuickString()))
stream.close()
} catch(_: BadConfigException) {
viewModelScope.launch {
@@ -141,6 +133,13 @@ class MainViewModel @Inject constructor(private val application : Application,
}
}
+ private fun saveTunnel(tunnelConfig : TunnelConfig) {
+ viewModelScope.launch {
+ tunnelRepo.save(tunnelConfig)
+ ShortcutsManager.createTunnelShortcuts(application.applicationContext, tunnelConfig)
+ }
+ }
+
@SuppressLint("Range")
private fun getFileName(context: Context, uri: Uri): String {
if (uri.scheme == "content") {
diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt
index b8a3505..256fb7d 100644
--- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt
+++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/ui/screens/settings/SettingsViewModel.kt
@@ -7,9 +7,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.repository.Repository
-import com.zaneschepke.wireguardautotunnel.service.foreground.Action
-import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceTracker
-import com.zaneschepke.wireguardautotunnel.service.foreground.WireGuardConnectivityWatcherService
+import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.Settings
import com.zaneschepke.wireguardautotunnel.service.tunnel.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.ui.ViewState
@@ -77,32 +75,18 @@ class SettingsViewModel @Inject constructor(private val application : Applicatio
return
}
if(_settings.value.isAutoTunnelEnabled) {
- actionOnWatcherService(Action.STOP)
+ ServiceManager.stopWatcherService(application)
} else {
- actionOnWatcherService(Action.START)
+ if(_settings.value.defaultTunnel != null) {
+ val defaultTunnel = _settings.value.defaultTunnel
+ ServiceManager.startWatcherService(application, defaultTunnel!!)
+ }
}
settingsRepo.save(_settings.value.copy(
isAutoTunnelEnabled = !_settings.value.isAutoTunnelEnabled
))
}
- private fun actionOnWatcherService(action : Action) {
- when(action) {
- Action.START -> {
- if(_settings.value.defaultTunnel != null) {
- val defaultTunnel = _settings.value.defaultTunnel
- ServiceTracker.actionOnService(
- action, application,
- WireGuardConnectivityWatcherService::class.java,
- mapOf(application.resources.getString(R.string.tunnel_extras_key) to defaultTunnel.toString()))
- }
- }
- Action.STOP -> {
- ServiceTracker.actionOnService( Action.STOP, application,
- WireGuardConnectivityWatcherService::class.java)
- }
- }
- }
suspend fun showSnackBarMessage(message : String) {
_viewState.emit(_viewState.value.copy(
showSnackbarMessage = true,
diff --git a/app/src/main/res/drawable/vpn_off.xml b/app/src/main/res/drawable/vpn_off.xml
new file mode 100644
index 0000000..d33949b
--- /dev/null
+++ b/app/src/main/res/drawable/vpn_off.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/vpn_on.xml b/app/src/main/res/drawable/vpn_on.xml
new file mode 100644
index 0000000..1339fb3
--- /dev/null
+++ b/app/src/main/res/drawable/vpn_on.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b84d101..14df420 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -86,4 +86,7 @@
Request
Toggle VPN
No tunnels available
+ Search packages
+ Clear Icon
+ Search Icon
\ No newline at end of file