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