├── .github └── ISSUE_TEMPLATE │ ├── 01-bug-report-en.md │ ├── 02-feature-request-en.md │ ├── 03-bug-report-zh-cn.md │ ├── 04-feature-request-zh-cn.md │ └── config.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── NOTICE ├── PRIVACY_POLICY.md ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ ├── ApkBrokenActivity.kt │ │ ├── BaseActivity.kt │ │ ├── Constants.kt │ │ ├── CreateProfileActivity.kt │ │ ├── LogViewerActivity.kt │ │ ├── LogcatService.kt │ │ ├── LogsActivity.kt │ │ ├── MainActivity.kt │ │ ├── MainApplication.kt │ │ ├── PackagesActivity.kt │ │ ├── ProfileEditActivity.kt │ │ ├── ProfilesActivity.kt │ │ ├── ProxiesActivity.kt │ │ ├── SettingsActivity.kt │ │ ├── SettingsBehaviorActivity.kt │ │ ├── SettingsInterfaceActivity.kt │ │ ├── SettingsNetworkActivity.kt │ │ ├── SupportActivity.kt │ │ ├── TileService.kt │ │ ├── adapter │ │ ├── LiveLogAdapter.kt │ │ ├── LogAdapter.kt │ │ ├── LogFileAdapter.kt │ │ ├── PackagesAdapter.kt │ │ ├── ProfileAdapter.kt │ │ ├── ProxyAdapter.kt │ │ └── ProxyChipAdapter.kt │ │ ├── dump │ │ └── LogcatDumper.kt │ │ ├── fragment │ │ └── ProfileEditFragment.kt │ │ ├── model │ │ └── LogFile.kt │ │ ├── pipeline │ │ ├── Pipeline.kt │ │ └── ProxiesPipeline.kt │ │ ├── preference │ │ └── UiSettings.kt │ │ ├── remote │ │ ├── Broadcasts.kt │ │ ├── Calls.kt │ │ ├── ClashClient.kt │ │ ├── ProfileClient.kt │ │ ├── Remote.kt │ │ └── RemoteUtils.kt │ │ ├── settings │ │ ├── BaseSettingFragment.kt │ │ ├── BehaviorFragment.kt │ │ ├── InterfaceFragment.kt │ │ ├── NetworkFragment.kt │ │ └── SettingsDataStore.kt │ │ ├── utils │ │ ├── ApplicationObserver.kt │ │ ├── DateUtils.kt │ │ ├── FileUtils.kt │ │ ├── IntervalUtils.kt │ │ ├── PrefixMerger.kt │ │ ├── ProxySorter.kt │ │ ├── QuickSmoothScroller.kt │ │ ├── ScrollBinding.kt │ │ └── StringUtils.kt │ │ └── weight │ │ └── ProfilesMenu.kt │ └── res │ ├── drawable │ ├── ic_about.xml │ ├── ic_adb.xml │ ├── ic_clear.xml │ ├── ic_clear_all.xml │ ├── ic_content.xml │ ├── ic_copy.xml │ ├── ic_delete_colorful.xml │ ├── ic_download.xml │ ├── ic_feedback.xml │ ├── ic_file.xml │ ├── ic_flash.xml │ ├── ic_info.xml │ ├── ic_input.xml │ ├── ic_interface.xml │ ├── ic_label_outline.xml │ ├── ic_launcher_foreground.xml │ ├── ic_logo.xml │ ├── ic_logs.xml │ ├── ic_network.xml │ ├── ic_new.xml │ ├── ic_play_for_work.xml │ ├── ic_profiles.xml │ ├── ic_properties.xml │ ├── ic_proxies.xml │ ├── ic_save.xml │ ├── ic_settings.xml │ ├── ic_settings_applications.xml │ ├── ic_started.xml │ ├── ic_stop.xml │ ├── ic_stopped.xml │ ├── ic_update.xml │ └── ic_vertex.xml │ ├── layout │ ├── activity_access_control_packages.xml │ ├── activity_application_broken.xml │ ├── activity_create_profile.xml │ ├── activity_fragment.xml │ ├── activity_log_viewer.xml │ ├── activity_logs.xml │ ├── activity_main.xml │ ├── activity_profile_edit.xml │ ├── activity_profiles.xml │ ├── activity_proxies.xml │ ├── activity_settings.xml │ ├── activity_support.xml │ ├── adapter_grid_proxy.xml │ ├── adapter_grid_proxy_group.xml │ ├── adapter_log.xml │ ├── adapter_log_file.xml │ ├── adapter_package.xml │ ├── adapter_profile_entity.xml │ ├── adapter_profile_footer.xml │ ├── adapter_proxies_chip.xml │ ├── adapter_url_provider.xml │ └── dialog_abort.xml │ ├── menu │ ├── packages.xml │ └── proxies.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── values-night │ ├── bools.xml │ └── colors.xml │ ├── values-zh │ ├── arrays.xml │ └── strings.xml │ ├── values │ ├── arrays.xml │ ├── attrs.xml │ ├── bools.xml │ ├── colors.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ ├── full_backup_content.xml │ ├── settings_behavior.xml │ ├── settings_interface.xml │ └── settings_network.xml ├── build.gradle.kts ├── common ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ └── common │ │ ├── Constants.kt │ │ ├── Global.kt │ │ ├── Permissions.kt │ │ ├── ids │ │ ├── Intents.kt │ │ ├── NotificationChannels.kt │ │ ├── NotificationIds.kt │ │ └── PendingIds.kt │ │ ├── serialization │ │ ├── MergedParcels.kt │ │ └── Parcels.kt │ │ ├── settings │ │ └── BaseSettings.kt │ │ └── utils │ │ ├── ByteFormatter.kt │ │ ├── ComponentUtils.kt │ │ ├── LanguageUtils.kt │ │ ├── Log.kt │ │ └── ServiceUtils.kt │ └── res │ ├── values-zh │ └── strings.xml │ └── values │ └── strings.xml ├── core ├── .gitignore ├── build.gradle.kts ├── clash.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── golang │ ├── bridge │ │ ├── callback.go │ │ ├── general.go │ │ ├── init.go │ │ ├── profiles.go │ │ ├── proxies.go │ │ ├── statistics.go │ │ └── tun.go │ ├── config │ │ ├── fetch.go │ │ ├── load.go │ │ └── patch.go │ ├── go.mod │ ├── go.sum │ ├── main.go │ ├── tun │ │ ├── dns.go │ │ ├── log.go │ │ ├── tun.go │ │ └── udp.go │ └── utils │ │ └── close.go │ └── java │ └── com │ └── github │ └── kr328 │ └── clash │ └── core │ ├── Clash.kt │ ├── event │ └── LogEvent.kt │ ├── model │ ├── General.kt │ ├── Proxy.kt │ ├── ProxyGroup.kt │ ├── ProxyGroupList.kt │ └── Traffic.kt │ └── transact │ ├── DoneCallbackImpl.kt │ └── ProxyCollections.kt ├── design ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ └── design │ │ ├── common │ │ ├── Base.kt │ │ ├── Category.kt │ │ ├── CommonUiBuilder.kt │ │ ├── CommonUiScreen.kt │ │ ├── Option.kt │ │ ├── TextInput.kt │ │ └── Tips.kt │ │ └── view │ │ ├── ColorfulTextCard.kt │ │ ├── CommonUiLayout.kt │ │ └── TextCard.kt │ └── res │ ├── drawable │ └── ic_edit.xml │ ├── layout │ ├── dialog_input_text.xml │ ├── view_category.xml │ ├── view_colorful_text_card.xml │ ├── view_setting_option.xml │ ├── view_setting_text_input.xml │ ├── view_setting_tip.xml │ └── view_text_card.xml │ └── values │ ├── attrs.xml │ ├── strings.xml │ └── style.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── service ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ ├── core │ │ ├── event │ │ │ └── Event.aidl │ │ └── model │ │ │ └── Packet.aidl │ │ └── service │ │ ├── IClashManager.aidl │ │ ├── IProfileService.aidl │ │ ├── model │ │ └── Profile.aidl │ │ └── transact │ │ ├── IStreamCallback.aidl │ │ └── ParcelableContainer.aidl │ ├── java │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ └── service │ │ ├── BaseService.kt │ │ ├── ClashManager.kt │ │ ├── ClashManagerService.kt │ │ ├── ClashService.kt │ │ ├── Constants.kt │ │ ├── ProfileBackgroundService.kt │ │ ├── ProfileDocumentProvider.kt │ │ ├── ProfileProcessor.kt │ │ ├── ProfileProvider.kt │ │ ├── ProfileReceiver.kt │ │ ├── ProfileService.kt │ │ ├── RestartReceiver.kt │ │ ├── ServiceSettingsProvider.kt │ │ ├── ServiceStatusProvider.kt │ │ ├── TunService.kt │ │ ├── clash │ │ ├── ClashRuntime.kt │ │ └── module │ │ │ ├── CloseModule.kt │ │ │ ├── DnsInjectModule.kt │ │ │ ├── DynamicNotificationModule.kt │ │ │ ├── Module.kt │ │ │ ├── NetworkObserveModule.kt │ │ │ ├── ReloadModule.kt │ │ │ ├── StaticNotificationModule.kt │ │ │ └── TunModule.kt │ │ ├── data │ │ ├── Database.kt │ │ ├── ProfileDao.kt │ │ ├── ProfileEntity.kt │ │ ├── SelectedProxyDao.kt │ │ ├── SelectedProxyEntity.kt │ │ └── migrations │ │ │ ├── Migration12.kt │ │ │ ├── Migration23.kt │ │ │ ├── Migration34.kt │ │ │ └── Migrations.kt │ │ ├── files │ │ ├── ProfileDirectoryResolver.kt │ │ ├── ProfilesResolver.kt │ │ ├── ProviderResolver.kt │ │ └── VirtualFile.kt │ │ ├── model │ │ ├── Converters.kt │ │ ├── Profile.kt │ │ └── Serializers.kt │ │ ├── settings │ │ └── ServiceSettings.kt │ │ ├── transact │ │ └── ParcelableContainer.kt │ │ └── util │ │ ├── BroadcastUtils.kt │ │ ├── FileUtils.kt │ │ ├── InetAddressUtils.kt │ │ ├── Net.kt │ │ └── ServiceUtils.kt │ └── res │ ├── drawable │ ├── ic_icon.xml │ └── ic_notification.xml │ ├── values-zh │ └── strings.xml │ ├── values │ ├── arrays.xml │ ├── colors.xml │ └── strings.xml │ └── xml │ └── profile_provider.xml └── settings.gradle.kts /.github/ISSUE_TEMPLATE/01-bug-report-en.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "[English] Bug report" 3 | about: Create a report to help us improve 4 | title: "[BUG] " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | **Describe the bug** 15 | A clear and concise description of what the bug is. 16 | 17 | **To Reproduce** 18 | Steps to reproduce the behavior: 19 | 1. Go to '...' 20 | 2. Click on '....' 21 | 3. Scroll down to '....' 22 | 4. See error 23 | 24 | **Expected behavior** 25 | A clear and concise description of what you expected to happen. 26 | 27 | **Screenshots** 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | **Device Info (please complete the following information):** 31 | 32 | - Device: [e.g. Pixel 4] 33 | - ROM: [e.g: AOSP] 34 | - ROM Version: 35 | - Android Version [e.g. 10] 36 | 37 | **Application Info (please complete the following information):** 38 | 39 | - Version: [e.g. 1.1.10] 40 | - Apk File Name: [e.g. app-release-arm64-v8a.apk] 41 | - Distribution Channel: [e.g. Google Play] 42 | 43 | **Additional context** 44 | Add any other context about the problem here. 45 | 46 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02-feature-request-en.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "[English] Feature request" 3 | about: Suggest an idea for this app 4 | title: "[Feature Request] " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | **Is your feature request related to a problem? Please describe.** 15 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 16 | 17 | **Describe the solution you'd like** 18 | A clear and concise description of what you want to happen. 19 | 20 | **Describe alternatives you've considered** 21 | A clear and concise description of any alternative solutions or features you've considered. 22 | 23 | **Additional context** 24 | Add any other context or screenshots about the feature request here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/03-bug-report-zh-cn.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "[简体中文] 创建错误报告" 3 | about: 创建错误报告以帮助我们改进应用 4 | title: "[BUG] " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | **描述出现的错误** 15 | 请简洁的描述你遇到的错误 16 | 17 | **如何复现该错误** 18 | 复现步骤: 19 | 1. ... 20 | 2. ... 21 | 3. ... 22 | 4. ... 23 | 24 | **预期行为** 25 | 清晰简单的描述你预期的应用应该表现的行为 26 | 27 | **屏幕截图** 28 | 如果适用, 上传屏幕截图以帮助描述错误 29 | 30 | **设备信息 (请完成以下信息):** 31 | - 机型: [例如: Pixel 4] 32 | - 系统/ROM: [例如: MIUI 11] 33 | - Android 版本 [例如: 10] 34 | - ROM版本 [例如: 20.3.19] 35 | 36 | **应用信息** 37 | - 版本: [例如: 1.1.10] 38 | - 安装包文件名: [例如: app-release-arm64-v8a.apk] 39 | - 应用来源: [例如: Google Play] 40 | 41 | **附加信息** 42 | 其他的可能与改错误相关的信息 43 | 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/04-feature-request-zh-cn.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "[简体中文] 功能请求" 3 | about: 你希望的能够在应用中增加的功能 4 | title: "[Feature Request] " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | **功能描述** 15 | 请清晰的描述你想要的功能 16 | 17 | **描述你希望的实现方式** 18 | 清晰的描述应用应该如何实现该功能 19 | 20 | **附加信息** 21 | 其他的与改功能相关的附加信息 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | /app/release/ 4 | /captures 5 | 6 | # Ignore Gradle GUI config 7 | gradle-app.setting 8 | 9 | # Avoid ignoring Gradle wrapper jar targetFile (.jar files are usually ignored) 10 | !gradle-wrapper.jar 11 | 12 | # Cache of project 13 | .gradletasknamecache 14 | 15 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 16 | # gradle/wrapper/gradle-wrapper.properties 17 | 18 | # Ignore IDEA config 19 | .idea 20 | *.iml 21 | 22 | # KeyStore 23 | *.keystore 24 | *.jks 25 | 26 | # clion cmake build 27 | cmake-build-* 28 | 29 | # local.properties 30 | local.properties 31 | 32 | # keystore 33 | keystore.properties 34 | 35 | # vscode 36 | .vscode 37 | 38 | # cxx 39 | .cxx 40 | 41 | *.hprof 42 | 43 | # firebase 44 | google-services.json 45 | 46 | # Dolphin 47 | .directory 48 | 49 | # macOS file 50 | .DS_Store 51 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "core/src/main/golang/clash"] 2 | path = core/src/main/golang/clash 3 | url = https://github.com/goomadao/Clash 4 | branch = android 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ClashR for Android 2 | 3 | A Graphical user interface of [clash](https://github.com/Dreamacro/clash) for Android 4 | 5 | Get it on Google Play or [Releases](https://github.com/Kr328/ClashForAndroid/releases) 6 | 7 | ### Feature 8 | 9 | Fully feature of [clash](https://github.com/Dreamacro/clash) ~~(Exclude `external-controller`~~ 10 | 11 | ### Requirement 12 | 13 | - Android 7.0+ 14 | - `armeabi-v7a` , `arm64-v8a`, `x86` or `x86_64` Architecture 15 | 16 | ### License 17 | 18 | See also [LICENSE](./LICENSE) and [NOTICE](./NOTICE) 19 | 20 | ### Privacy Policy 21 | 22 | See also [PRIVACY_POLICY.md](./PRIVACY_POLICY.md) 23 | 24 | ### Build 25 | 26 | 1. Update submodules 27 | 28 | ```bash 29 | git submodule update --init --recursive 30 | ``` 31 | 32 | 2. Install `JDK 1.8`, `Android SDK` ,`Android NDK` and `Golang` 33 | 34 | 3. Create `local.properties` in project root with 35 | 36 | ```properties 37 | sdk.dir=/path/to/android-sdk 38 | ndk.dir=/path/to/android-ndk 39 | appcenter.key= # Optional, from "appcenter.ms" 40 | ``` 41 | 42 | 4. Create `keystore.properties` in project root with 43 | 44 | ```properties 45 | storeFile=/path/to/keystore/file 46 | storePassword= 47 | keyAlias= 48 | keyPassword= 49 | ``` 50 | 51 | 5. Build 52 | 53 | ```bash 54 | ./gradlew app:assembleRelease 55 | ``` 56 | 57 | 6. Pick `app-release-.apk` in `app/build/outputs/apks` 58 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -dontobfuscate -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goomadao/ClashForAndroid/d0aeb45beb90ede099fcf37d0be94eeb0eaaab96/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | object Constants { 4 | const val PREFERENCE_NAME_APP = "app" 5 | const val PREFERENCE_KEY_LAST_INSTALL = "last_install" 6 | 7 | const val LOG_DIR_NAME = "logs" 8 | 9 | const val URL_PROVIDER_INTENT_ACTION = "com.github.kr328.clash.action.PROVIDE_URL" 10 | const val URL_PROVIDER_INTENT_EXTRA_NAME = "name" 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.os.Bundle 4 | import com.github.kr328.clash.common.utils.intent 5 | import kotlinx.android.synthetic.main.activity_settings.* 6 | 7 | class SettingsActivity : BaseActivity() { 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | setContentView(R.layout.activity_settings) 11 | setSupportActionBar(toolbar) 12 | 13 | commonUi.build { 14 | option( 15 | icon = getDrawable(R.drawable.ic_settings_applications), 16 | title = getString(R.string.behavior) 17 | ) { 18 | paddingHeight = true 19 | 20 | onClick { 21 | startActivity(SettingsBehaviorActivity::class.intent) 22 | } 23 | } 24 | option( 25 | icon = getDrawable(R.drawable.ic_network), 26 | title = getString(R.string.network) 27 | ) { 28 | paddingHeight = true 29 | 30 | onClick { 31 | startActivity(SettingsNetworkActivity::class.intent) 32 | } 33 | } 34 | option( 35 | icon = getDrawable(R.drawable.ic_interface), 36 | title = getString(R.string.interface_) 37 | ) { 38 | paddingHeight = true 39 | 40 | onClick { 41 | startActivity(SettingsInterfaceActivity::class.intent) 42 | } 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/SettingsBehaviorActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.os.Bundle 4 | import com.github.kr328.clash.settings.BehaviorFragment 5 | 6 | class SettingsBehaviorActivity : BaseActivity() { 7 | override fun onCreate(savedInstanceState: Bundle?) { 8 | super.onCreate(savedInstanceState) 9 | 10 | setContentView(R.layout.activity_fragment) 11 | setSupportActionBar(findViewById(R.id.toolbar)) 12 | 13 | supportFragmentManager.beginTransaction() 14 | .replace(R.id.fragment, BehaviorFragment()) 15 | .commit() 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/SettingsInterfaceActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.os.Bundle 4 | import com.github.kr328.clash.settings.InterfaceFragment 5 | 6 | class SettingsInterfaceActivity : BaseActivity() { 7 | override fun onCreate(savedInstanceState: Bundle?) { 8 | super.onCreate(savedInstanceState) 9 | 10 | setContentView(R.layout.activity_fragment) 11 | setSupportActionBar(findViewById(R.id.toolbar)) 12 | 13 | supportFragmentManager.beginTransaction() 14 | .replace(R.id.fragment, InterfaceFragment()) 15 | .commit() 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/SettingsNetworkActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.os.Bundle 4 | import com.github.kr328.clash.settings.NetworkFragment 5 | import com.google.android.material.snackbar.Snackbar 6 | 7 | class SettingsNetworkActivity : BaseActivity() { 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | 11 | setContentView(R.layout.activity_fragment) 12 | setSupportActionBar(findViewById(R.id.toolbar)) 13 | 14 | supportFragmentManager.beginTransaction() 15 | .replace(R.id.fragment, NetworkFragment()) 16 | .commit() 17 | 18 | if (clashRunning) 19 | Snackbar.make(rootView, R.string.options_unavailable, Snackbar.LENGTH_INDEFINITE).show() 20 | } 21 | 22 | override suspend fun onClashStopped(reason: String?) { 23 | recreate() 24 | } 25 | 26 | override suspend fun onClashStarted() { 27 | recreate() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/adapter/LiveLogAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.adapter 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.collection.CircularArray 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.github.kr328.clash.R 9 | import com.github.kr328.clash.core.event.LogEvent 10 | 11 | class LiveLogAdapter(private val context: Context) : RecyclerView.Adapter() { 12 | companion object { 13 | const val MAX_LOG_ITEMS = 100 14 | } 15 | 16 | private val circularArray = CircularArray(MAX_LOG_ITEMS) 17 | 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LogAdapter.Holder { 19 | return LogAdapter.Holder( 20 | LayoutInflater.from(context).inflate( 21 | R.layout.adapter_log, 22 | parent, 23 | false 24 | ) 25 | ) 26 | } 27 | 28 | override fun getItemCount(): Int { 29 | return circularArray.size() 30 | } 31 | 32 | override fun onBindViewHolder(holder: LogAdapter.Holder, position: Int) { 33 | holder.bind(circularArray[position]) 34 | } 35 | 36 | fun insertItems(i: List) { 37 | val items = if (i.size > MAX_LOG_ITEMS) { 38 | i.subList(i.size - MAX_LOG_ITEMS, i.size) 39 | } else i 40 | 41 | val predictSize = items.size + circularArray.size() 42 | 43 | if (predictSize > MAX_LOG_ITEMS) { 44 | val removeSize = predictSize - MAX_LOG_ITEMS 45 | notifyItemRangeRemoved(MAX_LOG_ITEMS - removeSize, removeSize) 46 | circularArray.removeFromEnd(removeSize) 47 | } 48 | 49 | items.forEach { 50 | circularArray.addFirst(it) 51 | } 52 | 53 | notifyItemRangeInserted(0, items.size) 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/adapter/LogAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.adapter 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.github.kr328.clash.R 10 | import com.github.kr328.clash.core.event.LogEvent 11 | import com.github.kr328.clash.utils.format 12 | import java.util.* 13 | 14 | class LogAdapter( 15 | private val context: Context, 16 | private val logs: List 17 | ) : RecyclerView.Adapter() { 18 | class Holder(view: View) : RecyclerView.ViewHolder(view) { 19 | private val level: TextView = view.findViewById(R.id.level) 20 | private val time: TextView = view.findViewById(R.id.time) 21 | private val payload: TextView = view.findViewById(R.id.payload) 22 | 23 | fun bind(logEvent: LogEvent) { 24 | level.text = logEvent.level.toString() 25 | time.text = Date(logEvent.time).format(itemView.context, includeDate = false) 26 | payload.text = logEvent.message 27 | } 28 | } 29 | 30 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { 31 | return Holder(LayoutInflater.from(context).inflate(R.layout.adapter_log, parent, false)) 32 | } 33 | 34 | override fun getItemCount(): Int { 35 | return logs.size 36 | } 37 | 38 | override fun onBindViewHolder(holder: Holder, position: Int) { 39 | holder.bind(logs[position]) 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/adapter/LogFileAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.adapter 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.github.kr328.clash.R 10 | import com.github.kr328.clash.model.LogFile 11 | import com.github.kr328.clash.utils.format 12 | import java.util.* 13 | 14 | class LogFileAdapter( 15 | private val context: Context, 16 | private val onItemClicked: (LogFile) -> Unit, 17 | private val onMenuClicked: (LogFile) -> Unit 18 | ) : RecyclerView.Adapter() { 19 | var fileList: List = emptyList() 20 | 21 | class Holder(view: View) : RecyclerView.ViewHolder(view) { 22 | val root: View = view.findViewById(R.id.root) 23 | val fileName: TextView = view.findViewById(R.id.fileName) 24 | val date: TextView = view.findViewById(R.id.date) 25 | val menu: View = view.findViewById(R.id.menu) 26 | } 27 | 28 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { 29 | return Holder( 30 | LayoutInflater.from(context).inflate( 31 | R.layout.adapter_log_file, 32 | parent, 33 | false 34 | ) 35 | ) 36 | } 37 | 38 | override fun getItemCount(): Int { 39 | return fileList.size 40 | } 41 | 42 | override fun onBindViewHolder(holder: Holder, position: Int) { 43 | val current = fileList[position] 44 | val date = Date(current.date) 45 | 46 | holder.fileName.text = current.fileName 47 | holder.date.text = date.format(context) 48 | holder.menu.setOnClickListener { 49 | onMenuClicked(current) 50 | } 51 | holder.root.setOnClickListener { 52 | onItemClicked(current) 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/dump/LogcatDumper.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.dump 2 | 3 | object LogcatDumper { 4 | fun dumpCrash(): String { 5 | return try { 6 | val process = 7 | Runtime.getRuntime().exec(arrayOf("logcat", "-d", "-s", "Go", "AndroidRuntime", "DEBUG")) 8 | 9 | val result = process.inputStream.use { 10 | it.reader().readText() 11 | } 12 | 13 | process.waitFor() 14 | 15 | result 16 | } catch (e: Exception) { 17 | "" 18 | } 19 | } 20 | 21 | fun dumpAll(): String { 22 | return try { 23 | val process = 24 | Runtime.getRuntime().exec(arrayOf("logcat", "-d")) 25 | 26 | val result = process.inputStream.use { 27 | it.reader().readText() 28 | } 29 | 30 | process.waitFor() 31 | 32 | result 33 | } catch (e: Exception) { 34 | "" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/model/LogFile.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.model 2 | 3 | data class LogFile(val fileName: String, val date: Long) { 4 | companion object { 5 | private val REGEX_FILE = Regex("clash-(\\d+).log") 6 | private const val FORMAT_FILE_NAME = "clash-%d.log" 7 | 8 | fun parseFromFileName(fileName: String): LogFile? { 9 | return REGEX_FILE.matchEntire(fileName)?.run { 10 | LogFile(fileName, groupValues[1].toLong()) 11 | } 12 | } 13 | 14 | fun generate(date: Long = System.currentTimeMillis()): LogFile { 15 | val fileName = FORMAT_FILE_NAME.format(date) 16 | 17 | return LogFile(fileName, date) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/pipeline/Pipeline.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.pipeline 2 | 3 | import com.github.kr328.clash.common.settings.BaseSettings 4 | 5 | data class Pipeline(val input: T, val settings: BaseSettings) -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/preference/UiSettings.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.preference 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.common.settings.BaseSettings 5 | 6 | class UiSettings(context: Context) : 7 | BaseSettings(context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)) { 8 | companion object { 9 | private const val FILE_NAME = "ui" 10 | 11 | const val PROXY_SORT_DEFAULT = "default" 12 | const val PROXY_SORT_NAME = "name" 13 | const val PROXY_SORT_DELAY = "delay" 14 | 15 | const val DARK_MODE_AUTO = "auto" 16 | const val DARK_MODE_DARK = "dark" 17 | const val DARK_MODE_LIGHT = "light" 18 | 19 | val PROXY_GROUP_SORT = StringEntry("proxy_group_sort", PROXY_SORT_DEFAULT) 20 | val PROXY_PROXY_SORT = StringEntry("proxy_proxy_sort", PROXY_SORT_DEFAULT) 21 | val PROXY_LAST_SELECT_GROUP = StringEntry("proxy_last_select_group", "") 22 | val PROXY_MERGE_PREFIX = BooleanEntry("proxy_merge_prefix", false) 23 | val LANGUAGE = StringEntry("language", "") 24 | val DARK_MODE = StringEntry("dark_mode", DARK_MODE_AUTO) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/remote/Calls.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.remote 2 | 3 | suspend fun withClash(block: suspend ClashClient.() -> T): T { 4 | val client = Remote.clash.receive() 5 | 6 | return client.block() 7 | } 8 | 9 | suspend fun withProfile(block: suspend ProfileClient.() -> T): T { 10 | val client = Remote.profile.receive() 11 | 12 | return client.block() 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/remote/ClashClient.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.remote 2 | 3 | import android.os.RemoteException 4 | import com.github.kr328.clash.core.model.General 5 | import com.github.kr328.clash.core.model.ProxyGroup 6 | import com.github.kr328.clash.service.IClashManager 7 | import com.github.kr328.clash.service.transact.IStreamCallback 8 | import com.github.kr328.clash.service.transact.ParcelableContainer 9 | import kotlinx.coroutines.CompletableDeferred 10 | import kotlinx.coroutines.Dispatchers 11 | import kotlinx.coroutines.withContext 12 | 13 | class ClashClient(val service: IClashManager) { 14 | suspend fun setSelectProxy(name: String, proxy: String): Boolean = withContext(Dispatchers.IO) { 15 | service.setSelectProxy(name, proxy) 16 | } 17 | 18 | suspend fun startHealthCheck(group: String) = withContext(Dispatchers.IO) { 19 | CompletableDeferred().apply { 20 | service.startHealthCheck(group, object : IStreamCallback.Stub() { 21 | override fun complete() { 22 | this@apply.complete(Unit) 23 | } 24 | 25 | override fun completeExceptionally(reason: String?) { 26 | this@apply.completeExceptionally(RemoteException(reason)) 27 | } 28 | 29 | override fun send(data: ParcelableContainer?) {} 30 | }) 31 | } 32 | }.await() 33 | 34 | suspend fun queryAllProxyGroups(): List = withContext(Dispatchers.IO) { 35 | service.queryAllProxies().list 36 | } 37 | 38 | suspend fun queryGeneral(): General = withContext(Dispatchers.IO) { 39 | service.queryGeneral() 40 | } 41 | 42 | suspend fun queryBandwidth(): Long = withContext(Dispatchers.IO) { 43 | service.queryBandwidth() 44 | } 45 | 46 | suspend fun setProxyMode(mode: General.Mode) = withContext(Dispatchers.IO) { 47 | service.setProxyMode(mode.toString()) 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/remote/RemoteUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.remote 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import com.github.kr328.clash.ApkBrokenActivity 7 | import com.github.kr328.clash.common.utils.intent 8 | import com.github.kr328.clash.service.Constants 9 | import com.github.kr328.clash.service.ServiceStatusProvider 10 | import java.lang.Exception 11 | 12 | object RemoteUtils { 13 | fun detectClashRunning(context: Context): Boolean { 14 | try { 15 | val authority = Uri.Builder() 16 | .scheme("content") 17 | .authority("${context.packageName}${Constants.STATUS_PROVIDER_SUFFIX}") 18 | .build() 19 | 20 | val pong = context.contentResolver.call( 21 | authority, 22 | ServiceStatusProvider.METHOD_PING_CLASH_SERVICE, 23 | null, 24 | null 25 | ) 26 | 27 | return pong != null 28 | } catch (e: Exception) { 29 | context.startActivity(ApkBrokenActivity::class.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) 30 | 31 | return false 32 | } 33 | } 34 | 35 | fun getCurrentClashProfileName(context: Context): String? { 36 | try { 37 | val authority = Uri.Builder() 38 | .scheme("content") 39 | .authority("${context.packageName}${Constants.STATUS_PROVIDER_SUFFIX}") 40 | .build() 41 | 42 | val pong = context.contentResolver.call( 43 | authority, 44 | ServiceStatusProvider.METHOD_PING_CLASH_SERVICE, 45 | null, 46 | null 47 | ) 48 | 49 | return pong?.getString("name") 50 | } catch (e: Exception) { 51 | context.startActivity(ApkBrokenActivity::class.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) 52 | 53 | return null 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/settings/BaseSettingFragment.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.settings 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.github.kr328.clash.preference.UiSettings 8 | import com.github.kr328.clash.service.settings.ServiceSettings 9 | import moe.shizuku.preference.PreferenceFragment 10 | 11 | abstract class BaseSettingFragment : PreferenceFragment() { 12 | abstract fun onCreateDataStore(): SettingsDataStore 13 | abstract val xmlResourceId: Int 14 | 15 | protected val service: ServiceSettings by lazy { ServiceSettings(requireActivity()) } 16 | protected val ui: UiSettings by lazy { UiSettings(requireActivity()) } 17 | 18 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 19 | preferenceManager.preferenceDataStore = onCreateDataStore() 20 | 21 | setPreferencesFromResource(xmlResourceId, rootKey) 22 | } 23 | 24 | override fun onCreateView( 25 | inflater: LayoutInflater, 26 | container: ViewGroup?, 27 | savedInstanceState: Bundle? 28 | ): View? { 29 | val result = super.onCreateView(inflater, container, savedInstanceState) 30 | 31 | setDivider(null) 32 | setDividerHeight(0) 33 | 34 | return result 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/settings/BehaviorFragment.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.settings 2 | 3 | import android.content.pm.PackageManager 4 | import android.os.Bundle 5 | import com.github.kr328.clash.R 6 | import com.github.kr328.clash.common.utils.componentName 7 | import com.github.kr328.clash.remote.Broadcasts 8 | import com.github.kr328.clash.service.RestartReceiver 9 | import com.github.kr328.clash.service.settings.ServiceSettings 10 | 11 | class BehaviorFragment : BaseSettingFragment() { 12 | companion object { 13 | private const val KEY_START_ON_BOOT = "start_on_boot" 14 | private const val KEY_SHOW_TRAFFIC = "show_traffic" 15 | } 16 | 17 | override fun onCreateDataStore(): SettingsDataStore { 18 | return SettingsDataStore().apply { 19 | on(KEY_START_ON_BOOT, StartOnBootSource()) 20 | on(KEY_SHOW_TRAFFIC, ServiceSettings.NOTIFICATION_REFRESH.asSource(service)) 21 | } 22 | } 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | 27 | findPreference(KEY_SHOW_TRAFFIC).isEnabled = !Broadcasts.clashRunning 28 | } 29 | 30 | override val xmlResourceId: Int 31 | get() = R.xml.settings_behavior 32 | 33 | private inner class StartOnBootSource : SettingsDataStore.Source { 34 | override fun set(value: Any?) { 35 | val v = value as Boolean? ?: return 36 | 37 | val status = if (v) 38 | PackageManager.COMPONENT_ENABLED_STATE_ENABLED 39 | else 40 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED 41 | 42 | requireActivity().packageManager.setComponentEnabledSetting( 43 | RestartReceiver::class.componentName, 44 | status, 45 | PackageManager.DONT_KILL_APP 46 | ) 47 | } 48 | 49 | override fun get(): Any? { 50 | val status = requireActivity().packageManager 51 | .getComponentEnabledSetting(RestartReceiver::class.componentName) 52 | 53 | return status == PackageManager.COMPONENT_ENABLED_STATE_ENABLED 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/settings/InterfaceFragment.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.settings 2 | 3 | import com.github.kr328.clash.R 4 | import com.github.kr328.clash.preference.UiSettings 5 | import com.github.kr328.clash.service.settings.ServiceSettings 6 | 7 | class InterfaceFragment : BaseSettingFragment() { 8 | companion object { 9 | private const val KEY_DARK_MODE = "dark_mode" 10 | private const val KEY_LANGUAGE = "language" 11 | } 12 | 13 | override fun onCreateDataStore(): SettingsDataStore { 14 | return SettingsDataStore().apply { 15 | on(KEY_DARK_MODE, UiSettings.DARK_MODE.asSource(ui)) 16 | on(KEY_LANGUAGE, UiSettings.LANGUAGE.asSource(ui)) 17 | 18 | onApply { 19 | service.commit { 20 | put(ServiceSettings.LANGUAGE, ui.get(UiSettings.LANGUAGE)) 21 | } 22 | 23 | requireActivity().recreate() 24 | } 25 | } 26 | } 27 | 28 | override val xmlResourceId: Int 29 | get() = R.xml.settings_interface 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/settings/NetworkFragment.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.settings 2 | 3 | import android.os.Bundle 4 | import com.github.kr328.clash.PackagesActivity 5 | import com.github.kr328.clash.R 6 | import com.github.kr328.clash.common.utils.intent 7 | import com.github.kr328.clash.remote.Broadcasts 8 | import com.github.kr328.clash.service.settings.ServiceSettings 9 | 10 | class NetworkFragment : BaseSettingFragment() { 11 | companion object { 12 | private const val KEY_ENABLE_VPN_SERVICE = "enable_vpn_service" 13 | private const val BYPASS_PRIVATE_NETWORK = "bypass_private_network" 14 | private const val KEY_DNS_HIJACKING = "dns_hijacking" 15 | private const val KEY_DNS_OVERRIDE = "dns_override" 16 | private const val KEY_APPEND_SYS_DNS = "append_system_dns" 17 | private const val KEY_ACCESS_CONTROL_MODE = "access_control_mode" 18 | private const val KEY_ACCESS_CONTROL_PACKAGES = "access_control_packages" 19 | } 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | 24 | preferenceScreen.isEnabled = !Broadcasts.clashRunning 25 | 26 | findPreference(KEY_ACCESS_CONTROL_PACKAGES).setOnPreferenceClickListener { 27 | startActivity(PackagesActivity::class.intent) 28 | true 29 | } 30 | } 31 | 32 | override fun onCreateDataStore(): SettingsDataStore { 33 | return SettingsDataStore().apply { 34 | on(KEY_ENABLE_VPN_SERVICE, ServiceSettings.ENABLE_VPN.asSource(service)) 35 | on(BYPASS_PRIVATE_NETWORK, ServiceSettings.BYPASS_PRIVATE_NETWORK.asSource(service)) 36 | on(KEY_DNS_HIJACKING, ServiceSettings.DNS_HIJACKING.asSource(service)) 37 | on(KEY_DNS_OVERRIDE, ServiceSettings.OVERRIDE_DNS.asSource(service)) 38 | on(KEY_APPEND_SYS_DNS, ServiceSettings.AUTO_ADD_SYSTEM_DNS.asSource(service)) 39 | on(KEY_ACCESS_CONTROL_MODE, ServiceSettings.ACCESS_CONTROL_MODE.asSource(service)) 40 | } 41 | } 42 | 43 | override val xmlResourceId: Int 44 | get() = R.xml.settings_network 45 | } 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/utils/ApplicationObserver.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.utils 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.os.Bundle 6 | 7 | class ApplicationObserver(val stateChanged: (Boolean) -> Unit) { 8 | private var applicationRunning = false 9 | private set(value) { 10 | if ( field != value ) 11 | stateChanged(value) 12 | 13 | field = value 14 | } 15 | private var activityCount: Int = 0 16 | 17 | private val activityObserver = object: Application.ActivityLifecycleCallbacks { 18 | override fun onActivityPaused(activity: Activity) {} 19 | override fun onActivityStarted(activity: Activity) {} 20 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} 21 | override fun onActivityStopped(activity: Activity) {} 22 | override fun onActivityResumed(activity: Activity) {} 23 | override fun onActivityDestroyed(activity: Activity) { 24 | synchronized(this) { 25 | activityCount-- 26 | applicationRunning = activityCount > 0 27 | } 28 | } 29 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { 30 | synchronized(this) { 31 | activityCount++ 32 | applicationRunning = activityCount > 0 33 | } 34 | } 35 | } 36 | 37 | fun register(application: Application) { 38 | application.registerActivityLifecycleCallbacks(activityObserver) 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/utils/DateUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.utils 2 | 3 | import android.content.Context 4 | import java.text.SimpleDateFormat 5 | import java.util.* 6 | 7 | const val DATE_DATE_ONLY = "yyyy-MM-dd" 8 | const val DATE_TIME_ONLY = "HH:mm:ss" 9 | const val DATE_ALL = "$DATE_DATE_ONLY $DATE_TIME_ONLY" 10 | 11 | fun Date.format( 12 | context: Context, 13 | includeDate: Boolean = true, 14 | includeTime: Boolean = true, 15 | custom: String = "" 16 | ): String { 17 | val locale = context.resources.configuration.locales[0] 18 | 19 | return when { 20 | custom.isNotEmpty() -> 21 | SimpleDateFormat(custom, locale).format(this) 22 | includeDate && includeTime -> 23 | SimpleDateFormat(DATE_ALL, locale).format(this) 24 | includeDate -> 25 | SimpleDateFormat(DATE_DATE_ONLY, locale).format(this) 26 | includeTime -> 27 | SimpleDateFormat(DATE_TIME_ONLY, locale).format(this) 28 | else -> "" 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/utils/FileUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.utils 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.Constants 5 | import java.io.File 6 | 7 | val Context.logsDir: File 8 | get() = (externalCacheDir ?: cacheDir).resolve(Constants.LOG_DIR_NAME) -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/utils/IntervalUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.utils 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.R 5 | 6 | object IntervalUtils { 7 | private const val MILLIS_SECOND = 1000L 8 | private const val MILLIS_MINUTE = MILLIS_SECOND * 60 9 | private const val MILLIS_HOUR = MILLIS_MINUTE * 60 10 | private const val MILLIS_DAY = MILLIS_HOUR * 24 11 | private const val MILLIS_MONTH = MILLIS_DAY * 30 12 | private const val MILLIS_YEAR = MILLIS_MONTH * 12 13 | 14 | fun intervalString(context: Context, interval: Long): String { 15 | val year = interval / MILLIS_YEAR 16 | val month = interval / MILLIS_MONTH 17 | val day = interval / MILLIS_DAY 18 | val hour = interval / MILLIS_HOUR 19 | val minute = interval / MILLIS_MINUTE 20 | 21 | System.currentTimeMillis() 22 | 23 | return when { 24 | year > 0 -> context.getString(R.string.format_years, year) 25 | month > 0 -> context.getString(R.string.format_months, month) 26 | day > 0 -> context.getString(R.string.format_days, day) 27 | hour > 0 -> context.getString(R.string.format_hours, hour) 28 | minute > 0 -> context.getString(R.string.format_minutes, minute) 29 | else -> context.getString(R.string.recently) 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/utils/QuickSmoothScroller.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.utils 2 | 3 | import android.content.Context 4 | import androidx.recyclerview.widget.GridLayoutManager 5 | import androidx.recyclerview.widget.LinearLayoutManager 6 | import androidx.recyclerview.widget.LinearSmoothScroller 7 | import androidx.recyclerview.widget.RecyclerView 8 | 9 | class QuickSmoothScroller(context: Context, target: Int) : 10 | LinearSmoothScroller(context) { 11 | companion object { 12 | const val MAX_OFFSET = 2 13 | } 14 | 15 | var started = {} 16 | var stopped = {} 17 | 18 | init { 19 | targetPosition = target 20 | } 21 | 22 | override fun getVerticalSnapPreference(): Int { 23 | return SNAP_TO_START 24 | } 25 | 26 | override fun onStop() { 27 | super.onStop() 28 | 29 | stopped() 30 | } 31 | 32 | override fun onStart() { 33 | super.onStart() 34 | 35 | started() 36 | } 37 | 38 | override fun onSeekTargetStep(dx: Int, dy: Int, state: RecyclerView.State, action: Action) { 39 | when (val lm = layoutManager) { 40 | is LinearLayoutManager -> { 41 | val current = lm.findFirstCompletelyVisibleItemPosition() 42 | 43 | if (targetPosition > current && targetPosition - current > MAX_OFFSET) 44 | action.jumpTo(targetPosition - MAX_OFFSET) 45 | else if (current > targetPosition && current - targetPosition > MAX_OFFSET) 46 | action.jumpTo(targetPosition + MAX_OFFSET) 47 | } 48 | is GridLayoutManager -> { 49 | val current = lm.findFirstCompletelyVisibleItemPosition() 50 | 51 | if (targetPosition > current && targetPosition - current > MAX_OFFSET) 52 | action.jumpTo(targetPosition - MAX_OFFSET) 53 | else if (current > targetPosition && current - targetPosition > MAX_OFFSET) 54 | action.jumpTo(targetPosition + MAX_OFFSET) 55 | } 56 | } 57 | 58 | super.onSeekTargetStep(dx, dy, state, action) 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/utils/ScrollBinding.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.utils 2 | 3 | import android.content.Context 4 | import androidx.recyclerview.widget.LinearSmoothScroller 5 | import kotlinx.coroutines.channels.Channel 6 | import kotlinx.coroutines.delay 7 | 8 | class ScrollBinding( 9 | private val context: Context, 10 | private val callback: Callback 11 | ) { 12 | interface Callback { 13 | fun getCurrentMasterToken(): String 14 | fun onMasterTokenChanged(token: String) 15 | fun getMasterTokenPosition(token: String): Int 16 | fun doMasterScroll(scroller: LinearSmoothScroller, target: Int) 17 | } 18 | 19 | private val updateChannel = Channel(Channel.CONFLATED) 20 | private var preventSlaveScroll = false 21 | 22 | fun sendMasterScrolled() { 23 | updateChannel.offer(Unit) 24 | } 25 | 26 | fun scrollMaster(token: String) { 27 | val position = callback.getMasterTokenPosition(token) 28 | 29 | if (position < 0) 30 | return 31 | 32 | val scroller = QuickSmoothScroller(context, position) 33 | 34 | callback.doMasterScroll(scroller, position) 35 | } 36 | 37 | suspend fun exec() { 38 | var lastToken: String? = null 39 | 40 | while (true) { 41 | updateChannel.receive() 42 | 43 | val currentToken = callback.getCurrentMasterToken() 44 | if (preventSlaveScroll || lastToken == currentToken) 45 | continue 46 | 47 | lastToken = currentToken 48 | 49 | callback.onMasterTokenChanged(currentToken) 50 | 51 | delay(200) 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/utils/StringUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.utils 2 | 3 | fun String.toCodePointList(): List { 4 | var offset = 0 5 | val result = mutableListOf() 6 | 7 | while (offset < length) { 8 | val codePoint = codePointAt(offset) 9 | result.add(codePoint) 10 | 11 | offset += Character.charCount(codePoint) 12 | } 13 | 14 | return result.toList() 15 | } 16 | 17 | fun List.asCodePointString(): String { 18 | val sb = StringBuilder() 19 | 20 | forEach { 21 | sb.appendCodePoint(it) 22 | } 23 | 24 | return sb.toString() 25 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_about.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_adb.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clear.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_clear_all.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_content.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_copy.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete_colorful.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_download.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_feedback.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_file.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_flash.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_input.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_interface.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_label_outline.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_logo.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_logs.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_network.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_new.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play_for_work.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_profiles.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_properties.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_proxies.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_save.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_applications.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_started.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_stop.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_stopped.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_update.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_vertex.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_access_control_packages.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 17 | 18 | 19 | 23 | 27 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_application_broken.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 17 | 18 | 19 | 22 | 23 | 27 | 28 | 35 | 36 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_create_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 16 | 17 | 18 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 17 | 18 | 19 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_log_viewer.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 17 | 18 | 28 | 29 | 30 | 31 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_logs.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 17 | 18 | 28 | 29 | 30 | 31 | 35 | 36 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_profile_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 16 | 21 | 22 | 32 | 33 | 40 | 41 | 42 | 43 | 44 | 47 | 48 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_profiles.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 17 | 18 | 19 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_proxies.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 18 | 19 | 20 | 25 | 26 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 17 | 18 | 19 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_support.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 17 | 18 | 19 | 22 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_grid_proxy.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 20 | 21 | 29 | 30 | 37 | 38 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_grid_proxy_group.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | 26 | 34 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_log.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 15 | 16 | 22 | 23 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_log_file.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 27 | 28 | 33 | 34 | 35 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_package.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 20 | 21 | 28 | 29 | 35 | 36 | 41 | 42 | 43 | 51 | -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_profile_footer.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 18 | 19 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_proxies_chip.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/adapter_url_provider.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 22 | 23 | 29 | 30 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/dialog_abort.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 13 | 21 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/menu/packages.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 10 | 11 | 12 | 16 | 19 | 22 | 25 | 26 | 30 | 31 | 32 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/menu/proxies.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goomadao/ClashForAndroid/d0aeb45beb90ede099fcf37d0be94eeb0eaaab96/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goomadao/ClashForAndroid/d0aeb45beb90ede099fcf37d0be94eeb0eaaab96/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goomadao/ClashForAndroid/d0aeb45beb90ede099fcf37d0be94eeb0eaaab96/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goomadao/ClashForAndroid/d0aeb45beb90ede099fcf37d0be94eeb0eaaab96/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goomadao/ClashForAndroid/d0aeb45beb90ede099fcf37d0be94eeb0eaaab96/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goomadao/ClashForAndroid/d0aeb45beb90ede099fcf37d0be94eeb0eaaab96/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goomadao/ClashForAndroid/d0aeb45beb90ede099fcf37d0be94eeb0eaaab96/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goomadao/ClashForAndroid/d0aeb45beb90ede099fcf37d0be94eeb0eaaab96/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goomadao/ClashForAndroid/d0aeb45beb90ede099fcf37d0be94eeb0eaaab96/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goomadao/ClashForAndroid/d0aeb45beb90ede099fcf37d0be94eeb0eaaab96/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/bools.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #121212 5 | #000000 6 | #000000 7 | #121212 8 | #242424 9 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 允许所有应用 5 | 仅允许已选择的应用 6 | 不允许已选择的应用 7 | 8 | 9 | 自动 10 | 暗黑 11 | 明亮 12 | 13 | 14 | 自动 15 | 英文 16 | 简体中文 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Allow all apps 5 | Only allowing selected apps 6 | Disallow selected apps 7 | 8 | 9 | Auto 10 | Dark 11 | Light 12 | 13 | 14 | Auto 15 | English 16 | Simplified Chinese 17 | 18 | 19 | 20 | auto 21 | dark 22 | light 23 | 24 | 25 | access_control_mode_all 26 | access_control_mode_whitelist 27 | access_control_mode_blacklist 28 | 29 | 30 | 31 | en 32 | zh-rCN 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/values/bools.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1E4376 4 | #FAFAFA 5 | #FAFAFA 6 | #FAFAFA 7 | #EDEDED 8 | 9 | #FF888888 10 | #1E4376 11 | 12 | #121212 13 | 14 | #FFFFFF 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/xml/full_backup_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/xml/settings_behavior.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/xml/settings_interface.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/xml/settings_network.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 15 | 20 | 25 | 30 | 37 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | val kotlinVersion = "1.3.72" 5 | 6 | rootProject.extra.apply { 7 | this["gBuildToolsVersion"] = "29.0.3" 8 | 9 | this["gCompileSdkVersion"] = 29 10 | this["gMinSdkVersion"] = 24 11 | this["gTargetSdkVersion"] = 29 12 | 13 | this["gVersionCode"] = 10215 14 | this["gVersionName"] = "1.2.15" 15 | 16 | this["gKotlinVersion"] = kotlinVersion 17 | this["gKotlinCoroutineVersion"] = "1.3.7" 18 | this["gKotlinSerializationVersion"] = "0.20.0" 19 | this["gRoomVersion"] = "2.2.5" 20 | this["gAppCenterVersion"] = "2.5.1" 21 | this["gAndroidKtxVersion"] = "1.2.0" 22 | this["gRecyclerviewVersion"] = "1.1.0" 23 | this["gAppCompatVersion"] = "1.1.0" 24 | this["gMaterialDesignVersion"] = "1.1.0" 25 | this["gShizukuPreferenceVersion"] = "4.2.0" 26 | this["gMultiprocessPreferenceVersion"] = "1.0.0" 27 | } 28 | repositories { 29 | google() 30 | jcenter() 31 | } 32 | dependencies { 33 | classpath("com.android.tools.build:gradle:4.0.0-rc01") 34 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") 35 | classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion") 36 | } 37 | } 38 | 39 | allprojects { 40 | repositories { 41 | google() 42 | jcenter() 43 | 44 | maven { 45 | url = java.net.URI("https://dl.bintray.com/rikkaw/Libraries") 46 | } 47 | } 48 | } 49 | 50 | task("clean", type = Delete::class) { 51 | delete(rootProject.buildDir) 52 | } 53 | -------------------------------------------------------------------------------- /common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("kotlin-android") 4 | id("kotlin-android-extensions") 5 | } 6 | 7 | val rootExtra = rootProject.extra 8 | 9 | val gCompileSdkVersion: Int by rootExtra 10 | val gBuildToolsVersion: String by rootExtra 11 | 12 | val gMinSdkVersion: Int by rootExtra 13 | val gTargetSdkVersion: Int by rootExtra 14 | 15 | val gVersionCode: Int by rootExtra 16 | val gVersionName: String by rootExtra 17 | 18 | val gKotlinVersion: String by rootExtra 19 | val gKotlinCoroutineVersion: String by rootExtra 20 | val gAndroidKtxVersion: String by rootExtra 21 | val gKotlinSerializationVersion: String by rootExtra 22 | 23 | android { 24 | compileSdkVersion(gCompileSdkVersion) 25 | buildToolsVersion(gBuildToolsVersion) 26 | 27 | defaultConfig { 28 | minSdkVersion(gMinSdkVersion) 29 | targetSdkVersion(gTargetSdkVersion) 30 | 31 | versionCode = gVersionCode 32 | versionName = gVersionName 33 | 34 | consumerProguardFiles("consumer-rules.pro") 35 | } 36 | 37 | buildTypes { 38 | maybeCreate("release").apply { 39 | isMinifyEnabled = false 40 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 41 | } 42 | } 43 | 44 | compileOptions { 45 | sourceCompatibility = JavaVersion.VERSION_1_8 46 | targetCompatibility = JavaVersion.VERSION_1_8 47 | } 48 | 49 | kotlinOptions { 50 | jvmTarget = "1.8" 51 | } 52 | } 53 | 54 | dependencies { 55 | implementation("androidx.core:core-ktx:$gAndroidKtxVersion") 56 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$gKotlinVersion") 57 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$gKotlinCoroutineVersion") 58 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:$gKotlinSerializationVersion") 59 | } 60 | 61 | repositories { 62 | mavenCentral() 63 | } 64 | -------------------------------------------------------------------------------- /common/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goomadao/ClashForAndroid/d0aeb45beb90ede099fcf37d0be94eeb0eaaab96/common/consumer-rules.pro -------------------------------------------------------------------------------- /common/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common 2 | 3 | object Constants { 4 | const val TAG = "ClashForAndroid" 5 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/Global.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common 2 | 3 | import android.app.Application 4 | import android.content.Intent 5 | 6 | object Global { 7 | var openMainIntent: () -> Intent = { Intent() } 8 | var openProfileIntent: (Long) -> Intent = { Intent() } 9 | 10 | lateinit var application: Application 11 | private set 12 | 13 | fun init(application: Application) { 14 | Global.application = application 15 | } 16 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/Permissions.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common 2 | 3 | object Permissions { 4 | val PERMISSION_RECEIVE_BROADCASTS: String 5 | get() = Global.application.packageName + ".permission.RECEIVE_BROADCASTS" 6 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/ids/Intents.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.ids 2 | 3 | import com.github.kr328.clash.common.BuildConfig 4 | 5 | object Intents { 6 | const val INTENT_ACTION_CLASH_STARTED = 7 | "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.action.clash.STARTED" 8 | const val INTENT_ACTION_CLASH_STOPPED = 9 | "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.action.clash.STOPPED" 10 | const val INTENT_ACTION_CLASH_REQUEST_STOP = 11 | "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.action.clash.REQUEST_STOP" 12 | const val INTENT_ACTION_PROFILE_CHANGED = 13 | "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.action.profile.CHANGED" 14 | const val INTENT_ACTION_PROFILE_REQUEST_UPDATE = 15 | "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.action.profile.REQUEST_UPDATE" 16 | const val INTENT_ACTION_PROFILE_LOADED = 17 | "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.action.profile.LOADED" 18 | const val INTENT_ACTION_NETWORK_CHANGED = 19 | "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.action.network.CHANGED" 20 | 21 | const val INTENT_EXTRA_CLASH_STOP_REASON = 22 | "${BuildConfig.LIBRARY_PACKAGE_NAME}.intent.extra.clash.STOP_REASON" 23 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/ids/NotificationChannels.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.ids 2 | 3 | object NotificationChannels { 4 | const val CLASH_STATUS = "clash_status_channel" 5 | const val PROFILE_STATUS = "profile_status_channel" 6 | const val PROFILE_RESULT = "profile_result_channel" 7 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/ids/NotificationIds.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.ids 2 | 3 | object NotificationIds { 4 | const val CLASH_STATUS = 1 5 | const val PROFILE_STATUS = 2 6 | private val PROFILE_RESULT = 10000..20000 7 | 8 | fun generateProfileResultId(profileId: Long): Int { 9 | val bound = PROFILE_RESULT.last - PROFILE_RESULT.first 10 | return (profileId % bound + PROFILE_RESULT.first).toInt() 11 | } 12 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/ids/PendingIds.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.ids 2 | 3 | object PendingIds { 4 | const val CLASH_VPN = 1 5 | 6 | fun generateProfileResultId(profileId: Long): Int { 7 | return NotificationIds.generateProfileResultId(profileId) 8 | } 9 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/settings/BaseSettings.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.settings 2 | 3 | import android.content.SharedPreferences 4 | 5 | abstract class BaseSettings(private val preferences: SharedPreferences) { 6 | interface Entry { 7 | fun get(preferences: SharedPreferences): T 8 | fun put(editor: SharedPreferences.Editor, value: T) 9 | } 10 | 11 | class StringEntry(private val key: String, private val defaultValue: String) : 12 | Entry { 13 | override fun get(preferences: SharedPreferences): String { 14 | return preferences.getString(key, defaultValue)!! 15 | } 16 | 17 | override fun put(editor: SharedPreferences.Editor, value: String) { 18 | editor.putString(key, value) 19 | } 20 | } 21 | 22 | class BooleanEntry(private val key: String, private val defaultValue: Boolean) : 23 | Entry { 24 | override fun get(preferences: SharedPreferences): Boolean { 25 | return preferences.getBoolean(key, defaultValue) 26 | } 27 | 28 | override fun put(editor: SharedPreferences.Editor, value: Boolean) { 29 | editor.putBoolean(key, value) 30 | } 31 | } 32 | 33 | class StringSetEntry(private val key: String, private val defaultValue: Set) : 34 | Entry> { 35 | override fun get(preferences: SharedPreferences): Set { 36 | return preferences.getStringSet(key, defaultValue)!! 37 | } 38 | 39 | override fun put(editor: SharedPreferences.Editor, value: Set) { 40 | editor.putStringSet(key, value) 41 | } 42 | } 43 | 44 | class Editor(private val editor: SharedPreferences.Editor) { 45 | fun put(entry: Entry, value: T) { 46 | entry.put(editor, value) 47 | } 48 | } 49 | 50 | fun get(entry: Entry): T { 51 | return entry.get(preferences) 52 | } 53 | 54 | fun commit(async: Boolean = true, block: Editor.() -> Unit) { 55 | val editor = preferences.edit() 56 | 57 | Editor(editor).apply(block) 58 | 59 | if (async) 60 | editor.apply() 61 | else 62 | editor.commit() 63 | } 64 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/utils/ByteFormatter.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.utils 2 | 3 | object ByteFormatter { 4 | fun byteToString(bytes: Long): String { 5 | return when { 6 | bytes > 1024 * 1024 * 1024 -> 7 | String.format("%.2f GiB", (bytes.toDouble() / 1024 / 1024 / 1024)) 8 | bytes > 1024 * 1024 -> 9 | String.format("%.2f MiB", (bytes.toDouble() / 1024 / 1024)) 10 | bytes > 1024 -> 11 | String.format("%.2f KiB", (bytes.toDouble() / 1024)) 12 | else -> 13 | "$bytes Bytes" 14 | } 15 | } 16 | 17 | fun byteToStringSecond(bytes: Long): String { 18 | return byteToString(bytes) + "/s" 19 | } 20 | } 21 | 22 | fun Long.asBytesString(): String { 23 | return ByteFormatter.byteToString(this) 24 | } 25 | 26 | fun Long.asSpeedString(): String { 27 | return ByteFormatter.byteToStringSecond(this) 28 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/utils/ComponentUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.utils 2 | 3 | import android.content.ComponentName 4 | import android.content.Intent 5 | import com.github.kr328.clash.common.Global 6 | import kotlin.reflect.KClass 7 | 8 | val KClass<*>.componentName: ComponentName 9 | get() = ComponentName.createRelative(Global.application, this.java.name) 10 | 11 | val KClass<*>.intent: Intent 12 | get() = Intent(Global.application, this.java) 13 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/utils/LanguageUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.utils 2 | 3 | import android.content.Context 4 | import android.content.res.Configuration 5 | import java.util.* 6 | 7 | fun Context.createLanguageConfigurationContext(language: String): Context { 8 | if (language.isBlank()) { 9 | return this 10 | } 11 | 12 | val split = language.split("-") 13 | val locale = if (split.size == 1) 14 | Locale(split[0]) 15 | else 16 | Locale(split[0], split[1]) 17 | 18 | val configuration = Configuration() 19 | 20 | configuration.setLocale(locale) 21 | 22 | return createConfigurationContext(configuration) 23 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/utils/Log.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.utils 2 | 3 | import com.github.kr328.clash.common.Constants.TAG 4 | 5 | object Log { 6 | fun i(message: String, throwable: Throwable? = null) = 7 | android.util.Log.i(TAG, message, throwable) 8 | 9 | fun w(message: String, throwable: Throwable? = null) = 10 | android.util.Log.w(TAG, message, throwable) 11 | 12 | fun e(message: String, throwable: Throwable? = null) = 13 | android.util.Log.e(TAG, message, throwable) 14 | 15 | fun d(message: String, throwable: Throwable? = null) = 16 | android.util.Log.d(TAG, message, throwable) 17 | 18 | fun v(message: String, throwable: Throwable? = null) = 19 | android.util.Log.v(TAG, message, throwable) 20 | } 21 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/utils/ServiceUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.utils 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Build 6 | 7 | fun Context.startForegroundServiceCompat(intent: Intent) { 8 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 9 | startForegroundService(intent) 10 | } else { 11 | startService(intent) 12 | } 13 | } -------------------------------------------------------------------------------- /common/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 接收 Clash 广播 4 | 接收来自 Clash 内部的广播 5 | -------------------------------------------------------------------------------- /common/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Receive Clash Broadcasts 4 | Receive broadcasts of clash services 5 | 6 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /core/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class go.* { 2 | *; 3 | } 4 | -keep class bridge.* { 5 | *; 6 | } -------------------------------------------------------------------------------- /core/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/src/main/golang/bridge/callback.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | type DoneCallback interface { 4 | Done() 5 | DoneWithError(error) 6 | } 7 | -------------------------------------------------------------------------------- /core/src/main/golang/bridge/general.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/hub/executor" 5 | "github.com/Dreamacro/clash/tunnel" 6 | ) 7 | 8 | type TunnelGeneral struct { 9 | Mode string 10 | HTTPPort int 11 | SocksPort int 12 | RedirectPort int 13 | } 14 | 15 | func QueryGeneral() *TunnelGeneral { 16 | result := &TunnelGeneral{} 17 | 18 | g := executor.GetGeneral() 19 | m := tunnel.Mode() 20 | 21 | result.Mode = m.String() 22 | result.HTTPPort = g.Port 23 | result.SocksPort = g.SocksPort 24 | result.RedirectPort = g.RedirPort 25 | 26 | return result 27 | } 28 | 29 | func SetProxyMode(mode string) { 30 | switch mode { 31 | case "Direct": 32 | tunnel.SetMode(tunnel.Direct) 33 | case "Global": 34 | tunnel.SetMode(tunnel.Global) 35 | case "Rule": 36 | tunnel.SetMode(tunnel.Rule) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /core/src/main/golang/bridge/init.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/component/mmdb" 5 | C "github.com/Dreamacro/clash/constant" 6 | "github.com/Dreamacro/clash/log" 7 | "github.com/Dreamacro/clash/tunnel" 8 | "github.com/kr328/cfa/config" 9 | "sync" 10 | ) 11 | 12 | var ( 13 | logCallback LogCallback 14 | logSubscribe sync.Once 15 | ) 16 | 17 | type LogCallback interface { 18 | OnLogEvent(level, payload string) 19 | } 20 | 21 | func InitCore(geoipDatabase[] byte, homeDir string, version string) { 22 | dataClone := make([]byte, len(geoipDatabase)) 23 | copy(dataClone, geoipDatabase) 24 | 25 | mmdb.LoadFromBytes(dataClone) 26 | C.SetHomeDir(homeDir) 27 | config.ApplicationVersion = version 28 | 29 | Reset() 30 | 31 | log.Infoln("Initialed") 32 | } 33 | 34 | func Reset() { 35 | config.LoadDefault() 36 | tunnel.DefaultManager.ResetStatistic() 37 | } 38 | 39 | func SetLogCallback(callback LogCallback) { 40 | logSubscribe.Do(func() { 41 | go func() { 42 | sub := log.Subscribe() 43 | defer log.UnSubscribe(sub) 44 | 45 | for { 46 | elm := <-sub 47 | l := elm.(*log.Event) 48 | 49 | if l.LogLevel < log.Level() { 50 | continue 51 | } 52 | 53 | if cb := logCallback; cb != nil { 54 | cb.OnLogEvent(l.LogLevel.String(), l.Payload) 55 | } 56 | } 57 | }() 58 | }) 59 | 60 | logCallback = callback 61 | } 62 | -------------------------------------------------------------------------------- /core/src/main/golang/bridge/profiles.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/kr328/cfa/config" 7 | ) 8 | 9 | func ResetDnsAppend(dns string) { 10 | if len(dns) == 0 { 11 | config.NameServersAppend = make([]string, 0) 12 | } else { 13 | config.NameServersAppend = strings.Split(dns, ",") 14 | } 15 | } 16 | 17 | func SetDnsOverrideEnabled(enabled bool) { 18 | if enabled { 19 | config.DnsPatch = config.OptionalDnsPatch 20 | } else { 21 | config.DnsPatch = nil 22 | } 23 | } 24 | 25 | func LoadProfileFile(path, baseDir string, callback DoneCallback) { 26 | go func() { 27 | call(config.LoadFromFile(path, baseDir), callback) 28 | }() 29 | } 30 | 31 | func DownloadProfileAndCheck(url, output, baseDir string, callback DoneCallback) { 32 | go func() { 33 | call(config.PullRemote(url, output, baseDir), callback) 34 | }() 35 | } 36 | 37 | func ReadProfileAndCheck(fd int, output, baseDir string, callback DoneCallback) { 38 | go func() { 39 | call(config.PullLocal(fd, output, baseDir), callback) 40 | }() 41 | } 42 | 43 | func call(err error, callback DoneCallback) { 44 | if err != nil { 45 | callback.DoneWithError(err) 46 | } else { 47 | callback.Done() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /core/src/main/golang/bridge/statistics.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/Dreamacro/clash/tunnel" 7 | ) 8 | 9 | type EventPoll struct { 10 | stop sync.Once 11 | 12 | onStop func() 13 | } 14 | 15 | func (e *EventPoll) Stop() { 16 | e.stop.Do(func() { 17 | e.onStop() 18 | }) 19 | } 20 | 21 | type Traffic struct { 22 | Download int64 23 | Upload int64 24 | } 25 | 26 | type Logs interface { 27 | OnEvent(level, payload string) 28 | } 29 | 30 | func QueryBandwidth() *Traffic { 31 | upload := tunnel.DefaultManager.UploadTotal() 32 | download := tunnel.DefaultManager.DownloadTotal() 33 | 34 | return &Traffic{ 35 | Upload: upload, 36 | Download: download, 37 | } 38 | } 39 | 40 | func QueryTraffic() *Traffic { 41 | up, down := tunnel.DefaultManager.Now() 42 | 43 | return &Traffic{ 44 | Upload: up, 45 | Download: down, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /core/src/main/golang/bridge/tun.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | 7 | "github.com/Dreamacro/clash/component/dialer" 8 | "github.com/kr328/cfa/tun" 9 | ) 10 | 11 | type TunCallback interface { 12 | OnCreateSocket(fd int) 13 | OnStop() 14 | } 15 | 16 | var callback TunCallback 17 | 18 | func init() { 19 | dialer.DialerHook = onNewDialer 20 | dialer.ListenConfigHook = onNewListenConfig 21 | } 22 | 23 | func onNewDialer(dialer *net.Dialer) error { 24 | dialer.Control = onNewSocket 25 | return nil 26 | } 27 | 28 | func onNewListenConfig(listen *net.ListenConfig) error { 29 | listen.Control = onNewSocket 30 | return nil 31 | } 32 | 33 | func onNewSocket(_, _ string, c syscall.RawConn) error { 34 | if cb := callback; cb != nil { 35 | _ = c.Control(func(fd uintptr) { 36 | cb.OnCreateSocket(int(fd)) 37 | }) 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func StartTunDevice(fd, mtu int, gateway, mirror, dns string, cb TunCallback) error { 44 | callback = cb 45 | 46 | return tun.StartTunDevice(fd, mtu, gateway, mirror, dns) 47 | } 48 | 49 | func StopTunDevice() { 50 | tun.StopTunDevice() 51 | 52 | if c := callback; c != nil { 53 | c.OnStop() 54 | } 55 | 56 | callback = nil 57 | } 58 | -------------------------------------------------------------------------------- /core/src/main/golang/config/load.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | 7 | "github.com/Dreamacro/clash/config" 8 | "github.com/Dreamacro/clash/hub/executor" 9 | "github.com/Dreamacro/clash/log" 10 | "github.com/kr328/cfa/tun" 11 | ) 12 | 13 | // LoadDefault - load default configure 14 | func LoadDefault() { 15 | DnsPatch = nil 16 | NameServersAppend = make([]string, 0) 17 | 18 | defaultC, err := config.Parse([]byte{}) 19 | if err != nil { 20 | log.Warnln("Load Default Failure " + err.Error()) 21 | return 22 | } 23 | 24 | executor.ApplyConfig(defaultC, true) 25 | 26 | tun.InitialResolver() 27 | } 28 | 29 | // LoadFromFile - load file 30 | func LoadFromFile(path, baseDir string) error { 31 | data, err := ioutil.ReadFile(path) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | cfg, err := parseConfig(data, baseDir) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | for _, ns := range cfg.DNS.NameServer { 42 | log.Infoln("DNS: %s", ns.Addr) 43 | } 44 | 45 | executor.ApplyConfig(cfg, true) 46 | 47 | tun.InitialResolver() 48 | 49 | log.Infoln("Profile " + path + " loaded") 50 | 51 | return nil 52 | } 53 | 54 | func parseConfig(data []byte, baseDir string) (*config.Config, error) { 55 | raw, err := config.UnmarshalRawConfig(data) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | patchRawConfig(raw) 61 | 62 | if len(raw.Proxy) == 0 && len(raw.ProxyProvider) == 0 && 63 | len(raw.ProxyOld) == 0 && len(raw.ProxyProviderOld) == 0 { 64 | return nil, errors.New("Empty Profile") 65 | } 66 | 67 | cfg, err := config.ParseRawConfig(raw, baseDir) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | patchConfig(cfg) 73 | 74 | return cfg, nil 75 | } 76 | -------------------------------------------------------------------------------- /core/src/main/golang/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kr328/cfa 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/Dreamacro/clash v0.0.0 // local 7 | github.com/kr328/tun2socket v0.0.0-20200524072930-c348f97fe81e 8 | github.com/miekg/dns v1.1.29 9 | ) 10 | 11 | replace github.com/Dreamacro/clash => ./clash 12 | -------------------------------------------------------------------------------- /core/src/main/golang/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() {} 4 | -------------------------------------------------------------------------------- /core/src/main/golang/tun/log.go: -------------------------------------------------------------------------------- 1 | package tun 2 | 3 | import "github.com/Dreamacro/clash/log" 4 | 5 | type ClashLogger struct{} 6 | 7 | func (c *ClashLogger) D(format string, args ...interface{}) { 8 | log.Debugln(format, args...) 9 | } 10 | 11 | func (c *ClashLogger) I(format string, args ...interface{}) { 12 | log.Infoln(format, args...) 13 | } 14 | 15 | func (c *ClashLogger) W(format string, args ...interface{}) { 16 | log.Warnln(format, args...) 17 | } 18 | 19 | func (c *ClashLogger) E(format string, args ...interface{}) { 20 | log.Errorln(format, args...) 21 | } 22 | -------------------------------------------------------------------------------- /core/src/main/golang/tun/udp.go: -------------------------------------------------------------------------------- 1 | package tun 2 | 3 | import ( 4 | "errors" 5 | "github.com/kr328/tun2socket/binding" 6 | "github.com/kr328/tun2socket/redirect" 7 | "net" 8 | ) 9 | 10 | type udpPacket struct { 11 | payload []byte 12 | endpoint *binding.Endpoint 13 | send redirect.UDPSender 14 | recycle func([]byte) 15 | } 16 | 17 | func (conn *udpPacket) Data() []byte { 18 | return conn.payload 19 | } 20 | 21 | func (conn *udpPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) { 22 | if addr == nil { 23 | addr = &net.UDPAddr{ 24 | IP: conn.endpoint.Target.IP, 25 | Port: int(conn.endpoint.Target.Port), 26 | Zone: "", 27 | } 28 | } 29 | 30 | udpAddr, ok := addr.(*net.UDPAddr) 31 | if !ok { 32 | return 0, errors.New("Invalid udp address") 33 | } 34 | 35 | ep := &binding.Endpoint{ 36 | Source: binding.Address{ 37 | IP: udpAddr.IP, 38 | Port: uint16(udpAddr.Port), 39 | }, 40 | Target: conn.endpoint.Source, 41 | } 42 | 43 | return len(b), conn.send(b, ep) 44 | } 45 | 46 | func (conn *udpPacket) LocalAddr() net.Addr { 47 | return &net.UDPAddr{ 48 | IP: conn.endpoint.Source.IP, 49 | Port: int(conn.endpoint.Source.Port), 50 | Zone: "", 51 | } 52 | } 53 | 54 | func (conn *udpPacket) Drop() { 55 | conn.recycle(conn.payload) 56 | } 57 | -------------------------------------------------------------------------------- /core/src/main/golang/utils/close.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "io" 4 | 5 | func CloseSilent(closer io.Closer) { 6 | _ = closer.Close() 7 | } 8 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/event/LogEvent.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.event 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import com.github.kr328.clash.common.serialization.Parcels 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | @Suppress("UNUSED") 10 | data class LogEvent( 11 | val level: Level, 12 | val message: String, 13 | val time: Long = System.currentTimeMillis() 14 | ) : Parcelable { 15 | companion object { 16 | const val DEBUG_VALUE = "debug" 17 | const val INFO_VALUE = "info" 18 | const val WARN_VALUE = "warning" 19 | const val ERROR_VALUE = "error" 20 | 21 | @JvmField 22 | val CREATOR = object : Parcelable.Creator { 23 | override fun createFromParcel(parcel: Parcel): LogEvent { 24 | return Parcels.load(serializer(), parcel) 25 | } 26 | 27 | override fun newArray(size: Int): Array { 28 | return arrayOfNulls(size) 29 | } 30 | } 31 | } 32 | 33 | enum class Level { 34 | DEBUG, 35 | INFO, 36 | WARN, 37 | ERROR, 38 | UNKNOWN; 39 | 40 | companion object { 41 | fun fromString(type: String): Level { 42 | return when (type) { 43 | DEBUG_VALUE -> DEBUG 44 | INFO_VALUE -> INFO 45 | WARN_VALUE -> WARN 46 | ERROR_VALUE -> ERROR 47 | else -> UNKNOWN 48 | } 49 | } 50 | } 51 | } 52 | 53 | override fun writeToParcel(parcel: Parcel, flags: Int) { 54 | Parcels.dump(serializer(), this, parcel) 55 | } 56 | 57 | override fun describeContents(): Int { 58 | return 0 59 | } 60 | } -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/General.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.model 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import com.github.kr328.clash.common.serialization.Parcels 6 | import kotlinx.serialization.* 7 | 8 | @Serializable 9 | data class General(val mode: Mode, val http: Int, val socks: Int, val redirect: Int) : Parcelable { 10 | @Serializable 11 | enum class Mode(val string: String) { 12 | DIRECT("Direct"), GLOBAL("Global"), RULE("Rule"); 13 | 14 | override fun toString(): String { 15 | return string 16 | } 17 | 18 | companion object { 19 | fun fromString(mode: String): Mode { 20 | return when (mode) { 21 | DIRECT.string -> DIRECT 22 | GLOBAL.string -> GLOBAL 23 | RULE.string -> RULE 24 | else -> throw IllegalArgumentException("Invalid mode $mode") 25 | } 26 | } 27 | } 28 | } 29 | 30 | override fun writeToParcel(parcel: Parcel, flags: Int) { 31 | Parcels.dump(serializer(), this, parcel) 32 | } 33 | 34 | override fun describeContents(): Int { 35 | return 0 36 | } 37 | 38 | companion object { 39 | @JvmField 40 | val CREATOR = object : Parcelable.Creator { 41 | override fun createFromParcel(parcel: Parcel): General { 42 | return Parcels.load(serializer(), parcel) 43 | } 44 | 45 | override fun newArray(size: Int): Array { 46 | return arrayOfNulls(size) 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/Proxy.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.model 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Proxy( 7 | val name: String, 8 | val type: Type, 9 | val delay: Long 10 | ) { 11 | enum class Type(val text: String, val group: Boolean) { 12 | DIRECT("Direct", false), 13 | REJECT("Reject", false), 14 | 15 | SHADOWSOCKS("Shadowsocks", false), 16 | SHADOWSOCKSR("ShadowsocksR", false), 17 | SNELL("Snell", false), 18 | SOCKS5("Socks5", false), 19 | HTTP("Http", false), 20 | VMESS("Vmess", false), 21 | TROJAN("Trojan", false), 22 | 23 | RELAY("Relay", true), 24 | SELECT("Selector", true), 25 | FALLBACK("Fallback", true), 26 | URL_TEST("URLTest", true), 27 | LOAD_BALANCE("LoadBalance", true), 28 | 29 | UNKNOWN("Unknown", false); 30 | 31 | override fun toString(): String { 32 | return text 33 | } 34 | 35 | companion object { 36 | fun fromString(type: String): Type { 37 | return when (type) { 38 | DIRECT.text -> DIRECT 39 | REJECT.text -> REJECT 40 | SHADOWSOCKS.text -> SHADOWSOCKS 41 | SHADOWSOCKSR.text -> SHADOWSOCKSR 42 | SNELL.text -> SNELL 43 | SOCKS5.text -> SOCKS5 44 | HTTP.text -> HTTP 45 | VMESS.text -> VMESS 46 | TROJAN.text -> TROJAN 47 | RELAY.text -> RELAY 48 | SELECT.text -> SELECT 49 | FALLBACK.text -> FALLBACK 50 | URL_TEST.text -> URL_TEST 51 | LOAD_BALANCE.text -> LOAD_BALANCE 52 | UNKNOWN.text -> UNKNOWN 53 | else -> UNKNOWN 54 | } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/ProxyGroup.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.model 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class ProxyGroup( 7 | val name: String, 8 | val type: Proxy.Type, 9 | val delay: Long, 10 | val current: String, 11 | val proxies: List 12 | ) -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/ProxyGroupList.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.model 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import com.github.kr328.clash.common.serialization.MergedParcels 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | data class ProxyGroupList(val list: List) : Parcelable { 10 | override fun writeToParcel(parcel: Parcel, flags: Int) { 11 | MergedParcels.dump(serializer(), this, parcel) 12 | } 13 | 14 | override fun describeContents(): Int { 15 | return 0 16 | } 17 | 18 | companion object CREATOR : Parcelable.Creator { 19 | override fun createFromParcel(parcel: Parcel): ProxyGroupList { 20 | return MergedParcels.load(serializer(), parcel) 21 | } 22 | 23 | override fun newArray(size: Int): Array { 24 | return arrayOfNulls(size) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/Traffic.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.model 2 | 3 | data class Traffic(val upload: Long, val download: Long) -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/transact/DoneCallbackImpl.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.transact 2 | 3 | import bridge.DoneCallback 4 | import kotlinx.coroutines.CompletableDeferred 5 | 6 | class DoneCallbackImpl : DoneCallback, CompletableDeferred by CompletableDeferred() { 7 | override fun doneWithError(e: Exception?) { 8 | completeExceptionally(e ?: return done()) 9 | } 10 | 11 | override fun done() { 12 | complete(Unit) 13 | } 14 | } -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/transact/ProxyCollections.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.transact 2 | 3 | import bridge.ProxyCollection 4 | import bridge.ProxyGroupCollection 5 | import bridge.ProxyGroupItem 6 | import bridge.ProxyItem 7 | import java.util.* 8 | 9 | class ProxyCollectionImpl : LinkedList(), ProxyCollection 10 | class ProxyGroupCollectionImpl : LinkedList(), ProxyGroupCollection -------------------------------------------------------------------------------- /design/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /design/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("kotlin-android") 4 | id("kotlin-android-extensions") 5 | } 6 | 7 | val rootExtra = rootProject.extra 8 | 9 | val gCompileSdkVersion: Int by rootExtra 10 | val gBuildToolsVersion: String by rootExtra 11 | 12 | val gMinSdkVersion: Int by rootExtra 13 | val gTargetSdkVersion: Int by rootExtra 14 | 15 | val gVersionCode: Int by rootExtra 16 | val gVersionName: String by rootExtra 17 | 18 | val gKotlinVersion: String by rootExtra 19 | val gAndroidKtxVersion: String by rootExtra 20 | val gAppCompatVersion: String by rootExtra 21 | val gMaterialDesignVersion: String by rootExtra 22 | 23 | android { 24 | compileSdkVersion(gCompileSdkVersion) 25 | buildToolsVersion(gBuildToolsVersion) 26 | 27 | defaultConfig { 28 | minSdkVersion(gMinSdkVersion) 29 | targetSdkVersion(gTargetSdkVersion) 30 | 31 | versionCode = gVersionCode 32 | versionName = gVersionName 33 | 34 | consumerProguardFiles("consumer-rules.pro") 35 | } 36 | 37 | buildTypes { 38 | maybeCreate("release").apply { 39 | isMinifyEnabled = false 40 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 41 | } 42 | } 43 | 44 | compileOptions { 45 | sourceCompatibility = JavaVersion.VERSION_1_8 46 | targetCompatibility = JavaVersion.VERSION_1_8 47 | } 48 | 49 | kotlinOptions { 50 | jvmTarget = "1.8" 51 | } 52 | } 53 | 54 | dependencies { 55 | implementation(project(":common")) 56 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$gKotlinVersion") 57 | implementation("androidx.appcompat:appcompat:$gAppCompatVersion") 58 | implementation("androidx.core:core-ktx:$gAndroidKtxVersion") 59 | implementation("com.google.android.material:material:$gMaterialDesignVersion") 60 | } 61 | -------------------------------------------------------------------------------- /design/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goomadao/ClashForAndroid/d0aeb45beb90ede099fcf37d0be94eeb0eaaab96/design/consumer-rules.pro -------------------------------------------------------------------------------- /design/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /design/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/common/Base.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.common 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.view.View 6 | 7 | @Suppress("MemberVisibilityCanBePrivate") 8 | abstract class Base(val screen: CommonUiScreen) { 9 | val context: Context 10 | get() = screen.layout.context 11 | 12 | var id: String? = null 13 | 14 | var dependOn: Base? = null 15 | 16 | var isEnabled: Boolean = true 17 | get() = field && (dependOn?.isEnabled ?: true) 18 | set(value) { 19 | field = value 20 | screen.postReapplyAttribute() 21 | } 22 | var isHidden: Boolean = false 23 | get() = field || (dependOn?.isHidden ?: false) 24 | set(value) { 25 | field = value 26 | 27 | reapplyAttribute() 28 | 29 | screen.postReapplyAttribute() 30 | } 31 | 32 | fun reapplyAttribute() { 33 | applyAttribute(isEnabled, isHidden) 34 | } 35 | 36 | abstract val view: View 37 | abstract fun saveState(bundle: Bundle) 38 | abstract fun restoreState(bundle: Bundle) 39 | protected open fun applyAttribute(enabled: Boolean, hidden: Boolean) { 40 | view.isEnabled = enabled 41 | view.visibility = if (hidden) View.GONE else View.VISIBLE 42 | } 43 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/common/Category.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.common 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.widget.TextView 7 | import com.github.kr328.clash.design.R 8 | 9 | class Category(screen: CommonUiScreen) : Base(screen) { 10 | override val view: View = 11 | LayoutInflater.from(context).inflate(R.layout.view_category, screen.layout, false) 12 | 13 | private val vText: TextView = view.findViewById(R.id.text) 14 | private val vTopSeparator: View = view.findViewById(R.id.topSeparator) 15 | private val vBottomSeparator: View = view.findViewById(R.id.bottomSeparator) 16 | 17 | var text: CharSequence 18 | get() = vText.text 19 | set(value) { 20 | vText.text = value 21 | } 22 | 23 | var showTopSeparator: Boolean 24 | get() = vTopSeparator.visibility == View.VISIBLE 25 | set(value) { 26 | vTopSeparator.visibility = 27 | if (value) View.VISIBLE else View.GONE 28 | } 29 | var showBottomSeparator: Boolean 30 | get() = vBottomSeparator.visibility == View.VISIBLE 31 | set(value) { 32 | vBottomSeparator.visibility = 33 | if (value) View.VISIBLE else View.GONE 34 | } 35 | 36 | override fun saveState(bundle: Bundle) {} 37 | override fun restoreState(bundle: Bundle) {} 38 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/common/CommonUiScreen.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.common 2 | 3 | import android.os.Bundle 4 | import android.os.Handler 5 | import com.github.kr328.clash.design.view.CommonUiLayout 6 | 7 | class CommonUiScreen(val layout: CommonUiLayout) { 8 | private val handler = Handler() 9 | val elements = mutableListOf() 10 | 11 | fun clear() { 12 | elements.clear() 13 | layout.removeAllViews() 14 | } 15 | 16 | inline fun getElement(id: String): T? { 17 | return elements.singleOrNull { it.id == id } as T? 18 | } 19 | 20 | inline fun requireElement(id: String): T { 21 | return requireNotNull(getElement(id)) 22 | } 23 | 24 | fun addElement(element: Base) { 25 | layout.addView(element.view) 26 | elements.add(element) 27 | } 28 | 29 | fun postReapplyAttribute() { 30 | handler.post { 31 | elements.forEach { 32 | it.reapplyAttribute() 33 | } 34 | } 35 | } 36 | 37 | fun saveState(bundle: Bundle) { 38 | elements.forEach { 39 | it.saveState(bundle) 40 | } 41 | } 42 | 43 | fun restoreState(bundle: Bundle?) { 44 | if (bundle == null) 45 | return 46 | 47 | elements.forEach { 48 | it.restoreState(bundle) 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/common/Option.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.common 2 | 3 | import android.graphics.drawable.Drawable 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.widget.TextView 8 | import com.github.kr328.clash.design.R 9 | 10 | class Option(screen: CommonUiScreen) : Base(screen) { 11 | override val view: View = 12 | LayoutInflater.from(context).inflate(R.layout.view_setting_option, screen.layout, false) 13 | 14 | private val vIcon: View = view.findViewById(android.R.id.icon) 15 | private val vTitle: TextView = view.findViewById(android.R.id.title) 16 | private val vSummary: TextView = view.findViewById(android.R.id.summary) 17 | private val vPadding: View = view.findViewById(R.id.heightPadding) 18 | 19 | private var click: () -> Unit = {} 20 | 21 | var icon: Drawable? 22 | get() = vIcon.background 23 | set(value) { 24 | vIcon.background = value 25 | } 26 | var title: CharSequence 27 | get() = vTitle.text 28 | set(value) { 29 | vTitle.text = value 30 | } 31 | var summary: CharSequence 32 | get() = vSummary.text 33 | set(value) { 34 | vSummary.text = value 35 | if (value.isEmpty()) 36 | vSummary.visibility = View.GONE 37 | else 38 | vSummary.visibility = View.VISIBLE 39 | } 40 | var textColor: Int 41 | get() = vTitle.textColors.defaultColor 42 | set(value) { 43 | vTitle.setTextColor(value) 44 | vSummary.setTextColor(value) 45 | } 46 | var paddingHeight: Boolean 47 | get() = vPadding.visibility != View.GONE 48 | set(value) { 49 | if (value) 50 | vPadding.visibility = View.INVISIBLE 51 | else 52 | vPadding.visibility = View.GONE 53 | } 54 | 55 | init { 56 | view.setOnClickListener { 57 | click() 58 | } 59 | } 60 | 61 | fun onClick(block: () -> Unit) { 62 | this.click = block 63 | } 64 | 65 | override fun saveState(bundle: Bundle) {} 66 | override fun restoreState(bundle: Bundle) {} 67 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/common/Tips.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.common 2 | 3 | import android.graphics.drawable.Drawable 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.widget.TextView 8 | import com.github.kr328.clash.design.R 9 | 10 | class Tips(screen: CommonUiScreen) : Base(screen) { 11 | override val view: View = LayoutInflater.from(context) 12 | .inflate(R.layout.view_setting_tip, screen.layout, false) 13 | 14 | private val vIcon: View = view.findViewById(android.R.id.icon) 15 | private val vTitle: TextView = view.findViewById(android.R.id.title) 16 | 17 | var icon: Drawable? 18 | get() = vIcon.background 19 | set(value) { 20 | vIcon.background = value 21 | } 22 | 23 | var title: CharSequence 24 | get() = vTitle.text 25 | set(value) { 26 | vTitle.text = value 27 | } 28 | 29 | override fun saveState(bundle: Bundle) {} 30 | override fun restoreState(bundle: Bundle) {} 31 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/view/ColorfulTextCard.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.view 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.Drawable 5 | import android.util.AttributeSet 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.widget.TextView 9 | import com.github.kr328.clash.design.R 10 | import com.google.android.material.card.MaterialCardView 11 | 12 | class ColorfulTextCard @JvmOverloads constructor( 13 | context: Context, 14 | attributeSet: AttributeSet? = null, 15 | defStyleAttr: Int = 0 16 | ) : MaterialCardView(context, attributeSet, defStyleAttr) { 17 | private val iconView: View 18 | private val titleView: TextView 19 | private val summaryView: TextView 20 | 21 | var title: CharSequence 22 | get() = titleView.text 23 | set(value) { 24 | titleView.text = value 25 | } 26 | 27 | var summary: CharSequence 28 | get() = summaryView.text 29 | set(value) { 30 | summaryView.text = value 31 | } 32 | 33 | var icon: Drawable? 34 | get() = iconView.background 35 | set(value) { 36 | iconView.background = value 37 | } 38 | 39 | init { 40 | LayoutInflater.from(context).inflate(R.layout.view_colorful_text_card, this, true).apply { 41 | iconView = findViewById(android.R.id.icon) 42 | titleView = findViewById(android.R.id.title) 43 | summaryView = findViewById(android.R.id.summary) 44 | } 45 | 46 | // Custom attrs 47 | context.theme.obtainStyledAttributes(attributeSet, R.styleable.ColorfulTextCard, 0, 0) 48 | .apply { 49 | try { 50 | iconView.background = getDrawable(R.styleable.ColorfulTextCard_icon) 51 | titleView.text = getString(R.styleable.ColorfulTextCard_title) 52 | summaryView.text = getString(R.styleable.ColorfulTextCard_summary) 53 | } finally { 54 | recycle() 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/view/CommonUiLayout.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.LinearLayout 6 | import com.github.kr328.clash.design.common.CommonUiBuilder 7 | import com.github.kr328.clash.design.common.CommonUiScreen 8 | 9 | class CommonUiLayout @JvmOverloads constructor( 10 | context: Context, 11 | attributeSet: AttributeSet? = null, 12 | defStyleAttr: Int = 0 13 | ) : LinearLayout(context, attributeSet, defStyleAttr) { 14 | val screen: CommonUiScreen = CommonUiScreen(this) 15 | 16 | init { 17 | orientation = VERTICAL 18 | } 19 | 20 | fun build(builder: CommonUiBuilder.() -> Unit) { 21 | screen.clear() 22 | 23 | CommonUiBuilder(screen).apply(builder) 24 | } 25 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/view/TextCard.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.view 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.Drawable 5 | import android.util.AttributeSet 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.widget.TextView 9 | import com.github.kr328.clash.design.R 10 | import com.google.android.material.card.MaterialCardView 11 | 12 | class TextCard @JvmOverloads constructor( 13 | context: Context, 14 | attributeSet: AttributeSet? = null, 15 | defStyleAttr: Int = 0 16 | ) : MaterialCardView(context, attributeSet, defStyleAttr) { 17 | private val iconView: View 18 | private val titleView: TextView 19 | private val summaryView: TextView 20 | 21 | var title: CharSequence 22 | get() = titleView.text 23 | set(value) { 24 | titleView.text = value 25 | } 26 | 27 | var summary: CharSequence 28 | get() = summaryView.text 29 | set(value) { 30 | summaryView.text = value 31 | } 32 | 33 | var icon: Drawable? 34 | get() = iconView.background 35 | set(value) { 36 | iconView.background = value 37 | } 38 | 39 | init { 40 | LayoutInflater.from(context).inflate(R.layout.view_text_card, this, true).apply { 41 | iconView = findViewById(android.R.id.icon) 42 | titleView = findViewById(android.R.id.title) 43 | summaryView = findViewById(android.R.id.summary) 44 | } 45 | 46 | // Custom attrs 47 | context.theme.obtainStyledAttributes(attributeSet, R.styleable.TextCard, 0, 0).apply { 48 | try { 49 | iconView.background = getDrawable(R.styleable.TextCard_icon) 50 | titleView.text = getString(R.styleable.TextCard_title) 51 | summaryView.text = getString(R.styleable.TextCard_summary) 52 | } finally { 53 | recycle() 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_edit.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /design/src/main/res/layout/dialog_input_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 14 | -------------------------------------------------------------------------------- /design/src/main/res/layout/view_category.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 12 | 21 | 27 | -------------------------------------------------------------------------------- /design/src/main/res/layout/view_colorful_text_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 25 | 26 | 35 | -------------------------------------------------------------------------------- /design/src/main/res/layout/view_setting_option.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 25 | 33 | 34 | 40 | 41 | 46 | 47 | 52 | 53 | -------------------------------------------------------------------------------- /design/src/main/res/layout/view_setting_text_input.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 25 | 26 | 34 | 35 | 44 | 45 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /design/src/main/res/layout/view_setting_tip.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 14 | 24 | -------------------------------------------------------------------------------- /design/src/main/res/layout/view_text_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 24 | 25 | 33 | -------------------------------------------------------------------------------- /design/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /design/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | OK 3 | Cancel 4 | 5 | -------------------------------------------------------------------------------- /design/src/main/res/values/style.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | 23 | kapt.incremental.apt=false 24 | 25 | org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goomadao/ClashForAndroid/d0aeb45beb90ede099fcf37d0be94eeb0eaaab96/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Mar 03 00:37:05 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /service/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /service/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goomadao/ClashForAndroid/d0aeb45beb90ede099fcf37d0be94eeb0eaaab96/service/consumer-rules.pro -------------------------------------------------------------------------------- /service/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /service/src/main/aidl/com/github/kr328/clash/core/event/Event.aidl: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.event; 2 | 3 | parcelable LogEvent; -------------------------------------------------------------------------------- /service/src/main/aidl/com/github/kr328/clash/core/model/Packet.aidl: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.model; 2 | 3 | parcelable ProxyGroupList; 4 | parcelable General; -------------------------------------------------------------------------------- /service/src/main/aidl/com/github/kr328/clash/service/IClashManager.aidl: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service; 2 | 3 | import com.github.kr328.clash.service.transact.IStreamCallback; 4 | import com.github.kr328.clash.core.model.Packet; 5 | 6 | interface IClashManager { 7 | // Control 8 | boolean setSelectProxy(String proxy, String selected); 9 | void startHealthCheck(String group, IStreamCallback callback); 10 | void setProxyMode(String mode); 11 | 12 | // Query 13 | ProxyGroupList queryAllProxies(); 14 | General queryGeneral(); 15 | long queryBandwidth(); 16 | 17 | // Events 18 | void registerLogListener(String key, IStreamCallback callback); 19 | void unregisterLogListener(String key); 20 | } 21 | -------------------------------------------------------------------------------- /service/src/main/aidl/com/github/kr328/clash/service/IProfileService.aidl: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service; 2 | 3 | import com.github.kr328.clash.service.transact.IStreamCallback; 4 | import com.github.kr328.clash.service.model.Profile; 5 | 6 | interface IProfileService { 7 | long acquireUnused(String type, String source); 8 | long acquireCloned(long id); 9 | String acquireTempUri(long id); 10 | void release(long id); 11 | void update(long id, in Profile metadata); 12 | void commit(long id, in IStreamCallback callback); 13 | void delete(long id); 14 | void clear(long id); 15 | 16 | Profile queryById(long id); 17 | Profile[] queryAll(); 18 | Profile queryActive(); 19 | 20 | void setActive(long id); 21 | } 22 | -------------------------------------------------------------------------------- /service/src/main/aidl/com/github/kr328/clash/service/model/Profile.aidl: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.model; 2 | 3 | parcelable Profile; -------------------------------------------------------------------------------- /service/src/main/aidl/com/github/kr328/clash/service/transact/IStreamCallback.aidl: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.transact; 2 | 3 | import com.github.kr328.clash.service.transact.ParcelableContainer; 4 | 5 | interface IStreamCallback { 6 | void send(in ParcelableContainer data); 7 | void complete(); 8 | void completeExceptionally(String reason); 9 | } 10 | -------------------------------------------------------------------------------- /service/src/main/aidl/com/github/kr328/clash/service/transact/ParcelableContainer.aidl: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.transact; 2 | 3 | parcelable ParcelableContainer; -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/BaseService.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service 2 | 3 | import android.app.Service 4 | import android.content.Context 5 | import com.github.kr328.clash.common.utils.createLanguageConfigurationContext 6 | import com.github.kr328.clash.service.settings.ServiceSettings 7 | import kotlinx.coroutines.CoroutineScope 8 | import kotlinx.coroutines.MainScope 9 | import kotlinx.coroutines.cancel 10 | 11 | abstract class BaseService : Service(), CoroutineScope by MainScope() { 12 | lateinit var settings: ServiceSettings 13 | 14 | override fun attachBaseContext(base: Context?) { 15 | settings = ServiceSettings(base ?: return super.attachBaseContext(base)) 16 | 17 | val language = settings.get(ServiceSettings.LANGUAGE) 18 | 19 | super.attachBaseContext(base.createLanguageConfigurationContext(language)) 20 | } 21 | 22 | override fun onDestroy() { 23 | super.onDestroy() 24 | 25 | cancel() 26 | } 27 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/ClashManagerService.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service 2 | 3 | import android.content.Intent 4 | import android.os.IBinder 5 | 6 | class ClashManagerService : BaseService() { 7 | override fun onBind(intent: Intent?): IBinder? { 8 | return ClashManager(this) 9 | } 10 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service 2 | 3 | object Constants { 4 | const val SERVICE_SETTING_FILE_NAME = "service" 5 | const val CLASH_DIR = "clash" 6 | const val PROFILES_DIR = "profiles" 7 | 8 | const val PROFILE_PROVIDER_SUFFIX = ".profiles" 9 | const val STATUS_PROVIDER_SUFFIX = ".status" 10 | const val SETTING_PROVIDER_SUFFIX = ".settings" 11 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/ProfileProvider.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | import androidx.core.content.FileProvider 6 | import java.io.File 7 | 8 | class ProfileProvider : FileProvider() { 9 | companion object { 10 | fun resolveUri(context: Context, file: File): Uri { 11 | return getUriForFile( 12 | context, 13 | context.packageName + Constants.PROFILE_PROVIDER_SUFFIX, 14 | file 15 | ) 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/RestartReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.github.kr328.clash.common.utils.intent 7 | import com.github.kr328.clash.common.utils.startForegroundServiceCompat 8 | import com.github.kr328.clash.service.util.startClashService 9 | 10 | class RestartReceiver : BroadcastReceiver() { 11 | override fun onReceive(context: Context?, intent: Intent?) { 12 | if (context == null) 13 | return 14 | 15 | when (intent?.action) { 16 | Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_MY_PACKAGE_REPLACED -> { 17 | } 18 | else -> return 19 | } 20 | 21 | context.startForegroundServiceCompat(ProfileBackgroundService::class.intent) 22 | 23 | if (ServiceStatusProvider.shouldStartClashOnBoot) 24 | context.startClashService() 25 | } 26 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/ServiceSettingsProvider.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import rikka.preference.MultiProcessPreference 6 | import rikka.preference.PreferenceProvider 7 | 8 | class ServiceSettingsProvider : PreferenceProvider() { 9 | override fun onCreatePreference(context: Context?): SharedPreferences { 10 | return context!!.getSharedPreferences( 11 | Constants.SERVICE_SETTING_FILE_NAME, 12 | Context.MODE_PRIVATE 13 | ) 14 | } 15 | 16 | companion object { 17 | fun createSharedPreferencesFromContext(context: Context): SharedPreferences { 18 | return when (context) { 19 | is BaseService, is TunService -> 20 | context.getSharedPreferences( 21 | Constants.SERVICE_SETTING_FILE_NAME, 22 | Context.MODE_PRIVATE 23 | ) 24 | else -> 25 | MultiProcessPreference( 26 | context, 27 | context.packageName + Constants.SETTING_PROVIDER_SUFFIX 28 | ) 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/clash/module/CloseModule.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.clash.module 2 | 3 | import android.content.Intent 4 | import com.github.kr328.clash.common.ids.Intents 5 | 6 | class CloseModule : Module() { 7 | override val receiveBroadcasts: Set 8 | get() = setOf(Intents.INTENT_ACTION_CLASH_REQUEST_STOP) 9 | 10 | private var callback: () -> Unit = {} 11 | 12 | fun onClosed(cb: () -> Unit) { 13 | callback = cb 14 | } 15 | 16 | override suspend fun onBroadcastReceived(intent: Intent) { 17 | when (intent.action) { 18 | Intents.INTENT_ACTION_CLASH_REQUEST_STOP -> 19 | callback() 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/clash/module/DnsInjectModule.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.clash.module 2 | 3 | import com.github.kr328.clash.core.Clash 4 | 5 | class DnsInjectModule : Module() { 6 | var dnsOverride: Boolean = false 7 | set(value) { 8 | Clash.setDnsOverrideEnabled(value) 9 | field = value 10 | } 11 | var appendDns: List = emptyList() 12 | set(value) { 13 | Clash.appendDns(value) 14 | field = value 15 | } 16 | 17 | override suspend fun onStart() { 18 | Clash.setDnsOverrideEnabled(dnsOverride) 19 | Clash.appendDns(appendDns) 20 | } 21 | 22 | override suspend fun onStop() { 23 | Clash.setDnsOverrideEnabled(false) 24 | Clash.appendDns(emptyList()) 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/clash/module/Module.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.clash.module 2 | 3 | import android.content.Intent 4 | 5 | abstract class Module { 6 | open suspend fun onCreate() {} 7 | open suspend fun onStart() {} 8 | open suspend fun onStop() {} 9 | open suspend fun onTick() {} 10 | open suspend fun onBroadcastReceived(intent: Intent) {} 11 | 12 | open val receiveBroadcasts: Set = emptySet() 13 | var enableTicker: Boolean = false 14 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Database.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import androidx.room.RoomDatabase 6 | import com.github.kr328.clash.common.Global 7 | import com.github.kr328.clash.service.data.migrations.MIGRATIONS 8 | import androidx.room.Database as DatabaseMetadata 9 | 10 | @DatabaseMetadata( 11 | version = 4, 12 | exportSchema = false, 13 | entities = [ProfileEntity::class, SelectedProxyEntity::class] 14 | ) 15 | abstract class Database : RoomDatabase() { 16 | abstract fun openProfileDao(): ProfileDao 17 | abstract fun openSelectedProxyDao(): SelectedProxyDao 18 | 19 | companion object { 20 | val database = open(Global.application) 21 | 22 | private fun open(context: Context): Database { 23 | return Room.databaseBuilder( 24 | context.applicationContext, 25 | Database::class.java, 26 | "clash-config" 27 | ).addMigrations(*MIGRATIONS).build() 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/ProfileDao.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.* 4 | 5 | @Dao 6 | interface ProfileDao { 7 | @Query("UPDATE profiles SET active = CASE WHEN id = :id THEN 1 ELSE 0 END") 8 | suspend fun setActive(id: Long) 9 | 10 | @Query("SELECT * FROM profiles WHERE active = 1 LIMIT 1") 11 | suspend fun queryActive(): ProfileEntity? 12 | 13 | @Query("SELECT * FROM profiles") 14 | suspend fun queryAll(): List 15 | 16 | @Query("SELECT * FROM profiles WHERE id = :id") 17 | suspend fun queryById(id: Long): ProfileEntity? 18 | 19 | @Query("SELECT id FROM profiles") 20 | suspend fun queryAllIds(): List 21 | 22 | @Insert(onConflict = OnConflictStrategy.ABORT) 23 | suspend fun insert(profile: ProfileEntity): Long 24 | 25 | @Update(onConflict = OnConflictStrategy.ABORT) 26 | suspend fun update(profile: ProfileEntity) 27 | 28 | @Query("DELETE FROM profiles WHERE id = :id") 29 | suspend fun remove(id: Long) 30 | 31 | companion object : ProfileDao by Database.database.openProfileDao() 32 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/ProfileEntity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.annotation.Keep 4 | import androidx.room.ColumnInfo 5 | import androidx.room.Entity 6 | 7 | @Entity(tableName = "profiles", primaryKeys = ["id"]) 8 | @Keep 9 | data class ProfileEntity( 10 | @ColumnInfo(name = "name") val name: String, 11 | @ColumnInfo(name = "type") val type: Int, 12 | @ColumnInfo(name = "uri") val uri: String, 13 | @ColumnInfo(name = "source") val source: String?, 14 | @ColumnInfo(name = "active") val active: Boolean, 15 | @ColumnInfo(name = "interval") val interval: Long, 16 | @ColumnInfo(name = "id") val id: Long 17 | ) { 18 | companion object { 19 | const val TYPE_FILE = 1 20 | const val TYPE_URL = 2 21 | const val TYPE_EXTERNAL = 3 22 | const val TYPE_UNKNOWN = -1 23 | } 24 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/SelectedProxyDao.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | 8 | @Dao 9 | interface SelectedProxyDao { 10 | @Insert(onConflict = OnConflictStrategy.REPLACE) 11 | suspend fun setSelectedForProfile(item: SelectedProxyEntity) 12 | 13 | @Query("SELECT * FROM selected_proxies WHERE profile_id = :id") 14 | suspend fun querySelectedForProfile(id: Long): List 15 | 16 | @Query("DELETE FROM selected_proxies WHERE profile_id = :id AND proxy in (:selected)") 17 | suspend fun removeSelectedForProfile(id: Long, selected: List) 18 | 19 | companion object : SelectedProxyDao by Database.database.openSelectedProxyDao() 20 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/SelectedProxyEntity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | 7 | @Entity( 8 | tableName = "selected_proxies", 9 | foreignKeys = [ForeignKey( 10 | entity = ProfileEntity::class, 11 | childColumns = ["profile_id"], 12 | parentColumns = ["id"], 13 | onDelete = ForeignKey.CASCADE, 14 | onUpdate = ForeignKey.CASCADE 15 | )], 16 | primaryKeys = ["profile_id", "proxy"] 17 | ) 18 | data class SelectedProxyEntity( 19 | @ColumnInfo(name = "profile_id") val profileId: Long, 20 | @ColumnInfo(name = "proxy") val proxy: String, 21 | @ColumnInfo(name = "selected") val selected: String 22 | ) -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/migrations/Migrations.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data.migrations 2 | 3 | import androidx.room.migration.Migration 4 | 5 | val MIGRATIONS: Array = arrayOf(Migration12, Migration23, Migration34) -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/files/ProviderResolver.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.files 2 | 3 | import android.content.Context 4 | import android.os.ParcelFileDescriptor 5 | import com.github.kr328.clash.service.util.resolveBaseDir 6 | import java.io.FileNotFoundException 7 | import java.net.URLDecoder 8 | 9 | class ProviderResolver(private val context: Context) { 10 | fun resolve(id: Long, fileName: String): VirtualFile { 11 | val file = context.resolveBaseDir(id).resolve(fileName) 12 | if (!file.exists()) 13 | throw FileNotFoundException() 14 | 15 | return object : VirtualFile { 16 | override fun name(): String { 17 | return URLDecoder.decode(file.name, "utf-8") 18 | } 19 | 20 | override fun lastModified(): Long { 21 | return file.lastModified() 22 | } 23 | 24 | override fun size(): Long { 25 | return file.length() 26 | } 27 | 28 | override fun mimeType(): String { 29 | return "text/plain" 30 | } 31 | 32 | override fun listFiles(): List { 33 | return emptyList() 34 | } 35 | 36 | override fun openFile(mode: Int): ParcelFileDescriptor { 37 | return ParcelFileDescriptor.open(file, mode) 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/files/VirtualFile.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.files 2 | 3 | import android.os.ParcelFileDescriptor 4 | 5 | interface VirtualFile { 6 | fun name(): String 7 | fun lastModified(): Long 8 | fun size(): Long 9 | fun mimeType(): String 10 | fun listFiles(): List 11 | fun openFile(mode: Int): ParcelFileDescriptor 12 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/model/Converters.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.model 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | import com.github.kr328.clash.service.data.ProfileEntity 6 | import com.github.kr328.clash.service.util.resolveProfileFile 7 | 8 | fun ProfileEntity.asProfile(context: Context): Profile { 9 | val type = when (this.type) { 10 | ProfileEntity.TYPE_FILE -> Profile.Type.FILE 11 | ProfileEntity.TYPE_URL -> Profile.Type.URL 12 | ProfileEntity.TYPE_EXTERNAL -> Profile.Type.EXTERNAL 13 | else -> Profile.Type.UNKNOWN 14 | } 15 | val lastModified = context.resolveProfileFile(id).lastModified() 16 | 17 | return Profile( 18 | id = id, 19 | name = name, 20 | type = type, 21 | uri = Uri.parse(uri), 22 | source = source, 23 | active = active, 24 | interval = interval, 25 | lastModified = lastModified 26 | ) 27 | } 28 | 29 | fun Profile.asEntity(): ProfileEntity { 30 | val type = when (this.type) { 31 | Profile.Type.FILE -> ProfileEntity.TYPE_FILE 32 | Profile.Type.URL -> ProfileEntity.TYPE_URL 33 | Profile.Type.EXTERNAL -> ProfileEntity.TYPE_EXTERNAL 34 | Profile.Type.UNKNOWN -> ProfileEntity.TYPE_UNKNOWN 35 | } 36 | 37 | return ProfileEntity( 38 | name = name, 39 | type = type, 40 | uri = uri.toString(), 41 | source = source, 42 | active = active, 43 | interval = interval, 44 | id = id 45 | ) 46 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/model/Profile.kt: -------------------------------------------------------------------------------- 1 | @file:UseSerializers(UriSerializer::class) 2 | 3 | package com.github.kr328.clash.service.model 4 | 5 | import android.net.Uri 6 | import android.os.Parcel 7 | import android.os.Parcelable 8 | import com.github.kr328.clash.common.serialization.Parcels 9 | import kotlinx.serialization.Serializable 10 | import kotlinx.serialization.UseSerializers 11 | 12 | @Serializable 13 | data class Profile( 14 | val id: Long, 15 | val name: String, 16 | val type: Type, 17 | val uri: Uri, 18 | val source: String?, 19 | val active: Boolean, 20 | val interval: Long, 21 | val lastModified: Long 22 | ) : Parcelable { 23 | enum class Type { 24 | FILE, URL, EXTERNAL, UNKNOWN 25 | } 26 | 27 | override fun writeToParcel(parcel: Parcel, flags: Int) { 28 | Parcels.dump(serializer(), this, parcel) 29 | } 30 | 31 | override fun describeContents(): Int { 32 | return 0 33 | } 34 | 35 | companion object CREATOR : Parcelable.Creator { 36 | override fun createFromParcel(parcel: Parcel): Profile { 37 | return Parcels.load(serializer(), parcel) 38 | } 39 | 40 | override fun newArray(size: Int): Array { 41 | return arrayOfNulls(size) 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/model/Serializers.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.model 2 | 3 | import android.net.Uri 4 | import kotlinx.serialization.* 5 | 6 | class UriSerializer : KSerializer { 7 | override val descriptor: SerialDescriptor 8 | get() = PrimitiveDescriptor("Uri", PrimitiveKind.STRING) 9 | 10 | override fun deserialize(decoder: Decoder): Uri { 11 | return Uri.parse(decoder.decodeString()) 12 | } 13 | 14 | override fun serialize(encoder: Encoder, value: Uri) { 15 | encoder.encodeString(value.toString()) 16 | } 17 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/settings/ServiceSettings.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.settings 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import com.github.kr328.clash.common.settings.BaseSettings 6 | import com.github.kr328.clash.service.ServiceSettingsProvider 7 | 8 | class ServiceSettings(preference: SharedPreferences) : 9 | BaseSettings(preference) { 10 | constructor(context: Context) : this( 11 | ServiceSettingsProvider.createSharedPreferencesFromContext(context) 12 | ) 13 | 14 | companion object { 15 | const val ACCESS_CONTROL_MODE_ALL = "access_control_mode_all" 16 | const val ACCESS_CONTROL_MODE_BLACKLIST = "access_control_mode_blacklist" 17 | const val ACCESS_CONTROL_MODE_WHITELIST = "access_control_mode_whitelist" 18 | 19 | val ENABLE_VPN = 20 | BooleanEntry("enable_vpn", true) 21 | val LANGUAGE = 22 | StringEntry("language", "") 23 | val BYPASS_PRIVATE_NETWORK = 24 | BooleanEntry("bypass_private_network", true) 25 | val ACCESS_CONTROL_MODE = 26 | StringEntry("access_control_mode", ACCESS_CONTROL_MODE_ALL) 27 | val ACCESS_CONTROL_PACKAGES = 28 | StringSetEntry("access_control_packages", emptySet()) 29 | val DNS_HIJACKING = 30 | BooleanEntry("dns_hijacking", true) 31 | val NOTIFICATION_REFRESH = 32 | BooleanEntry("notification_refresh", true) 33 | val AUTO_ADD_SYSTEM_DNS = 34 | BooleanEntry("auto_add_system_dns", true) 35 | val OVERRIDE_DNS = 36 | BooleanEntry("override_dns", true) 37 | } 38 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/transact/ParcelableContainer.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.transact 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | 6 | data class ParcelableContainer(val data: Parcelable?) : Parcelable { 7 | constructor(parcel: Parcel) : 8 | this(parcel.readParcelable(ParcelableContainer::class.java.classLoader)) 9 | 10 | override fun writeToParcel(parcel: Parcel, flags: Int) { 11 | parcel.writeParcelable(data, 0) 12 | } 13 | 14 | override fun describeContents(): Int { 15 | return 0 16 | } 17 | 18 | companion object CREATOR : Parcelable.Creator { 19 | override fun createFromParcel(parcel: Parcel): ParcelableContainer { 20 | return ParcelableContainer(parcel) 21 | } 22 | 23 | override fun newArray(size: Int): Array { 24 | return arrayOfNulls(size) 25 | } 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/BroadcastUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import com.github.kr328.clash.common.Permissions 6 | import com.github.kr328.clash.common.ids.Intents 7 | 8 | fun Context.sendBroadcastSelf(intent: Intent) { 9 | this.sendBroadcast( 10 | intent.setPackage(this.packageName), 11 | Permissions.PERMISSION_RECEIVE_BROADCASTS 12 | ) 13 | } 14 | 15 | fun Context.broadcastProfileChanged() { 16 | val intent = Intent(Intents.INTENT_ACTION_PROFILE_CHANGED) 17 | 18 | this.sendBroadcastSelf(intent) 19 | } 20 | 21 | fun Context.broadcastProfileLoaded() { 22 | val intent = Intent(Intents.INTENT_ACTION_PROFILE_LOADED) 23 | 24 | this.sendBroadcastSelf(intent) 25 | } 26 | 27 | fun Context.broadcastNetworkChanged() { 28 | this.sendBroadcastSelf(Intent(Intents.INTENT_ACTION_NETWORK_CHANGED)) 29 | } 30 | 31 | fun Context.broadcastClashStarted() { 32 | this.sendBroadcastSelf(Intent(Intents.INTENT_ACTION_CLASH_STARTED)) 33 | } 34 | 35 | fun Context.broadcastClashStopped(reason: String?) { 36 | this.sendBroadcastSelf( 37 | Intent(Intents.INTENT_ACTION_CLASH_STOPPED).putExtra( 38 | Intents.INTENT_EXTRA_CLASH_STOP_REASON, 39 | reason 40 | ) 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/FileUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.service.Constants 5 | import java.io.File 6 | 7 | fun Context.resolveProfileFile(id: Long): File { 8 | return filesDir.resolve(Constants.PROFILES_DIR).resolve("$id.yaml") 9 | } 10 | 11 | fun Context.resolveBaseDir(id: Long): File { 12 | return filesDir.resolve(Constants.CLASH_DIR).resolve(id.toString()) 13 | } 14 | 15 | fun Context.resolveTempProfileFile(id: Long): File { 16 | return cacheDir.resolve(Constants.PROFILES_DIR).resolve("$id.yaml") 17 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/InetAddressUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import java.net.Inet4Address 4 | import java.net.Inet6Address 5 | import java.net.InetAddress 6 | 7 | fun InetAddress.asSocketAddressText(port: Int): String { 8 | return when (this) { 9 | is Inet6Address -> 10 | "[${numericToTextFormat(this.address)}]:$port" 11 | is Inet4Address -> 12 | "${this.hostAddress}:$port" 13 | else -> throw IllegalArgumentException("Unsupported Inet type ${this.javaClass}") 14 | } 15 | } 16 | 17 | private const val INT16SZ = 2 18 | private const val INADDRSZ = 16 19 | private fun numericToTextFormat(src: ByteArray): String { 20 | val sb = StringBuilder(39) 21 | for (i in 0 until INADDRSZ / INT16SZ) { 22 | sb.append( 23 | Integer.toHexString( 24 | src[i shl 1].toInt() shl 8 and 0xff00 25 | or (src[(i shl 1) + 1].toInt() and 0xff) 26 | ) 27 | ) 28 | if (i < INADDRSZ / INT16SZ - 1) { 29 | sb.append(":") 30 | } 31 | } 32 | return sb.toString() 33 | } 34 | 35 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Net.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import java.net.InetAddress 4 | 5 | data class IPNet(val ip: InetAddress, val prefix: Int) 6 | 7 | fun parseCIDR(cidr: String): IPNet { 8 | val s = cidr.split("/", limit = 2) 9 | 10 | if (s.size != 2) 11 | throw IllegalArgumentException("Invalid address") 12 | 13 | val address = InetAddress.getByName(s[0]) 14 | val prefix = s[1].toInt() 15 | 16 | return IPNet(address, prefix) 17 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/ServiceUtils.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.VpnService 6 | import com.github.kr328.clash.common.ids.Intents 7 | import com.github.kr328.clash.common.utils.intent 8 | import com.github.kr328.clash.common.utils.startForegroundServiceCompat 9 | import com.github.kr328.clash.service.ClashService 10 | import com.github.kr328.clash.service.TunService 11 | import com.github.kr328.clash.service.settings.ServiceSettings 12 | 13 | fun Context.startClashService(): Intent? { 14 | val startTun = ServiceSettings(this).get(ServiceSettings.ENABLE_VPN) 15 | 16 | if (startTun) { 17 | val vpnRequest = VpnService.prepare(this) 18 | if (vpnRequest != null) 19 | return vpnRequest 20 | 21 | startForegroundServiceCompat(TunService::class.intent) 22 | } else { 23 | startForegroundServiceCompat(ClashService::class.intent) 24 | } 25 | 26 | return null 27 | } 28 | 29 | fun Context.stopClashService() { 30 | sendBroadcastSelf(Intent(Intents.INTENT_ACTION_CLASH_REQUEST_STOP)) 31 | } -------------------------------------------------------------------------------- /service/src/main/res/drawable/ic_icon.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /service/src/main/res/drawable/ic_notification.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /service/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clash 状态 4 | 配置服务状态 5 | 配置处理状态 6 | 正在运行 7 | %d 个项目在队列中 8 | 更新 %s 完成 9 | 更新 %s 失败 10 | 处理结果 11 | 正在处理配置文件 12 | 正在回收资源 13 | 销毁中 14 | Clash for Android 15 | 配置文件和外部引用 16 | 配置文件.yaml 17 | 外部引用文件 18 | 等待中 19 | 载入中 20 | -------------------------------------------------------------------------------- /service/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1E4376 4 | -------------------------------------------------------------------------------- /service/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | "%1$s↑\t%2$s↓" 4 | 5 | Clash Status 6 | Profile Service Status 7 | Processing Profiles 8 | %d items in queue 9 | Waiting 10 | Process Result 11 | Update %s Completed 12 | Update %s Failure 13 | Profile Processing Status 14 | Running 15 | Loading 16 | Destroying 17 | Recycling resources 18 | Clash for Android 19 | Profiles and Providers 20 | Profile.yaml 21 | Provider Files 22 | 23 | -------------------------------------------------------------------------------- /service/src/main/res/xml/profile_provider.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | include(":app") 2 | include(":core") 3 | include(":service") 4 | include(":design") 5 | include(":common") 6 | 7 | rootProject.name = "ClashForAndroid" 8 | --------------------------------------------------------------------------------