├── .gitattributes ├── .github └── workflows │ ├── Build Release2.yml │ ├── Sync_Fork.yml │ ├── build-debug.yaml │ ├── build-pre-release.yaml1 │ ├── build-release.yaml │ └── update-dependencies.yaml ├── .gitignore ├── .gitmodules ├── .idea └── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── PRIVACY_POLICY.md ├── README.md ├── app ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── ic_launcher-web.png │ ├── java │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ ├── AccessControlActivity.kt │ │ ├── ApkBrokenActivity.kt │ │ ├── AppCrashedActivity.kt │ │ ├── AppSettingsActivity.kt │ │ ├── BaseActivity.kt │ │ ├── DialerReceiver.kt │ │ ├── ExternalControlActivity.kt │ │ ├── FilesActivity.kt │ │ ├── HelpActivity.kt │ │ ├── LogcatActivity.kt │ │ ├── LogcatService.kt │ │ ├── LogsActivity.kt │ │ ├── MainActivity.kt │ │ ├── MainApplication.kt │ │ ├── MetaFeatureSettingsActivity.kt │ │ ├── NetworkSettingsActivity.kt │ │ ├── NewProfileActivity.kt │ │ ├── OverrideSettingsActivity.kt │ │ ├── ProfilesActivity.kt │ │ ├── PropertiesActivity.kt │ │ ├── ProvidersActivity.kt │ │ ├── ProxyActivity.kt │ │ ├── RestartReceiver.kt │ │ ├── SettingsActivity.kt │ │ ├── TileService.kt │ │ ├── log │ │ ├── LogcatCache.kt │ │ ├── LogcatFilter.kt │ │ ├── LogcatReader.kt │ │ ├── LogcatWriter.kt │ │ └── SystemLogcat.kt │ │ ├── remote │ │ ├── Broadcasts.kt │ │ ├── FilesClient.kt │ │ ├── Remote.kt │ │ ├── Resource.kt │ │ ├── Service.kt │ │ └── StatusClient.kt │ │ ├── store │ │ ├── AppStore.kt │ │ └── TipsStore.kt │ │ └── util │ │ ├── Activity.kt │ │ ├── Application.kt │ │ ├── Clash.kt │ │ ├── Content.kt │ │ ├── Files.kt │ │ ├── Remote.kt │ │ ├── Service.kt │ │ └── Uri.kt │ └── res │ ├── drawable │ └── ic_launcher_foreground.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_banner.png │ ├── 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 │ └── themes.xml │ ├── values │ ├── colors.xml │ ├── ic_banner_background.xml │ ├── ic_launcher_background.xml │ ├── ids.xml │ └── themes.xml │ └── xml │ ├── full_backup_content.xml │ └── network_security_config.xml ├── build.gradle.kts ├── common ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ └── common │ │ ├── Global.kt │ │ ├── compat │ │ ├── App.kt │ │ ├── Context.kt │ │ ├── Html.kt │ │ ├── Intents.kt │ │ ├── Package.kt │ │ ├── Resource.kt │ │ ├── Services.kt │ │ ├── UI.kt │ │ └── View.kt │ │ ├── constants │ │ ├── Authorities.kt │ │ ├── Components.kt │ │ ├── Intents.kt │ │ ├── Metadata.kt │ │ └── Permissions.kt │ │ ├── id │ │ └── UndefinedIds.kt │ │ ├── log │ │ └── Log.kt │ │ ├── store │ │ ├── Providers.kt │ │ ├── Store.kt │ │ └── StoreProvider.kt │ │ └── util │ │ ├── Components.kt │ │ ├── Global.kt │ │ ├── Intent.kt │ │ ├── Parcelable.kt │ │ ├── Patterns.kt │ │ └── Ticker.kt │ └── res │ ├── values-ru │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ └── values │ └── strings.xml ├── core ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── foss │ └── golang │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ └── main │ ├── AndroidManifest.xml │ ├── cpp │ ├── CMakeLists.txt │ ├── bridge_helper.c │ ├── bridge_helper.h │ ├── jni_helper.c │ ├── jni_helper.h │ ├── main.c │ └── version.h.in │ ├── golang │ ├── .idea │ │ └── codeStyles │ │ │ ├── Project.xml │ │ │ └── codeStyleConfig.xml │ ├── go.mod │ ├── go.sum │ └── native │ │ ├── all │ │ └── imports.go │ │ ├── app.go │ │ ├── app │ │ ├── app.go │ │ ├── content.go │ │ ├── dns.go │ │ ├── tun.go │ │ └── ui.go │ │ ├── bridge.c │ │ ├── bridge.h │ │ ├── common │ │ └── path.go │ │ ├── config.go │ │ ├── config │ │ ├── defaults.go │ │ ├── fetch.go │ │ ├── load.go │ │ ├── override.go │ │ ├── process.go │ │ └── provider.go │ │ ├── debug.go │ │ ├── delegate │ │ └── init.go │ │ ├── log.go │ │ ├── main.go │ │ ├── platform │ │ ├── limit.go │ │ └── procfs.go │ │ ├── proxy.go │ │ ├── proxy │ │ └── http.go │ │ ├── trace.c │ │ ├── trace.h │ │ ├── tun.go │ │ ├── tun │ │ └── tun.go │ │ ├── tunnel.go │ │ ├── tunnel │ │ ├── conn.go │ │ ├── connectivity.go │ │ ├── loopback.go │ │ ├── providers.go │ │ ├── proxies.go │ │ ├── state.go │ │ ├── statistic.go │ │ └── suspend.go │ │ └── utils.go │ └── java │ └── com │ └── github │ └── kr328 │ └── clash │ └── core │ ├── Clash.kt │ ├── bridge │ ├── Bridge.kt │ ├── ClashException.kt │ ├── Content.kt │ ├── FetchCallback.kt │ ├── LogcatInterface.kt │ └── TunInterface.kt │ ├── model │ ├── ConfigurationOverride.kt │ ├── FetchStatus.kt │ ├── LogMessage.kt │ ├── Provider.kt │ ├── ProviderList.kt │ ├── Proxy.kt │ ├── ProxyGroup.kt │ ├── ProxySort.kt │ ├── Traffic.kt │ ├── TunnelState.kt │ └── UiConfiguration.kt │ └── util │ ├── Net.kt │ ├── Parcelizer.kt │ ├── Serializers.kt │ └── Traffic.kt ├── design ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ └── design │ │ ├── AccessControlDesign.kt │ │ ├── ApkBrokenDesign.kt │ │ ├── AppCrashedDesign.kt │ │ ├── AppSettingsDesign.kt │ │ ├── Design.kt │ │ ├── FilesDesign.kt │ │ ├── HelpDesign.kt │ │ ├── LogcatDesign.kt │ │ ├── LogsDesign.kt │ │ ├── MainDesign.kt │ │ ├── MetaFeatureSettingsDesign.kt │ │ ├── NetworkSettingsDesign.kt │ │ ├── NewProfileDesign.kt │ │ ├── OverrideSettingsDesign.kt │ │ ├── ProfilesDesign.kt │ │ ├── PropertiesDesign.kt │ │ ├── ProvidersDesign.kt │ │ ├── ProxyDesign.kt │ │ ├── SettingsDesign.kt │ │ ├── adapter │ │ ├── AppAdapter.kt │ │ ├── EditableTextListAdapter.kt │ │ ├── EditableTextMapAdapter.kt │ │ ├── FileAdapter.kt │ │ ├── LogFileAdapter.kt │ │ ├── LogMessageAdapter.kt │ │ ├── PopupListAdapter.kt │ │ ├── ProfileAdapter.kt │ │ ├── ProfileProviderAdapter.kt │ │ ├── ProviderAdapter.kt │ │ ├── ProxyAdapter.kt │ │ └── ProxyPageAdapter.kt │ │ ├── component │ │ ├── AccessControlMenu.kt │ │ ├── ProxyMenu.kt │ │ ├── ProxyPageFactory.kt │ │ ├── ProxyView.kt │ │ ├── ProxyViewConfig.kt │ │ └── ProxyViewState.kt │ │ ├── dialog │ │ ├── Dialogs.kt │ │ ├── Input.kt │ │ └── Progress.kt │ │ ├── model │ │ ├── AppInfo.kt │ │ ├── AppInfoSort.kt │ │ ├── Behavior.kt │ │ ├── DarkMode.kt │ │ ├── File.kt │ │ ├── LogFile.kt │ │ ├── ProfilePageState.kt │ │ ├── ProfileProvider.kt │ │ ├── ProviderState.kt │ │ ├── ProxyPageState.kt │ │ └── ProxyState.kt │ │ ├── preference │ │ ├── Category.kt │ │ ├── Clickable.kt │ │ ├── EditableText.kt │ │ ├── EditableTextList.kt │ │ ├── EditableTextMap.kt │ │ ├── Overlay.kt │ │ ├── Preference.kt │ │ ├── Screen.kt │ │ ├── SelectableList.kt │ │ ├── Switch.kt │ │ ├── Tips.kt │ │ └── Value.kt │ │ ├── store │ │ └── UiStore.kt │ │ ├── ui │ │ ├── DayNight.kt │ │ ├── Insets.kt │ │ ├── ObservableCurrentTime.kt │ │ ├── Surface.kt │ │ └── ToastDuration.kt │ │ ├── util │ │ ├── ActivityBar.kt │ │ ├── App.kt │ │ ├── Binding.kt │ │ ├── Context.kt │ │ ├── Diff.kt │ │ ├── Elevation.kt │ │ ├── I18n.kt │ │ ├── Inserts.kt │ │ ├── Interval.kt │ │ ├── Landscape.kt │ │ ├── ListView.kt │ │ ├── RecyclerView.kt │ │ ├── ScrollView.kt │ │ ├── Theme.kt │ │ ├── Toast.kt │ │ ├── Validator.kt │ │ └── View.kt │ │ └── view │ │ ├── ActionLabel.kt │ │ ├── ActionTextField.kt │ │ ├── ActivityBarLayout.kt │ │ ├── AppRecyclerView.kt │ │ ├── LargeActionCard.kt │ │ ├── LargeActionLabel.kt │ │ ├── ObservableScrollView.kt │ │ └── VerticalScrollableHost.kt │ └── res │ ├── anim │ └── rotate_infinite.xml │ ├── drawable │ ├── bg_b.xml │ ├── bg_bottom_sheet.xml │ ├── ic_baseline_adb.xml │ ├── ic_baseline_add.xml │ ├── ic_baseline_apps.xml │ ├── ic_baseline_arrow_back.xml │ ├── ic_baseline_assignment.xml │ ├── ic_baseline_attach_file.xml │ ├── ic_baseline_brightness_4.xml │ ├── ic_baseline_clear_all.xml │ ├── ic_baseline_close.xml │ ├── ic_baseline_cloud_download.xml │ ├── ic_baseline_content_copy.xml │ ├── ic_baseline_delete.xml │ ├── ic_baseline_dns.xml │ ├── ic_baseline_domain.xml │ ├── ic_baseline_edit.xml │ ├── ic_baseline_extension.xml │ ├── ic_baseline_flash_on.xml │ ├── ic_baseline_get_app.xml │ ├── ic_baseline_help_center.xml │ ├── ic_baseline_hide.xml │ ├── ic_baseline_info.xml │ ├── ic_baseline_meta.xml │ ├── ic_baseline_more_vert.xml │ ├── ic_baseline_publish.xml │ ├── ic_baseline_replay.xml │ ├── ic_baseline_restore.xml │ ├── ic_baseline_save.xml │ ├── ic_baseline_search.xml │ ├── ic_baseline_settings.xml │ ├── ic_baseline_stop.xml │ ├── ic_baseline_swap_vert.xml │ ├── ic_baseline_swap_vertical_circle.xml │ ├── ic_baseline_sync.xml │ ├── ic_baseline_update.xml │ ├── ic_baseline_view_list.xml │ ├── ic_baseline_vpn_lock.xml │ ├── ic_baseline_work.xml │ ├── ic_clash.xml │ ├── ic_outline_article.xml │ ├── ic_outline_check_circle.xml │ ├── ic_outline_delete.xml │ ├── ic_outline_folder.xml │ ├── ic_outline_inbox.xml │ ├── ic_outline_info.xml │ ├── ic_outline_label.xml │ ├── ic_outline_not_interested.xml │ └── ic_outline_update.xml │ ├── layout │ ├── adapter_app.xml │ ├── adapter_editable_text_list.xml │ ├── adapter_editable_text_map.xml │ ├── adapter_file.xml │ ├── adapter_log_message.xml │ ├── adapter_profile.xml │ ├── adapter_profile_provider.xml │ ├── adapter_provider.xml │ ├── adapter_sideload_provider.xml │ ├── common_activity_bar.xml │ ├── common_recycler_list.xml │ ├── component_action_label.xml │ ├── component_action_text_field.xml │ ├── component_large_action_label.xml │ ├── design_about.xml │ ├── design_access_control.xml │ ├── design_app_crashed.xml │ ├── design_files.xml │ ├── design_logcat.xml │ ├── design_logs.xml │ ├── design_main.xml │ ├── design_new_profile.xml │ ├── design_profiles.xml │ ├── design_properties.xml │ ├── design_providers.xml │ ├── design_proxy.xml │ ├── design_settings.xml │ ├── design_settings_common.xml │ ├── design_settings_meta_feature.xml │ ├── design_settings_overide.xml │ ├── dialog_editable_map_text_field.xml │ ├── dialog_fetch_status.xml │ ├── dialog_files_menu.xml │ ├── dialog_preference_list.xml │ ├── dialog_profiles_menu.xml │ ├── dialog_search.xml │ ├── dialog_text_field.xml │ ├── preference_category.xml │ ├── preference_clickable.xml │ ├── preference_switch.xml │ └── preference_tips.xml │ ├── menu │ ├── menu_access_control.xml │ └── menu_proxy.xml │ ├── values-ja-rJP │ └── strings.xml │ ├── values-ko-rKR │ └── strings.xml │ ├── values-ru │ └── strings.xml │ ├── values-v23 │ └── themes.xml │ ├── values-v27 │ └── themes.xml │ ├── values-v29 │ └── themes.xml │ ├── values-vi │ └── strings.xml │ ├── values-zh-rHK │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── ids.xml │ ├── strings.xml │ ├── styles.xml │ └── themes.xml ├── fastlane └── metadata │ └── android │ ├── en-US │ ├── full_description.txt │ └── short_description.txt │ └── zh-CN │ ├── full_description.txt │ └── short_description.txt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hideapi ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── android │ └── app │ └── ActivityThread.java ├── release.keystore ├── renovate.json ├── service ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ └── service │ │ ├── BaseService.kt │ │ ├── ClashManager.kt │ │ ├── ClashService.kt │ │ ├── FilesProvider.kt │ │ ├── PreferenceProvider.kt │ │ ├── ProfileManager.kt │ │ ├── ProfileProcessor.kt │ │ ├── ProfileReceiver.kt │ │ ├── ProfileWorker.kt │ │ ├── RemoteService.kt │ │ ├── StatusProvider.kt │ │ ├── TunService.kt │ │ ├── clash │ │ ├── ClashRuntime.kt │ │ └── module │ │ │ ├── AppListCacheModule.kt │ │ │ ├── CloseModule.kt │ │ │ ├── ConfigurationModule.kt │ │ │ ├── DynamicNotificationModule.kt │ │ │ ├── Module.kt │ │ │ ├── NetworkObserveModule.kt │ │ │ ├── StaticNotificationModule.kt │ │ │ ├── SuspendModule.kt │ │ │ ├── TimeZoneModule.kt │ │ │ └── TunModule.kt │ │ ├── data │ │ ├── Converters.kt │ │ ├── Daos.kt │ │ ├── Database.kt │ │ ├── Imported.kt │ │ ├── ImportedDao.kt │ │ ├── Pending.kt │ │ ├── PendingDao.kt │ │ ├── Selection.kt │ │ ├── SelectionDao.kt │ │ └── migrations │ │ │ ├── LegacyMigration.kt │ │ │ └── Migrations.kt │ │ ├── document │ │ ├── Document.kt │ │ ├── FileDocument.kt │ │ ├── Flag.kt │ │ ├── Path.kt │ │ ├── Paths.kt │ │ ├── Picker.kt │ │ └── VirtualDocument.kt │ │ ├── model │ │ ├── AccessControlMode.kt │ │ └── Profile.kt │ │ ├── remote │ │ ├── IClashManager.kt │ │ ├── IFetchObserver.kt │ │ ├── ILogObserver.kt │ │ ├── IProfileManager.kt │ │ └── IRemoteService.kt │ │ ├── store │ │ └── ServiceStore.kt │ │ └── util │ │ ├── Address.kt │ │ ├── Broadcast.kt │ │ ├── Connectivity.kt │ │ ├── Coroutine.kt │ │ ├── Database.kt │ │ ├── Files.kt │ │ ├── Intent.kt │ │ ├── Net.kt │ │ └── Serializers.kt │ └── res │ ├── drawable │ └── ic_logo_service.xml │ ├── values-ja-rJP │ └── strings.xml │ ├── values-ko-rKR │ └── strings.xml │ ├── values-ru │ └── strings.xml │ ├── values-zh-rHK │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ └── values │ ├── arrays.xml │ ├── colors.xml │ ├── ids.xml │ └── strings.xml └── settings.gradle.kts /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.bat text eol=crlf 4 | *.jar binary 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | /app/foss/release 4 | /app/premium/release 5 | /captures 6 | 7 | # Ignore Gradle GUI config 8 | gradle-app.setting 9 | 10 | # Avoid ignoring Gradle wrapper jar targetFile (.jar files are usually ignored) 11 | !gradle-wrapper.jar 12 | 13 | # Cache of project 14 | .gradletasknamecache 15 | 16 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 17 | # gradle/wrapper/gradle-wrapper.properties 18 | 19 | # Ignore IDEA config 20 | *.iml 21 | /.idea/* 22 | !/.idea/codeStyles 23 | /core/src/main/golang/.idea/* 24 | !/core/src/main/golang/.idea/codeStyles 25 | /core/src/foss/golang/.idea/* 26 | !/core/src/foss/golang/.idea/codeStyles 27 | /core/src/premium/golang/.idea/* 28 | !/core/src/premium/golang/.idea/codeStyles 29 | 30 | # Ignore builtin geofiles 31 | app/src/main/assets 32 | 33 | # KeyStore 34 | signing.properties 35 | *.keystore 36 | *.jks 37 | 38 | # clion cmake build 39 | cmake-build-* 40 | 41 | # local.properties 42 | local.properties 43 | 44 | 45 | # tracker 46 | tracker.properties 47 | 48 | # vscode 49 | .vscode 50 | 51 | # cxx 52 | .cxx 53 | 54 | *.hprof 55 | 56 | # firebase 57 | google-services.json 58 | 59 | # Dolphin 60 | .directory 61 | 62 | # logs 63 | *.log 64 | 65 | # MacOS 66 | .DS_Store 67 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "clash-foss"] 2 | path = core/src/foss/golang/clash 3 | url = https://github.com/MetaCubeX/mihomo 4 | branch = Alpha 5 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to Clash for Android 2 | 3 | #### Code Style 4 | 5 | Please use `Android Studio` or `Intellij IDEA` to open the project and use the project code style profile. 6 | 7 | `File` -> `Settings` -> `Editor` -> `Code Style` -> `C/C++ and Kotlin` -> `Scheme` -> `Project` 8 | 9 | 10 | 11 | #### License 12 | 13 | Contributing to Clash for Android that assumes you allow code to be merged into closed-source branch of Clash for Android. Other terms follow the [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/ApkBrokenActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import com.github.kr328.clash.design.ApkBrokenDesign 6 | import kotlinx.coroutines.isActive 7 | 8 | class ApkBrokenActivity : BaseActivity() { 9 | override suspend fun main() { 10 | val design = ApkBrokenDesign(this) 11 | 12 | setContentDesign(design) 13 | 14 | while (isActive) { 15 | val req = design.requests.receive() 16 | 17 | startActivity(Intent(Intent.ACTION_VIEW).setData(Uri.parse(req.url))) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/AppCrashedActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import com.github.kr328.clash.common.compat.versionCodeCompat 4 | import com.github.kr328.clash.common.log.Log 5 | import com.github.kr328.clash.design.AppCrashedDesign 6 | import com.github.kr328.clash.log.SystemLogcat 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.isActive 9 | import kotlinx.coroutines.withContext 10 | 11 | class AppCrashedActivity : BaseActivity() { 12 | override suspend fun main() { 13 | val design = AppCrashedDesign(this) 14 | 15 | setContentDesign(design) 16 | 17 | val packageInfo = withContext(Dispatchers.IO) { 18 | packageManager.getPackageInfo(packageName, 0) 19 | } 20 | 21 | Log.i("App version: versionName = ${packageInfo.versionName} versionCode = ${packageInfo.versionCodeCompat}") 22 | 23 | val logs = withContext(Dispatchers.IO) { 24 | SystemLogcat.dumpCrash() 25 | } 26 | 27 | design.setAppLogs(logs) 28 | 29 | while (isActive) { 30 | events.receive() 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/DialerReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | 7 | class DialerReceiver : BroadcastReceiver() { 8 | override fun onReceive(context: Context, intent: Intent) { 9 | val intent = Intent(context, MainActivity::class.java) 10 | .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 11 | context.startActivity(intent) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/HelpActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.content.Intent 4 | import com.github.kr328.clash.design.HelpDesign 5 | import kotlinx.coroutines.isActive 6 | 7 | class HelpActivity : BaseActivity() { 8 | override suspend fun main() { 9 | val design = HelpDesign(this) { 10 | startActivity(Intent(Intent.ACTION_VIEW).setData(it)) 11 | } 12 | 13 | setContentDesign(design) 14 | 15 | while (isActive) { 16 | events.receive() 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/NetworkSettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import com.github.kr328.clash.common.util.intent 4 | import com.github.kr328.clash.design.NetworkSettingsDesign 5 | import com.github.kr328.clash.service.store.ServiceStore 6 | import kotlinx.coroutines.isActive 7 | import kotlinx.coroutines.selects.select 8 | 9 | class NetworkSettingsActivity : BaseActivity() { 10 | override suspend fun main() { 11 | val design = NetworkSettingsDesign( 12 | this, 13 | uiStore, 14 | ServiceStore(this), 15 | clashRunning, 16 | ) 17 | 18 | setContentDesign(design) 19 | 20 | while (isActive) { 21 | select { 22 | events.onReceive { 23 | when (it) { 24 | Event.ClashStart, Event.ClashStop, Event.ServiceRecreated -> 25 | recreate() 26 | else -> Unit 27 | } 28 | } 29 | design.requests.onReceive { 30 | when (it) { 31 | NetworkSettingsDesign.Request.StartAccessControlList -> 32 | startActivity(AccessControlActivity::class.intent) 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/RestartReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.github.kr328.clash.service.StatusProvider 7 | import com.github.kr328.clash.util.startClashService 8 | 9 | class RestartReceiver : BroadcastReceiver() { 10 | override fun onReceive(context: Context, intent: Intent) { 11 | when (intent.action) { 12 | Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_MY_PACKAGE_REPLACED -> { 13 | if (StatusProvider.shouldStartClashOnBoot) 14 | context.startClashService() 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import com.github.kr328.clash.common.util.intent 4 | import com.github.kr328.clash.design.SettingsDesign 5 | import kotlinx.coroutines.isActive 6 | import kotlinx.coroutines.selects.select 7 | 8 | class SettingsActivity : BaseActivity() { 9 | override suspend fun main() { 10 | val design = SettingsDesign(this) 11 | 12 | setContentDesign(design) 13 | 14 | while (isActive) { 15 | select { 16 | events.onReceive { 17 | 18 | } 19 | design.requests.onReceive { 20 | when (it) { 21 | SettingsDesign.Request.StartApp -> 22 | startActivity(AppSettingsActivity::class.intent) 23 | SettingsDesign.Request.StartNetwork -> 24 | startActivity(NetworkSettingsActivity::class.intent) 25 | SettingsDesign.Request.StartOverride -> 26 | startActivity(OverrideSettingsActivity::class.intent) 27 | SettingsDesign.Request.StartMetaFeature -> 28 | startActivity(MetaFeatureSettingsActivity::class.intent) 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/log/LogcatCache.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.log 2 | 3 | import androidx.collection.CircularArray 4 | import com.github.kr328.clash.core.model.LogMessage 5 | import kotlinx.coroutines.sync.Mutex 6 | import kotlinx.coroutines.sync.withLock 7 | 8 | class LogcatCache { 9 | data class Snapshot(val messages: List, val removed: Int, val appended: Int) 10 | 11 | private val array = CircularArray(CAPACITY) 12 | private val lock = Mutex() 13 | 14 | private var removed: Int = 0 15 | private var appended: Int = 0 16 | 17 | suspend fun append(msg: LogMessage) { 18 | lock.withLock { 19 | if (array.size() >= CAPACITY) { 20 | array.removeFromStart(1) 21 | 22 | removed++ 23 | appended-- 24 | } 25 | 26 | array.addLast(msg) 27 | 28 | appended++ 29 | } 30 | } 31 | 32 | suspend fun snapshot(full: Boolean): Snapshot? { 33 | return lock.withLock { 34 | if (!full && removed == 0 && appended == 0) { 35 | return@withLock null 36 | } 37 | 38 | Snapshot( 39 | List(array.size()) { array[it] }, 40 | removed, 41 | if (full) array.size() + appended else appended 42 | ).also { 43 | removed = 0 44 | appended = 0 45 | } 46 | } 47 | } 48 | 49 | companion object { 50 | const val CAPACITY = 128 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/log/LogcatFilter.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.log 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.core.model.LogMessage 5 | import com.github.kr328.clash.design.util.format 6 | import java.io.BufferedWriter 7 | import java.io.Writer 8 | import java.util.* 9 | 10 | class LogcatFilter(output: Writer, private val context: Context) : BufferedWriter(output) { 11 | fun writeHeader(time: Date) { 12 | appendLine("# Capture on ${time.format(context)}") 13 | } 14 | 15 | fun writeMessage(message: LogMessage) { 16 | val time = message.time.format(context, includeDate = false) 17 | val level = message.level.name 18 | 19 | appendLine(FORMAT.format(time, level, message.message)) 20 | } 21 | 22 | companion object { 23 | private const val FORMAT = "%12s %7s: %s" 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/log/LogcatWriter.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.log 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.core.model.LogMessage 5 | import com.github.kr328.clash.design.model.LogFile 6 | import com.github.kr328.clash.util.logsDir 7 | import java.io.BufferedWriter 8 | import java.io.FileWriter 9 | 10 | class LogcatWriter(context: Context) : AutoCloseable { 11 | private val file = LogFile.generate() 12 | private val writer = BufferedWriter(FileWriter(context.logsDir.resolve(file.fileName))) 13 | 14 | override fun close() { 15 | writer.close() 16 | } 17 | 18 | fun appendMessage(message: LogMessage) { 19 | writer.appendLine(FORMAT.format(message.time.time, message.level.name, message.message)) 20 | } 21 | 22 | companion object { 23 | private const val FORMAT = "%d:%s:%s" 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/log/SystemLogcat.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.log 2 | 3 | object SystemLogcat { 4 | private val command = arrayOf( 5 | "logcat", 6 | "-d", 7 | "-s", 8 | "Go", 9 | "DEBUG", 10 | "AndroidRuntime", 11 | "ClashMetaForAndroid", 12 | "LwIP", 13 | ) 14 | 15 | fun dumpCrash(): String { 16 | return try { 17 | val process = Runtime.getRuntime().exec(command) 18 | 19 | val result = process.inputStream.use { stream -> 20 | stream.reader().readLines() 21 | .filterNot { it.startsWith("------") } 22 | .joinToString("\n") 23 | } 24 | 25 | process.waitFor() 26 | 27 | result.trim() 28 | } catch (e: Exception) { 29 | "" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/remote/StatusClient.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.remote 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | import com.github.kr328.clash.common.constants.Authorities 6 | import com.github.kr328.clash.common.log.Log 7 | import com.github.kr328.clash.service.StatusProvider 8 | 9 | class StatusClient(private val context: Context) { 10 | private val uri: Uri 11 | get() { 12 | return Uri.Builder() 13 | .scheme("content") 14 | .authority(Authorities.STATUS_PROVIDER) 15 | .build() 16 | } 17 | 18 | fun currentProfile(): String? { 19 | return try { 20 | val result = context.contentResolver.call( 21 | uri, 22 | StatusProvider.METHOD_CURRENT_PROFILE, 23 | null, 24 | null 25 | ) 26 | 27 | result?.getString("name") 28 | } catch (e: Exception) { 29 | Log.w("Query current profile: $e", e) 30 | 31 | null 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/store/AppStore.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.store 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.common.store.Store 5 | import com.github.kr328.clash.common.store.asStoreProvider 6 | 7 | class AppStore(context: Context) { 8 | private val store = Store( 9 | context 10 | .getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE) 11 | .asStoreProvider() 12 | ) 13 | 14 | var updatedAt: Long by store.long( 15 | key = "updated_at", 16 | defaultValue = -1, 17 | ) 18 | 19 | companion object { 20 | private const val FILE_NAME = "app" 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/store/TipsStore.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.store 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.common.store.Store 5 | import com.github.kr328.clash.common.store.asStoreProvider 6 | 7 | class TipsStore(context: Context) { 8 | private val store = Store( 9 | context 10 | .getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE) 11 | .asStoreProvider() 12 | ) 13 | 14 | var requestDonate: Boolean by store.boolean( 15 | key = "request_donate", 16 | defaultValue = true, 17 | ) 18 | 19 | var primaryVersion: Int by store.int( 20 | key = "primary_version", 21 | defaultValue = -1, 22 | ) 23 | 24 | companion object { 25 | const val CURRENT_PRIMARY_VERSION = 1 26 | 27 | private const val FILE_NAME = "tips" 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Activity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.lifecycle.LifecycleOwner 5 | import androidx.lifecycle.LifecycleRegistry 6 | import kotlinx.coroutines.NonCancellable 7 | import kotlinx.coroutines.withContext 8 | 9 | class ActivityResultLifecycle : LifecycleOwner { 10 | override val lifecycle = LifecycleRegistry(this) 11 | 12 | init { 13 | lifecycle.currentState = Lifecycle.State.INITIALIZED 14 | } 15 | 16 | suspend fun use(block: suspend (lifecycle: ActivityResultLifecycle, start: () -> Unit) -> T): T { 17 | return try { 18 | markCreated() 19 | 20 | block(this, this::markStarted) 21 | } finally { 22 | withContext(NonCancellable) { 23 | markDestroy() 24 | } 25 | } 26 | } 27 | 28 | private fun markCreated() { 29 | lifecycle.currentState = Lifecycle.State.CREATED 30 | } 31 | 32 | private fun markStarted() { 33 | lifecycle.currentState = Lifecycle.State.STARTED 34 | lifecycle.currentState = Lifecycle.State.RESUMED 35 | } 36 | 37 | private fun markDestroy() { 38 | lifecycle.currentState = Lifecycle.State.DESTROYED 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Clash.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.VpnService 6 | import com.github.kr328.clash.common.compat.startForegroundServiceCompat 7 | import com.github.kr328.clash.common.constants.Intents 8 | import com.github.kr328.clash.common.util.intent 9 | import com.github.kr328.clash.design.store.UiStore 10 | import com.github.kr328.clash.service.ClashService 11 | import com.github.kr328.clash.service.TunService 12 | import com.github.kr328.clash.service.util.sendBroadcastSelf 13 | 14 | fun Context.startClashService(): Intent? { 15 | val startTun = UiStore(this).enableVpn 16 | 17 | if (startTun) { 18 | val vpnRequest = VpnService.prepare(this) 19 | if (vpnRequest != null) 20 | return vpnRequest 21 | 22 | startForegroundServiceCompat(TunService::class.intent) 23 | } else { 24 | startForegroundServiceCompat(ClashService::class.intent) 25 | } 26 | 27 | return null 28 | } 29 | 30 | fun Context.stopClashService() { 31 | sendBroadcastSelf(Intent(Intents.ACTION_CLASH_REQUEST_STOP)) 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Content.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.content.ContentResolver 4 | import android.net.Uri 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import java.io.FileNotFoundException 8 | 9 | private fun fileNotFound(file: Uri): FileNotFoundException { 10 | return FileNotFoundException("$file not found") 11 | } 12 | 13 | @Suppress("BlockingMethodInNonBlockingContext") 14 | suspend fun ContentResolver.copyContentTo( 15 | source: Uri, 16 | target: Uri 17 | ) { 18 | withContext(Dispatchers.IO) { 19 | (openInputStream(source) ?: throw fileNotFound(source)).use { input -> 20 | (openOutputStream(target, "rwt") ?: throw fileNotFound(target)).use { output -> 21 | input.copyTo(output) 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Files.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.content.Context 4 | import java.io.File 5 | 6 | val Context.logsDir: File 7 | get() = cacheDir.resolve("logs") 8 | 9 | val Context.clashDir: File 10 | get() = filesDir.resolve("clash") -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Remote.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.os.DeadObjectException 4 | import com.github.kr328.clash.common.log.Log 5 | import com.github.kr328.clash.remote.Remote 6 | import com.github.kr328.clash.service.remote.IClashManager 7 | import com.github.kr328.clash.service.remote.IProfileManager 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.withContext 10 | import kotlin.coroutines.CoroutineContext 11 | 12 | suspend fun withClash( 13 | context: CoroutineContext = Dispatchers.IO, 14 | block: suspend IClashManager.() -> T 15 | ): T { 16 | while (true) { 17 | val remote = Remote.service.remote.get() 18 | val client = remote.clash() 19 | 20 | try { 21 | return withContext(context) { client.block() } 22 | } catch (e: DeadObjectException) { 23 | Log.w("Remote services panic") 24 | 25 | Remote.service.remote.reset(remote) 26 | } 27 | } 28 | } 29 | 30 | suspend fun withProfile( 31 | context: CoroutineContext = Dispatchers.IO, 32 | block: suspend IProfileManager.() -> T 33 | ): T { 34 | while (true) { 35 | val remote = Remote.service.remote.get() 36 | val client = remote.profile() 37 | 38 | try { 39 | return withContext(context) { client.block() } 40 | } catch (e: DeadObjectException) { 41 | Log.w("Remote services panic") 42 | 43 | Remote.service.remote.reset(remote) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Service.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.content.Context 4 | import android.content.ServiceConnection 5 | 6 | fun Context.unbindServiceSilent(connection: ServiceConnection) { 7 | try { 8 | unbindService(connection) 9 | } catch (e: Exception) { 10 | // ignore 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Uri.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.net.Uri 4 | 5 | val Uri.fileName: String? 6 | get() = schemeSpecificPart.split("/").lastOrNull() -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-xhdpi/ic_banner.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | A Graphical user interface of Clash.Meta for Android. 2 | 3 |

Features

4 |
    5 |
  • Local HTTP/HTTPS/SOCKS server
  • 6 |
  • VMess, Shadowsocks, Trojan, Snell protocol support for remote connections
  • 7 |
  • Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP
  • 8 |
  • Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes
  • 9 |
  • Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency
  • 10 |
  • Remote providers, allowing users to get node lists remotely instead of hardcoding in config
  • 11 |
-------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | A rule-based tunnel -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/full_description.txt: -------------------------------------------------------------------------------- 1 | Clash.Meta 的 Android 图形界面。 2 | 3 |

功能

4 |
    5 |
  • 本地 HTTP/HTTPS/SOCKS 服务器
  • 6 |
  • 支持 VMess, Shadowsocks, Trojan, Snell 协议远程连接
  • 7 |
  • 内置 DNS 服务器,旨在最小化 DNS 污染攻击影响,支持 DoH/DoT 上游和 fake IP
  • 8 |
  • 基于规则根据域名,GEOIP, IPCIDR 或进程转发数据包到不同节点
  • 9 |
  • 远程分组允许用户实现强大的规则。支持自动回退,负载均衡或基于延迟自动选择节点
  • 10 |
  • 远程提供者,允许用户远程获取节点列表而不是在配置中硬编码
  • 11 |
-------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/short_description.txt: -------------------------------------------------------------------------------- 1 | 基于规则的隧道 -------------------------------------------------------------------------------- /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=-Xmx4g -XX:+UseZGC -Dfile.encoding=UTF-8 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 | # Gradle parallel build 23 | org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jan 14 14:06:42 CST 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /hideapi/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | } 4 | -------------------------------------------------------------------------------- /hideapi/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/hideapi/consumer-rules.pro -------------------------------------------------------------------------------- /hideapi/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.kts. 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 -------------------------------------------------------------------------------- /hideapi/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /hideapi/src/main/java/android/app/ActivityThread.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | public class ActivityThread { 4 | public static String currentProcessName() { 5 | throw new IllegalArgumentException("Stub!"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /release.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/release.keystore -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /service/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("android") 3 | id("kotlinx-serialization") 4 | id("com.android.library") 5 | id("com.google.devtools.ksp") 6 | } 7 | 8 | dependencies { 9 | implementation(project(":core")) 10 | implementation(project(":common")) 11 | 12 | ksp(libs.kaidl.compiler) 13 | ksp(libs.androidx.room.compiler) 14 | 15 | implementation(libs.kotlin.coroutine) 16 | implementation(libs.kotlin.serialization.json) 17 | implementation(libs.androidx.core) 18 | implementation(libs.androidx.room.runtime) 19 | implementation(libs.androidx.room.ktx) 20 | implementation(libs.kaidl.runtime) 21 | implementation(libs.rikkax.multiprocess) 22 | implementation(platform("com.squareup.okhttp3:okhttp-bom:4.12.0")) 23 | 24 | // define any required OkHttp artifacts without version 25 | implementation("com.squareup.okhttp3:okhttp") 26 | implementation("com.squareup.okhttp3:logging-interceptor") 27 | } 28 | 29 | afterEvaluate { 30 | android { 31 | libraryVariants.forEach { 32 | sourceSets[it.name].kotlin.srcDir(buildDir.resolve("generated/ksp/${it.name}/kotlin")) 33 | sourceSets[it.name].java.srcDir(buildDir.resolve("generated/ksp/${it.name}/java")) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /service/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/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/java/com/github/kr328/clash/service/BaseService.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service 2 | 3 | import android.app.Service 4 | import com.github.kr328.clash.service.util.cancelAndJoinBlocking 5 | import kotlinx.coroutines.CoroutineScope 6 | import kotlinx.coroutines.Dispatchers 7 | 8 | abstract class BaseService : Service(), CoroutineScope by CoroutineScope(Dispatchers.Default) { 9 | override fun onDestroy() { 10 | super.onDestroy() 11 | 12 | cancelAndJoinBlocking() 13 | } 14 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/PreferenceProvider.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import com.github.kr328.clash.common.constants.Authorities 6 | import rikka.preference.MultiProcessPreference 7 | import rikka.preference.PreferenceProvider 8 | 9 | class PreferenceProvider : PreferenceProvider() { 10 | override fun onCreatePreference(context: Context): SharedPreferences { 11 | return context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE) 12 | } 13 | 14 | companion object { 15 | private const val FILE_NAME = "service" 16 | 17 | fun createSharedPreferencesFromContext(context: Context): SharedPreferences { 18 | return when (context) { 19 | is BaseService, is TunService -> 20 | context.getSharedPreferences( 21 | FILE_NAME, 22 | Context.MODE_PRIVATE 23 | ) 24 | else -> 25 | MultiProcessPreference( 26 | context, 27 | Authorities.SETTINGS_PROVIDER 28 | ) 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/RemoteService.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service 2 | 3 | import android.content.Intent 4 | import android.os.IBinder 5 | import com.github.kr328.clash.service.remote.IClashManager 6 | import com.github.kr328.clash.service.remote.IRemoteService 7 | import com.github.kr328.clash.service.remote.IProfileManager 8 | import com.github.kr328.clash.service.remote.wrap 9 | import com.github.kr328.clash.service.util.cancelAndJoinBlocking 10 | 11 | class RemoteService : BaseService(), IRemoteService { 12 | private val binder = this.wrap() 13 | 14 | private var clash: ClashManager? = null 15 | private var profile: ProfileManager? = null 16 | private var clashBinder: IClashManager? = null 17 | private var profileBinder: IProfileManager? = null 18 | 19 | override fun onCreate() { 20 | super.onCreate() 21 | 22 | clash = ClashManager(this) 23 | profile = ProfileManager(this) 24 | clashBinder = clash?.wrap() as IClashManager? 25 | profileBinder = profile?.wrap() as IProfileManager? 26 | } 27 | 28 | override fun onDestroy() { 29 | super.onDestroy() 30 | 31 | clash?.cancelAndJoinBlocking() 32 | profile?.cancelAndJoinBlocking() 33 | } 34 | 35 | override fun onBind(intent: Intent?): IBinder { 36 | return binder 37 | } 38 | 39 | override fun clash(): IClashManager { 40 | return clashBinder!! 41 | } 42 | 43 | override fun profile(): IProfileManager { 44 | return profileBinder!! 45 | } 46 | } -------------------------------------------------------------------------------- /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.app.Service 4 | import com.github.kr328.clash.common.constants.Intents 5 | import com.github.kr328.clash.common.log.Log 6 | 7 | class CloseModule(service: Service) : Module(service) { 8 | object RequestClose 9 | 10 | override suspend fun run() { 11 | val broadcasts = receiveBroadcast { 12 | addAction(Intents.ACTION_CLASH_REQUEST_STOP) 13 | } 14 | 15 | broadcasts.receive() 16 | 17 | Log.d("User request close") 18 | 19 | return enqueueEvent(RequestClose) 20 | } 21 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/clash/module/TimeZoneModule.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.clash.module 2 | 3 | import android.app.Service 4 | import android.content.Intent 5 | import com.github.kr328.clash.core.Clash 6 | import java.util.* 7 | 8 | class TimeZoneModule(service: Service) : Module(service) { 9 | override suspend fun run() { 10 | val timeZones = receiveBroadcast { 11 | addAction(Intent.ACTION_TIMEZONE_CHANGED) 12 | } 13 | 14 | while (true) { 15 | val timeZone = TimeZone.getDefault() 16 | 17 | Clash.notifyTimeZoneChanged(timeZone.id, timeZone.rawOffset / 1000) 18 | 19 | timeZones.receive() 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Converters.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.TypeConverter 4 | import com.github.kr328.clash.service.model.Profile 5 | import java.util.* 6 | 7 | class Converters { 8 | @TypeConverter 9 | fun fromUUID(uuid: UUID): String { 10 | return uuid.toString() 11 | } 12 | 13 | @TypeConverter 14 | fun toUUID(uuid: String): UUID { 15 | return UUID.fromString(uuid) 16 | } 17 | 18 | @TypeConverter 19 | fun fromProfileType(type: Profile.Type): String { 20 | return type.name 21 | } 22 | 23 | @TypeConverter 24 | fun toProfileType(type: String): Profile.Type { 25 | return Profile.Type.valueOf(type) 26 | } 27 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Daos.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | fun ImportedDao(): ImportedDao { 4 | return Database.database.openImportedDao() 5 | } 6 | 7 | fun PendingDao(): PendingDao { 8 | return Database.database.openPendingDao() 9 | } 10 | 11 | fun SelectionDao(): SelectionDao { 12 | return Database.database.openSelectionProxyDao() 13 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Imported.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.TypeConverters 6 | import com.github.kr328.clash.service.model.Profile 7 | import java.util.* 8 | 9 | @Entity(tableName = "imported", primaryKeys = ["uuid"]) 10 | @TypeConverters(Converters::class) 11 | data class Imported( 12 | @ColumnInfo(name = "uuid") val uuid: UUID, 13 | @ColumnInfo(name = "name") val name: String, 14 | @ColumnInfo(name = "type") val type: Profile.Type, 15 | @ColumnInfo(name = "source") val source: String, 16 | @ColumnInfo(name = "interval") val interval: Long, 17 | @ColumnInfo(name = "upload") val upload: Long, 18 | @ColumnInfo(name = "download") val download: Long, 19 | @ColumnInfo(name = "total") val total: Long, 20 | @ColumnInfo(name = "expire") val expire: Long, 21 | @ColumnInfo(name = "createdAt") val createdAt: Long, 22 | ) -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/ImportedDao.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.* 4 | import java.util.* 5 | 6 | @Dao 7 | @TypeConverters(Converters::class) 8 | interface ImportedDao { 9 | @Query("SELECT * FROM imported WHERE uuid = :uuid") 10 | suspend fun queryByUUID(uuid: UUID): Imported? 11 | 12 | @Query("SELECT uuid FROM imported ORDER BY createdAt") 13 | suspend fun queryAllUUIDs(): List 14 | 15 | @Insert(onConflict = OnConflictStrategy.ABORT) 16 | suspend fun insert(imported: Imported): Long 17 | 18 | @Update(onConflict = OnConflictStrategy.ABORT) 19 | suspend fun update(imported: Imported) 20 | 21 | @Query("DELETE FROM imported WHERE uuid = :uuid") 22 | suspend fun remove(uuid: UUID) 23 | 24 | @Query("SELECT EXISTS(SELECT 1 FROM imported WHERE uuid = :uuid)") 25 | suspend fun exists(uuid: UUID): Boolean 26 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Pending.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.TypeConverters 6 | import com.github.kr328.clash.service.model.Profile 7 | import java.util.* 8 | 9 | @Entity(tableName = "pending", primaryKeys = ["uuid"]) 10 | @TypeConverters(Converters::class) 11 | data class Pending( 12 | @ColumnInfo(name = "uuid") val uuid: UUID, 13 | @ColumnInfo(name = "name") val name: String, 14 | @ColumnInfo(name = "type") val type: Profile.Type, 15 | @ColumnInfo(name = "source") val source: String, 16 | @ColumnInfo(name = "interval") val interval: Long, 17 | @ColumnInfo(name = "upload") val upload: Long, 18 | @ColumnInfo(name = "download") val download: Long, 19 | @ColumnInfo(name = "total") val total: Long, 20 | @ColumnInfo(name = "expire") val expire: Long, 21 | @ColumnInfo(name = "createdAt") val createdAt: Long = System.currentTimeMillis(), 22 | ) -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/PendingDao.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.* 4 | import java.util.* 5 | 6 | @Dao 7 | @TypeConverters(Converters::class) 8 | interface PendingDao { 9 | @Query("SELECT * FROM pending WHERE uuid = :uuid") 10 | suspend fun queryByUUID(uuid: UUID): Pending? 11 | 12 | @Query("DELETE FROM pending WHERE uuid = :uuid") 13 | suspend fun remove(uuid: UUID) 14 | 15 | @Query("SELECT EXISTS(SELECT 1 FROM pending WHERE uuid = :uuid)") 16 | suspend fun exists(uuid: UUID): Boolean 17 | 18 | @Query("SELECT uuid FROM pending ORDER BY createdAt") 19 | suspend fun queryAllUUIDs(): List 20 | 21 | @Insert(onConflict = OnConflictStrategy.REPLACE) 22 | suspend fun insert(pending: Pending) 23 | 24 | @Update(onConflict = OnConflictStrategy.REPLACE) 25 | suspend fun update(pending: Pending) 26 | } 27 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Selection.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 | import androidx.room.TypeConverters 7 | import java.util.* 8 | 9 | @Entity( 10 | tableName = "selections", 11 | foreignKeys = [ForeignKey( 12 | entity = Imported::class, 13 | childColumns = ["uuid"], 14 | parentColumns = ["uuid"], 15 | onDelete = ForeignKey.CASCADE, 16 | onUpdate = ForeignKey.CASCADE 17 | )], 18 | primaryKeys = ["uuid", "proxy"] 19 | ) 20 | @TypeConverters(Converters::class) 21 | data class Selection( 22 | @ColumnInfo(name = "uuid") val uuid: UUID, 23 | @ColumnInfo(name = "proxy") val proxy: String, 24 | @ColumnInfo(name = "selected") val selected: String, 25 | ) -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/SelectionDao.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.* 4 | import java.util.* 5 | 6 | @Dao 7 | @TypeConverters(Converters::class) 8 | interface SelectionDao { 9 | @Insert(onConflict = OnConflictStrategy.REPLACE) 10 | fun setSelected(selection: Selection) 11 | 12 | @Query("DELETE FROM selections WHERE uuid = :uuid AND proxy = :proxy") 13 | fun removeSelected(uuid: UUID, proxy: String) 14 | 15 | @Query("SELECT * FROM selections WHERE uuid = :uuid") 16 | suspend fun querySelections(uuid: UUID): List 17 | 18 | @Query("DELETE FROM selections WHERE uuid = :uuid AND proxy in (:proxies)") 19 | suspend fun removeSelections(uuid: UUID, proxies: List) 20 | } 21 | -------------------------------------------------------------------------------- /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() 6 | 7 | val LEGACY_MIGRATION = ::migrationFromLegacy -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/document/Document.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.document 2 | 3 | interface Document { 4 | val id: String 5 | val name: String 6 | val mimeType: String 7 | val size: Long 8 | val updatedAt: Long 9 | val flags: Set 10 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/document/FileDocument.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.document 2 | 3 | import android.provider.DocumentsContract 4 | import java.io.File 5 | 6 | class FileDocument( 7 | val file: File, 8 | override val flags: Set, 9 | private val idOverride: String? = null, 10 | private val nameOverride: String? = null, 11 | ) : Document { 12 | override val id: String 13 | get() = idOverride ?: file.name 14 | override val name: String 15 | get() = nameOverride ?: file.name 16 | override val mimeType: String 17 | get() = if (file.isDirectory) DocumentsContract.Document.MIME_TYPE_DIR else "text/plain" 18 | override val size: Long 19 | get() = file.length() 20 | override val updatedAt: Long 21 | get() = file.lastModified() 22 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/document/Flag.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.document 2 | 3 | enum class Flag { 4 | Writable, Deletable, Virtual 5 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/document/Path.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.document 2 | 3 | import java.util.* 4 | 5 | data class Path( 6 | val uuid: UUID?, 7 | val scope: Scope?, 8 | val relative: List? 9 | ) { 10 | enum class Scope { 11 | Configuration, Providers 12 | } 13 | 14 | override fun toString(): String { 15 | if (uuid == null) 16 | return "/" 17 | 18 | if (scope == null) 19 | return "/$uuid" 20 | 21 | val sc = when (scope) { 22 | Scope.Configuration -> Paths.CONFIGURATION_ID 23 | Scope.Providers -> Paths.PROVIDERS_ID 24 | } 25 | 26 | if (relative == null) 27 | return "/$uuid/$sc" 28 | 29 | return "/$uuid/$sc/${relative.joinToString(separator = "/")}" 30 | } 31 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/document/VirtualDocument.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.document 2 | 3 | class VirtualDocument( 4 | override val id: String, 5 | override val name: String, 6 | override val mimeType: String, 7 | override val size: Long, 8 | override val updatedAt: Long, 9 | override val flags: Set, 10 | ) : Document 11 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/model/AccessControlMode.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.model 2 | 3 | enum class AccessControlMode { 4 | AcceptAll, AcceptSelected, DenySelected 5 | } 6 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/model/Profile.kt: -------------------------------------------------------------------------------- 1 | @file:UseSerializers(UUIDSerializer::class) 2 | 3 | package com.github.kr328.clash.service.model 4 | 5 | import android.os.Parcel 6 | import android.os.Parcelable 7 | import com.github.kr328.clash.core.util.Parcelizer 8 | import com.github.kr328.clash.service.util.UUIDSerializer 9 | import kotlinx.serialization.Serializable 10 | import kotlinx.serialization.UseSerializers 11 | import java.util.* 12 | 13 | @Serializable 14 | data class Profile( 15 | val uuid: UUID, 16 | val name: String, 17 | val type: Type, 18 | val source: String, 19 | val active: Boolean, 20 | val interval: Long, 21 | val upload: Long, 22 | var download: Long, 23 | val total: Long, 24 | val expire: Long, 25 | 26 | 27 | val updatedAt: Long, 28 | val imported: Boolean, 29 | val pending: Boolean, 30 | ) : Parcelable { 31 | enum class Type { 32 | File, Url, External 33 | } 34 | 35 | override fun writeToParcel(parcel: Parcel, flags: Int) { 36 | Parcelizer.encodeToParcel(serializer(), parcel, this) 37 | } 38 | 39 | override fun describeContents(): Int { 40 | return 0 41 | } 42 | 43 | companion object CREATOR : Parcelable.Creator { 44 | override fun createFromParcel(parcel: Parcel): Profile { 45 | return Parcelizer.decodeFromParcel(serializer(), parcel) 46 | } 47 | 48 | override fun newArray(size: Int): Array { 49 | return arrayOfNulls(size) 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/remote/IClashManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.remote 2 | 3 | import com.github.kr328.clash.core.Clash 4 | import com.github.kr328.clash.core.model.* 5 | import com.github.kr328.kaidl.BinderInterface 6 | 7 | @BinderInterface 8 | interface IClashManager { 9 | fun queryTunnelState(): TunnelState 10 | fun queryTrafficTotal(): Long 11 | fun queryProxyGroupNames(excludeNotSelectable: Boolean): List 12 | fun queryProxyGroup(name: String, proxySort: ProxySort): ProxyGroup 13 | fun queryConfiguration(): UiConfiguration 14 | fun queryProviders(): ProviderList 15 | 16 | fun patchSelector(group: String, name: String): Boolean 17 | 18 | suspend fun healthCheck(group: String) 19 | suspend fun updateProvider(type: Provider.Type, name: String) 20 | 21 | fun queryOverride(slot: Clash.OverrideSlot): ConfigurationOverride 22 | fun patchOverride(slot: Clash.OverrideSlot, configuration: ConfigurationOverride) 23 | fun clearOverride(slot: Clash.OverrideSlot) 24 | 25 | fun setLogObserver(observer: ILogObserver?) 26 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/remote/IFetchObserver.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.remote 2 | 3 | import com.github.kr328.clash.core.model.FetchStatus 4 | import com.github.kr328.kaidl.BinderInterface 5 | 6 | @BinderInterface 7 | fun interface IFetchObserver { 8 | fun updateStatus(status: FetchStatus) 9 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/remote/ILogObserver.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.remote 2 | 3 | import com.github.kr328.clash.core.model.LogMessage 4 | import com.github.kr328.kaidl.BinderInterface 5 | 6 | @BinderInterface 7 | interface ILogObserver { 8 | fun newItem(log: LogMessage) 9 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/remote/IProfileManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.remote 2 | 3 | import com.github.kr328.clash.service.model.Profile 4 | import com.github.kr328.kaidl.BinderInterface 5 | import java.util.* 6 | 7 | @BinderInterface 8 | interface IProfileManager { 9 | suspend fun create(type: Profile.Type, name: String, source: String = ""): UUID 10 | suspend fun clone(uuid: UUID): UUID 11 | suspend fun commit(uuid: UUID, callback: IFetchObserver? = null) 12 | suspend fun release(uuid: UUID) 13 | suspend fun delete(uuid: UUID) 14 | suspend fun patch(uuid: UUID, name: String, source: String, interval: Long) 15 | suspend fun update(uuid: UUID) 16 | suspend fun queryByUUID(uuid: UUID): Profile? 17 | suspend fun queryAll(): List 18 | suspend fun queryActive(): Profile? 19 | suspend fun setActive(profile: Profile) 20 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/remote/IRemoteService.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.remote 2 | 3 | import com.github.kr328.kaidl.BinderInterface 4 | 5 | @BinderInterface 6 | interface IRemoteService { 7 | fun clash(): IClashManager 8 | fun profile(): IProfileManager 9 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Connectivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import android.net.ConnectivityManager 4 | import android.net.Network 5 | 6 | fun ConnectivityManager.resolveDns(network: Network?): List { 7 | val properties = getLinkProperties(network) ?: return listOf() 8 | return properties.dnsServers.map { it.asSocketAddressText(53) } 9 | } 10 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Coroutine.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.job 5 | import kotlinx.coroutines.runBlocking 6 | 7 | fun CoroutineScope.cancelAndJoinBlocking() { 8 | val scope = this 9 | 10 | runBlocking { 11 | scope.coroutineContext.job.cancel() 12 | scope.coroutineContext.job.join() 13 | } 14 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Database.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import com.github.kr328.clash.service.data.ImportedDao 4 | import com.github.kr328.clash.service.data.PendingDao 5 | import java.util.* 6 | 7 | suspend fun generateProfileUUID(): UUID { 8 | var result = UUID.randomUUID() 9 | 10 | while (ImportedDao().exists(result) || PendingDao().exists(result)) { 11 | result = UUID.randomUUID() 12 | } 13 | 14 | return result 15 | } 16 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Files.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import android.content.Context 4 | import java.io.File 5 | 6 | val Context.importedDir: File 7 | get() = filesDir.resolve("imported") 8 | 9 | val Context.pendingDir: File 10 | get() = filesDir.resolve("pending") 11 | 12 | val Context.processingDir: File 13 | get() = filesDir.resolve("processing") 14 | 15 | val File.directoryLastModified: Long? 16 | get() { 17 | return walk().map { it.lastModified() }.maxOrNull() 18 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Intent.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import android.content.Intent 4 | 5 | val Intent.packageName: String? 6 | get() { 7 | return data?.takeIf { it.scheme == "package" }?.schemeSpecificPart 8 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Net.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | data class IPNet(val ip: String, val prefix: Int) 4 | 5 | fun parseCIDR(cidr: String): IPNet { 6 | val s = cidr.split("/", limit = 2) 7 | 8 | if (s.size != 2) 9 | throw IllegalArgumentException("Invalid address") 10 | 11 | val address = s[0] 12 | val prefix = s[1].toInt() 13 | 14 | return IPNet(address, prefix) 15 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Serializers.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import kotlinx.serialization.KSerializer 4 | import kotlinx.serialization.descriptors.PrimitiveKind 5 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 6 | import kotlinx.serialization.descriptors.SerialDescriptor 7 | import kotlinx.serialization.encoding.Decoder 8 | import kotlinx.serialization.encoding.Encoder 9 | import java.util.* 10 | 11 | class UUIDSerializer : KSerializer { 12 | override val descriptor: SerialDescriptor = 13 | PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING) 14 | 15 | override fun deserialize(decoder: Decoder): UUID { 16 | return UUID.fromString(decoder.decodeString()) 17 | } 18 | 19 | override fun serialize(encoder: Encoder, value: UUID) { 20 | encoder.encodeString(value.toString()) 21 | } 22 | } -------------------------------------------------------------------------------- /service/src/main/res/values-ja-rJP/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clashステータス 4 | プロファイルサービスのステータス 5 | プロファイルの処理状況 6 | プロファイルプロセスの結果 7 | 正常に更新されました 8 | 更新に失敗しました 9 | %sを更新しました 10 | 更新 %1$s: %2$s 11 | 実行中 12 | 読み込み中 13 | Clash Meta for Android 14 | プロファイルと外部リソース 15 | コンフィグ.yaml 16 | 外部リソースファイル 17 | プロファイルの更新 18 | プロファイルを更新中 19 | -------------------------------------------------------------------------------- /service/src/main/res/values-ko-rKR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clash 상태 4 | 구성 파일 서비스 상태 5 | 구성 파일 처리 상태 6 | 구성 파일 처리 결과 7 | 업데이트 성공 8 | 업데이트 실패 9 | %s 업데이트 성공 10 | 업데이트 %1$s: %2$s 11 | 연결됨 12 | 로딩중 13 | Clash Meta for Android 14 | 구성 파일과 외부 리소스 15 | 구성 파일.yaml 16 | 외부 리소스 파일 17 | 구성 파일 업데이트 18 | 구성 파일 업데이트 중 19 | -------------------------------------------------------------------------------- /service/src/main/res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | "%1$s↑\t%2$s↓" 4 | 5 | Статус Clash 6 | Статус сервиса профиля 7 | Статус обработки профиля 8 | Результат обработки профиля 9 | Успешно обновлено 10 | Не удалось обновить 11 | Обновление %s завершено 12 | Обновление %1$s: %2$s 13 | Работает 14 | Загружается 15 | Clash Meta для Android 16 | Профили и провайдеры 17 | Configuration.yaml 18 | Файлы провайдера 19 | Обновление профиля 20 | Профиль обновляется 21 | 22 | -------------------------------------------------------------------------------- /service/src/main/res/values-zh-rHK/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clash 狀態 4 | 正在運行 5 | 更新 %s 成功 6 | "更新 %1$s: %2$s " 7 | Clash Meta for Android 8 | 配置文件和外部資源 9 | 配置文件.yaml 10 | 外部資源文件列表 11 | 載入中 12 | 配置文件處理狀態 13 | 更新成功 14 | 更新失敗 15 | 配置更新服務 16 | 配置更新中 17 | 配置文件服務狀態 18 | 配置文件處理結果 19 | -------------------------------------------------------------------------------- /service/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clash 狀態 4 | 正在運作 5 | 更新 %s 成功 6 | "更新 %1$s: %2$s " 7 | Clash Meta for Android 8 | 設定檔和外部資源 9 | 設定檔.yaml 10 | 外部資源文件列表 11 | 載入中 12 | 設定檔處理狀態 13 | 更新成功 14 | 更新失敗 15 | 設定檔更新服務 16 | 設定檔更新中 17 | 設定檔服務狀態 18 | 設定檔處理結果 19 | 20 | -------------------------------------------------------------------------------- /service/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clash 状态 4 | 正在运行 5 | 更新 %s 成功 6 | "更新 %1$s: %2$s " 7 | Clash Meta for Android 8 | 配置文件和外部资源 9 | 配置文件.yaml 10 | 外部资源文件列表 11 | 载入中 12 | 配置文件处理状态 13 | 更新成功 14 | 更新失败 15 | 配置更新服务 16 | 配置更新中 17 | 配置文件服务状态 18 | 配置文件处理结果 19 | -------------------------------------------------------------------------------- /service/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1E4376 4 | -------------------------------------------------------------------------------- /service/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /service/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | "%1$s↑\t%2$s↓" 4 | 5 | Clash Status 6 | Profile Service Status 7 | Profile Processing Status 8 | Profile Process Result 9 | Update Successfully 10 | Update Failure 11 | Update %s completed 12 | Update %1$s: %2$s 13 | Running 14 | Loading 15 | Clash Meta for Android 16 | Profiles and Providers 17 | Configuration.yaml 18 | Provider Files 19 | Profile Updater 20 | Profile Updating 21 | 22 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "ClashMetaForAndroid" 2 | 3 | include(":app") 4 | include(":core") 5 | include(":service") 6 | include(":design") 7 | include(":common") 8 | include(":hideapi") 9 | 10 | pluginManagement { 11 | repositories { 12 | mavenLocal() 13 | mavenCentral() 14 | gradlePluginPortal() 15 | } 16 | } 17 | --------------------------------------------------------------------------------