Merge pull request #9 from krlvm/quicktile

Quick Tile support
This commit is contained in:
dovecoteescapee 2024-08-05 01:41:17 +03:00 committed by GitHub
commit e6a9a75848
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 201 additions and 41 deletions

View File

@ -23,6 +23,7 @@
android:launchMode="singleInstance"> android:launchMode="singleInstance">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
@ -55,6 +56,18 @@
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="proxy" /> android:value="proxy" />
</service> </service>
<service
android:name=".services.QuickTileService"
android:icon="@drawable/ic_notification"
android:label="@string/app_name"
android:exported="true"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action
android:name="android.service.quicksettings.action.QS_TILE"/>
</intent-filter>
</service>
</application> </application>
</manifest> </manifest>

View File

@ -15,8 +15,6 @@ import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope 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.R
import io.github.dovecoteescapee.byedpi.data.AppStatus import io.github.dovecoteescapee.byedpi.data.AppStatus
import io.github.dovecoteescapee.byedpi.data.FAILED_BROADCAST 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.data.Sender
import io.github.dovecoteescapee.byedpi.fragments.SettingsFragment import io.github.dovecoteescapee.byedpi.fragments.SettingsFragment
import io.github.dovecoteescapee.byedpi.databinding.ActivityMainBinding import io.github.dovecoteescapee.byedpi.databinding.ActivityMainBinding
import io.github.dovecoteescapee.byedpi.services.ByeDpiProxyService import io.github.dovecoteescapee.byedpi.services.ServiceManager
import io.github.dovecoteescapee.byedpi.services.ByeDpiVpnService
import io.github.dovecoteescapee.byedpi.services.appStatus import io.github.dovecoteescapee.byedpi.services.appStatus
import io.github.dovecoteescapee.byedpi.utility.getPreferences import io.github.dovecoteescapee.byedpi.utility.getPreferences
import io.github.dovecoteescapee.byedpi.utility.mode import io.github.dovecoteescapee.byedpi.utility.mode
@ -57,7 +54,7 @@ class MainActivity : AppCompatActivity() {
private val vpnRegister = private val vpnRegister =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) { if (it.resultCode == RESULT_OK) {
startVpn() ServiceManager.start(this, Mode.VPN)
} else { } else {
Toast.makeText(this, R.string.vpn_permission_denied, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.vpn_permission_denied, Toast.LENGTH_SHORT).show()
updateStatus() updateStatus()
@ -212,48 +209,16 @@ class MainActivity : AppCompatActivity() {
if (intentPrepare != null) { if (intentPrepare != null) {
vpnRegister.launch(intentPrepare) vpnRegister.launch(intentPrepare)
} else { } 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() { private fun stop() {
val (_, mode) = appStatus ServiceManager.stop(this)
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)
} }
private fun updateStatus() { private fun updateStatus() {

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}
}

View File

@ -38,7 +38,7 @@ fun createConnectionNotification(
service: Class<*>, service: Class<*>,
): Notification = ): Notification =
NotificationCompat.Builder(context, channelId) NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.mipmap.ic_launcher) .setSmallIcon(R.drawable.ic_notification)
.setSilent(true) .setSilent(true)
.setContentTitle(context.getString(title)) .setContentTitle(context.getString(title))
.setContentText(context.getString(content)) .setContentText(context.getString(content))

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB