├── .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 |
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 |
--------------------------------------------------------------------------------
/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 |
14 |
15 |
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 |
--------------------------------------------------------------------------------