Initial: initial commit

This commit is contained in:
kr328
2021-05-15 00:51:08 +08:00
commit 07e8afa69a
483 changed files with 26328 additions and 0 deletions

47
common/build.gradle.kts Normal file
View File

@@ -0,0 +1,47 @@
plugins {
id("com.android.library")
kotlin("android")
}
android {
compileSdk = buildTargetSdkVersion
defaultConfig {
minSdk = buildMinSdkVersion
targetSdk = buildTargetSdkVersion
versionCode = buildVersionCode
versionName = buildVersionName
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
named("release") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
compileOnly(project(":hideapi"))
implementation(kotlin("stdlib-jdk7"))
implementation("androidx.core:core-ktx:$ktxVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion")
}
repositories {
mavenCentral()
google()
}

View File

21
common/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.kr328.clash.common">
<permission
android:name="${applicationId}.permission.RECEIVE_BROADCASTS"
android:description="@string/receive_broadcasts_of_clash"
android:label="@string/receive_clash_broadcasts"
android:protectionLevel="privileged|signature" />
<uses-permission android:name="${applicationId}.permission.RECEIVE_BROADCASTS" />
</manifest>

View File

@@ -0,0 +1,14 @@
package com.github.kr328.clash.common
import android.app.Application
object Global {
val application: Application
get() = application_
private lateinit var application_: Application
fun init(application: Application) {
this.application_ = application
}
}

View File

@@ -0,0 +1,20 @@
package com.github.kr328.clash.common.compat
import android.app.ActivityThread
import android.app.Application
import android.os.Build
import com.github.kr328.clash.common.log.Log
val Application.currentProcessName: String
get() {
if (Build.VERSION.SDK_INT >= 28)
return Application.getProcessName()
return try {
ActivityThread.currentProcessName()
} catch (throwable: Throwable) {
Log.w("Resolve process name: $throwable")
packageName
}
}

View File

@@ -0,0 +1,22 @@
@file:Suppress("DEPRECATION")
package com.github.kr328.clash.common.compat
import android.content.Context
import android.graphics.drawable.Drawable
import android.os.Build
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
fun Context.getColorCompat(@ColorRes id: Int): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
this.getColor(id)
} else {
resources.getColor(id)
}
}
fun Context.getDrawableCompat(@DrawableRes id: Int): Drawable? {
return ContextCompat.getDrawable(this, id)
}

View File

@@ -0,0 +1,15 @@
@file:Suppress("DEPRECATION")
package com.github.kr328.clash.common.compat
import android.os.Build
import android.text.Html
import android.text.Spanned
fun fromHtmlCompat(content: String): Spanned {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Html.fromHtml(content, Html.FROM_HTML_MODE_COMPACT)
} else {
Html.fromHtml(content)
}
}

View File

@@ -0,0 +1,12 @@
package com.github.kr328.clash.common.compat
import android.app.PendingIntent
import android.os.Build
fun pendingIntentFlags(flags: Int, immutable: Boolean = false): Int {
return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M && immutable) {
flags or PendingIntent.FLAG_IMMUTABLE
} else {
flags
}
}

View File

@@ -0,0 +1,14 @@
@file:Suppress("DEPRECATION")
package com.github.kr328.clash.common.compat
import android.content.pm.PackageInfo
val PackageInfo.versionCodeCompat: Long
get() {
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
longVersionCode
} else {
versionCode.toLong()
}
}

View File

@@ -0,0 +1,16 @@
@file:Suppress("DEPRECATION")
package com.github.kr328.clash.common.compat
import android.content.res.Configuration
import android.os.Build
import java.util.*
val Configuration.preferredLocale: Locale
get() {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
locales[0]
} else {
locale
}
}

View File

@@ -0,0 +1,13 @@
package com.github.kr328.clash.common.compat
import android.content.Context
import android.content.Intent
import android.os.Build
fun Context.startForegroundServiceCompat(intent: Intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
}

View File

@@ -0,0 +1,126 @@
@file:Suppress("DEPRECATION")
package com.github.kr328.clash.common.compat
import android.annotation.TargetApi
import android.os.Build
import android.view.View
import android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
import android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
import android.view.Window
import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
import android.view.WindowManager
var Window.isSystemBarsTranslucentCompat: Boolean
get() {
throw UnsupportedOperationException("set value only")
}
set(value) {
if (Build.VERSION.SDK_INT >= 30) {
setDecorFitsSystemWindows(!value)
} else {
decorView.systemUiVisibility =
if (value) {
decorView.systemUiVisibility or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
} else {
decorView.systemUiVisibility and
(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION).inv()
}
}
if (Build.VERSION.SDK_INT >= 28) {
if (value) {
attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
} else {
attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
}
}
}
var Window.isLightStatusBarsCompat: Boolean
get() {
throw UnsupportedOperationException("set value only")
}
@TargetApi(23)
set(value) {
if (value) {
if (Build.VERSION.SDK_INT >= 30) {
decorView.windowInsetsController?.apply {
setSystemBarsAppearance(
APPEARANCE_LIGHT_STATUS_BARS,
APPEARANCE_LIGHT_STATUS_BARS
)
}
} else {
decorView.systemUiVisibility =
decorView.systemUiVisibility or SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
} else {
if (Build.VERSION.SDK_INT >= 30) {
decorView.windowInsetsController?.apply {
setSystemBarsAppearance(
0,
APPEARANCE_LIGHT_STATUS_BARS
)
}
} else {
decorView.systemUiVisibility =
decorView.systemUiVisibility and SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
}
}
}
var Window.isLightNavigationBarCompat: Boolean
get() {
throw UnsupportedOperationException("set value only")
}
@TargetApi(27)
set(value) {
if (value) {
if (Build.VERSION.SDK_INT >= 30) {
decorView.windowInsetsController?.apply {
setSystemBarsAppearance(
APPEARANCE_LIGHT_NAVIGATION_BARS,
APPEARANCE_LIGHT_NAVIGATION_BARS
)
}
} else {
decorView.systemUiVisibility =
decorView.systemUiVisibility or SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
}
} else {
if (Build.VERSION.SDK_INT >= 30) {
decorView.windowInsetsController?.apply {
setSystemBarsAppearance(
0,
APPEARANCE_LIGHT_NAVIGATION_BARS
)
}
} else {
decorView.systemUiVisibility =
decorView.systemUiVisibility and SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
}
}
}
var Window.isAllowForceDarkCompat: Boolean
get() {
return if (Build.VERSION.SDK_INT >= 29) {
decorView.isForceDarkAllowed
} else {
false
}
}
set(value) {
if (Build.VERSION.SDK_INT >= 29) {
decorView.isForceDarkAllowed = value
}
}

View File

@@ -0,0 +1,17 @@
@file:Suppress("DEPRECATION")
package com.github.kr328.clash.common.compat
import android.os.Build
import android.widget.TextView
import androidx.annotation.StyleRes
var TextView.textAppearance: Int
get() = throw UnsupportedOperationException("set value only")
set(@StyleRes value) {
if (Build.VERSION.SDK_INT >= 23) {
setTextAppearance(value)
} else {
setTextAppearance(context, value)
}
}

View File

@@ -0,0 +1,9 @@
package com.github.kr328.clash.common.constants
import com.github.kr328.clash.common.util.packageName
object Authorities {
val STATUS_PROVIDER = "$packageName.status"
val SETTINGS_PROVIDER = "$packageName.settings"
val FILES_PROVIDER = "$packageName.files"
}

View File

@@ -0,0 +1,9 @@
package com.github.kr328.clash.common.constants
import android.content.ComponentName
import com.github.kr328.clash.common.util.packageName
object Components {
val MAIN_ACTIVITY = ComponentName(packageName, "$packageName.MainActivity")
val PROPERTIES_ACTIVITY = ComponentName(packageName, "$packageName.PropertiesActivity")
}

View File

@@ -0,0 +1,24 @@
package com.github.kr328.clash.common.constants
import com.github.kr328.clash.common.util.packageName
object Intents {
// Public
val ACTION_PROVIDE_URL = "$packageName.action.PROVIDE_URL"
const val EXTRA_NAME = "name"
// Self
val ACTION_SERVICE_RECREATED = "$packageName.intent.action.CLASH_RECREATED"
val ACTION_CLASH_STARTED = "$packageName.intent.action.CLASH_STARTED"
val ACTION_CLASH_STOPPED = "$packageName.intent.action.CLASH_STOPPED"
val ACTION_CLASH_REQUEST_STOP = "$packageName.intent.action.CLASH_REQUEST_STOP"
val ACTION_PROFILE_CHANGED = "$packageName.intent.action.PROFILE_CHANGED"
val ACTION_PROFILE_REQUEST_UPDATE = "$packageName.intent.action.REQUEST_UPDATE"
val ACTION_PROFILE_SCHEDULE_UPDATES = "$packageName.intent.action.SCHEDULE_UPDATES"
val ACTION_PROFILE_LOADED = "$packageName.intent.action.PROFILE_LOADED"
val ACTION_OVERRIDE_CHANGED = "$packageName.intent.action.OVERRIDE_CHANGED"
const val EXTRA_STOP_REASON = "stop_reason"
const val EXTRA_UUID = "uuid"
}

View File

@@ -0,0 +1,7 @@
package com.github.kr328.clash.common.constants
import com.github.kr328.clash.common.util.packageName
object Metadata {
val GEOIP_FILE_NAME = "$packageName.GEOIP_FILE_NAME"
}

View File

@@ -0,0 +1,7 @@
package com.github.kr328.clash.common.constants
import com.github.kr328.clash.common.util.packageName
object Permissions {
val RECEIVE_SELF_BROADCASTS = "$packageName.permission.RECEIVE_BROADCASTS"
}

View File

@@ -0,0 +1,15 @@
package com.github.kr328.clash.common.id
object UndefinedIds {
private const val PREFIX = 0x14000000
private const val MASK = 0x00FFFFFF
private var current: Int = 0
@Synchronized
fun next(): Int {
current = ((current and MASK) + 1 or PREFIX)
return current
}
}

View File

@@ -0,0 +1,23 @@
package com.github.kr328.clash.common.log
object Log {
private const val TAG = "ClashForAndroid"
fun i(message: String, throwable: Throwable? = null) =
android.util.Log.i(TAG, message, throwable)
fun w(message: String, throwable: Throwable? = null) =
android.util.Log.w(TAG, message, throwable)
fun e(message: String, throwable: Throwable? = null) =
android.util.Log.e(TAG, message, throwable)
fun d(message: String, throwable: Throwable? = null) =
android.util.Log.d(TAG, message, throwable)
fun v(message: String, throwable: Throwable? = null) =
android.util.Log.v(TAG, message, throwable)
fun f(message: String, throwable: Throwable) =
android.util.Log.wtf(message, throwable)
}

View File

@@ -0,0 +1,60 @@
package com.github.kr328.clash.common.store
import android.content.SharedPreferences
import androidx.core.content.edit
class SharedPreferenceProvider(private val preferences: SharedPreferences) : StoreProvider {
override fun getInt(key: String, defaultValue: Int): Int {
return preferences.getInt(key, defaultValue)
}
override fun setInt(key: String, value: Int) {
preferences.edit {
putInt(key, value)
}
}
override fun getLong(key: String, defaultValue: Long): Long {
return preferences.getLong(key, defaultValue)
}
override fun setLong(key: String, value: Long) {
preferences.edit {
putLong(key, value)
}
}
override fun getString(key: String, defaultValue: String): String {
return preferences.getString(key, defaultValue)!!
}
override fun setString(key: String, value: String) {
preferences.edit {
putString(key, value)
}
}
override fun getStringSet(key: String, defaultValue: Set<String>): Set<String> {
return preferences.getStringSet(key, defaultValue)!!
}
override fun setStringSet(key: String, value: Set<String>) {
preferences.edit {
putStringSet(key, value)
}
}
override fun getBoolean(key: String, defaultValue: Boolean): Boolean {
return preferences.getBoolean(key, defaultValue)
}
override fun setBoolean(key: String, value: Boolean) {
preferences.edit {
putBoolean(key, value)
}
}
}
fun SharedPreferences.asStoreProvider(): StoreProvider {
return SharedPreferenceProvider(this)
}

View File

@@ -0,0 +1,98 @@
package com.github.kr328.clash.common.store
import kotlin.reflect.KProperty
class Store(val provider: StoreProvider) {
interface Delegate<T> {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T)
}
fun int(key: String, defaultValue: Int): Delegate<Int> {
return object : Delegate<Int> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return provider.getInt(key, defaultValue)
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
provider.setInt(key, value)
}
}
}
fun long(key: String, defaultValue: Long): Delegate<Long> {
return object : Delegate<Long> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Long {
return provider.getLong(key, defaultValue)
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Long) {
provider.setLong(key, value)
}
}
}
fun string(key: String, defaultValue: String): Delegate<String> {
return object : Delegate<String> {
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
return provider.getString(key, defaultValue)
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
provider.setString(key, value)
}
}
}
fun stringSet(key: String, defaultValue: Set<String>): Delegate<Set<String>> {
return object : Delegate<Set<String>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Set<String> {
return provider.getStringSet(key, defaultValue)
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Set<String>) {
provider.setStringSet(key, value)
}
}
}
fun boolean(key: String, defaultValue: Boolean): Delegate<Boolean> {
return object : Delegate<Boolean> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
return provider.getBoolean(key, defaultValue)
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
provider.setBoolean(key, value)
}
}
}
fun <T : Enum<T>> enum(key: String, defaultValue: T, values: Array<T>): Delegate<T> {
return object : Delegate<T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val name = provider.getString(key, defaultValue.name)
return values.find { name == it.name } ?: defaultValue
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
provider.setString(key, value.name)
}
}
}
fun <T> typedString(key: String, from: (String) -> T?, to: (T?) -> String): Delegate<T?> {
return object : Delegate<T?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
val value = provider.getString(key, to(null))
return from(value)
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
provider.setString(key, to(value))
}
}
}
}

View File

@@ -0,0 +1,18 @@
package com.github.kr328.clash.common.store
interface StoreProvider {
fun getInt(key: String, defaultValue: Int): Int
fun setInt(key: String, value: Int)
fun getLong(key: String, defaultValue: Long): Long
fun setLong(key: String, value: Long)
fun getString(key: String, defaultValue: String): String
fun setString(key: String, value: String)
fun getStringSet(key: String, defaultValue: Set<String>): Set<String>
fun setStringSet(key: String, value: Set<String>)
fun getBoolean(key: String, defaultValue: Boolean): Boolean
fun setBoolean(key: String, value: Boolean)
}

View File

@@ -0,0 +1,12 @@
package com.github.kr328.clash.common.util
import android.content.ComponentName
import android.content.Intent
import com.github.kr328.clash.common.Global
import kotlin.reflect.KClass
val KClass<*>.componentName: ComponentName
get() = ComponentName(Global.application.packageName, this.java.name)
val KClass<*>.intent: Intent
get() = Intent(Global.application, this.java)

View File

@@ -0,0 +1,5 @@
package com.github.kr328.clash.common.util
import com.github.kr328.clash.common.Global
val packageName: String = Global.application.packageName

View File

@@ -0,0 +1,47 @@
package com.github.kr328.clash.common.util
import android.content.Intent
import android.net.Uri
import java.util.*
fun Intent.grantPermissions(read: Boolean = true, write: Boolean = true): Intent {
var flags = 0
if (read)
flags = flags or Intent.FLAG_GRANT_READ_URI_PERMISSION
if (write)
flags = flags or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
addFlags(flags)
return this
}
var Intent.fileName: String?
get() {
return data?.takeIf { it.scheme == "file" }?.schemeSpecificPart
}
set(value) {
data = Uri.fromParts("file", value, null)
}
var Intent.uuid: UUID?
get() {
return data?.takeIf { it.scheme == "uuid" }?.schemeSpecificPart?.let(UUID::fromString)
}
set(value) {
data = Uri.fromParts("uuid", value.toString(), null)
}
fun Intent.setUUID(uuid: UUID): Intent {
this.uuid = uuid
return this
}
fun Intent.setFileName(fileName: String): Intent {
this.fileName = fileName
return this
}

View File

@@ -0,0 +1,89 @@
package com.github.kr328.clash.common.util
import android.os.Binder
import android.os.Parcel
import android.os.Parcelable
private class SliceParcelableListBpBinder(val list: List<Parcelable>, val flags: Int) : Binder() {
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, tFlags: Int): Boolean {
when (code) {
TRANSACTION_GET_ITEMS -> {
reply ?: return false
val offset = data.readInt()
val chunk = data.readInt()
val end = (offset + chunk).coerceAtMost(list.size)
reply.writeInt(end - offset)
for (i in offset until end) {
list[i].writeToParcel(reply, flags)
}
return true
}
}
return super.onTransact(code, data, reply, flags)
}
companion object {
const val TRANSACTION_GET_ITEMS = 10
}
}
fun <T : Parcelable> List<T>.writeToParcelSlice(parcel: Parcel, flags: Int) {
val bp = SliceParcelableListBpBinder(this, flags)
parcel.writeInt(size)
parcel.writeStrongBinder(bp)
}
fun <T : Parcelable> Parcelable.Creator<T>.createListFromParcelSlice(
parcel: Parcel,
flags: Int,
chunk: Int,
): List<T> {
val total = parcel.readInt()
val remote = parcel.readStrongBinder()
val result = ArrayList<T>(total)
var offset = 0
while (offset < total) {
val data = Parcel.obtain()
val reply = Parcel.obtain()
try {
data.writeInt(offset)
data.writeInt(chunk)
if (!remote.transact(
SliceParcelableListBpBinder.TRANSACTION_GET_ITEMS,
data,
reply,
flags
)
) {
break
}
val size = reply.readInt()
repeat(size) {
result.add(createFromParcel(reply))
}
offset += size
if (size == 0)
break
} finally {
data.recycle()
reply.recycle()
}
}
return result
}

View File

@@ -0,0 +1,3 @@
package com.github.kr328.clash.common.util
val PatternFileName = Regex("[^*&%\\n\\r/]+")

View File

@@ -0,0 +1,25 @@
package com.github.kr328.clash.common.util
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
fun CoroutineScope.ticker(period: Long): Channel<Long> {
val channel = Channel<Long>(Channel.RENDEZVOUS)
launch {
try {
while (isActive) {
channel.send(System.currentTimeMillis())
delay(period)
}
} catch (ignored: Exception) {
}
}
return channel
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="receive_clash_broadcasts">接收 Clash 广播</string>
<string name="receive_broadcasts_of_clash">接收来自 Clash 内部的广播</string>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="receive_clash_broadcasts">Receive Clash Broadcasts</string>
<string name="receive_broadcasts_of_clash">Receive broadcasts of clash services</string>
</resources>