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 - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: 'zulu' distribution: "temurin"
java-version: 17 java-version: 21
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v4

View File

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

View File

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

View File

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

View File

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

View File

@ -120,6 +120,9 @@ class AccessControlActivity : BaseActivity<AccessControlDesign>() {
.filter { .filter {
it.packageName == "android" || it.requestedPermissions?.contains(INTERNET) == true it.packageName == "android" || it.requestedPermissions?.contains(INTERNET) == true
} }
.filter {
it.applicationInfo != null
}
.filter { .filter {
systemApp || !it.isSystemApp systemApp || !it.isSystemApp
} }
@ -132,6 +135,6 @@ class AccessControlActivity : BaseActivity<AccessControlDesign>() {
private val PackageInfo.isSystemApp: Boolean private val PackageInfo.isSystemApp: Boolean
get() { 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 java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
import com.github.kr328.clash.design.R
abstract class BaseActivity<D : Design<*>> : AppCompatActivity(), abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
CoroutineScope by MainScope(), CoroutineScope by MainScope(),

View File

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

View File

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

View File

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

View File

@ -1,6 +1,13 @@
package com.github.kr328.clash 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
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.intent
import com.github.kr328.clash.common.util.ticker import com.github.kr328.clash.common.util.ticker
import com.github.kr328.clash.design.MainDesign import com.github.kr328.clash.design.MainDesign
@ -15,6 +22,7 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.selects.select import kotlinx.coroutines.selects.select
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import com.github.kr328.clash.design.R
class MainActivity : BaseActivity<MainDesign>() { class MainActivity : BaseActivity<MainDesign>() {
override suspend fun main() { override suspend fun main() {
@ -129,4 +137,20 @@ class MainActivity : BaseActivity<MainDesign>() {
packageManager.getPackageInfo(packageName, 0).versionName + "\n" + Bridge.nativeCoreVersion().replace("_", "-") 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 kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import com.github.kr328.clash.design.R
class MetaFeatureSettingsActivity : BaseActivity<MetaFeatureSettingsDesign>() { class MetaFeatureSettingsActivity : BaseActivity<MetaFeatureSettingsDesign>() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,13 +32,19 @@ subprojects {
apply(plugin = if (isApp) "com.android.application" else "com.android.library") apply(plugin = if (isApp) "com.android.application" else "com.android.library")
extensions.configure<BaseExtension> { extensions.configure<BaseExtension> {
buildFeatures.buildConfig = true
defaultConfig { defaultConfig {
if (isApp) { if (isApp) {
applicationId = "com.github.metacubex.clash" 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 minSdk = 21
targetSdk = 31 targetSdk = 35
versionName = "2.11.5" versionName = "2.11.5"
versionCode = 211005 versionCode = 211005
@ -59,7 +65,7 @@ subprojects {
} }
} }
ndkVersion = "23.0.7599858" ndkVersion = "27.2.12479018"
compileSdkVersion(defaultConfig.targetSdk!!) compileSdkVersion(defaultConfig.targetSdk!!)
@ -134,7 +140,7 @@ subprojects {
named("release") { named("release") {
isMinifyEnabled = isApp isMinifyEnabled = isApp
isShrinkResources = isApp isShrinkResources = isApp
signingConfig = signingConfigs.findByName("release") signingConfig = signingConfigs.findByName("release") ?: signingConfigs["debug"]
proguardFiles( proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro" "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" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
package="com.github.kr328.clash.common">
<permission <permission
android:name="${applicationId}.permission.RECEIVE_BROADCASTS" android:name="${applicationId}.permission.RECEIVE_BROADCASTS"

View File

@ -2,8 +2,13 @@
package com.github.kr328.clash.common.compat package com.github.kr328.clash.common.compat
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.IntentFilter
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Handler
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -15,3 +20,18 @@ fun Context.getColorCompat(@ColorRes id: Int): Int {
fun Context.getDrawableCompat(@DrawableRes id: Int): Drawable? { fun Context.getDrawableCompat(@DrawableRes id: Int): Drawable? {
return ContextCompat.getDrawable(this, id) 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 package com.github.kr328.clash.common.compat
import android.app.Notification
import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Build import android.os.Build
fun Context.startForegroundServiceCompat(intent: Intent) { fun Context.startForegroundServiceCompat(intent: Intent) {
@ -11,3 +14,11 @@ fun Context.startForegroundServiceCompat(intent: Intent) {
startService(intent) 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.GolangBuildTask
import com.github.kr328.golang.GolangPlugin import com.github.kr328.golang.GolangPlugin
import java.io.FileOutputStream
import java.net.URL
import java.time.Duration
plugins { plugins {
kotlin("android") kotlin("android")
@ -63,3 +61,14 @@ afterEvaluate {
it.inputs.dir(golangSource) 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" <manifest xmlns:android="http://schemas.android.com/apk/res/android" >
package="com.github.kr328.clash.core">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
</manifest> </manifest>

View File

@ -61,7 +61,7 @@ object Bridge {
.detachFd() .detachFd()
val home = ctx.filesDir.resolve("clash").apply { mkdirs() }.absolutePath 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 val sdkVersion = Build.VERSION.SDK_INT
Log.d("Home = $home") 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 { init {
binding.self = this 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) binding.colorClashStopped = context.resolveThemedColor(R.attr.colorClashStopped)
} }

View File

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

View File

@ -15,9 +15,9 @@ class PopupListAdapter(
private val texts: List<CharSequence>, private val texts: List<CharSequence>,
private val selected: Int, private val selected: Int,
) : BaseAdapter() { ) : BaseAdapter() {
private val colorPrimary = context.resolveThemedColor(R.attr.colorPrimary) private val colorPrimary = context.resolveThemedColor(com.google.android.material.R.attr.colorPrimary)
private val colorOnPrimary = context.resolveThemedColor(R.attr.colorOnPrimary) private val colorOnPrimary = context.resolveThemedColor(com.google.android.material.R.attr.colorOnPrimary)
private val colorControlNormal = context.resolveThemedColor(R.attr.colorControlNormal) private val colorControlNormal = context.resolveThemedColor(com.google.android.material.R.attr.colorControlNormal)
override fun getCount(): Int { override fun getCount(): Int {
return texts.size 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 import com.github.kr328.clash.design.util.resolveThemedResourceId
class ProxyViewConfig(val context: Context, var proxyLine: Int) { 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 = val clickableBackground =
context.resolveThemedResourceId(android.R.attr.selectableItemBackground) context.resolveThemedResourceId(android.R.attr.selectableItemBackground)
val selectedControl = context.resolveThemedColor(R.attr.colorOnPrimary) val selectedControl = context.resolveThemedColor(com.google.android.material.R.attr.colorOnPrimary)
val selectedBackground = context.resolveThemedColor(R.attr.colorPrimary) 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 val unselectedBackground: Int
get() = if (proxyLine==1) Color.TRANSPARENT else colorSurface 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 { fun PackageInfo.toAppInfo(pm: PackageManager): AppInfo {
return AppInfo( return AppInfo(
packageName = packageName, packageName = packageName,
icon = applicationInfo.loadIcon(pm).foreground(), icon = applicationInfo!!.loadIcon(pm).foreground(),
label = applicationInfo.loadLabel(pm).toString(), label = applicationInfo!!.loadLabel(pm).toString(),
installTime = firstInstallTime, installTime = firstInstallTime,
updateDate = lastUpdateTime, updateDate = lastUpdateTime,
) )

View File

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

View File

@ -14,12 +14,4 @@ class AppRecyclerView @JvmOverloads constructor(
init { init {
isFocusable = false 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) minimumHeight = context.getPixels(R.dimen.large_action_card_min_height)
radius = context.getPixels(R.dimen.large_action_card_radius).toFloat() radius = context.getPixels(R.dimen.large_action_card_radius).toFloat()
elevation = context.getPixels(R.dimen.large_action_card_elevation).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"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="geofile_import_failed">Nhập thất bại</string> <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_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</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="press_to_import">Chạm để nhập...</string>
<string name="meta_features">Tính năng của Clash Meta</string> <string name="meta_features">Tính năng của Clash Meta</string>
<string name="allow_ipv6">Cho phép Ipv6</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 distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=fe696c020f241a5f69c30f763c5a7f38eec54b490db19cd2b0962dda420d7d12 distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionSha256Sum=fe696c020f241a5f69c30f763c5a7f38eec54b490db19cd2b0962dda420d7d12

View File

@ -1,2 +1,2 @@
<?xml version="1.0" encoding="utf-8"?> <?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.androidx.room.ktx)
implementation(libs.kaidl.runtime) implementation(libs.kaidl.runtime)
implementation(libs.rikkax.multiprocess) 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 // define any required OkHttp artifacts without version
implementation("com.squareup.okhttp3:okhttp") implementation("com.squareup.okhttp3:okhttp")

View File

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

View File

@ -9,6 +9,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import com.github.kr328.clash.common.compat.getColorCompat import com.github.kr328.clash.common.compat.getColorCompat
import com.github.kr328.clash.common.compat.pendingIntentFlags 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.Components
import com.github.kr328.clash.common.constants.Intents import com.github.kr328.clash.common.constants.Intents
import com.github.kr328.clash.common.id.UndefinedIds import com.github.kr328.clash.common.id.UndefinedIds
@ -123,7 +124,7 @@ class ProfileWorker : BaseService() {
.setOnlyAlertOnce(true) .setOnlyAlertOnce(true)
.build() .build()
startForeground(R.id.nf_profile_worker, notification) startForegroundCompat(R.id.nf_profile_worker, notification)
} }
private suspend inline fun processing(name: String, block: () -> Unit) { 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) { class AppListCacheModule(service: Service) : Module<Unit>(service) {
private fun PackageInfo.uniqueUidName(): String = private fun PackageInfo.uniqueUidName(): String =
if (sharedUserId != null && sharedUserId.isNotBlank()) sharedUserId else packageName if (sharedUserId?.isNotBlank() == true) sharedUserId!! else packageName
private fun reload() { private fun reload() {
val packages = service.packageManager.getInstalledPackages(0) val packages = service.packageManager.getInstalledPackages(0)
.filter { it.applicationInfo != null }
.groupBy { it.uniqueUidName() } .groupBy { it.uniqueUidName() }
.map { (_, v) -> .map { (_, v) ->
val info = v[0] 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 // Force use package name if only one app in a single sharedUid group
// Example: firefox // Example: firefox
info.applicationInfo.uid to info.packageName info.applicationInfo!!.uid to info.packageName
} else { } 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.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter 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.constants.Permissions
import com.github.kr328.clash.common.log.Log import com.github.kr328.clash.common.log.Log
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
@ -44,9 +45,9 @@ abstract class Module<E>(val service: Service) {
} }
if (requireSelf) { if (requireSelf) {
service.registerReceiver(receiver, filter, Permissions.RECEIVE_SELF_BROADCASTS, null) service.registerReceiverCompat(receiver, filter, Permissions.RECEIVE_SELF_BROADCASTS, null)
} else { } else {
service.registerReceiver(receiver, filter) service.registerReceiverCompat(receiver, filter)
} }
receivers.add(receiver) receivers.add(receiver)

View File

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