mirror of
https://github.com/SagerNet/sing-box-for-android.git
synced 2025-04-02 19:37:39 +03:00
Implement send notification
This commit is contained in:
parent
e38f352c48
commit
108f8b05af
15 changed files with 116 additions and 45 deletions
|
@ -10,7 +10,7 @@ plugins {
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace "io.nekohasekai.sfa"
|
namespace "io.nekohasekai.sfa"
|
||||||
compileSdk 34
|
compileSdk 35
|
||||||
|
|
||||||
ndkVersion "27.2.12479018"
|
ndkVersion "27.2.12479018"
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "io.nekohasekai.sfa"
|
applicationId "io.nekohasekai.sfa"
|
||||||
minSdk 21
|
minSdk 21
|
||||||
targetSdk 34
|
targetSdk 35
|
||||||
versionCode getVersionProps("VERSION_CODE").toInteger()
|
versionCode getVersionProps("VERSION_CODE").toInteger()
|
||||||
versionName getVersionProps("VERSION_NAME")
|
versionName getVersionProps("VERSION_NAME")
|
||||||
setProperty("archivesBaseName", "SFA-" + versionName)
|
setProperty("archivesBaseName", "SFA-" + versionName)
|
||||||
|
@ -90,23 +90,23 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(fileTree("libs"))
|
implementation(fileTree("libs"))
|
||||||
|
|
||||||
implementation "androidx.core:core-ktx:1.13.1"
|
implementation "androidx.core:core-ktx:1.15.0"
|
||||||
implementation "androidx.appcompat:appcompat:1.7.0"
|
implementation "androidx.appcompat:appcompat:1.7.0"
|
||||||
implementation "com.google.android.material:material:1.12.0"
|
implementation "com.google.android.material:material:1.12.0"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
|
implementation "androidx.constraintlayout:constraintlayout:2.2.0"
|
||||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.6"
|
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.8.7"
|
||||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6"
|
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7"
|
||||||
implementation "androidx.navigation:navigation-fragment-ktx:2.8.3"
|
implementation "androidx.navigation:navigation-fragment-ktx:2.8.3"
|
||||||
implementation "androidx.navigation:navigation-ui-ktx:2.8.3"
|
implementation "androidx.navigation:navigation-ui-ktx:2.8.3"
|
||||||
implementation "com.google.zxing:core:3.5.3"
|
implementation "com.google.zxing:core:3.5.3"
|
||||||
implementation "androidx.room:room-runtime:2.6.1"
|
implementation "androidx.room:room-runtime:2.6.1"
|
||||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||||
implementation "androidx.preference:preference-ktx:1.2.1"
|
implementation "androidx.preference:preference-ktx:1.2.1"
|
||||||
implementation "androidx.camera:camera-view:1.3.4"
|
implementation "androidx.camera:camera-view:1.4.0"
|
||||||
implementation "androidx.camera:camera-lifecycle:1.3.4"
|
implementation "androidx.camera:camera-lifecycle:1.4.0"
|
||||||
implementation "androidx.camera:camera-camera2:1.3.4"
|
implementation "androidx.camera:camera-camera2:1.4.0"
|
||||||
ksp "androidx.room:room-compiler:2.6.1"
|
ksp "androidx.room:room-compiler:2.6.1"
|
||||||
implementation "androidx.work:work-runtime-ktx:2.9.1"
|
implementation "androidx.work:work-runtime-ktx:2.10.0"
|
||||||
implementation "androidx.browser:browser:1.8.0"
|
implementation "androidx.browser:browser:1.8.0"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0"
|
||||||
|
|
||||||
|
@ -120,6 +120,8 @@ dependencies {
|
||||||
implementation "com.google.guava:guava:33.0.0-android"
|
implementation "com.google.guava:guava:33.0.0-android"
|
||||||
playImplementation "com.google.android.play:app-update-ktx:2.1.0"
|
playImplementation "com.google.android.play:app-update-ktx:2.1.0"
|
||||||
playImplementation "com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1"
|
playImplementation "com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1"
|
||||||
|
|
||||||
|
implementation "com.github.tiann:FreeReflection:3.1.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
def playCredentialsJSON = rootProject.file("service-account-credentials.json")
|
def playCredentialsJSON = rootProject.file("service-account-credentials.json")
|
||||||
|
|
|
@ -16,13 +16,14 @@ import io.nekohasekai.sfa.bg.UpdateProfileWork
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import me.weishu.reflection.Reflection
|
||||||
import io.nekohasekai.sfa.Application as BoxApplication
|
import io.nekohasekai.sfa.Application as BoxApplication
|
||||||
|
|
||||||
class Application : Application() {
|
class Application : Application() {
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context?) {
|
override fun attachBaseContext(base: Context?) {
|
||||||
super.attachBaseContext(base)
|
super.attachBaseContext(base)
|
||||||
|
Reflection.unseal(base)
|
||||||
application = this
|
application = this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
package io.nekohasekai.sfa.bg
|
package io.nekohasekai.sfa.bg
|
||||||
|
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import go.Seq
|
import go.Seq
|
||||||
|
@ -17,6 +22,7 @@ import io.nekohasekai.libbox.BoxService
|
||||||
import io.nekohasekai.libbox.CommandServer
|
import io.nekohasekai.libbox.CommandServer
|
||||||
import io.nekohasekai.libbox.CommandServerHandler
|
import io.nekohasekai.libbox.CommandServerHandler
|
||||||
import io.nekohasekai.libbox.Libbox
|
import io.nekohasekai.libbox.Libbox
|
||||||
|
import io.nekohasekai.libbox.Notification
|
||||||
import io.nekohasekai.libbox.PlatformInterface
|
import io.nekohasekai.libbox.PlatformInterface
|
||||||
import io.nekohasekai.libbox.SystemProxyStatus
|
import io.nekohasekai.libbox.SystemProxyStatus
|
||||||
import io.nekohasekai.sfa.Application
|
import io.nekohasekai.sfa.Application
|
||||||
|
@ -27,6 +33,7 @@ import io.nekohasekai.sfa.constant.Status
|
||||||
import io.nekohasekai.sfa.database.ProfileManager
|
import io.nekohasekai.sfa.database.ProfileManager
|
||||||
import io.nekohasekai.sfa.database.Settings
|
import io.nekohasekai.sfa.database.Settings
|
||||||
import io.nekohasekai.sfa.ktx.hasPermission
|
import io.nekohasekai.sfa.ktx.hasPermission
|
||||||
|
import io.nekohasekai.sfa.ui.MainActivity
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
@ -36,8 +43,7 @@ import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class BoxService(
|
class BoxService(
|
||||||
private val service: Service,
|
private val service: Service, private val platformInterface: PlatformInterface
|
||||||
private val platformInterface: PlatformInterface
|
|
||||||
) : CommandServerHandler {
|
) : CommandServerHandler {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -101,8 +107,7 @@ class BoxService(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startCommandServer() {
|
private fun startCommandServer() {
|
||||||
val commandServer =
|
val commandServer = CommandServer(this, 300)
|
||||||
CommandServer(this, 300)
|
|
||||||
commandServer.start()
|
commandServer.start()
|
||||||
this.commandServer = commandServer
|
this.commandServer = commandServer
|
||||||
}
|
}
|
||||||
|
@ -328,4 +333,45 @@ class BoxService(
|
||||||
commandServer?.writeMessage(message)
|
commandServer?.writeMessage(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun sendNotification(notification: Notification) {
|
||||||
|
val builder =
|
||||||
|
NotificationCompat.Builder(service, notification.identifier).setShowWhen(false)
|
||||||
|
.setContentTitle(notification.title)
|
||||||
|
.setContentText(notification.body)
|
||||||
|
.setOnlyAlertOnce(true)
|
||||||
|
.setSmallIcon(R.drawable.ic_menu)
|
||||||
|
.setCategory(NotificationCompat.CATEGORY_EVENT)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
if (!notification.subtitle.isNullOrBlank()) {
|
||||||
|
builder.setContentInfo(notification.subtitle)
|
||||||
|
}
|
||||||
|
if (!notification.openURL.isNullOrBlank()) {
|
||||||
|
builder.setContentIntent(
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
service,
|
||||||
|
0,
|
||||||
|
Intent(
|
||||||
|
service, MainActivity::class.java
|
||||||
|
).apply {
|
||||||
|
setAction(Action.OPEN_URL).setData(Uri.parse(notification.openURL))
|
||||||
|
setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
||||||
|
},
|
||||||
|
ServiceNotification.flags,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
Application.notification.createNotificationChannel(
|
||||||
|
NotificationChannel(
|
||||||
|
notification.identifier,
|
||||||
|
notification.typeName,
|
||||||
|
NotificationManager.IMPORTANCE_HIGH
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Application.notification.notify(notification.typeID, builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package io.nekohasekai.sfa.bg
|
package io.nekohasekai.sfa.bg
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
|
@ -136,6 +137,9 @@ interface PlatformInterfaceWrapper : PlatformInterface {
|
||||||
element.interfaceAddresses.mapTo(mutableListOf()) { it.toPrefix() }
|
element.interfaceAddresses.mapTo(mutableListOf()) { it.toPrefix() }
|
||||||
.iterator()
|
.iterator()
|
||||||
)
|
)
|
||||||
|
runCatching {
|
||||||
|
flags = element.flags
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,6 +150,13 @@ interface PlatformInterfaceWrapper : PlatformInterface {
|
||||||
"${address.hostAddress}/${networkPrefixLength}"
|
"${address.hostAddress}/${networkPrefixLength}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val NetworkInterface.flags: Int
|
||||||
|
@SuppressLint("SoonBlockedPrivateApi")
|
||||||
|
get() {
|
||||||
|
val getFlagsMethod = NetworkInterface::class.java.getDeclaredMethod("getFlags")
|
||||||
|
return getFlagsMethod.invoke(this) as Int
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StringArray(private val iterator: Iterator<String>) : StringIterator {
|
private class StringArray(private val iterator: Iterator<String>) : StringIterator {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.nekohasekai.sfa.bg
|
||||||
|
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import io.nekohasekai.libbox.Notification
|
||||||
|
|
||||||
class ProxyService : Service(), PlatformInterfaceWrapper {
|
class ProxyService : Service(), PlatformInterfaceWrapper {
|
||||||
|
|
||||||
|
@ -14,4 +15,8 @@ class ProxyService : Service(), PlatformInterfaceWrapper {
|
||||||
override fun onDestroy() = service.onDestroy()
|
override fun onDestroy() = service.onDestroy()
|
||||||
|
|
||||||
override fun writeLog(message: String) = service.writeLog(message)
|
override fun writeLog(message: String) = service.writeLog(message)
|
||||||
|
|
||||||
|
override fun sendNotification(notification: Notification) =
|
||||||
|
service.sendNotification(notification)
|
||||||
|
|
||||||
}
|
}
|
|
@ -33,7 +33,7 @@ class ServiceNotification(
|
||||||
companion object {
|
companion object {
|
||||||
private const val notificationId = 1
|
private const val notificationId = 1
|
||||||
private const val notificationChannel = "service"
|
private const val notificationChannel = "service"
|
||||||
private val flags =
|
val flags =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
|
||||||
|
|
||||||
fun checkPermission(): Boolean {
|
fun checkPermission(): Boolean {
|
||||||
|
@ -83,7 +83,7 @@ class ServiceNotification(
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
Application.notification.createNotificationChannel(
|
Application.notification.createNotificationChannel(
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
notificationChannel, "sing-box service", NotificationManager.IMPORTANCE_LOW
|
notificationChannel, "Service Notifications", NotificationManager.IMPORTANCE_LOW
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.net.ProxyInfo
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import io.nekohasekai.libbox.Notification
|
||||||
import io.nekohasekai.libbox.TunOptions
|
import io.nekohasekai.libbox.TunOptions
|
||||||
import io.nekohasekai.sfa.database.Settings
|
import io.nekohasekai.sfa.database.Settings
|
||||||
import io.nekohasekai.sfa.ktx.toIpPrefix
|
import io.nekohasekai.sfa.ktx.toIpPrefix
|
||||||
|
@ -188,4 +189,7 @@ class VPNService : VpnService(), PlatformInterfaceWrapper {
|
||||||
|
|
||||||
override fun writeLog(message: String) = service.writeLog(message)
|
override fun writeLog(message: String) = service.writeLog(message)
|
||||||
|
|
||||||
|
override fun sendNotification(notification: Notification) =
|
||||||
|
service.sendNotification(notification)
|
||||||
|
|
||||||
}
|
}
|
|
@ -3,4 +3,5 @@ package io.nekohasekai.sfa.constant
|
||||||
object Action {
|
object Action {
|
||||||
const val SERVICE = "io.nekohasekai.sfa.SERVICE"
|
const val SERVICE = "io.nekohasekai.sfa.SERVICE"
|
||||||
const val SERVICE_CLOSE = "io.nekohasekai.sfa.SERVICE_CLOSE"
|
const val SERVICE_CLOSE = "io.nekohasekai.sfa.SERVICE_CLOSE"
|
||||||
|
const val OPEN_URL = "io.nekohasekai.sfa.SERVICE_OPEN_URL"
|
||||||
}
|
}
|
|
@ -33,6 +33,7 @@ import io.nekohasekai.sfa.Application
|
||||||
import io.nekohasekai.sfa.R
|
import io.nekohasekai.sfa.R
|
||||||
import io.nekohasekai.sfa.bg.ServiceConnection
|
import io.nekohasekai.sfa.bg.ServiceConnection
|
||||||
import io.nekohasekai.sfa.bg.ServiceNotification
|
import io.nekohasekai.sfa.bg.ServiceNotification
|
||||||
|
import io.nekohasekai.sfa.constant.Action
|
||||||
import io.nekohasekai.sfa.constant.Alert
|
import io.nekohasekai.sfa.constant.Alert
|
||||||
import io.nekohasekai.sfa.constant.ServiceMode
|
import io.nekohasekai.sfa.constant.ServiceMode
|
||||||
import io.nekohasekai.sfa.constant.Status
|
import io.nekohasekai.sfa.constant.Status
|
||||||
|
@ -43,6 +44,7 @@ import io.nekohasekai.sfa.database.TypedProfile
|
||||||
import io.nekohasekai.sfa.databinding.ActivityMainBinding
|
import io.nekohasekai.sfa.databinding.ActivityMainBinding
|
||||||
import io.nekohasekai.sfa.ktx.errorDialogBuilder
|
import io.nekohasekai.sfa.ktx.errorDialogBuilder
|
||||||
import io.nekohasekai.sfa.ktx.hasPermission
|
import io.nekohasekai.sfa.ktx.hasPermission
|
||||||
|
import io.nekohasekai.sfa.ktx.launchCustomTab
|
||||||
import io.nekohasekai.sfa.ui.profile.NewProfileActivity
|
import io.nekohasekai.sfa.ui.profile.NewProfileActivity
|
||||||
import io.nekohasekai.sfa.ui.settings.CoreFragment
|
import io.nekohasekai.sfa.ui.settings.CoreFragment
|
||||||
import io.nekohasekai.sfa.ui.shared.AbstractActivity
|
import io.nekohasekai.sfa.ui.shared.AbstractActivity
|
||||||
|
@ -127,6 +129,12 @@ class MainActivity : AbstractActivity<ActivityMainBinding>(),
|
||||||
override public fun onNewIntent(intent: Intent) {
|
override public fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
val uri = intent.data ?: return
|
val uri = intent.data ?: return
|
||||||
|
when (intent.action) {
|
||||||
|
Action.OPEN_URL -> {
|
||||||
|
launchCustomTab(uri.toString())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if (uri.scheme == "sing-box" && uri.host == "import-remote-profile") {
|
if (uri.scheme == "sing-box" && uri.host == "import-remote-profile") {
|
||||||
val profile = try {
|
val profile = try {
|
||||||
Libbox.parseRemoteProfileImportLink(uri.toString())
|
Libbox.parseRemoteProfileImportLink(uri.toString())
|
||||||
|
|
|
@ -25,7 +25,6 @@ import io.nekohasekai.sfa.databinding.ViewClashModeButtonBinding
|
||||||
import io.nekohasekai.sfa.databinding.ViewProfileItemBinding
|
import io.nekohasekai.sfa.databinding.ViewProfileItemBinding
|
||||||
import io.nekohasekai.sfa.ktx.errorDialogBuilder
|
import io.nekohasekai.sfa.ktx.errorDialogBuilder
|
||||||
import io.nekohasekai.sfa.ktx.getAttrColor
|
import io.nekohasekai.sfa.ktx.getAttrColor
|
||||||
import io.nekohasekai.sfa.ktx.launchCustomTab
|
|
||||||
import io.nekohasekai.sfa.ui.MainActivity
|
import io.nekohasekai.sfa.ui.MainActivity
|
||||||
import io.nekohasekai.sfa.utils.CommandClient
|
import io.nekohasekai.sfa.utils.CommandClient
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
@ -41,7 +40,7 @@ class OverviewFragment : Fragment() {
|
||||||
private val activity: MainActivity? get() = super.getActivity() as MainActivity?
|
private val activity: MainActivity? get() = super.getActivity() as MainActivity?
|
||||||
private var binding: FragmentDashboardOverviewBinding? = null
|
private var binding: FragmentDashboardOverviewBinding? = null
|
||||||
private val statusClient =
|
private val statusClient =
|
||||||
CommandClient(lifecycleScope, CommandClient.ConnectionType.Status, StatusClient(), true)
|
CommandClient(lifecycleScope, CommandClient.ConnectionType.Status, StatusClient())
|
||||||
private val clashModeClient =
|
private val clashModeClient =
|
||||||
CommandClient(lifecycleScope, CommandClient.ConnectionType.ClashMode, ClashModeClient())
|
CommandClient(lifecycleScope, CommandClient.ConnectionType.ClashMode, ClashModeClient())
|
||||||
|
|
||||||
|
@ -172,10 +171,6 @@ class OverviewFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openURL(url: String) {
|
|
||||||
requireContext().launchCustomTab(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ClashModeClient : CommandClient.Handler {
|
inner class ClashModeClient : CommandClient.Handler {
|
||||||
|
|
|
@ -78,9 +78,9 @@ class VPNScanActivity : AbstractActivity<ActivityVpnScanBinding>() {
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
fun bind(element: AppInfo) {
|
fun bind(element: AppInfo) {
|
||||||
binding.appIcon.setImageDrawable(element.packageInfo.applicationInfo.loadIcon(binding.root.context.packageManager))
|
binding.appIcon.setImageDrawable(element.packageInfo.applicationInfo!!.loadIcon(binding.root.context.packageManager))
|
||||||
binding.appName.text =
|
binding.appName.text =
|
||||||
element.packageInfo.applicationInfo.loadLabel(binding.root.context.packageManager)
|
element.packageInfo.applicationInfo!!.loadLabel(binding.root.context.packageManager)
|
||||||
binding.packageName.text = element.packageInfo.packageName
|
binding.packageName.text = element.packageInfo.packageName
|
||||||
val appType = element.vpnType.appType
|
val appType = element.vpnType.appType
|
||||||
if (appType != null) {
|
if (appType != null) {
|
||||||
|
@ -129,7 +129,8 @@ class VPNScanActivity : AbstractActivity<ActivityVpnScanBinding>() {
|
||||||
}
|
}
|
||||||
val vpnAppList =
|
val vpnAppList =
|
||||||
installedPackages.filter {
|
installedPackages.filter {
|
||||||
it.services?.any { it.permission == Manifest.permission.BIND_VPN_SERVICE } ?: false
|
it.services?.any { it.permission == Manifest.permission.BIND_VPN_SERVICE && it.applicationInfo != null }
|
||||||
|
?: false
|
||||||
}
|
}
|
||||||
for ((index, packageInfo) in vpnAppList.withIndex()) {
|
for ((index, packageInfo) in vpnAppList.withIndex()) {
|
||||||
val appType = runCatching { getVPNAppType(packageInfo) }.getOrNull()
|
val appType = runCatching { getVPNAppType(packageInfo) }.getOrNull()
|
||||||
|
@ -181,7 +182,7 @@ class VPNScanActivity : AbstractActivity<ActivityVpnScanBinding>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getVPNAppType(packageInfo: PackageInfo): String? {
|
private fun getVPNAppType(packageInfo: PackageInfo): String? {
|
||||||
ZipFile(File(packageInfo.applicationInfo.publicSourceDir)).use { packageFile ->
|
ZipFile(File(packageInfo.applicationInfo!!.publicSourceDir)).use { packageFile ->
|
||||||
for (packageEntry in packageFile.entries()) {
|
for (packageEntry in packageFile.entries()) {
|
||||||
if (!(packageEntry.name.startsWith("classes") && packageEntry.name.endsWith(
|
if (!(packageEntry.name.startsWith("classes") && packageEntry.name.endsWith(
|
||||||
".dex"
|
".dex"
|
||||||
|
@ -235,8 +236,8 @@ class VPNScanActivity : AbstractActivity<ActivityVpnScanBinding>() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getVPNCoreType(packageInfo: PackageInfo): VPNCoreType? {
|
private fun getVPNCoreType(packageInfo: PackageInfo): VPNCoreType? {
|
||||||
val packageFiles = mutableListOf(packageInfo.applicationInfo.publicSourceDir)
|
val packageFiles = mutableListOf(packageInfo.applicationInfo!!.publicSourceDir)
|
||||||
packageInfo.applicationInfo.splitPublicSourceDirs?.also {
|
packageInfo.applicationInfo!!.splitPublicSourceDirs?.also {
|
||||||
packageFiles.addAll(it)
|
packageFiles.addAll(it)
|
||||||
}
|
}
|
||||||
val vpnType = try {
|
val vpnType = try {
|
||||||
|
|
|
@ -53,24 +53,25 @@ class PerAppProxyActivity : AbstractActivity<ActivityPerAppProxyBinding>() {
|
||||||
|
|
||||||
inner class PackageCache(
|
inner class PackageCache(
|
||||||
private val packageInfo: PackageInfo,
|
private val packageInfo: PackageInfo,
|
||||||
|
private val appInfo: ApplicationInfo,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val packageName: String get() = packageInfo.packageName
|
val packageName: String get() = packageInfo.packageName
|
||||||
|
|
||||||
val uid get() = packageInfo.applicationInfo.uid
|
val uid get() = packageInfo.applicationInfo!!.uid
|
||||||
|
|
||||||
val installTime get() = packageInfo.firstInstallTime
|
val installTime get() = packageInfo.firstInstallTime
|
||||||
val updateTime get() = packageInfo.lastUpdateTime
|
val updateTime get() = packageInfo.lastUpdateTime
|
||||||
val isSystem get() = packageInfo.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM == 1
|
val isSystem get() = appInfo.flags and ApplicationInfo.FLAG_SYSTEM == 1
|
||||||
val isOffline get() = packageInfo.requestedPermissions?.contains(Manifest.permission.INTERNET) != true
|
val isOffline get() = packageInfo.requestedPermissions?.contains(Manifest.permission.INTERNET) != true
|
||||||
val isDisabled get() = packageInfo.applicationInfo.flags and ApplicationInfo.FLAG_INSTALLED == 0
|
val isDisabled get() = appInfo.flags and ApplicationInfo.FLAG_INSTALLED == 0
|
||||||
|
|
||||||
val applicationIcon by lazy {
|
val applicationIcon by lazy {
|
||||||
packageInfo.applicationInfo.loadIcon(packageManager)
|
appInfo.loadIcon(packageManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
val applicationLabel by lazy {
|
val applicationLabel by lazy {
|
||||||
packageInfo.applicationInfo.loadLabel(packageManager).toString()
|
appInfo.loadLabel(packageManager).toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +131,8 @@ class PerAppProxyActivity : AbstractActivity<ActivityPerAppProxyBinding>() {
|
||||||
val packages = mutableListOf<PackageCache>()
|
val packages = mutableListOf<PackageCache>()
|
||||||
for (packageInfo in installedPackages) {
|
for (packageInfo in installedPackages) {
|
||||||
if (packageInfo.packageName == packageName) continue
|
if (packageInfo.packageName == packageName) continue
|
||||||
packages.add(PackageCache(packageInfo))
|
val appInfo = packageInfo.applicationInfo ?: continue
|
||||||
|
packages.add(PackageCache(packageInfo, appInfo))
|
||||||
}
|
}
|
||||||
val selectedPackageNames = Settings.perAppProxyList.toMutableSet()
|
val selectedPackageNames = Settings.perAppProxyList.toMutableSet()
|
||||||
val selectedUIDs = mutableSetOf<Int>()
|
val selectedUIDs = mutableSetOf<Int>()
|
||||||
|
@ -699,6 +701,7 @@ class PerAppProxyActivity : AbstractActivity<ActivityPerAppProxyBinding>() {
|
||||||
packageName, packageManagerFlags
|
packageName, packageManagerFlags
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
val appInfo = packageInfo.applicationInfo ?: return false
|
||||||
packageInfo.services?.forEach {
|
packageInfo.services?.forEach {
|
||||||
if (it.name.matches(chinaAppRegex)) {
|
if (it.name.matches(chinaAppRegex)) {
|
||||||
Log.d("PerAppProxyActivity", "Match service ${it.name} in $packageName")
|
Log.d("PerAppProxyActivity", "Match service ${it.name} in $packageName")
|
||||||
|
@ -723,7 +726,7 @@ class PerAppProxyActivity : AbstractActivity<ActivityPerAppProxyBinding>() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ZipFile(File(packageInfo.applicationInfo.publicSourceDir)).use {
|
ZipFile(File(appInfo.publicSourceDir)).use {
|
||||||
for (packageEntry in it.entries()) {
|
for (packageEntry in it.entries()) {
|
||||||
if (packageEntry.name.startsWith("firebase-")) return false
|
if (packageEntry.name.startsWith("firebase-")) return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ open class CommandClient(
|
||||||
private val scope: CoroutineScope,
|
private val scope: CoroutineScope,
|
||||||
private val connectionType: ConnectionType,
|
private val connectionType: ConnectionType,
|
||||||
private val handler: Handler,
|
private val handler: Handler,
|
||||||
private val isMainClient: Boolean = false
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
enum class ConnectionType {
|
enum class ConnectionType {
|
||||||
|
@ -34,7 +33,6 @@ open class CommandClient(
|
||||||
fun onDisconnected() {}
|
fun onDisconnected() {}
|
||||||
|
|
||||||
fun updateStatus(status: StatusMessage) {}
|
fun updateStatus(status: StatusMessage) {}
|
||||||
fun openURL(url: String) {}
|
|
||||||
|
|
||||||
fun clearLogs() {}
|
fun clearLogs() {}
|
||||||
fun appendLogs(message: List<String>) {}
|
fun appendLogs(message: List<String>) {}
|
||||||
|
@ -57,7 +55,6 @@ open class CommandClient(
|
||||||
ConnectionType.Log -> Libbox.CommandLog
|
ConnectionType.Log -> Libbox.CommandLog
|
||||||
ConnectionType.ClashMode -> Libbox.CommandClashMode
|
ConnectionType.ClashMode -> Libbox.CommandClashMode
|
||||||
}
|
}
|
||||||
options.isMainClient = isMainClient
|
|
||||||
options.statusInterval = 1 * 1000 * 1000 * 1000
|
options.statusInterval = 1 * 1000 * 1000 * 1000
|
||||||
val commandClient = CommandClient(clientHandler, options)
|
val commandClient = CommandClient(clientHandler, options)
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
|
@ -129,10 +126,6 @@ open class CommandClient(
|
||||||
handler.updateStatus(message)
|
handler.updateStatus(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun openURL(url: String) {
|
|
||||||
handler.openURL(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun initializeClashMode(modeList: StringIterator, currentMode: String) {
|
override fun initializeClashMode(modeList: StringIterator, currentMode: String) {
|
||||||
handler.initializeClashMode(modeList.toList(), currentMode)
|
handler.initializeClashMode(modeList.toList(), currentMode)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '8.7.1' apply false
|
id 'com.android.application' version '8.7.2' apply false
|
||||||
id 'com.android.library' version '8.7.1' apply false
|
id 'com.android.library' version '8.7.2' apply false
|
||||||
id 'org.jetbrains.kotlin.android' version '1.9.23' apply false
|
id 'org.jetbrains.kotlin.android' version '1.9.23' apply false
|
||||||
id 'com.google.devtools.ksp' version '1.9.23-1.0.20' apply false
|
id 'com.google.devtools.ksp' version '1.9.23-1.0.20' apply false
|
||||||
id 'com.github.triplet.play' version '3.8.4' apply false
|
id 'com.github.triplet.play' version '3.8.4' apply false
|
||||||
|
|
|
@ -10,6 +10,7 @@ dependencyResolutionManagement {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rootProject.name = "sing-box"
|
rootProject.name = "sing-box"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue