diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 95bf82d..e593f5c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -23,6 +23,7 @@
android:launchMode="singleInstance">
+
@@ -55,6 +56,18 @@
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="proxy" />
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/io/github/dovecoteescapee/byedpi/activities/MainActivity.kt b/app/src/main/java/io/github/dovecoteescapee/byedpi/activities/MainActivity.kt
index c115b1d..58b1cd1 100644
--- a/app/src/main/java/io/github/dovecoteescapee/byedpi/activities/MainActivity.kt
+++ b/app/src/main/java/io/github/dovecoteescapee/byedpi/activities/MainActivity.kt
@@ -15,8 +15,6 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
-import io.github.dovecoteescapee.byedpi.data.START_ACTION
-import io.github.dovecoteescapee.byedpi.data.STOP_ACTION
import io.github.dovecoteescapee.byedpi.R
import io.github.dovecoteescapee.byedpi.data.AppStatus
import io.github.dovecoteescapee.byedpi.data.FAILED_BROADCAST
@@ -27,8 +25,7 @@ import io.github.dovecoteescapee.byedpi.data.STOPPED_BROADCAST
import io.github.dovecoteescapee.byedpi.data.Sender
import io.github.dovecoteescapee.byedpi.fragments.SettingsFragment
import io.github.dovecoteescapee.byedpi.databinding.ActivityMainBinding
-import io.github.dovecoteescapee.byedpi.services.ByeDpiProxyService
-import io.github.dovecoteescapee.byedpi.services.ByeDpiVpnService
+import io.github.dovecoteescapee.byedpi.services.ServiceManager
import io.github.dovecoteescapee.byedpi.services.appStatus
import io.github.dovecoteescapee.byedpi.utility.getPreferences
import io.github.dovecoteescapee.byedpi.utility.mode
@@ -57,7 +54,7 @@ class MainActivity : AppCompatActivity() {
private val vpnRegister =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
- startVpn()
+ ServiceManager.start(this, Mode.VPN)
} else {
Toast.makeText(this, R.string.vpn_permission_denied, Toast.LENGTH_SHORT).show()
updateStatus()
@@ -212,48 +209,16 @@ class MainActivity : AppCompatActivity() {
if (intentPrepare != null) {
vpnRegister.launch(intentPrepare)
} else {
- startVpn()
+ ServiceManager.start(this, Mode.VPN)
}
}
- Mode.Proxy -> startProxy()
+ Mode.Proxy -> ServiceManager.start(this, Mode.Proxy)
}
}
- private fun startVpn() {
- Log.i(TAG, "Starting VPN")
- val intent = Intent(this, ByeDpiVpnService::class.java)
- intent.action = START_ACTION
- startService(intent)
- }
-
- private fun startProxy() {
- Log.i(TAG, "Starting proxy")
- val intent = Intent(this, ByeDpiProxyService::class.java)
- intent.action = START_ACTION
- startService(intent)
- }
-
private fun stop() {
- val (_, mode) = appStatus
- when (mode) {
- Mode.VPN -> stopVpn()
- Mode.Proxy -> stopProxy()
- }
- }
-
- private fun stopVpn() {
- Log.i(TAG, "Stopping VPN")
- val intent = Intent(this, ByeDpiVpnService::class.java)
- intent.action = STOP_ACTION
- startService(intent)
- }
-
- private fun stopProxy() {
- Log.i(TAG, "Stopping proxy")
- val intent = Intent(this, ByeDpiProxyService::class.java)
- intent.action = STOP_ACTION
- startService(intent)
+ ServiceManager.stop(this)
}
private fun updateStatus() {
diff --git a/app/src/main/java/io/github/dovecoteescapee/byedpi/services/QuickTileService.kt b/app/src/main/java/io/github/dovecoteescapee/byedpi/services/QuickTileService.kt
new file mode 100644
index 0000000..3647d6b
--- /dev/null
+++ b/app/src/main/java/io/github/dovecoteescapee/byedpi/services/QuickTileService.kt
@@ -0,0 +1,133 @@
+package io.github.dovecoteescapee.byedpi.services
+
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.VpnService
+import android.os.Build
+import android.service.quicksettings.Tile
+import android.service.quicksettings.TileService
+import android.util.Log
+import android.widget.Toast
+import androidx.annotation.RequiresApi
+import androidx.core.content.ContextCompat
+import androidx.core.service.quicksettings.PendingIntentActivityWrapper
+import androidx.core.service.quicksettings.TileServiceCompat
+import io.github.dovecoteescapee.byedpi.R
+import io.github.dovecoteescapee.byedpi.activities.MainActivity
+import io.github.dovecoteescapee.byedpi.data.AppStatus
+import io.github.dovecoteescapee.byedpi.data.FAILED_BROADCAST
+import io.github.dovecoteescapee.byedpi.data.Mode
+import io.github.dovecoteescapee.byedpi.data.SENDER
+import io.github.dovecoteescapee.byedpi.data.STARTED_BROADCAST
+import io.github.dovecoteescapee.byedpi.data.STOPPED_BROADCAST
+import io.github.dovecoteescapee.byedpi.data.Sender
+import io.github.dovecoteescapee.byedpi.utility.getPreferences
+import io.github.dovecoteescapee.byedpi.utility.mode
+
+
+@RequiresApi(Build.VERSION_CODES.N)
+class QuickTileService : TileService() {
+
+ companion object {
+ private val TAG: String = QuickTileService::class.java.simpleName
+ }
+
+ private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val senderOrd = intent.getIntExtra(SENDER, -1)
+ val sender = Sender.entries.getOrNull(senderOrd)
+ if (sender == null) {
+ Log.w(TAG, "Received intent with unknown sender: $senderOrd")
+ return
+ }
+
+ when (val action = intent.action) {
+ STARTED_BROADCAST,
+ STOPPED_BROADCAST -> updateStatus()
+
+ FAILED_BROADCAST -> {
+ Toast.makeText(
+ context,
+ getString(R.string.failed_to_start, sender.name),
+ Toast.LENGTH_SHORT,
+ ).show()
+ updateStatus()
+ }
+
+ else -> Log.w(TAG, "Unknown action: $action")
+ }
+ }
+ }
+
+ override fun onStartListening() {
+ updateStatus()
+ ContextCompat.registerReceiver(
+ this,
+ receiver,
+ IntentFilter().apply {
+ addAction(STARTED_BROADCAST)
+ addAction(STOPPED_BROADCAST)
+ addAction(FAILED_BROADCAST)
+ },
+ ContextCompat.RECEIVER_EXPORTED,
+ )
+ }
+
+ override fun onStopListening() {
+ unregisterReceiver(receiver)
+ }
+
+ private fun launchActivity() {
+ TileServiceCompat.startActivityAndCollapse(
+ this, PendingIntentActivityWrapper(
+ this, 0, Intent(this, MainActivity::class.java),
+ PendingIntent.FLAG_UPDATE_CURRENT, false
+ )
+ )
+ }
+
+ override fun onClick() {
+ if (qsTile.state == Tile.STATE_UNAVAILABLE) {
+ return
+ }
+
+ unlockAndRun(this::handleClick)
+ }
+
+ private fun setState(newState: Int) {
+ qsTile.apply {
+ state = newState
+ updateTile()
+ }
+ }
+
+ private fun updateStatus() {
+ val (status) = appStatus
+ setState(if (status == AppStatus.Halted) Tile.STATE_INACTIVE else Tile.STATE_ACTIVE)
+ }
+
+ private fun handleClick() {
+ setState(Tile.STATE_ACTIVE)
+ setState(Tile.STATE_UNAVAILABLE)
+
+ val (status) = appStatus
+ when (status) {
+ AppStatus.Halted -> {
+ val mode = getPreferences(this).mode()
+
+ if (mode == Mode.VPN && VpnService.prepare(this) != null) {
+ updateStatus()
+ launchActivity()
+ return
+ }
+
+ ServiceManager.start(this, mode)
+ }
+
+ AppStatus.Running -> ServiceManager.stop(this)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/dovecoteescapee/byedpi/services/ServiceManager.kt b/app/src/main/java/io/github/dovecoteescapee/byedpi/services/ServiceManager.kt
new file mode 100644
index 0000000..0e82742
--- /dev/null
+++ b/app/src/main/java/io/github/dovecoteescapee/byedpi/services/ServiceManager.kt
@@ -0,0 +1,49 @@
+package io.github.dovecoteescapee.byedpi.services
+
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import io.github.dovecoteescapee.byedpi.data.Mode
+import io.github.dovecoteescapee.byedpi.data.START_ACTION
+import io.github.dovecoteescapee.byedpi.data.STOP_ACTION
+
+object ServiceManager {
+ private val TAG: String = ServiceManager::class.java.simpleName
+
+ fun start(context: Context, mode: Mode) {
+ when (mode) {
+ Mode.VPN -> {
+ Log.i(TAG, "Starting VPN")
+ val intent = Intent(context, ByeDpiVpnService::class.java)
+ intent.action = START_ACTION
+ context.startService(intent)
+ }
+
+ Mode.Proxy -> {
+ Log.i(TAG, "Starting proxy")
+ val intent = Intent(context, ByeDpiProxyService::class.java)
+ intent.action = START_ACTION
+ context.startService(intent)
+ }
+ }
+ }
+
+ fun stop(context: Context) {
+ val (_, mode) = appStatus
+ when (mode) {
+ Mode.VPN -> {
+ Log.i(TAG, "Stopping VPN")
+ val intent = Intent(context, ByeDpiVpnService::class.java)
+ intent.action = STOP_ACTION
+ context.startService(intent)
+ }
+
+ Mode.Proxy -> {
+ Log.i(TAG, "Stopping proxy")
+ val intent = Intent(context, ByeDpiProxyService::class.java)
+ intent.action = STOP_ACTION
+ context.startService(intent)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/dovecoteescapee/byedpi/utility/NotificationUtils.kt b/app/src/main/java/io/github/dovecoteescapee/byedpi/utility/NotificationUtils.kt
index e7850a7..4ec01ae 100644
--- a/app/src/main/java/io/github/dovecoteescapee/byedpi/utility/NotificationUtils.kt
+++ b/app/src/main/java/io/github/dovecoteescapee/byedpi/utility/NotificationUtils.kt
@@ -38,7 +38,7 @@ fun createConnectionNotification(
service: Class<*>,
): Notification =
NotificationCompat.Builder(context, channelId)
- .setSmallIcon(R.mipmap.ic_launcher)
+ .setSmallIcon(R.drawable.ic_notification)
.setSilent(true)
.setContentTitle(context.getString(title))
.setContentText(context.getString(content))
diff --git a/app/src/main/res/drawable-hdpi/ic_notification.png b/app/src/main/res/drawable-hdpi/ic_notification.png
new file mode 100644
index 0000000..7ce6f29
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_notification.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_notification.png b/app/src/main/res/drawable-mdpi/ic_notification.png
new file mode 100644
index 0000000..3cbb23e
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_notification.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_notification.png b/app/src/main/res/drawable-xhdpi/ic_notification.png
new file mode 100644
index 0000000..36ef0ee
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_notification.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_notification.png b/app/src/main/res/drawable-xxhdpi/ic_notification.png
new file mode 100644
index 0000000..bbb4a71
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_notification.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_notification.png b/app/src/main/res/drawable-xxxhdpi/ic_notification.png
new file mode 100644
index 0000000..48db59d
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_notification.png differ