mirror of
https://github.com/MetaCubeX/ClashMetaForAndroid.git
synced 2026-05-09 18:11:26 +08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8b1c0b7b8 | ||
|
|
8bf908b731 | ||
|
|
568f1f12e7 | ||
|
|
ca739a5e2f | ||
|
|
7833f747d5 | ||
|
|
977519b383 | ||
|
|
b39b3812d0 | ||
|
|
f7dddb41fa | ||
|
|
053300919c | ||
|
|
93dff86383 | ||
|
|
1b7e9c6b12 | ||
|
|
f072cd0839 | ||
|
|
f626a0fc5c | ||
|
|
63da12f046 | ||
|
|
ce1beafb08 |
@@ -17,6 +17,7 @@ android {
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
@@ -27,6 +28,12 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
splits {
|
||||
abi {
|
||||
enable true
|
||||
universalApk true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -13,8 +13,10 @@ class ApkBrokenActivity : BaseActivity() {
|
||||
setContentView(R.layout.activity_application_broken)
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
text.text = Html.fromHtml(getString(R.string.application_broken_description),
|
||||
Html.FROM_HTML_MODE_COMPACT)
|
||||
text.text = Html.fromHtml(
|
||||
getString(R.string.application_broken_description),
|
||||
Html.FROM_HTML_MODE_COMPACT
|
||||
)
|
||||
|
||||
commonUi.build {
|
||||
option(
|
||||
@@ -22,8 +24,10 @@ class ApkBrokenActivity : BaseActivity() {
|
||||
title = getString(R.string.learn_more_about_split_apks)
|
||||
) {
|
||||
onClick {
|
||||
startActivity(Intent(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(getString(R.string.about_split_apks_url))))
|
||||
startActivity(
|
||||
Intent(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(getString(R.string.about_split_apks_url)))
|
||||
)
|
||||
}
|
||||
}
|
||||
option(
|
||||
@@ -31,8 +35,10 @@ class ApkBrokenActivity : BaseActivity() {
|
||||
title = getString(R.string.reinstall_from_google_play)
|
||||
) {
|
||||
onClick {
|
||||
startActivity(Intent(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(getString(R.string.google_play_url))))
|
||||
startActivity(
|
||||
Intent(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(getString(R.string.google_play_url)))
|
||||
)
|
||||
}
|
||||
}
|
||||
option(
|
||||
@@ -40,8 +46,10 @@ class ApkBrokenActivity : BaseActivity() {
|
||||
title = getString(R.string.download_from_github_releases)
|
||||
) {
|
||||
onClick {
|
||||
startActivity(Intent(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(getString(R.string.github_releases_url))))
|
||||
startActivity(
|
||||
Intent(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(getString(R.string.github_releases_url)))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,8 +104,7 @@ class LogViewerActivity : BaseActivity() {
|
||||
.filter { it.size == 3 }
|
||||
.map { LogEvent(LogEvent.Level.valueOf(it[1]), it[2], it[0].toLong()) }
|
||||
.toList()
|
||||
}
|
||||
catch (e: Exception) {
|
||||
} catch (e: Exception) {
|
||||
makeSnackbarException(getString(R.string.open_log_failure), e.message)
|
||||
|
||||
throw CancellationException()
|
||||
|
||||
@@ -8,7 +8,10 @@ import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.*
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.IInterface
|
||||
import androidx.collection.CircularArray
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
@@ -24,8 +27,6 @@ import com.github.kr328.clash.service.util.createLanguageConfigurationContext
|
||||
import com.github.kr328.clash.service.util.intent
|
||||
import com.github.kr328.clash.utils.format
|
||||
import com.github.kr328.clash.utils.logsDir
|
||||
import com.microsoft.appcenter.analytics.Analytics
|
||||
import com.microsoft.appcenter.crashes.Crashes
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.selects.select
|
||||
@@ -39,6 +40,7 @@ class LogcatService : Service(), CoroutineScope by MainScope(), IInterface {
|
||||
private const val NOTIFICATION_CHANNEL_ID = "clash_logcat_channel"
|
||||
private const val NOTIFICATION_ID = 256
|
||||
private const val MAX_CACHE_COUNT = 200
|
||||
private const val LOG_LISTENER_KEY = "logcat_service"
|
||||
|
||||
private const val LOG_CONTENT_FORMAT = "%d %s %s"
|
||||
|
||||
@@ -55,29 +57,23 @@ class LogcatService : Service(), CoroutineScope by MainScope(), IInterface {
|
||||
private val entity = LogFile.generate()
|
||||
|
||||
private val connection = object : ServiceConnection {
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
logChannel.offer(LogEvent(LogEvent.Level.ERROR, "Clash Service Crashed"))
|
||||
private var manager: IClashManager? = null
|
||||
|
||||
Crashes.trackError(RemoteException("Clash Service Crashed"))
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
manager?.unregisterLogListener(LOG_LISTENER_KEY)
|
||||
}
|
||||
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
val manager = IClashManager.Stub.asInterface(service) ?: return stopSelf()
|
||||
|
||||
manager.openLogEvent(object : IStreamCallback.Stub() {
|
||||
override fun complete() {
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override fun completeExceptionally(reason: String?) {
|
||||
stopSelf()
|
||||
}
|
||||
manager = IClashManager.Stub.asInterface(service) ?: return stopSelf()
|
||||
|
||||
manager?.registerLogListener(LOG_LISTENER_KEY, object : IStreamCallback.Stub() {
|
||||
override fun complete() {}
|
||||
override fun completeExceptionally(reason: String?) {}
|
||||
override fun send(data: ParcelableContainer?) {
|
||||
val logEvent = (data?.data as LogEvent?) ?: return
|
||||
data ?: return
|
||||
data.data ?: return
|
||||
|
||||
if (!logChannel.offer(logEvent))
|
||||
Log.w("Drop log $logEvent")
|
||||
logChannel.offer(data.data as LogEvent)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -103,6 +99,8 @@ class LogcatService : Service(), CoroutineScope by MainScope(), IInterface {
|
||||
|
||||
cancel()
|
||||
|
||||
connection.onServiceDisconnected(null)
|
||||
|
||||
stopForeground(true)
|
||||
|
||||
super.onDestroy()
|
||||
@@ -182,8 +180,7 @@ class LogcatService : Service(), CoroutineScope by MainScope(), IInterface {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
} catch (e: Exception) {
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,12 +67,16 @@ class LogsActivity : BaseActivity() {
|
||||
showClearAllDialog()
|
||||
}
|
||||
|
||||
val adapter = LogFileAdapter(this@LogsActivity,
|
||||
val adapter = LogFileAdapter(
|
||||
this@LogsActivity,
|
||||
onItemClicked = {
|
||||
startActivity(LogViewerActivity::class.intent
|
||||
.setData(Uri.fromFile(logsDir.resolve(it.fileName))))
|
||||
startActivity(
|
||||
LogViewerActivity::class.intent
|
||||
.setData(Uri.fromFile(logsDir.resolve(it.fileName)))
|
||||
)
|
||||
},
|
||||
onMenuClicked = this::showMenu)
|
||||
onMenuClicked = this::showMenu
|
||||
)
|
||||
val layoutManager = LinearLayoutManager(this@LogsActivity)
|
||||
|
||||
mainList.layoutManager = layoutManager
|
||||
@@ -136,7 +140,10 @@ class LogsActivity : BaseActivity() {
|
||||
|
||||
val result = withContext(Dispatchers.Default) {
|
||||
DiffUtil.calculateDiff(object : DiffUtil.Callback() {
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||
override fun areItemsTheSame(
|
||||
oldItemPosition: Int,
|
||||
newItemPosition: Int
|
||||
): Boolean {
|
||||
return old[oldItemPosition].fileName == files[newItemPosition].fileName
|
||||
}
|
||||
|
||||
@@ -188,7 +195,8 @@ class LogsActivity : BaseActivity() {
|
||||
menu.build {
|
||||
option(
|
||||
icon = getDrawable(R.drawable.ic_save),
|
||||
title = getString(R.string.export)) {
|
||||
title = getString(R.string.export)
|
||||
) {
|
||||
onClick {
|
||||
export(logFile)
|
||||
|
||||
@@ -197,7 +205,8 @@ class LogsActivity : BaseActivity() {
|
||||
}
|
||||
option(
|
||||
icon = getDrawable(R.drawable.ic_delete_colorful),
|
||||
title = getString(R.string.delete)) {
|
||||
title = getString(R.string.delete)
|
||||
) {
|
||||
textColor = errorColor
|
||||
|
||||
onClick {
|
||||
|
||||
@@ -12,9 +12,7 @@ import com.github.kr328.clash.core.model.General
|
||||
import com.github.kr328.clash.core.utils.asBytesString
|
||||
import com.github.kr328.clash.remote.withClash
|
||||
import com.github.kr328.clash.remote.withProfile
|
||||
import com.github.kr328.clash.service.ClashService
|
||||
import com.github.kr328.clash.service.util.intent
|
||||
import com.github.kr328.clash.service.util.startForegroundServiceCompat
|
||||
import com.github.kr328.clash.utils.startClashService
|
||||
import com.github.kr328.clash.utils.stopClashService
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
|
||||
@@ -3,11 +3,15 @@ package com.github.kr328.clash
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.github.kr328.clash.core.Global
|
||||
import com.github.kr328.clash.dump.LogcatDumper
|
||||
import com.github.kr328.clash.remote.Broadcasts
|
||||
import com.github.kr328.clash.remote.Remote
|
||||
import com.microsoft.appcenter.AppCenter
|
||||
import com.microsoft.appcenter.analytics.Analytics
|
||||
import com.microsoft.appcenter.crashes.AbstractCrashesListener
|
||||
import com.microsoft.appcenter.crashes.Crashes
|
||||
import com.microsoft.appcenter.crashes.ingestion.models.ErrorAttachmentLog
|
||||
import com.microsoft.appcenter.crashes.model.ErrorReport
|
||||
|
||||
@Suppress("unused")
|
||||
class MainApplication : Application() {
|
||||
@@ -27,6 +31,21 @@ class MainApplication : Application() {
|
||||
BuildConfig.APP_CENTER_KEY,
|
||||
Analytics::class.java, Crashes::class.java
|
||||
)
|
||||
|
||||
Crashes.setListener(object : AbstractCrashesListener() {
|
||||
override fun getErrorAttachments(report: ErrorReport?): MutableIterable<ErrorAttachmentLog> {
|
||||
report ?: return mutableListOf()
|
||||
|
||||
if (!report.stackTrace.contains("DeadObjectException"))
|
||||
return mutableListOf()
|
||||
|
||||
val logcat = LogcatDumper.dump().joinToString(separator = "\n")
|
||||
|
||||
return mutableListOf(
|
||||
ErrorAttachmentLog.attachmentWithText(logcat, "logcat.txt")
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Remote.init(this)
|
||||
|
||||
@@ -15,7 +15,9 @@ class OnBootReceiver: BroadcastReceiver() {
|
||||
return
|
||||
|
||||
context.startClashService()
|
||||
context.startForegroundServiceCompat(Intent(Intents.INTENT_ACTION_PROFILE_SETUP)
|
||||
.setComponent(ProfileBackgroundService::class.componentName))
|
||||
context.startForegroundServiceCompat(
|
||||
Intent(Intents.INTENT_ACTION_PROFILE_SETUP)
|
||||
.setComponent(ProfileBackgroundService::class.componentName)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -59,8 +59,10 @@ class ProxiesActivity : BaseActivity(), ScrollBinding.Callback {
|
||||
|
||||
override fun onStop() {
|
||||
uiSettings.commit {
|
||||
put(UiSettings.PROXY_LAST_SELECT_GROUP,
|
||||
(chipList.adapter!! as ProxyChipAdapter).selected)
|
||||
put(
|
||||
UiSettings.PROXY_LAST_SELECT_GROUP,
|
||||
(chipList.adapter!! as ProxyChipAdapter).selected
|
||||
)
|
||||
}
|
||||
|
||||
super.onStop()
|
||||
|
||||
@@ -13,7 +13,8 @@ class SettingsActivity: BaseActivity() {
|
||||
commonUi.build {
|
||||
option(
|
||||
icon = getDrawable(R.drawable.ic_settings_applications),
|
||||
title = getString(R.string.behavior)) {
|
||||
title = getString(R.string.behavior)
|
||||
) {
|
||||
paddingHeight = true
|
||||
|
||||
onClick {
|
||||
@@ -22,7 +23,8 @@ class SettingsActivity: BaseActivity() {
|
||||
}
|
||||
option(
|
||||
icon = getDrawable(R.drawable.ic_network),
|
||||
title = getString(R.string.network)) {
|
||||
title = getString(R.string.network)
|
||||
) {
|
||||
paddingHeight = true
|
||||
|
||||
onClick {
|
||||
@@ -31,7 +33,8 @@ class SettingsActivity: BaseActivity() {
|
||||
}
|
||||
option(
|
||||
icon = getDrawable(R.drawable.ic_interface),
|
||||
title = getString(R.string.interface_)) {
|
||||
title = getString(R.string.interface_)
|
||||
) {
|
||||
paddingHeight = true
|
||||
|
||||
onClick {
|
||||
|
||||
@@ -24,10 +24,13 @@ class SupportActivity: BaseActivity() {
|
||||
|
||||
option(
|
||||
title = getString(R.string.clash),
|
||||
summary = getString(R.string.clash_url)) {
|
||||
summary = getString(R.string.clash_url)
|
||||
) {
|
||||
onClick {
|
||||
startActivity(Intent(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(getString(R.string.clash_url))))
|
||||
startActivity(
|
||||
Intent(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(getString(R.string.clash_url)))
|
||||
)
|
||||
}
|
||||
}
|
||||
option(
|
||||
@@ -35,8 +38,10 @@ class SupportActivity: BaseActivity() {
|
||||
summary = getString(R.string.clash_for_android_url)
|
||||
) {
|
||||
onClick {
|
||||
startActivity(Intent(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(getString(R.string.clash_for_android_url))))
|
||||
startActivity(
|
||||
Intent(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(getString(R.string.clash_for_android_url)))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,20 +64,25 @@ class SupportActivity: BaseActivity() {
|
||||
summary = getString(R.string.github_issues_url)
|
||||
) {
|
||||
onClick {
|
||||
startActivity(Intent(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(getString(R.string.github_issues_url))))
|
||||
startActivity(
|
||||
Intent(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(getString(R.string.github_issues_url)))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (resources.configuration.locales.get(0)
|
||||
.language.equals("zh", true) ) {
|
||||
.language.equals("zh", true)
|
||||
) {
|
||||
option(
|
||||
title = getString(R.string.telegram_channel),
|
||||
summary = getString(R.string.telegram_channel_url)
|
||||
) {
|
||||
onClick {
|
||||
startActivity(Intent(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(getString(R.string.telegram_channel_url))))
|
||||
startActivity(
|
||||
Intent(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(getString(R.string.telegram_channel_url)))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,7 @@ class LiveLogAdapter(private val context: Context) : RecyclerView.Adapter<LogAda
|
||||
fun insertItems(i: List<LogEvent>) {
|
||||
val items = if (i.size > MAX_LOG_ITEMS) {
|
||||
i.subList(i.size - MAX_LOG_ITEMS, i.size)
|
||||
}
|
||||
else i
|
||||
} else i
|
||||
|
||||
val predictSize = items.size + circularArray.size()
|
||||
|
||||
|
||||
@@ -15,11 +15,15 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.streams.toList
|
||||
|
||||
class PackagesAdapter(private val context: Context,
|
||||
private val apps: List<AppInfo>) :
|
||||
class PackagesAdapter(
|
||||
private val context: Context,
|
||||
private val apps: List<AppInfo>
|
||||
) :
|
||||
RecyclerView.Adapter<PackagesAdapter.Holder>() {
|
||||
data class AppInfo(val packageName: String, val label: String, val icon: Drawable,
|
||||
val installTime: Long, val updateTime: Long, val isSystem: Boolean)
|
||||
data class AppInfo(
|
||||
val packageName: String, val label: String, val icon: Drawable,
|
||||
val installTime: Long, val updateTime: Long, val isSystem: Boolean
|
||||
)
|
||||
|
||||
enum class Sort {
|
||||
NAME, PACKAGE, INSTALL_TIME, UPDATE_TIME
|
||||
@@ -117,8 +121,7 @@ class PackagesAdapter(private val context: Context,
|
||||
holder.root.setOnClickListener {
|
||||
if (selectedPackages.contains(current.packageName)) {
|
||||
selectedPackages.remove(current.packageName)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
selectedPackages.add(current.packageName)
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ class ProxyAdapter(
|
||||
groupCache[it.name] = index
|
||||
is ProxyRenderInfo -> {
|
||||
if (it.info.active)
|
||||
activeCache[it.name] = index
|
||||
activeCache[it.group] = index
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,8 +194,10 @@ class ProxyAdapter(
|
||||
is ProxyGroupHeader -> {
|
||||
val current = renderList[position] as ProxyGroupRenderInfo
|
||||
|
||||
holder.title.text = context.getString(R.string.format_proxy_group_title,
|
||||
current.info.name, current.info.current)
|
||||
holder.title.text = context.getString(
|
||||
R.string.format_proxy_group_title,
|
||||
current.info.name, current.info.current
|
||||
)
|
||||
holder.urlTest.setOnClickListener {
|
||||
holder.urlTest.visibility = View.GONE
|
||||
holder.urlTestProgress.visibility = View.VISIBLE
|
||||
@@ -237,14 +239,16 @@ class ProxyAdapter(
|
||||
if (current.info.selectable) {
|
||||
holder.root.setOnClickListener {
|
||||
val oldPosition = activeList[current.group] ?: return@setOnClickListener
|
||||
val groupPosition = groupPosition[current.group] ?: return@setOnClickListener
|
||||
val groupPosition =
|
||||
groupPosition[current.group] ?: return@setOnClickListener
|
||||
val old = renderList[oldPosition] as ProxyRenderInfo
|
||||
val new = renderList[position] as ProxyRenderInfo
|
||||
val group = renderList[groupPosition] as ProxyGroupRenderInfo
|
||||
|
||||
renderList[oldPosition] = old.copy(info = old.info.copy(active = false))
|
||||
renderList[position] = new.copy(info = new.info.copy(active = true))
|
||||
renderList[groupPosition] = group.copy(info = group.info.copy(current = current.name))
|
||||
renderList[groupPosition] =
|
||||
group.copy(info = group.info.copy(current = current.name))
|
||||
|
||||
activeList[current.group] = position
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.github.kr328.clash.dump
|
||||
|
||||
object LogcatDumper {
|
||||
fun dump(): List<String> {
|
||||
return try {
|
||||
val process =
|
||||
Runtime.getRuntime().exec(arrayOf("logcat", "-d", "-s", "-v", "raw", "Go"))
|
||||
|
||||
val result = process.inputStream.bufferedReader().useLines {
|
||||
var list = mutableListOf<String>()
|
||||
var capture = false
|
||||
|
||||
it.forEach { line ->
|
||||
if (line.startsWith("panic")) {
|
||||
capture = true
|
||||
|
||||
list = mutableListOf()
|
||||
}
|
||||
|
||||
if (capture)
|
||||
list.add(line)
|
||||
}
|
||||
|
||||
list
|
||||
}
|
||||
|
||||
process.waitFor()
|
||||
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,10 @@ suspend fun Pipeline<List<ProxyGroup>>.mergePrefix(): Pipeline<Map<ProxyEntry, P
|
||||
}
|
||||
.flatMap {
|
||||
it.second.map { merged ->
|
||||
ProxyEntry(it.first, merged.value.name) to ProxyMerged(merged.prefix, merged.content)
|
||||
ProxyEntry(it.first, merged.value.name) to ProxyMerged(
|
||||
merged.prefix,
|
||||
merged.content
|
||||
)
|
||||
}
|
||||
}
|
||||
.toMap()
|
||||
@@ -66,7 +69,10 @@ suspend fun Pipeline<List<ProxyGroup>>.sort(): Pipeline<List<ProxyGroup>> {
|
||||
return copy(input = sorter.sort(input))
|
||||
}
|
||||
|
||||
suspend fun Pipeline<List<ProxyGroup>>.toAdapterElement(prefixMerged: Map<ProxyEntry, ProxyMerged>, general: General):
|
||||
suspend fun Pipeline<List<ProxyGroup>>.toAdapterElement(
|
||||
prefixMerged: Map<ProxyEntry, ProxyMerged>,
|
||||
general: General
|
||||
):
|
||||
List<ProxyAdapter.ProxyGroupInfo> {
|
||||
return input.map { group ->
|
||||
val proxies = group.proxies.map { proxy ->
|
||||
@@ -74,9 +80,11 @@ suspend fun Pipeline<List<ProxyGroup>>.toAdapterElement(prefixMerged: Map<ProxyE
|
||||
it.prefix.isNotBlank() && it.content.isNotBlank()
|
||||
} ?: ProxyMerged(proxy.type.toString(), proxy.name)
|
||||
|
||||
ProxyAdapter.ProxyInfo(proxy.name, group.name, merged.content, merged.prefix,
|
||||
ProxyAdapter.ProxyInfo(
|
||||
proxy.name, group.name, merged.content, merged.prefix,
|
||||
proxy.delay.toShort(), group.type == Proxy.Type.SELECT,
|
||||
group.current == proxy.name)
|
||||
group.current == proxy.name
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import kotlinx.coroutines.withContext
|
||||
|
||||
class ClashClient(val service: IClashManager) {
|
||||
suspend fun setSelectProxy(name: String, proxy: String): Boolean = withContext(Dispatchers.IO) {
|
||||
return@withContext service.setSelectProxy(name, proxy)
|
||||
service.setSelectProxy(name, proxy)
|
||||
}
|
||||
|
||||
suspend fun startHealthCheck(group: String) = withContext(Dispatchers.IO) {
|
||||
@@ -39,8 +39,7 @@ class ClashClient(val service: IClashManager) {
|
||||
service.queryGeneral()
|
||||
}
|
||||
|
||||
suspend fun queryBandwidth(): Long =
|
||||
withContext(Dispatchers.IO) {
|
||||
suspend fun queryBandwidth(): Long = withContext(Dispatchers.IO) {
|
||||
service.queryBandwidth()
|
||||
}
|
||||
|
||||
|
||||
@@ -7,17 +7,21 @@ import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.RemoteException
|
||||
import androidx.core.content.edit
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.github.kr328.clash.ApkBrokenActivity
|
||||
import com.github.kr328.clash.Constants
|
||||
import com.github.kr328.clash.dump.LogcatDumper
|
||||
import com.github.kr328.clash.service.ClashManagerService
|
||||
import com.github.kr328.clash.service.IClashManager
|
||||
import com.github.kr328.clash.service.IProfileService
|
||||
import com.github.kr328.clash.service.ProfileService
|
||||
import com.github.kr328.clash.service.util.intent
|
||||
import com.microsoft.appcenter.crashes.Crashes
|
||||
import com.microsoft.appcenter.crashes.ingestion.models.ErrorAttachmentLog
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import java.util.zip.ZipFile
|
||||
@@ -44,6 +48,16 @@ object Remote {
|
||||
instance = ClashClient(IClashManager.Stub.asInterface(service))
|
||||
|
||||
service?.linkToDeath({
|
||||
val log = LogcatDumper.dump().joinToString(separator = "\n")
|
||||
|
||||
val attachmentLog = ErrorAttachmentLog
|
||||
.attachmentWithText(log, "logcat.txt")
|
||||
|
||||
Crashes.trackError(
|
||||
RemoteException("Clash Service Crashed"),
|
||||
null, listOf(attachmentLog)
|
||||
)
|
||||
|
||||
onServiceDisconnected(null)
|
||||
}, 0)
|
||||
|
||||
@@ -92,8 +106,10 @@ object Remote {
|
||||
|
||||
GlobalScope.launch {
|
||||
if (!verifyApk(application)) {
|
||||
application.startActivity(ApkBrokenActivity::class.intent
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
application.startActivity(
|
||||
ApkBrokenActivity::class.intent
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package com.github.kr328.clash.remote
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import com.github.kr328.clash.ApkBrokenActivity
|
||||
import com.github.kr328.clash.service.Constants
|
||||
import com.github.kr328.clash.service.ServiceStatusProvider
|
||||
import com.github.kr328.clash.service.util.intent
|
||||
|
||||
object RemoteUtils {
|
||||
fun detectClashRunning(context: Context): Boolean {
|
||||
try {
|
||||
val authority = Uri.Builder()
|
||||
.scheme("content")
|
||||
.authority("${context.packageName}${Constants.STATUS_PROVIDER_SUFFIX}")
|
||||
@@ -20,9 +24,15 @@ object RemoteUtils {
|
||||
)
|
||||
|
||||
return pong != null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
context.startActivity(ApkBrokenActivity::class.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun getCurrentClashProfileName(context: Context): String? {
|
||||
try {
|
||||
val authority = Uri.Builder()
|
||||
.scheme("content")
|
||||
.authority("${context.packageName}${Constants.STATUS_PROVIDER_SUFFIX}")
|
||||
@@ -36,5 +46,10 @@ object RemoteUtils {
|
||||
)
|
||||
|
||||
return pong?.getString("name")
|
||||
} catch (e: IllegalArgumentException) {
|
||||
context.startActivity(ApkBrokenActivity::class.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,8 @@ class BehaviorFragment: BaseSettingFragment() {
|
||||
requireActivity().packageManager.setComponentEnabledSetting(
|
||||
OnBootReceiver::class.componentName,
|
||||
status,
|
||||
PackageManager.DONT_KILL_APP)
|
||||
PackageManager.DONT_KILL_APP
|
||||
)
|
||||
}
|
||||
|
||||
override fun get(): Any? {
|
||||
|
||||
@@ -8,7 +8,12 @@ const val DATE_DATE_ONLY = "yyyy-MM-dd"
|
||||
const val DATE_TIME_ONLY = "HH:mm:ss"
|
||||
const val DATE_ALL = "$DATE_DATE_ONLY $DATE_TIME_ONLY"
|
||||
|
||||
fun Date.format(context: Context, includeDate: Boolean = true, includeTime: Boolean = true, custom: String = ""): String {
|
||||
fun Date.format(
|
||||
context: Context,
|
||||
includeDate: Boolean = true,
|
||||
includeTime: Boolean = true,
|
||||
custom: String = ""
|
||||
): String {
|
||||
val locale = context.resources.configuration.locales[0]
|
||||
|
||||
return when {
|
||||
|
||||
@@ -56,8 +56,12 @@ object PrefixMerger {
|
||||
val prefix = it.first.subList(0, diffIndex)
|
||||
val content = it.first.subList(diffIndex, it.first.size)
|
||||
|
||||
result.add(Result(prefix.asCodePointString().replace(REGEX_PREFIX_TRIM, ""),
|
||||
content.asCodePointString(), it.second))
|
||||
result.add(
|
||||
Result(
|
||||
prefix.asCodePointString().replace(REGEX_PREFIX_TRIM, ""),
|
||||
content.asCodePointString(), it.second
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,7 @@ fun Context.startClashService(): Intent? {
|
||||
return vpnRequest
|
||||
|
||||
startForegroundServiceCompat(TunService::class.intent)
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
startForegroundServiceCompat(ClashService::class.intent)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ buildscript {
|
||||
gMinSdkVersion = 24
|
||||
gTargetSdkVersion = 29
|
||||
|
||||
gVersionCode = 10103
|
||||
gVersionName = "1.1.3"
|
||||
gVersionCode = 10105
|
||||
gVersionName = "1.1.5"
|
||||
|
||||
gKotlinVersion = '1.3.61'
|
||||
gKotlinCoroutineVersion = '1.3.3'
|
||||
@@ -20,7 +20,7 @@ buildscript {
|
||||
gLifecycleVersion = "2.2.0"
|
||||
gRecyclerviewVersion = "1.1.0"
|
||||
gAppCompatVersion = "1.1.0"
|
||||
gMaterialDesignVersion = "1.2.0-alpha04"
|
||||
gMaterialDesignVersion = '1.2.0-alpha05'
|
||||
gShizukuPreferenceVersion = "4.2.0"
|
||||
gMultiprocessPreferenceVersion = "1.0.0"
|
||||
}
|
||||
@@ -29,7 +29,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
classpath 'com.android.tools.build:gradle:3.6.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$gKotlinVersion"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$gKotlinVersion"
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
||||
@@ -103,7 +103,7 @@ open class GolangBindTask : DefaultTask() {
|
||||
.copyRecursively(goPath.resolve("src"), overwrite = true)
|
||||
|
||||
"gomobile init".exec(goBuildPath)
|
||||
"gomobile bind -target=android \"-gcflags=all=-trimpath=$goPath\" github.com/kr328/cfa/bridge".exec(goBuildPath)
|
||||
"gomobile bind -target=android \"-gcflags=all=-trimpath=$goPath\" \"-ldflags=-w -s\" github.com/kr328/cfa/bridge".exec(goBuildPath)
|
||||
|
||||
nativeOutput.deleteRecursively()
|
||||
javaOutput.deleteRecursively()
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/component/mmdb"
|
||||
C "github.com/Dreamacro/clash/constant"
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
"github.com/kr328/cfa/profile"
|
||||
)
|
||||
|
||||
var (
|
||||
logCallback LogCallback
|
||||
logSubscribe sync.Once
|
||||
)
|
||||
|
||||
type LogCallback interface {
|
||||
OnLogEvent(level, payload string)
|
||||
}
|
||||
|
||||
func LoadMMDB(data []byte) {
|
||||
dataClone := make([]byte, len(data))
|
||||
copy(dataClone, data)
|
||||
@@ -26,3 +38,27 @@ func Reset() {
|
||||
func SetApplicationVersion(version string) {
|
||||
profile.ApplicationVersion = version
|
||||
}
|
||||
|
||||
func SetLogCallback(callback LogCallback) {
|
||||
logSubscribe.Do(func() {
|
||||
go func() {
|
||||
sub := log.Subscribe()
|
||||
defer log.UnSubscribe(sub)
|
||||
|
||||
for {
|
||||
elm := <-sub
|
||||
l := elm.(*log.Event)
|
||||
|
||||
if l.LogLevel < log.Level() {
|
||||
continue
|
||||
}
|
||||
|
||||
if cb := logCallback; cb != nil {
|
||||
cb.OnLogEvent(l.LogLevel.String(), l.Payload)
|
||||
}
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
logCallback = callback
|
||||
}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"github.com/Dreamacro/clash/log"
|
||||
"sync"
|
||||
|
||||
"github.com/Dreamacro/clash/tunnel"
|
||||
)
|
||||
|
||||
type EventPoll struct {
|
||||
stop sync.Once
|
||||
|
||||
onStop func()
|
||||
}
|
||||
|
||||
func (e *EventPoll) Stop() {
|
||||
e.stop.Do(func() {
|
||||
e.onStop()
|
||||
})
|
||||
}
|
||||
|
||||
type Traffic struct {
|
||||
@@ -40,35 +45,3 @@ func QueryTraffic() *Traffic {
|
||||
Download: down,
|
||||
}
|
||||
}
|
||||
|
||||
func PollLogs(logs Logs) *EventPoll {
|
||||
stopChannel := make(chan int, 1)
|
||||
sub := log.Subscribe()
|
||||
|
||||
go func() {
|
||||
defer log.UnSubscribe(sub)
|
||||
defer close(stopChannel)
|
||||
defer log.Infoln("Logs Poll Stopped")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopChannel:
|
||||
return
|
||||
case elm := <-sub:
|
||||
l := elm.(*log.Event)
|
||||
|
||||
if l.LogLevel < log.Level() {
|
||||
break
|
||||
}
|
||||
|
||||
logs.OnEvent(l.Type(), l.Payload)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return &EventPoll{
|
||||
onStop: func() {
|
||||
stopChannel <- 0
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Submodule core/src/main/golang/clash updated: c6eb441961...d68467fe37
@@ -83,10 +83,14 @@ func ReadAndCheck(fd int, output, baseDir string) error {
|
||||
}
|
||||
|
||||
func SaveAndCheck(data []byte, output, baseDir string) error {
|
||||
_, err := parseConfig(data, baseDir)
|
||||
cfg, err := parseConfig(data, baseDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range cfg.Providers {
|
||||
v.Destroy()
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(output, data, defaultFileMode)
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ func parseConfig(data []byte, baseDir string) (*config.Config, error) {
|
||||
raw.Experimental.Interface = ""
|
||||
raw.ExternalUI = ""
|
||||
raw.ExternalController = ""
|
||||
raw.Rule = append([]string{fmt.Sprintf("IP-CIDR,%s,REJECT", tunAddress)}, raw.Rule...)
|
||||
raw.Rule = append([]string{fmt.Sprintf("IP-CIDR,%s,REJECT,no-resolve", tunAddress)}, raw.Rule...)
|
||||
|
||||
patchRawConfig(raw)
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.github.kr328.clash.core
|
||||
import android.content.Context
|
||||
import bridge.Bridge
|
||||
import bridge.TunCallback
|
||||
import com.github.kr328.clash.core.event.EventStream
|
||||
import com.github.kr328.clash.core.event.LogEvent
|
||||
import com.github.kr328.clash.core.model.General
|
||||
import com.github.kr328.clash.core.model.Proxy
|
||||
@@ -12,12 +11,13 @@ import com.github.kr328.clash.core.model.Traffic
|
||||
import com.github.kr328.clash.core.transact.DoneCallbackImpl
|
||||
import com.github.kr328.clash.core.transact.ProxyCollectionImpl
|
||||
import com.github.kr328.clash.core.transact.ProxyGroupCollectionImpl
|
||||
import com.github.kr328.clash.core.utils.Log
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
|
||||
object Clash {
|
||||
private val logReceivers = mutableMapOf<String, (LogEvent) -> Unit>()
|
||||
|
||||
private var initialized = false
|
||||
|
||||
@Synchronized
|
||||
@@ -33,8 +33,6 @@ object Clash {
|
||||
Bridge.setHome(context.cacheDir.absolutePath)
|
||||
Bridge.setApplicationVersion(BuildConfig.VERSION_NAME)
|
||||
Bridge.reset()
|
||||
|
||||
Log.d("MMDB loaded ${bytes.size}")
|
||||
}
|
||||
|
||||
fun start() {
|
||||
@@ -56,6 +54,7 @@ object Clash {
|
||||
override fun onCreateSocket(fd: Long) {
|
||||
onNewSocket(fd.toInt())
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
onTunStop()
|
||||
}
|
||||
@@ -143,14 +142,27 @@ object Clash {
|
||||
return Traffic(data.upload, data.download)
|
||||
}
|
||||
|
||||
fun openLogEvent(): EventStream<LogEvent> {
|
||||
return object : EventStream<LogEvent>() {
|
||||
val log = Bridge.pollLogs { level, payload ->
|
||||
send(LogEvent(LogEvent.Level.fromString(level), payload))
|
||||
fun registerLogReceiver(key: String, receiver: (LogEvent) -> Unit) {
|
||||
synchronized(logReceivers) {
|
||||
logReceivers[key] = receiver
|
||||
|
||||
Bridge.setLogCallback(this::onLogEvent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClose() {
|
||||
log.stop()
|
||||
fun unregisterLogReceiver(key: String) {
|
||||
synchronized(logReceivers) {
|
||||
logReceivers.remove(key)
|
||||
|
||||
if (logReceivers.isEmpty())
|
||||
Bridge.setLogCallback(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onLogEvent(level: String, payload: String) {
|
||||
synchronized(logReceivers) {
|
||||
logReceivers.forEach {
|
||||
it.value(LogEvent(LogEvent.Level.fromString(level), payload))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import android.widget.TextView
|
||||
import com.github.kr328.clash.design.R
|
||||
|
||||
class Category(screen: CommonUiScreen) : Base(screen) {
|
||||
override val view: View = LayoutInflater.from(context).inflate(R.layout.view_category, screen.layout, false)
|
||||
override val view: View =
|
||||
LayoutInflater.from(context).inflate(R.layout.view_category, screen.layout, false)
|
||||
|
||||
private val vText: TextView = view.findViewById(R.id.text)
|
||||
private val vTopSeparator: View = view.findViewById(R.id.topSeparator)
|
||||
@@ -15,7 +16,9 @@ class Category(screen: CommonUiScreen): Base(screen) {
|
||||
|
||||
var text: CharSequence
|
||||
get() = vText.text
|
||||
set(value) { vText.text = value }
|
||||
set(value) {
|
||||
vText.text = value
|
||||
}
|
||||
|
||||
var showTopSeparator: Boolean
|
||||
get() = vTopSeparator.visibility == View.VISIBLE
|
||||
|
||||
@@ -73,7 +73,8 @@ class CommonUiBuilder(val screen: CommonUiScreen) {
|
||||
fun tips(
|
||||
title: String = "",
|
||||
icon: Drawable? = null,
|
||||
setup: Tips.() -> Unit) {
|
||||
setup: Tips.() -> Unit
|
||||
) {
|
||||
val tips = Tips(screen)
|
||||
|
||||
tips.title = title
|
||||
|
||||
@@ -8,7 +8,8 @@ import android.widget.TextView
|
||||
import com.github.kr328.clash.design.R
|
||||
|
||||
class Option(screen: CommonUiScreen) : Base(screen) {
|
||||
override val view: View = LayoutInflater.from(context).inflate(R.layout.view_setting_option, screen.layout, false)
|
||||
override val view: View =
|
||||
LayoutInflater.from(context).inflate(R.layout.view_setting_option, screen.layout, false)
|
||||
|
||||
private val vIcon: View = view.findViewById(android.R.id.icon)
|
||||
private val vTitle: TextView = view.findViewById(android.R.id.title)
|
||||
@@ -19,10 +20,14 @@ class Option(screen: CommonUiScreen): Base(screen) {
|
||||
|
||||
var icon: Drawable?
|
||||
get() = vIcon.background
|
||||
set(value) { vIcon.background = value }
|
||||
set(value) {
|
||||
vIcon.background = value
|
||||
}
|
||||
var title: CharSequence
|
||||
get() = vTitle.text
|
||||
set(value) { vTitle.text = value }
|
||||
set(value) {
|
||||
vTitle.text = value
|
||||
}
|
||||
var summary: CharSequence
|
||||
get() = vSummary.text
|
||||
set(value) {
|
||||
|
||||
@@ -16,11 +16,15 @@ class Tips(screen: CommonUiScreen): Base(screen) {
|
||||
|
||||
var icon: Drawable?
|
||||
get() = vIcon.background
|
||||
set(value) { vIcon.background = value }
|
||||
set(value) {
|
||||
vIcon.background = value
|
||||
}
|
||||
|
||||
var title: CharSequence
|
||||
get() = vTitle.text
|
||||
set(value) { vTitle.text = value }
|
||||
set(value) {
|
||||
vTitle.text = value
|
||||
}
|
||||
|
||||
override fun saveState(bundle: Bundle) {}
|
||||
override fun restoreState(bundle: Bundle) {}
|
||||
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Thu Nov 14 20:04:34 CST 2019
|
||||
#Tue Feb 25 12:40:41 CST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||
|
||||
@@ -16,9 +16,6 @@ interface IClashManager {
|
||||
long queryBandwidth();
|
||||
|
||||
// Events
|
||||
void openLogEvent(IStreamCallback callback);
|
||||
|
||||
// Settings
|
||||
boolean putSetting(String key, String value);
|
||||
String getSetting(String key);
|
||||
void registerLogListener(String key, IStreamCallback callback);
|
||||
void unregisterLogListener(String key);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.PowerManager
|
||||
import androidx.core.content.contentValuesOf
|
||||
import androidx.core.content.getSystemService
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.core.utils.Log
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.github.kr328.clash.service
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.core.model.General
|
||||
import com.github.kr328.clash.core.model.ProxyGroupList
|
||||
@@ -42,37 +41,12 @@ class ClashManager(context: Context, parent: CoroutineScope) :
|
||||
return Clash.setSelectedProxy(proxy, selected)
|
||||
}
|
||||
|
||||
override fun putSetting(key: String?, value: String?): Boolean {
|
||||
settings.edit(commit = false) {
|
||||
putString(key, value)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun queryBandwidth(): Long {
|
||||
val data = Clash.queryBandwidth()
|
||||
|
||||
return data.download + data.upload
|
||||
}
|
||||
|
||||
override fun openLogEvent(callback: IStreamCallback?) {
|
||||
require(callback != null)
|
||||
|
||||
Clash.openLogEvent().apply {
|
||||
callback.asBinder()?.linkToDeath({
|
||||
close()
|
||||
}, 0)
|
||||
|
||||
onEvent {
|
||||
try {
|
||||
callback.send(ParcelableContainer(it))
|
||||
} catch (e: Exception) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun startHealthCheck(group: String?, callback: IStreamCallback?) {
|
||||
require(group != null && callback != null)
|
||||
|
||||
@@ -84,7 +58,26 @@ class ClashManager(context: Context, parent: CoroutineScope) :
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSetting(key: String?): String? {
|
||||
return settings.getString(key, null)
|
||||
override fun registerLogListener(key: String?, callback: IStreamCallback?) {
|
||||
requireNotNull(key)
|
||||
requireNotNull(callback)
|
||||
|
||||
callback.asBinder().linkToDeath({
|
||||
Clash.unregisterLogReceiver(key)
|
||||
}, 0)
|
||||
|
||||
Clash.registerLogReceiver(key) {
|
||||
try {
|
||||
callback.send(ParcelableContainer(it))
|
||||
} catch (e: Exception) {
|
||||
Clash.unregisterLogReceiver(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun unregisterLogListener(key: String?) {
|
||||
requireNotNull(key)
|
||||
|
||||
Clash.unregisterLogReceiver(key)
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,10 @@ import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.github.kr328.clash.core.Clash
|
||||
import com.github.kr328.clash.core.utils.Log
|
||||
import com.github.kr328.clash.core.utils.asBytesString
|
||||
import com.github.kr328.clash.core.utils.asSpeedString
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.serializer
|
||||
|
||||
class ClashNotification(
|
||||
private val context: Service
|
||||
|
||||
@@ -2,10 +2,25 @@ package com.github.kr328.clash.service
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import rikka.preference.MultiProcessPreference
|
||||
import rikka.preference.PreferenceProvider
|
||||
|
||||
class ServiceSettingsProvider : PreferenceProvider() {
|
||||
override fun onCreatePreference(context: Context?): SharedPreferences {
|
||||
return context!!.getSharedPreferences(Constants.SERVICE_SETTING_FILE_NAME, Context.MODE_PRIVATE)
|
||||
return context!!.getSharedPreferences(
|
||||
Constants.SERVICE_SETTING_FILE_NAME,
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun createSharedPreferencesFromContext(context: Context): SharedPreferences {
|
||||
return when ( context ) {
|
||||
is BaseService, is TunService ->
|
||||
context.getSharedPreferences(Constants.SERVICE_SETTING_FILE_NAME, Context.MODE_PRIVATE)
|
||||
else ->
|
||||
MultiProcessPreference(context, context.packageName + Constants.SETTING_PROVIDER_SUFFIX)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ class TunService : VpnService(), CoroutineScope by MainScope() {
|
||||
private const val VPN_MTU = 1500
|
||||
private const val PRIVATE_VLAN4_SUBNET = 30
|
||||
private const val PRIVATE_VLAN4_CLIENT = "172.31.255.253"
|
||||
private const val PRIVATE_VLAN6_CLIENT = "fdfe:dcba:9876::1"
|
||||
private const val PRIVATE_VLAN_DNS = "172.31.255.254"
|
||||
private const val VLAN4_ANY = "0.0.0.0"
|
||||
}
|
||||
@@ -125,8 +126,11 @@ class TunService : VpnService(), CoroutineScope by MainScope() {
|
||||
}
|
||||
|
||||
private fun Builder.addBypassPrivateRoute(): Builder {
|
||||
val ipv6Support = settings.get(ServiceSettings.IPV6_SUPPORT)
|
||||
val bypassPrivate = settings.get(ServiceSettings.BYPASS_PRIVATE_NETWORK)
|
||||
|
||||
// IPv4
|
||||
if (settings.get(ServiceSettings.BYPASS_PRIVATE_NETWORK)) {
|
||||
if (bypassPrivate) {
|
||||
resources.getStringArray(R.array.bypass_private_route).forEach {
|
||||
val address = it.split("/")
|
||||
addRoute(address[0], address[1].toInt())
|
||||
@@ -136,7 +140,11 @@ class TunService : VpnService(), CoroutineScope by MainScope() {
|
||||
}
|
||||
|
||||
// IPv6
|
||||
if (settings.get(ServiceSettings.IPV6_SUPPORT)) {
|
||||
if (ipv6Support) {
|
||||
if (bypassPrivate)
|
||||
// from https://github.com/shadowsocks/shadowsocks-android/commit/cc840c9fddb3f4f6677005de18f1fcb387b84064#diff-e089fe63dcb3674c0a1e459a95508e3e
|
||||
addRoute("2000::", 3)
|
||||
else
|
||||
addRoute("::", 0)
|
||||
}
|
||||
|
||||
@@ -145,16 +153,9 @@ class TunService : VpnService(), CoroutineScope by MainScope() {
|
||||
|
||||
private fun Builder.addBypassApplications(): Builder {
|
||||
when (settings.get(ServiceSettings.ACCESS_CONTROL_MODE)) {
|
||||
ServiceSettings.ACCESS_CONTROL_MODE_ALL -> {
|
||||
for (app in resources.getStringArray(R.array.default_disallow_application)) {
|
||||
runCatching {
|
||||
addDisallowedApplication(app)
|
||||
}
|
||||
}
|
||||
}
|
||||
ServiceSettings.ACCESS_CONTROL_MODE_ALL -> {}
|
||||
ServiceSettings.ACCESS_CONTROL_MODE_WHITELIST -> {
|
||||
for (app in settings.get(ServiceSettings.ACCESS_CONTROL_PACKAGES).toSet() -
|
||||
resources.getStringArray(R.array.default_disallow_application)) {
|
||||
for (app in settings.get(ServiceSettings.ACCESS_CONTROL_PACKAGES).toSet()) {
|
||||
runCatching {
|
||||
addAllowedApplication(app)
|
||||
}.onFailure {
|
||||
@@ -163,8 +164,7 @@ class TunService : VpnService(), CoroutineScope by MainScope() {
|
||||
}
|
||||
}
|
||||
ServiceSettings.ACCESS_CONTROL_MODE_BLACKLIST -> {
|
||||
for (app in settings.get(ServiceSettings.ACCESS_CONTROL_PACKAGES).toSet() +
|
||||
resources.getStringArray(R.array.default_disallow_application)) {
|
||||
for (app in settings.get(ServiceSettings.ACCESS_CONTROL_PACKAGES).toSet()) {
|
||||
runCatching {
|
||||
addDisallowedApplication(app)
|
||||
}.onFailure {
|
||||
@@ -181,6 +181,9 @@ class TunService : VpnService(), CoroutineScope by MainScope() {
|
||||
private fun Builder.addAddress(): Builder {
|
||||
addAddress(PRIVATE_VLAN4_CLIENT, PRIVATE_VLAN4_SUBNET)
|
||||
|
||||
if (settings.get(ServiceSettings.IPV6_SUPPORT))
|
||||
addAddress(PRIVATE_VLAN6_CLIENT, 126)
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
@@ -68,13 +68,13 @@ object ClashDatabaseMigrations {
|
||||
cursor.moveToNext()
|
||||
}
|
||||
cursor.close()
|
||||
}
|
||||
catch (e: Exception) {
|
||||
} catch (e: Exception) {
|
||||
Log.d("Migration profiles failure", e)
|
||||
}
|
||||
|
||||
try {
|
||||
val cursor = database.query("SELECT profile_id, proxy, selected FROM _profile_select_proxies ORDER BY id")
|
||||
val cursor =
|
||||
database.query("SELECT profile_id, proxy, selected FROM _profile_select_proxies ORDER BY id")
|
||||
|
||||
cursor.moveToFirst()
|
||||
while (!cursor.isAfterLast) {
|
||||
@@ -98,8 +98,7 @@ object ClashDatabaseMigrations {
|
||||
cursor.moveToNext()
|
||||
}
|
||||
cursor.close()
|
||||
}
|
||||
catch (e: Exception) {
|
||||
} catch (e: Exception) {
|
||||
Log.d("Migration selected failure")
|
||||
}
|
||||
|
||||
@@ -109,8 +108,10 @@ object ClashDatabaseMigrations {
|
||||
// Migration settings
|
||||
val oldSettings = Global.application
|
||||
.getSharedPreferences("clash_service", Context.MODE_PRIVATE)
|
||||
val newSettings = ServiceSettings(Global.application
|
||||
.getSharedPreferences(Constants.SERVICE_SETTING_FILE_NAME, Context.MODE_PRIVATE))
|
||||
val newSettings = ServiceSettings(
|
||||
Global.application
|
||||
.getSharedPreferences(Constants.SERVICE_SETTING_FILE_NAME, Context.MODE_PRIVATE)
|
||||
)
|
||||
|
||||
val accessMode = oldSettings
|
||||
.getInt("key_access_control_mode", 0)
|
||||
@@ -143,8 +144,7 @@ object ClashDatabaseMigrations {
|
||||
try {
|
||||
process(database)
|
||||
Log.i("Database Migrated 1 -> 2")
|
||||
}
|
||||
catch (e: Exception) {
|
||||
} catch (e: Exception) {
|
||||
Log.e("Migration failure", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ class DefaultNetworkChannel(val context: Context, scope: CoroutineScope) :
|
||||
val cap = capabilitiesCache[network]
|
||||
|
||||
if (cap?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
!= networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
|
||||
!= networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
) {
|
||||
sendDefaultNetwork(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ abstract class BaseSettings(private val preferences: SharedPreferences) {
|
||||
}
|
||||
}
|
||||
|
||||
class BooleanEntry(private val key: String, private val defaultValue: Boolean): Entry<Boolean> {
|
||||
class BooleanEntry(private val key: String, private val defaultValue: Boolean) :
|
||||
Entry<Boolean> {
|
||||
override fun get(preferences: SharedPreferences): Boolean {
|
||||
return preferences.getBoolean(key, defaultValue)
|
||||
}
|
||||
@@ -28,7 +29,8 @@ abstract class BaseSettings(private val preferences: SharedPreferences) {
|
||||
}
|
||||
}
|
||||
|
||||
class StringSetEntry(private val key: String, private val defaultValue: Set<String>): Entry<Set<String>> {
|
||||
class StringSetEntry(private val key: String, private val defaultValue: Set<String>) :
|
||||
Entry<Set<String>> {
|
||||
override fun get(preferences: SharedPreferences): Set<String> {
|
||||
return preferences.getStringSet(key, defaultValue)!!
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@ package com.github.kr328.clash.service.settings
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import com.github.kr328.clash.service.Constants
|
||||
import com.github.kr328.clash.service.ServiceSettingsProvider
|
||||
import rikka.preference.MultiProcessPreference
|
||||
|
||||
class ServiceSettings(preference: SharedPreferences) :
|
||||
BaseSettings(preference) {
|
||||
constructor(context: Context): this(MultiProcessPreference(context,
|
||||
context.packageName + Constants.SETTING_PROVIDER_SUFFIX))
|
||||
constructor(context: Context) : this(
|
||||
ServiceSettingsProvider.createSharedPreferencesFromContext(context)
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val ACCESS_CONTROL_MODE_ALL = "access_control_mode_all"
|
||||
const val ACCESS_CONTROL_MODE_BLACKLIST = "access_control_mode_blacklist"
|
||||
|
||||
@@ -68,8 +68,4 @@
|
||||
<item>255.255.255.254/32</item>
|
||||
<item>172.31.255.252/30</item> <!-- tun device address -->
|
||||
</string-array>
|
||||
|
||||
<string-array name="default_disallow_application">
|
||||
<item>com.android.networkstack</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user