Compare commits

...

15 Commits
1.1.4 ... 1.1.5

Author SHA1 Message Date
Kr328
d8b1c0b7b8 remove debug symbol of clash core 2020-02-26 14:58:29 +08:00
Kr328
8bf908b731 remove default disallow package 2020-02-26 14:53:30 +08:00
Kr328
568f1f12e7 fix crash on ipv6 enable & reduce provider call 2020-02-26 14:06:46 +08:00
Kr328
ca739a5e2f add abi split 2020-02-26 13:53:42 +08:00
Kr328
7833f747d5 format & cleanup 2020-02-26 13:33:44 +08:00
Kr328
977519b383 change crash reporter
fix crash on log polling
fix crash on udp write back
fix goroutine leak
fix default rule inject
2020-02-26 13:32:29 +08:00
Kr328
b39b3812d0 add crash reporter 2020-02-25 20:53:39 +08:00
Kr328
f7dddb41fa add crash reporter 2020-02-25 20:53:26 +08:00
Kr328
053300919c add crash reporter 2020-02-25 20:38:55 +08:00
Kr328
93dff86383 clear dumper 2020-02-25 14:42:06 +08:00
Kr328
1b7e9c6b12 add golang crash dumper 2020-02-25 14:41:53 +08:00
Kr328
f072cd0839 update dependencies 2020-02-25 12:43:58 +08:00
Kr328
f626a0fc5c fix ipv6 support
cc840c9fdd (diff-e089fe63dcb3674c0a1e459a95508e3e)
2020-02-24 20:33:29 +08:00
Kr328
63da12f046 1.1.4 2020-02-23 23:50:41 +08:00
Kr328
ce1beafb08 fix proxies active cache 2020-02-23 23:22:32 +08:00
72 changed files with 521 additions and 335 deletions

View File

@@ -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 {

View File

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

View File

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

View File

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

View File

@@ -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 {

View File

@@ -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.*

View File

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

View File

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

View File

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

View File

@@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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? {

View File

@@ -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 {

View File

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

View File

@@ -20,8 +20,7 @@ fun Context.startClashService(): Intent? {
return vpnRequest
startForegroundServiceCompat(TunService::class.intent)
}
else {
} else {
startForegroundServiceCompat(ClashService::class.intent)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"

View File

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