Update app (#428)

This commit is contained in:
5ec1cff 2025-01-21 21:35:45 +08:00 committed by GitHub
parent 3100e1700c
commit e8e00108e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
47 changed files with 224 additions and 132 deletions

View File

@ -20,8 +20,8 @@ jobs:
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
distribution: "temurin"
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

View File

@ -19,8 +19,8 @@ jobs:
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
distribution: "temurin"
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

View File

@ -22,8 +22,8 @@ jobs:
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
distribution: "temurin"
java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

View File

@ -18,8 +18,8 @@ jobs:
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 17
distribution: "temurin"
java-version: 21
- name: Setup Go
uses: actions/setup-go@v5

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.github.kr328.clash">
xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.software.leanback"
@ -16,6 +15,8 @@
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application
android:name=".MainApplication"
@ -172,7 +173,11 @@
<service
android:name=".LogcatService"
android:exported="false"
android:label="@string/clash_logcat" />
android:label="@string/clash_logcat"
android:foregroundServiceType="specialUse">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="explanation_for_special_use"/>
</service>
<service
android:name=".TileService"
android:exported="true"

View File

@ -120,6 +120,9 @@ class AccessControlActivity : BaseActivity<AccessControlDesign>() {
.filter {
it.packageName == "android" || it.requestedPermissions?.contains(INTERNET) == true
}
.filter {
it.applicationInfo != null
}
.filter {
systemApp || !it.isSystemApp
}
@ -132,6 +135,6 @@ class AccessControlActivity : BaseActivity<AccessControlDesign>() {
private val PackageInfo.isSystemApp: Boolean
get() {
return applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0
return applicationInfo?.flags?.and(ApplicationInfo.FLAG_SYSTEM) != 0
}
}

View File

@ -28,6 +28,7 @@ import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import com.github.kr328.clash.design.R
abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
CoroutineScope by MainScope(),

View File

@ -20,6 +20,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import java.util.*
import com.github.kr328.clash.design.R
class ExternalControlActivity : Activity(), CoroutineScope by MainScope() {
override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -28,6 +28,7 @@ import kotlinx.coroutines.withContext
import java.io.OutputStreamWriter
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import com.github.kr328.clash.design.R
class LogcatActivity : BaseActivity<LogcatDesign>() {
private var conn: ServiceConnection? = null

View File

@ -14,6 +14,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.github.kr328.clash.common.compat.getColorCompat
import com.github.kr328.clash.common.compat.pendingIntentFlags
import com.github.kr328.clash.common.compat.startForegroundCompat
import com.github.kr328.clash.common.log.Log
import com.github.kr328.clash.common.util.intent
import com.github.kr328.clash.core.model.LogMessage
@ -130,17 +131,17 @@ class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.De
NotificationChannelCompat.Builder(
CHANNEL_ID,
NotificationManagerCompat.IMPORTANCE_DEFAULT
).setName(getString(R.string.clash_logcat)).build()
).setName(getString(com.github.kr328.clash.design.R.string.clash_logcat)).build()
)
}
private fun showNotification() {
val notification = NotificationCompat
.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_logo_service)
.setColor(getColorCompat(R.color.color_clash_light))
.setContentTitle(getString(R.string.clash_logcat))
.setContentText(getString(R.string.running))
.setSmallIcon(com.github.kr328.clash.service.R.drawable.ic_logo_service)
.setColor(getColorCompat(com.github.kr328.clash.design.R.color.color_clash_light))
.setContentTitle(getString(com.github.kr328.clash.design.R.string.clash_logcat))
.setContentText(getString(com.github.kr328.clash.design.R.string.running))
.setContentIntent(
PendingIntent.getActivity(
this,
@ -152,7 +153,7 @@ class LogcatService : Service(), CoroutineScope by CoroutineScope(Dispatchers.De
)
.build()
startForeground(R.id.nf_logcat_status, notification)
startForegroundCompat(R.id.nf_logcat_status, notification)
}
companion object {

View File

@ -1,6 +1,13 @@
package com.github.kr328.clash
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.PersistableBundle
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.github.kr328.clash.common.util.intent
import com.github.kr328.clash.common.util.ticker
import com.github.kr328.clash.design.MainDesign
@ -15,6 +22,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.withContext
import java.util.concurrent.TimeUnit
import com.github.kr328.clash.design.R
class MainActivity : BaseActivity<MainDesign>() {
override suspend fun main() {
@ -129,4 +137,20 @@ class MainActivity : BaseActivity<MainDesign>() {
packageManager.getPackageInfo(packageName, 0).versionName + "\n" + Bridge.nativeCoreVersion().replace("_", "-")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val requestPermissionLauncher =
registerForActivityResult(RequestPermission()
) { isGranted: Boolean ->
}
if (ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED) {
requestPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS)
}
}
}
}

View File

@ -16,6 +16,7 @@ import kotlinx.coroutines.selects.select
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import com.github.kr328.clash.design.R
class MetaFeatureSettingsActivity : BaseActivity<MetaFeatureSettingsDesign>() {

View File

@ -18,6 +18,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.withContext
import java.util.*
import com.github.kr328.clash.design.R
class NewProfileActivity : BaseActivity<NewProfileDesign>() {
private val self: NewProfileActivity

View File

@ -9,7 +9,6 @@ import com.github.kr328.clash.common.util.setUUID
import com.github.kr328.clash.common.util.ticker
import com.github.kr328.clash.design.ProfilesDesign
import com.github.kr328.clash.design.ui.ToastDuration
import com.github.kr328.clash.R
import com.github.kr328.clash.service.model.Profile
import com.github.kr328.clash.util.withProfile
import kotlinx.coroutines.Dispatchers
@ -19,6 +18,7 @@ import kotlinx.coroutines.selects.select
import kotlinx.coroutines.withContext
import java.util.*
import java.util.concurrent.TimeUnit
import com.github.kr328.clash.design.R
class ProfilesActivity : BaseActivity<ProfilesDesign>() {
override suspend fun main() {

View File

@ -12,9 +12,11 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.selects.select
import com.github.kr328.clash.design.R
class PropertiesActivity : BaseActivity<PropertiesDesign>() {
private var canceled: Boolean = false
private lateinit var original: Profile
override suspend fun main() {
setResult(RESULT_CANCELED)
@ -22,7 +24,7 @@ class PropertiesActivity : BaseActivity<PropertiesDesign>() {
val uuid = intent.uuid ?: return finish()
val design = PropertiesDesign(this)
val original = withProfile { queryByUUID(uuid) } ?: return finish()
original = withProfile { queryByUUID(uuid) } ?: return finish()
design.profile = original
@ -71,7 +73,7 @@ class PropertiesActivity : BaseActivity<PropertiesDesign>() {
design?.apply {
launch {
if (!progressing) {
if (requestExitWithoutSaving())
if (original == profile || requestExitWithoutSaving())
finish()
}
}

View File

@ -9,6 +9,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.selects.select
import java.util.concurrent.TimeUnit
import com.github.kr328.clash.design.R
class ProvidersActivity : BaseActivity<ProvidersDesign>() {
override suspend fun main() {

View File

@ -9,11 +9,13 @@ import android.os.Build
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import androidx.annotation.RequiresApi
import com.github.kr328.clash.common.compat.registerReceiverCompat
import com.github.kr328.clash.common.constants.Intents
import com.github.kr328.clash.common.constants.Permissions
import com.github.kr328.clash.remote.StatusClient
import com.github.kr328.clash.util.startClashService
import com.github.kr328.clash.util.stopClashService
import com.github.kr328.clash.service.R
@RequiresApi(Build.VERSION_CODES.N)
class TileService : TileService() {
@ -36,7 +38,7 @@ class TileService : TileService() {
override fun onStartListening() {
super.onStartListening()
registerReceiver(
registerReceiverCompat(
receiver,
IntentFilter().apply {
addAction(Intents.ACTION_CLASH_STARTED)

View File

@ -5,6 +5,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import com.github.kr328.clash.common.compat.registerReceiverCompat
import com.github.kr328.clash.common.constants.Intents
import com.github.kr328.clash.common.log.Log
import java.util.*
@ -88,7 +89,7 @@ class Broadcasts(private val context: Application) {
return
try {
context.registerReceiver(broadcastReceiver, IntentFilter().apply {
context.registerReceiverCompat(broadcastReceiver, IntentFilter().apply {
addAction(Intents.ACTION_SERVICE_RECREATED)
addAction(Intents.ACTION_CLASH_STARTED)
addAction(Intents.ACTION_CLASH_STOPPED)

View File

@ -7,16 +7,12 @@ import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.withContext
class ActivityResultLifecycle : LifecycleOwner {
private val lifecycle = LifecycleRegistry(this)
override val lifecycle = LifecycleRegistry(this)
init {
lifecycle.currentState = Lifecycle.State.INITIALIZED
}
override fun getLifecycle(): Lifecycle {
return lifecycle
}
suspend fun <T> use(block: suspend (lifecycle: ActivityResultLifecycle, start: () -> Unit) -> T): T {
return try {
markCreated()

View File

@ -32,13 +32,19 @@ subprojects {
apply(plugin = if (isApp) "com.android.application" else "com.android.library")
extensions.configure<BaseExtension> {
buildFeatures.buildConfig = true
defaultConfig {
if (isApp) {
applicationId = "com.github.metacubex.clash"
}
project.name.let { name ->
namespace = if (name == "app") "com.github.kr328.clash"
else "com.github.kr328.clash.$name"
}
minSdk = 21
targetSdk = 31
targetSdk = 35
versionName = "2.11.5"
versionCode = 211005
@ -59,7 +65,7 @@ subprojects {
}
}
ndkVersion = "23.0.7599858"
ndkVersion = "27.2.12479018"
compileSdkVersion(defaultConfig.targetSdk!!)
@ -134,7 +140,7 @@ subprojects {
named("release") {
isMinifyEnabled = isApp
isShrinkResources = isApp
signingConfig = signingConfigs.findByName("release")
signingConfig = signingConfigs.findByName("release") ?: signingConfigs["debug"]
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
@ -161,6 +167,11 @@ subprojects {
}
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
}
}

View File

@ -1,5 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.kr328.clash.common">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<permission
android:name="${applicationId}.permission.RECEIVE_BROADCASTS"

View File

@ -2,8 +2,13 @@
package com.github.kr328.clash.common.compat
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.IntentFilter
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Handler
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
@ -14,4 +19,19 @@ fun Context.getColorCompat(@ColorRes id: Int): Int {
fun Context.getDrawableCompat(@DrawableRes id: Int): Drawable? {
return ContextCompat.getDrawable(this, id)
}
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")
fun Context.registerReceiverCompat(
receiver: BroadcastReceiver,
filter: IntentFilter,
permission: String? = null,
handler: Handler? = null
) =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
registerReceiver(receiver, filter, permission, handler,
if (permission == null) Context.RECEIVER_EXPORTED else Context.RECEIVER_NOT_EXPORTED
)
else
registerReceiver(receiver, filter, permission, handler)

View File

@ -1,7 +1,10 @@
package com.github.kr328.clash.common.compat
import android.app.Notification
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Build
fun Context.startForegroundServiceCompat(intent: Intent) {
@ -10,4 +13,12 @@ fun Context.startForegroundServiceCompat(intent: Intent) {
} else {
startService(intent)
}
}
}
fun Service.startForegroundCompat(id: Int, notification: Notification) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(id, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
} else {
startForeground(id, notification)
}
}

1
core/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/src/main/cpp/version.h

View File

@ -1,8 +1,6 @@
import android.databinding.tool.ext.capitalizeUS
import com.github.kr328.golang.GolangBuildTask
import com.github.kr328.golang.GolangPlugin
import java.io.FileOutputStream
import java.net.URL
import java.time.Duration
plugins {
kotlin("android")
@ -62,4 +60,15 @@ afterEvaluate {
tasks.withType(GolangBuildTask::class.java).forEach {
it.inputs.dir(golangSource)
}
}
}
val abis = listOf("armeabi-v7a" to "ArmeabiV7a", "arm64-v8a" to "Arm64V8a", "x86_64" to "X8664", "x86" to "X86")
androidComponents.onVariants { variant ->
afterEvaluate {
for ((abi, goAbi) in abis) {
val cmakeName = if (variant.buildType == "debug") "Debug" else "RelWithDebInfo"
tasks.getByName("buildCMake$cmakeName[$abi]").dependsOn(tasks.getByName("externalGolangBuild${variant.name.capitalizeUS()}$goAbi"))
}
}
}

View File

@ -1,5 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.kr328.clash.core">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@ -61,7 +61,7 @@ object Bridge {
.detachFd()
val home = ctx.filesDir.resolve("clash").apply { mkdirs() }.absolutePath
val versionName = ctx.packageManager.getPackageInfo(ctx.packageName, 0).versionName
val versionName = ctx.packageManager.getPackageInfo(ctx.packageName, 0).versionName ?: "unknown"
val sdkVersion = Build.VERSION.SDK_INT
Log.d("Home = $home")

View File

@ -1 +1 @@
<manifest package="com.github.kr328.clash.design" />
<manifest />

View File

@ -81,7 +81,7 @@ class MainDesign(context: Context) : Design<MainDesign.Request>(context) {
init {
binding.self = this
binding.colorClashStarted = context.resolveThemedColor(R.attr.colorPrimary)
binding.colorClashStarted = context.resolveThemedColor(com.google.android.material.R.attr.colorPrimary)
binding.colorClashStopped = context.resolveThemedColor(R.attr.colorClashStopped)
}

View File

@ -108,7 +108,7 @@ class ProxyDesign(
binding.urlTestFloatView.visibility = View.GONE
} else {
binding.urlTestFloatView.supportImageTintList = ColorStateList.valueOf(
context.resolveThemedColor(R.attr.colorOnPrimary)
context.resolveThemedColor(com.google.android.material.R.attr.colorOnPrimary)
)
binding.pagesView.apply {

View File

@ -15,9 +15,9 @@ class PopupListAdapter(
private val texts: List<CharSequence>,
private val selected: Int,
) : BaseAdapter() {
private val colorPrimary = context.resolveThemedColor(R.attr.colorPrimary)
private val colorOnPrimary = context.resolveThemedColor(R.attr.colorOnPrimary)
private val colorControlNormal = context.resolveThemedColor(R.attr.colorControlNormal)
private val colorPrimary = context.resolveThemedColor(com.google.android.material.R.attr.colorPrimary)
private val colorOnPrimary = context.resolveThemedColor(com.google.android.material.R.attr.colorOnPrimary)
private val colorControlNormal = context.resolveThemedColor(com.google.android.material.R.attr.colorControlNormal)
override fun getCount(): Int {
return texts.size

View File

@ -8,15 +8,15 @@ import com.github.kr328.clash.design.util.resolveThemedColor
import com.github.kr328.clash.design.util.resolveThemedResourceId
class ProxyViewConfig(val context: Context, var proxyLine: Int) {
private val colorSurface = context.resolveThemedColor(R.attr.colorSurface)
private val colorSurface = context.resolveThemedColor(com.google.android.material.R.attr.colorSurface)
val clickableBackground =
context.resolveThemedResourceId(android.R.attr.selectableItemBackground)
val selectedControl = context.resolveThemedColor(R.attr.colorOnPrimary)
val selectedBackground = context.resolveThemedColor(R.attr.colorPrimary)
val selectedControl = context.resolveThemedColor(com.google.android.material.R.attr.colorOnPrimary)
val selectedBackground = context.resolveThemedColor(com.google.android.material.R.attr.colorPrimary)
val unselectedControl = context.resolveThemedColor(R.attr.colorOnSurface)
val unselectedControl = context.resolveThemedColor(com.google.android.material.R.attr.colorOnSurface)
val unselectedBackground: Int
get() = if (proxyLine==1) Color.TRANSPARENT else colorSurface

View File

@ -8,8 +8,8 @@ import com.github.kr328.clash.design.model.AppInfo
fun PackageInfo.toAppInfo(pm: PackageManager): AppInfo {
return AppInfo(
packageName = packageName,
icon = applicationInfo.loadIcon(pm).foreground(),
label = applicationInfo.loadLabel(pm).toString(),
icon = applicationInfo!!.loadIcon(pm).foreground(),
label = applicationInfo!!.loadLabel(pm).toString(),
installTime = firstInstallTime,
updateDate = lastUpdateTime,
)

View File

@ -28,7 +28,7 @@ fun View.setOnInsertsChangedListener(adaptLandscape: Boolean = true, listener: (
listener(if (adaptLandscape) rInsets.landscape(v.context) else rInsets)
compat.toWindowInsets()
compat.toWindowInsets()!!
}
requestApplyInsets()

View File

@ -14,12 +14,4 @@ class AppRecyclerView @JvmOverloads constructor(
init {
isFocusable = false
}
override fun onDraw(c: Canvas?) {
super.onDraw(c)
}
override fun dispatchDraw(canvas: Canvas?) {
super.dispatchDraw(canvas)
}
}
}

View File

@ -60,6 +60,6 @@ class LargeActionCard @JvmOverloads constructor(
minimumHeight = context.getPixels(R.dimen.large_action_card_min_height)
radius = context.getPixels(R.dimen.large_action_card_radius).toFloat()
elevation = context.getPixels(R.dimen.large_action_card_elevation).toFloat()
setCardBackgroundColor(context.resolveThemedColor(R.attr.colorSurface))
setCardBackgroundColor(context.resolveThemedColor(com.google.android.material.R.attr.colorSurface))
}
}

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="geofile_import_failed">Nhập thất bại</string>
<string name="toast_profile_updated_complete">Cập nhật thành công</string>
<string name="toast_profile_updated_failed">Cập nhật không thành công</string>
<string name="toast_profile_updated_complete">Cập nhật thành công %s</string>
<string name="toast_profile_updated_failed">Cập nhật không thành công %1$s %2$s</string>
<string name="press_to_import">Chạm để nhập...</string>
<string name="meta_features">Tính năng của Clash Meta</string>
<string name="allow_ipv6">Cho phép Ipv6</string>

43
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,43 @@
[versions]
agp = "8.8.0"
kotlin = "2.1.0"
ksp = "2.1.0-1.0.29"
golang = "1.0.4"
coroutine = "1.10.1"
coreKtx = "1.8.0"
activity = "1.5.0"
fragment = "1.5.0"
appcompat = "1.4.2"
coordinator = "1.2.0"
recyclerview = "1.2.1"
viewpager = "1.0.0"
material = "1.6.1"
serialization = "1.3.3"
kaidl = "1.15"
room = "2.4.2"
multiprocess = "1.0.0"
[libraries]
build-android = { module = "com.android.tools.build:gradle", version.ref = "agp" }
build-kotlin-common = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
build-kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
build-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" }
build-golang = { module = "com.github.kr328.golang:gradle-plugin", version.ref = "golang" }
kotlin-coroutine = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutine" }
kotlin-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
androidx-core = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
androidx-activity = { module = "androidx.activity:activity", version.ref = "activity" }
androidx-fragment = { module = "androidx.fragment:fragment", version.ref = "fragment" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
androidx-coordinator = { module = "androidx.coordinatorlayout:coordinatorlayout", version.ref = "coordinator" }
androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
androidx-viewpager = { module = "androidx.viewpager2:viewpager2", version.ref = "viewpager" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
google-material = { module = "com.google.android.material:material", version.ref = "material" }
kaidl-compiler = { module = "com.github.kr328.kaidl:kaidl", version.ref = "kaidl" }
kaidl-runtime = { module = "com.github.kr328.kaidl:kaidl-runtime", version.ref = "kaidl" }
rikkax-multiprocess = { module = "dev.rikka.rikkax.preference:multiprocess", version.ref = "multiprocess" }
[plugins]

View File

@ -1,8 +1,6 @@
#Tue Jan 14 14:06:42 CST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=fe696c020f241a5f69c30f763c5a7f38eec54b490db19cd2b0962dda420d7d12
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip
networkTimeout=10000
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionSha256Sum=fe696c020f241a5f69c30f763c5a7f38eec54b490db19cd2b0962dda420d7d12

View File

@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.github.kr328.clash.hideapi" />
<manifest />

View File

@ -19,7 +19,7 @@ dependencies {
implementation(libs.androidx.room.ktx)
implementation(libs.kaidl.runtime)
implementation(libs.rikkax.multiprocess)
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.10.0"))
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.12.0"))
// define any required OkHttp artifacts without version
implementation("com.squareup.okhttp3:okhttp")

View File

@ -1,6 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.github.kr328.clash.service">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@ -9,19 +8,27 @@
<uses-permission
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<application>
<service
android:name=".ClashService"
android:exported="false"
android:label="@string/clash_meta_for_android"
android:process=":background" />
android:process=":background"
android:foregroundServiceType="specialUse">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="explanation_for_special_use"/>
</service>
<service
android:name=".TunService"
android:exported="false"
android:label="@string/clash_meta_for_android"
android:permission="android.permission.BIND_VPN_SERVICE"
android:process=":background">
android:process=":background"
android:foregroundServiceType="specialUse">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="explanation_for_special_use"/>
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
@ -33,7 +40,11 @@
<service
android:name=".ProfileWorker"
android:exported="false"
android:process=":background" />
android:process=":background"
android:foregroundServiceType="specialUse">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="explanation_for_special_use"/>
</service>
<provider
android:name=".FilesProvider"

View File

@ -9,6 +9,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.github.kr328.clash.common.compat.getColorCompat
import com.github.kr328.clash.common.compat.pendingIntentFlags
import com.github.kr328.clash.common.compat.startForegroundCompat
import com.github.kr328.clash.common.constants.Components
import com.github.kr328.clash.common.constants.Intents
import com.github.kr328.clash.common.id.UndefinedIds
@ -123,7 +124,7 @@ class ProfileWorker : BaseService() {
.setOnlyAlertOnce(true)
.build()
startForeground(R.id.nf_profile_worker, notification)
startForegroundCompat(R.id.nf_profile_worker, notification)
}
private suspend inline fun processing(name: String, block: () -> Unit) {

View File

@ -11,10 +11,11 @@ import java.util.concurrent.TimeUnit
class AppListCacheModule(service: Service) : Module<Unit>(service) {
private fun PackageInfo.uniqueUidName(): String =
if (sharedUserId != null && sharedUserId.isNotBlank()) sharedUserId else packageName
if (sharedUserId?.isNotBlank() == true) sharedUserId!! else packageName
private fun reload() {
val packages = service.packageManager.getInstalledPackages(0)
.filter { it.applicationInfo != null }
.groupBy { it.uniqueUidName() }
.map { (_, v) ->
val info = v[0]
@ -23,9 +24,9 @@ class AppListCacheModule(service: Service) : Module<Unit>(service) {
// Force use package name if only one app in a single sharedUid group
// Example: firefox
info.applicationInfo.uid to info.packageName
info.applicationInfo!!.uid to info.packageName
} else {
info.applicationInfo.uid to info.uniqueUidName()
info.applicationInfo!!.uid to info.uniqueUidName()
}
}

View File

@ -5,6 +5,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import com.github.kr328.clash.common.compat.registerReceiverCompat
import com.github.kr328.clash.common.constants.Permissions
import com.github.kr328.clash.common.log.Log
import kotlinx.coroutines.NonCancellable
@ -44,9 +45,9 @@ abstract class Module<E>(val service: Service) {
}
if (requireSelf) {
service.registerReceiver(receiver, filter, Permissions.RECEIVE_SELF_BROADCASTS, null)
service.registerReceiverCompat(receiver, filter, Permissions.RECEIVE_SELF_BROADCASTS, null)
} else {
service.registerReceiver(receiver, filter)
service.registerReceiverCompat(receiver, filter)
}
receivers.add(receiver)

View File

@ -8,6 +8,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.github.kr328.clash.common.compat.getColorCompat
import com.github.kr328.clash.common.compat.pendingIntentFlags
import com.github.kr328.clash.common.compat.startForegroundCompat
import com.github.kr328.clash.common.constants.Components
import com.github.kr328.clash.common.constants.Intents
import com.github.kr328.clash.service.R
@ -47,7 +48,7 @@ class StaticNotificationModule(service: Service) : Module<Unit>(service) {
.setContentText(service.getText(R.string.running))
.build()
service.startForeground(R.id.nf_clash_status, notification)
service.startForegroundCompat(R.id.nf_clash_status, notification)
}
}
@ -74,7 +75,7 @@ class StaticNotificationModule(service: Service) : Module<Unit>(service) {
.setContentTitle(service.getText(R.string.loading))
.build()
service.startForeground(R.id.nf_clash_status, notification)
service.startForegroundCompat(R.id.nf_clash_status, notification)
}
}
}

View File

@ -14,49 +14,3 @@ pluginManagement {
gradlePluginPortal()
}
}
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
val agp = "7.2.1"
val kotlin = "1.7.0"
val ksp = "$kotlin-1.0.6"
val golang = "1.0.4"
val coroutine = "1.7.3"
val coreKtx = "1.8.0"
val activity = "1.5.0"
val fragment = "1.5.0"
val appcompat = "1.4.2"
val coordinator = "1.2.0"
val recyclerview = "1.2.1"
val viewpager = "1.0.0"
val material = "1.6.1"
val serialization = "1.3.3"
val kaidl = "1.15"
val room = "2.4.2"
val multiprocess = "1.0.0"
library("build-android", "com.android.tools.build:gradle:$agp")
library("build-kotlin-common", "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin")
library("build-kotlin-serialization", "org.jetbrains.kotlin:kotlin-serialization:$kotlin")
library("build-ksp", "com.google.devtools.ksp:symbol-processing-gradle-plugin:$ksp")
library("build-golang", "com.github.kr328.golang:gradle-plugin:$golang")
library("kotlin-coroutine", "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine")
library("kotlin-serialization-json", "org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization")
library("androidx-core", "androidx.core:core-ktx:$coreKtx")
library("androidx-activity", "androidx.activity:activity:$activity")
library("androidx-fragment", "androidx.fragment:fragment:$fragment")
library("androidx-appcompat", "androidx.appcompat:appcompat:$appcompat")
library("androidx-coordinator", "androidx.coordinatorlayout:coordinatorlayout:$coordinator")
library("androidx-recyclerview", "androidx.recyclerview:recyclerview:$recyclerview")
library("androidx-viewpager", "androidx.viewpager2:viewpager2:$viewpager")
library("androidx-room-compiler", "androidx.room:room-compiler:$room")
library("androidx-room-runtime", "androidx.room:room-runtime:$room")
library("androidx-room-ktx", "androidx.room:room-ktx:$room")
library("google-material", "com.google.android.material:material:$material")
library("kaidl-compiler", "com.github.kr328.kaidl:kaidl:$kaidl")
library("kaidl-runtime", "com.github.kr328.kaidl:kaidl-runtime:$kaidl")
library("rikkax-multiprocess", "dev.rikka.rikkax.preference:multiprocess:$multiprocess")
}
}
}