├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── 01-bug-report-en.yml │ ├── 02-feature-request-en.yml │ ├── 03-bug-report-zh-cn.yml │ ├── 04-feature-request-zh-cn.yml │ └── config.yml └── workflows │ └── build.yaml ├── .gitignore ├── .gitmodules ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── PRIVACY_POLICY.md ├── README.md ├── README_en.md ├── app ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ └── yos │ │ └── clash │ │ └── material │ │ ├── AccessControlActivity.kt │ │ ├── ApkBrokenActivity.kt │ │ ├── AppCrashedActivity.kt │ │ ├── AppSettingsActivity.kt │ │ ├── BaseActivity.kt │ │ ├── ExternalImportActivity.kt │ │ ├── FilesActivity.kt │ │ ├── HelpActivity.kt │ │ ├── LogcatActivity.kt │ │ ├── LogcatService.kt │ │ ├── LogsActivity.kt │ │ ├── MainActivity.kt │ │ ├── MainApplication.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 │ ├── 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 │ └── yos │ │ └── clash │ │ └── material │ │ └── 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-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ └── values │ └── strings.xml ├── core ├── 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 │ ├── 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 │ │ │ ├── process_open.go │ │ │ ├── process_premium.go │ │ │ ├── provider_open.go │ │ │ └── provider_premium.go │ │ │ ├── debug.go │ │ │ ├── delegate │ │ │ └── init.go │ │ │ ├── log_open.go │ │ │ ├── log_premium.go │ │ │ ├── main.go │ │ │ ├── platform │ │ │ ├── limit.go │ │ │ └── procfs.go │ │ │ ├── proxy.go │ │ │ ├── proxy │ │ │ └── http.go │ │ │ ├── trace.c │ │ │ ├── trace.h │ │ │ ├── tun.go │ │ │ ├── tun │ │ │ ├── dns.go │ │ │ ├── metadata_open.go │ │ │ ├── metadata_premium.go │ │ │ ├── tun.go │ │ │ └── udp.go │ │ │ ├── tunnel.go │ │ │ ├── tunnel │ │ │ ├── conn.go │ │ │ ├── connectivity.go │ │ │ ├── geoip.go │ │ │ ├── init.go │ │ │ ├── loopback_open.go │ │ │ ├── loopback_premium.go │ │ │ ├── providers_open.go │ │ │ ├── providers_premium.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 │ └── premium │ └── golang │ ├── go.mod │ ├── go.sum │ └── main.go ├── design ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── yos │ │ └── clash │ │ └── material │ │ └── design │ │ ├── AccessControlDesign.kt │ │ ├── ApkBrokenDesign.kt │ │ ├── AppCrashedDesign.kt │ │ ├── AppSettingsDesign.kt │ │ ├── Design.kt │ │ ├── FilesDesign.kt │ │ ├── HelpDesign.kt │ │ ├── LogcatDesign.kt │ │ ├── LogsDesign.kt │ │ ├── MainDesign.kt │ │ ├── NetworkSettingsDesign.kt │ │ ├── NewProfileDesign.kt │ │ ├── OverrideSettingsDesign.kt │ │ ├── ProfilesDesign.kt │ │ ├── PropertiesDesign.kt │ │ ├── ProvidersDesign.kt │ │ ├── ProxyDesign.kt │ │ ├── SettingsDesign.kt │ │ ├── YosConfigAchieve.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 │ │ └── SideloadProviderAdapter.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 │ │ ├── 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 │ ├── drawable │ ├── 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_info.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 │ ├── yos_shape.xml │ └── yos_shape_color.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_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-v23 │ └── themes.xml │ ├── values-v27 │ └── themes.xml │ ├── values-v29 │ └── themes.xml │ ├── values-v31 │ └── colors.xml │ ├── values-v34 │ └── colors.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 ├── gradle.properties ├── gradle └── 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 ├── service ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── yos │ │ └── clash │ │ └── material │ │ └── 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 │ │ │ ├── SideloadDatabaseModule.kt │ │ │ ├── StaticNotificationModule.kt │ │ │ ├── SuspendModule.kt │ │ │ ├── TimeZoneModule.kt │ │ │ └── TunModule.kt │ │ ├── data │ │ ├── Converters.kt │ │ ├── Daos.kt │ │ ├── Database.kt │ │ ├── Imported.kt │ │ ├── ImportedDao.kt │ │ ├── Pending.kt │ │ ├── PendingDao.kt │ │ ├── ProviderMoreInfo.kt │ │ ├── ProviderMoreInfoDao.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 │ │ ├── sideload │ │ └── ExternalGeoip.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-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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02-feature-request-en.yml: -------------------------------------------------------------------------------- 1 | name: "[English] Feature Request" 2 | description: "Create a report to help us improve" 3 | title: "[Feature Request] " 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this feature request! 9 | 10 | NOTE: Be sure to put a clear and concise title **AFTER** `[Feature Request]` in the text box above. 11 | 12 | 13 | - type: textarea 14 | id: "description" 15 | attributes: 16 | label: "Feature Description" 17 | description: | 18 | A clear and concise description of the feature. 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: "additional" 23 | attributes: 24 | label: "Additional" 25 | description: | 26 | Add any other context or screenshots about the feature request here. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/04-feature-request-zh-cn.yml: -------------------------------------------------------------------------------- 1 | name: "[简体中文] 功能请求" 2 | description: "您希望的能够在应用中增加功能" 3 | title: "[Feature Request] " 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | 感谢您在百忙之中填写此功能请求报告。 9 | 10 | 注意: 请务必在上方文本框的 `[Feature Request]` **之后**填写清晰明了的标题。 11 | 12 | 13 | - type: textarea 14 | id: "description" 15 | attributes: 16 | label: "功能描述" 17 | description: | 18 | 简介明了的描述此功能。 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: "additional" 23 | attributes: 24 | label: "附加信息" 25 | description: | 26 | 与此功能相关的其他附加信息。 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.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 | /core/src/main/golang/.idea/* 23 | /core/src/foss/golang/.idea/* 24 | /core/src/premium/golang/.idea/* 25 | 26 | # KeyStore 27 | signing.properties 28 | *.keystore 29 | *.jks 30 | 31 | # clion cmake build 32 | cmake-build-* 33 | 34 | # local.properties 35 | local.properties 36 | 37 | 38 | # tracker 39 | tracker.properties 40 | 41 | # vscode 42 | .vscode 43 | 44 | # cxx 45 | .cxx 46 | 47 | *.hprof 48 | 49 | # firebase 50 | google-services.json 51 | 52 | # Dolphin 53 | .directory 54 | 55 | # logs 56 | *.log 57 | 58 | # MacOS 59 | .DS_Store 60 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "clash-foss"] 2 | path = core/src/foss/golang/clash 3 | url = https://github.com/xuhaoyang/ClashForAndroid.git 4 | [submodule "clash-premium"] 5 | path = core/src/premium/golang/clash 6 | url = https://github.com/xuhaoyang/ClashForAndroid.git 7 | -------------------------------------------------------------------------------- /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/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("android") 3 | kotlin("kapt") 4 | id("com.android.application") 5 | } 6 | 7 | dependencies { 8 | repositories { 9 | mavenLocal() 10 | mavenCentral() 11 | gradlePluginPortal() 12 | google() 13 | maven("https://jitpack.io") 14 | maven("https://oss.sonatype.org/content/repositories/snapshots/") 15 | maven("https://maven.kr328.app/releases") 16 | } 17 | compileOnly(project(":hideapi")) 18 | 19 | implementation(project(":core")) 20 | implementation(project(":service")) 21 | implementation(project(":design")) 22 | implementation(project(":common")) 23 | 24 | implementation(libs.kotlin.coroutine) 25 | implementation(libs.androidx.core) 26 | implementation(libs.androidx.activity) 27 | implementation(libs.androidx.fragment) 28 | implementation(libs.androidx.appcompat) 29 | implementation(libs.androidx.coordinator) 30 | implementation(libs.androidx.recyclerview) 31 | implementation(libs.google.material) 32 | implementation(libs.androidx.splashscreen) 33 | implementation(libs.getactivity.xxpermission) 34 | } 35 | 36 | tasks.getByName("clean", type = Delete::class) { 37 | delete(file("release")) 38 | } 39 | /* 40 | android { 41 | defaultConfig { 42 | applicationId = "yos.clash.material" 43 | } 44 | } 45 | */ 46 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/yos/clash/material/ApkBrokenActivity.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import yos.clash.material.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/yos/clash/material/AppCrashedActivity.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material 2 | 3 | import yos.clash.material.common.compat.versionCodeCompat 4 | import yos.clash.material.common.log.Log 5 | import yos.clash.material.design.AppCrashedDesign 6 | import yos.clash.material.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/yos/clash/material/HelpActivity.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material 2 | 3 | import android.content.Intent 4 | import yos.clash.material.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/yos/clash/material/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen 6 | import yos.clash.material.common.Global 7 | import yos.clash.material.common.compat.currentProcessName 8 | import yos.clash.material.common.log.Log 9 | import yos.clash.material.remote.Remote 10 | import yos.clash.material.service.util.sendServiceRecreated 11 | 12 | @Suppress("unused") 13 | class MainApplication : Application() { 14 | override fun attachBaseContext(base: Context?) { 15 | super.attachBaseContext(base) 16 | 17 | Global.init(this) 18 | } 19 | 20 | override fun onCreate() { 21 | super.onCreate() 22 | val processName = currentProcessName 23 | 24 | Log.d("Process $processName started") 25 | 26 | if (processName == packageName) { 27 | Remote.launch() 28 | } else { 29 | sendServiceRecreated() 30 | } 31 | } 32 | 33 | fun finalize() { 34 | Global.destroy() 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/yos/clash/material/NetworkSettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material 2 | 3 | import yos.clash.material.common.util.intent 4 | import yos.clash.material.design.NetworkSettingsDesign 5 | import yos.clash.material.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/yos/clash/material/RestartReceiver.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import yos.clash.material.service.StatusProvider 7 | import yos.clash.material.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/yos/clash/material/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material 2 | 3 | import yos.clash.material.common.util.intent 4 | import yos.clash.material.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 | } 28 | } 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/yos/clash/material/log/LogcatFilter.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.log 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.core.model.LogMessage 5 | import yos.clash.material.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/yos/clash/material/log/LogcatReader.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.log 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.core.model.LogMessage 5 | import yos.clash.material.design.model.LogFile 6 | import yos.clash.material.util.logsDir 7 | import java.io.BufferedReader 8 | import java.io.FileReader 9 | import java.util.* 10 | 11 | class LogcatReader(context: Context, file: LogFile) : AutoCloseable { 12 | private val reader = BufferedReader(FileReader(context.logsDir.resolve(file.fileName))) 13 | 14 | override fun close() { 15 | reader.close() 16 | } 17 | 18 | fun readAll(): List { 19 | return reader.lineSequence() 20 | .map { it.trim() } 21 | .filter { !it.startsWith("#") } 22 | .map { it.split(":", limit = 3) } 23 | .map { 24 | LogMessage( 25 | time = Date(it[0].toLong()), 26 | level = LogMessage.Level.valueOf(it[1]), 27 | message = it[2] 28 | ) 29 | } 30 | .toList() 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/yos/clash/material/log/LogcatWriter.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.log 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.core.model.LogMessage 5 | import yos.clash.material.design.model.LogFile 6 | import yos.clash.material.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/yos/clash/material/log/SystemLogcat.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.log 2 | 3 | object SystemLogcat { 4 | private val command = arrayOf( 5 | "logcat", 6 | "-d", 7 | "-s", 8 | "Go", 9 | "DEBUG", 10 | "AndroidRuntime", 11 | "ClashForAndroid", 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/yos/clash/material/remote/StatusClient.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.remote 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | import yos.clash.material.common.constants.Authorities 6 | import yos.clash.material.common.log.Log 7 | import yos.clash.material.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/yos/clash/material/store/AppStore.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.store 2 | 3 | import android.content.Context 4 | import yos.clash.material.common.store.Store 5 | import yos.clash.material.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/yos/clash/material/store/TipsStore.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.store 2 | 3 | import android.content.Context 4 | import yos.clash.material.common.store.Store 5 | import yos.clash.material.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/yos/clash/material/util/Clash.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.util 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.VpnService 6 | import yos.clash.material.common.compat.startForegroundServiceCompat 7 | import yos.clash.material.common.constants.Intents 8 | import yos.clash.material.common.util.intent 9 | import yos.clash.material.design.store.UiStore 10 | import yos.clash.material.service.ClashService 11 | import yos.clash.material.service.TunService 12 | import yos.clash.material.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/yos/clash/material/util/Content.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/util/Files.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.util 2 | 3 | import android.content.Context 4 | import java.io.File 5 | 6 | val Context.logsDir: File 7 | get() = cacheDir.resolve("logs") -------------------------------------------------------------------------------- /app/src/main/java/yos/clash/material/util/Service.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/util/Uri.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.util 2 | 3 | import android.net.Uri 4 | 5 | val Uri.fileName: String? 6 | get() = schemeSpecificPart.split("/").lastOrNull() -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /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/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/app/src/main/res/mipmap-xhdpi/ic_banner.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/xml/full_backup_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 15 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("android") 3 | id("com.android.library") 4 | } 5 | 6 | dependencies { 7 | compileOnly(project(":hideapi")) 8 | 9 | implementation(libs.kotlin.coroutine) 10 | implementation(libs.androidx.core) 11 | } 12 | -------------------------------------------------------------------------------- /common/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/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 | 4 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/Global.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common 2 | 3 | import android.app.Application 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.cancel 7 | 8 | object Global : CoroutineScope by CoroutineScope(Dispatchers.IO) { 9 | val application: Application 10 | get() = application_ 11 | 12 | private lateinit var application_: Application 13 | 14 | fun init(application: Application) { 15 | this.application_ = application 16 | } 17 | 18 | fun destroy() { 19 | cancel() 20 | } 21 | } -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/compat/App.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.compat 2 | 3 | import android.app.ActivityThread 4 | import android.app.Application 5 | import android.graphics.drawable.AdaptiveIconDrawable 6 | import android.graphics.drawable.Drawable 7 | import android.os.Build 8 | import yos.clash.material.common.log.Log 9 | 10 | val Application.currentProcessName: String 11 | get() { 12 | if (Build.VERSION.SDK_INT >= 28) 13 | return Application.getProcessName() 14 | 15 | return try { 16 | ActivityThread.currentProcessName() 17 | } catch (throwable: Throwable) { 18 | Log.w("Resolve process name: $throwable") 19 | 20 | packageName 21 | } 22 | } 23 | 24 | fun Drawable.foreground(): Drawable { 25 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && 26 | this is AdaptiveIconDrawable && this.background == null 27 | ) { 28 | return this.foreground 29 | } 30 | return this 31 | } 32 | -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/compat/Context.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package yos.clash.material.common.compat 4 | 5 | import android.content.Context 6 | import android.graphics.drawable.Drawable 7 | import androidx.annotation.ColorRes 8 | import androidx.annotation.DrawableRes 9 | import androidx.core.content.ContextCompat 10 | 11 | fun Context.getColorCompat(@ColorRes id: Int): Int { 12 | return ContextCompat.getColor(this, id) 13 | } 14 | 15 | fun Context.getDrawableCompat(@DrawableRes id: Int): Drawable? { 16 | return ContextCompat.getDrawable(this, id) 17 | } -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/compat/Html.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package yos.clash.material.common.compat 4 | 5 | import android.os.Build 6 | import android.text.Html 7 | import android.text.Spanned 8 | 9 | fun fromHtmlCompat(content: String): Spanned { 10 | return if (Build.VERSION.SDK_INT >= 24) { 11 | Html.fromHtml(content, Html.FROM_HTML_MODE_COMPACT) 12 | } else { 13 | Html.fromHtml(content) 14 | } 15 | } -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/compat/Intents.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.compat 2 | 3 | import android.app.PendingIntent 4 | import android.os.Build 5 | 6 | fun pendingIntentFlags(flags: Int, mutable: Boolean = false): Int { 7 | return if (Build.VERSION.SDK_INT >= 24) { 8 | if (Build.VERSION.SDK_INT > 30 && mutable) { 9 | flags or PendingIntent.FLAG_MUTABLE 10 | } else { 11 | flags or PendingIntent.FLAG_IMMUTABLE 12 | } 13 | } else { 14 | flags 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/compat/Package.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package yos.clash.material.common.compat 4 | 5 | import android.content.pm.PackageInfo 6 | 7 | val PackageInfo.versionCodeCompat: Long 8 | get() { 9 | return if (android.os.Build.VERSION.SDK_INT >= 28) { 10 | longVersionCode 11 | } else { 12 | versionCode.toLong() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/compat/Resource.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package yos.clash.material.common.compat 4 | 5 | import android.content.res.Configuration 6 | import android.os.Build 7 | import java.util.* 8 | 9 | val Configuration.preferredLocale: Locale 10 | get() { 11 | return if (Build.VERSION.SDK_INT >= 24) { 12 | locales[0] 13 | } else { 14 | locale 15 | } 16 | } -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/compat/Services.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.compat 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 >= 26) { 9 | startForegroundService(intent) 10 | } else { 11 | startService(intent) 12 | } 13 | } -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/compat/View.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package yos.clash.material.common.compat 4 | 5 | import android.os.Build 6 | import android.widget.TextView 7 | import androidx.annotation.StyleRes 8 | 9 | var TextView.textAppearance: Int 10 | get() = throw UnsupportedOperationException("set value only") 11 | set(@StyleRes value) { 12 | if (Build.VERSION.SDK_INT >= 23) { 13 | setTextAppearance(value) 14 | } else { 15 | setTextAppearance(context, value) 16 | } 17 | } -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/constants/Authorities.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.constants 2 | 3 | import yos.clash.material.common.util.packageName 4 | 5 | object Authorities { 6 | val STATUS_PROVIDER = "$packageName.status" 7 | val SETTINGS_PROVIDER = "$packageName.settings" 8 | val FILES_PROVIDER = "$packageName.files" 9 | } -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/constants/Components.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.constants 2 | 3 | import android.content.ComponentName 4 | import yos.clash.material.common.util.packageName 5 | 6 | object Components { 7 | private const val componentsPackageName = "yos.clash.material" 8 | 9 | val MAIN_ACTIVITY = ComponentName(packageName, "$componentsPackageName.MainActivity") 10 | val PROPERTIES_ACTIVITY = ComponentName(packageName, "$componentsPackageName.PropertiesActivity") 11 | } -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/constants/Intents.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.constants 2 | 3 | import yos.clash.material.common.util.packageName 4 | 5 | object Intents { 6 | // Public 7 | val ACTION_PROVIDE_URL = "$packageName.action.PROVIDE_URL" 8 | 9 | const val EXTRA_NAME = "name" 10 | 11 | // Self 12 | val ACTION_SERVICE_RECREATED = "$packageName.intent.action.CLASH_RECREATED" 13 | val ACTION_CLASH_STARTED = "$packageName.intent.action.CLASH_STARTED" 14 | val ACTION_CLASH_STOPPED = "$packageName.intent.action.CLASH_STOPPED" 15 | val ACTION_CLASH_REQUEST_STOP = "$packageName.intent.action.CLASH_REQUEST_STOP" 16 | val ACTION_PROFILE_CHANGED = "$packageName.intent.action.PROFILE_CHANGED" 17 | val ACTION_PROFILE_REQUEST_UPDATE = "$packageName.intent.action.REQUEST_UPDATE" 18 | val ACTION_PROFILE_SCHEDULE_UPDATES = "$packageName.intent.action.SCHEDULE_UPDATES" 19 | val ACTION_PROFILE_LOADED = "$packageName.intent.action.PROFILE_LOADED" 20 | val ACTION_OVERRIDE_CHANGED = "$packageName.intent.action.OVERRIDE_CHANGED" 21 | 22 | const val EXTRA_STOP_REASON = "stop_reason" 23 | const val EXTRA_UUID = "uuid" 24 | } -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/constants/Metadata.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.constants 2 | 3 | import yos.clash.material.common.util.packageName 4 | 5 | object Metadata { 6 | val GEOIP_FILE_NAME = "$packageName.GEOIP_FILE_NAME" 7 | } -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/constants/Permissions.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.constants 2 | 3 | import yos.clash.material.common.util.packageName 4 | 5 | object Permissions { 6 | val RECEIVE_SELF_BROADCASTS = "$packageName.permission.RECEIVE_BROADCASTS" 7 | } -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/id/UndefinedIds.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.id 2 | 3 | object UndefinedIds { 4 | private const val PREFIX = 0x14000000 5 | private const val MASK = 0x00FFFFFF 6 | 7 | private var current: Int = 0 8 | 9 | @Synchronized 10 | fun next(): Int { 11 | current = ((current and MASK) + 1 or PREFIX) 12 | 13 | return current 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/log/Log.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.log 2 | 3 | object Log { 4 | private const val TAG = "ClashForAndroid" 5 | 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 | fun f(message: String, throwable: Throwable) = 22 | android.util.Log.wtf(message, throwable) 23 | } 24 | -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/store/StoreProvider.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.store 2 | 3 | interface StoreProvider { 4 | fun getInt(key: String, defaultValue: Int): Int 5 | fun setInt(key: String, value: Int) 6 | 7 | fun getLong(key: String, defaultValue: Long): Long 8 | fun setLong(key: String, value: Long) 9 | 10 | fun getString(key: String, defaultValue: String): String 11 | fun setString(key: String, value: String) 12 | 13 | fun getStringSet(key: String, defaultValue: Set): Set 14 | fun setStringSet(key: String, value: Set) 15 | 16 | fun getBoolean(key: String, defaultValue: Boolean): Boolean 17 | fun setBoolean(key: String, value: Boolean) 18 | } -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/util/Components.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.util 2 | 3 | import android.content.ComponentName 4 | import android.content.Intent 5 | import yos.clash.material.common.Global 6 | import kotlin.reflect.KClass 7 | 8 | val KClass<*>.componentName: ComponentName 9 | get() = ComponentName(Global.application.packageName, this.java.name) 10 | 11 | val KClass<*>.intent: Intent 12 | get() = Intent(Global.application, this.java) -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/util/Global.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.util 2 | 3 | import yos.clash.material.common.Global 4 | 5 | val packageName: String = Global.application.packageName -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/util/Intent.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.util 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import java.util.* 6 | 7 | fun Intent.grantPermissions(read: Boolean = true, write: Boolean = true): Intent { 8 | var flags = 0 9 | 10 | if (read) 11 | flags = flags or Intent.FLAG_GRANT_READ_URI_PERMISSION 12 | 13 | if (write) 14 | flags = flags or Intent.FLAG_GRANT_WRITE_URI_PERMISSION 15 | 16 | addFlags(flags) 17 | 18 | return this 19 | } 20 | 21 | var Intent.fileName: String? 22 | get() { 23 | return data?.takeIf { it.scheme == "file" }?.schemeSpecificPart 24 | } 25 | set(value) { 26 | data = Uri.fromParts("file", value, null) 27 | } 28 | 29 | var Intent.uuid: UUID? 30 | get() { 31 | return data?.takeIf { it.scheme == "uuid" }?.schemeSpecificPart?.let(UUID::fromString) 32 | } 33 | set(value) { 34 | data = Uri.fromParts("uuid", value.toString(), null) 35 | } 36 | 37 | fun Intent.setUUID(uuid: UUID): Intent { 38 | this.uuid = uuid 39 | 40 | return this 41 | } 42 | 43 | fun Intent.setFileName(fileName: String): Intent { 44 | this.fileName = fileName 45 | 46 | return this 47 | } -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/util/Patterns.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.util 2 | 3 | val PatternFileName = Regex("[^*&%\\n\\r/]+") -------------------------------------------------------------------------------- /common/src/main/java/yos/clash/material/common/util/Ticker.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.common.util 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.channels.Channel 5 | import kotlinx.coroutines.delay 6 | import kotlinx.coroutines.isActive 7 | import kotlinx.coroutines.launch 8 | 9 | fun CoroutineScope.ticker(period: Long): Channel { 10 | val channel = Channel(Channel.RENDEZVOUS) 11 | 12 | launch { 13 | try { 14 | while (isActive) { 15 | channel.send(System.currentTimeMillis()) 16 | 17 | delay(period) 18 | } 19 | } catch (ignored: Exception) { 20 | 21 | } 22 | } 23 | 24 | return channel 25 | } -------------------------------------------------------------------------------- /common/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 接收 Clash You 廣播 4 | 接收來自 Clash You 內部的廣播 5 | 6 | -------------------------------------------------------------------------------- /common/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 接收 Clash You 广播 4 | 接收来自 Clash You 内部的广播 5 | -------------------------------------------------------------------------------- /common/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Receive Clash You Broadcasts 4 | Receive broadcasts of Clash You services 5 | 6 | -------------------------------------------------------------------------------- /core/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class kotlinx.coroutines.CompletableDeferred { 2 | *; 3 | } 4 | 5 | -keep class kotlin.Unit { 6 | *; 7 | } 8 | 9 | -keepattributes *Annotation*, InnerClasses 10 | -dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations 11 | 12 | # kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer 13 | -keepclassmembers class kotlinx.serialization.json.** { 14 | *** Companion; 15 | } 16 | -keepclasseswithmembers class kotlinx.serialization.json.** { 17 | kotlinx.serialization.KSerializer serializer(...); 18 | } 19 | -------------------------------------------------------------------------------- /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/foss/golang/main.go: -------------------------------------------------------------------------------- 1 | package golang 2 | 3 | import ( 4 | _ "cfa/native/all" 5 | ) 6 | -------------------------------------------------------------------------------- /core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /core/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(clash-bridge C) 4 | 5 | set(CMAKE_POSITION_INDEPENDENT_CODE on) 6 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3") 7 | 8 | set(GO_OUTPUT_BASE ${GO_OUTPUT}/${FLAVOR_NAME}) 9 | 10 | if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") 11 | set(GO_OUTPUT_BASE "${GO_OUTPUT_BASE}Debug") 12 | elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "Release") 13 | set(GO_OUTPUT_BASE "${GO_OUTPUT_BASE}Release") 14 | elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") 15 | set(GO_OUTPUT_BASE "${GO_OUTPUT_BASE}Release") 16 | else () 17 | message(FATAL_ERROR "Unknown build type ${CMAKE_BUILD_TYPE}") 18 | endif () 19 | 20 | include_directories("${GO_OUTPUT_BASE}/${CMAKE_ANDROID_ARCH_ABI}") 21 | include_directories("${GO_SOURCE}") 22 | 23 | link_directories("${GO_OUTPUT_BASE}/${CMAKE_ANDROID_ARCH_ABI}") 24 | 25 | add_library(bridge SHARED main.c jni_helper.c bridge_helper.c) 26 | target_link_libraries(bridge log clash) -------------------------------------------------------------------------------- /core/src/main/cpp/bridge_helper.c: -------------------------------------------------------------------------------- 1 | #include "bridge_helper.h" 2 | 3 | uint64_t down_scale_traffic(uint64_t value) { 4 | if (value > 1042 * 1024 * 1024) 5 | return ((value * 100u / 1024u / 1024u / 1024u) & 0x3FFFFFFFu) | (3u << 30u); 6 | if (value > 1024 * 1024) 7 | return ((value * 100u / 1024u / 1024u) & 0x3FFFFFFFu) | (2u << 30u); 8 | if (value > 1024) 9 | return ((value * 100u / 1024u) & 0x3FFFFFFFu) | (1u << 30u); 10 | return value & 0x3FFFFFFFu; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /core/src/main/cpp/bridge_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | uint64_t down_scale_traffic(uint64_t value); -------------------------------------------------------------------------------- /core/src/main/cpp/jni_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct _scoped_jni { 10 | JNIEnv *env; 11 | int require_release; 12 | }; 13 | 14 | extern void initialize_jni(JavaVM *vm, JNIEnv *env); 15 | extern jstring jni_new_string(JNIEnv *env, const char *str); 16 | extern char *jni_get_string(JNIEnv *env, jstring str); 17 | extern int jni_catch_exception(JNIEnv *env); 18 | extern void jni_attach_thread(struct _scoped_jni *jni); 19 | extern void jni_detach_thread(struct _scoped_jni *env); 20 | extern void release_string(char **str); 21 | 22 | #define ATTACH_JNI() __attribute__((unused, cleanup(jni_detach_thread))) \ 23 | struct _scoped_jni _jni; \ 24 | jni_attach_thread(&_jni); \ 25 | JNIEnv *env = _jni.env 26 | 27 | #define scoped_string __attribute__((cleanup(release_string))) char* 28 | 29 | #define find_class(name) (*env)->FindClass(env, name) 30 | #define find_method(cls, name, signature) (*env)->GetMethodID(env, cls, name, signature) 31 | #define new_global(obj) (*env)->NewGlobalRef(env, obj) 32 | #define del_global(obj) (*env)->DeleteGlobalRef(env, obj) 33 | #define get_string(jstr) jni_get_string(env, jstr) 34 | #define new_string(cstr) jni_new_string(env, cstr) -------------------------------------------------------------------------------- /core/src/main/golang/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /core/src/main/golang/.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /core/src/main/golang/go.mod: -------------------------------------------------------------------------------- 1 | module cfa 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/Dreamacro/clash v1.7.1 7 | github.com/Kr328/tun2socket v0.0.0-20220414050025-d07c78d06d34 8 | github.com/dlclark/regexp2 v1.4.0 9 | github.com/miekg/dns v1.1.43 10 | github.com/oschwald/geoip2-golang v1.5.0 11 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c 12 | gopkg.in/yaml.v2 v2.4.0 13 | ) 14 | 15 | require ( 16 | github.com/Dreamacro/go-shadowsocks2 v0.1.7 // indirect 17 | github.com/gofrs/uuid v4.0.0+incompatible // indirect 18 | github.com/gorilla/websocket v1.4.2 // indirect 19 | github.com/insomniacslk/dhcp v0.0.0-20210827173440-b95caade3eac // indirect 20 | github.com/oschwald/maxminddb-golang v1.8.0 // indirect 21 | github.com/sirupsen/logrus v1.8.1 // indirect 22 | github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect 23 | go.uber.org/atomic v1.9.0 // indirect 24 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect 25 | golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f // indirect 26 | golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect 27 | golang.org/x/text v0.3.6 // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /core/src/main/golang/native/all/imports.go: -------------------------------------------------------------------------------- 1 | package all 2 | 3 | import ( 4 | _ "cfa/native/app" 5 | _ "cfa/native/common" 6 | _ "cfa/native/config" 7 | _ "cfa/native/delegate" 8 | _ "cfa/native/platform" 9 | _ "cfa/native/proxy" 10 | _ "cfa/native/tun" 11 | _ "cfa/native/tunnel" 12 | 13 | _ "golang.org/x/sync/semaphore" 14 | 15 | _ "github.com/Dreamacro/clash/log" 16 | ) 17 | -------------------------------------------------------------------------------- /core/src/main/golang/native/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //#include "bridge.h" 4 | import "C" 5 | 6 | import ( 7 | "errors" 8 | "unsafe" 9 | 10 | "cfa/native/app" 11 | 12 | "github.com/Dreamacro/clash/log" 13 | ) 14 | 15 | func openRemoteContent(url string) (int, error) { 16 | u := C.CString(url) 17 | e := (*C.char)(C.malloc(1024)) 18 | 19 | log.Debugln("Open remote url: %s", url) 20 | 21 | defer C.free(unsafe.Pointer(e)) 22 | 23 | fd := C.open_content(u, e, 1024) 24 | 25 | if fd < 0 { 26 | return -1, errors.New(C.GoString(e)) 27 | } 28 | 29 | return int(fd), nil 30 | } 31 | 32 | //export notifyDnsChanged 33 | func notifyDnsChanged(dnsList C.c_string) { 34 | d := C.GoString(dnsList) 35 | 36 | app.NotifyDnsChanged(d) 37 | } 38 | 39 | //export notifyInstalledAppsChanged 40 | func notifyInstalledAppsChanged(uids C.c_string) { 41 | u := C.GoString(uids) 42 | 43 | app.NotifyInstallAppsChanged(u) 44 | } 45 | 46 | //export notifyTimeZoneChanged 47 | func notifyTimeZoneChanged(name C.c_string, offset C.int) { 48 | app.NotifyTimeZoneChanged(C.GoString(name), int(offset)) 49 | } 50 | 51 | 52 | //export queryConfiguration 53 | func queryConfiguration() *C.char { 54 | response := &struct{}{} 55 | 56 | return marshalJson(&response) 57 | } 58 | 59 | func init() { 60 | app.ApplyContentContext(openRemoteContent) 61 | } -------------------------------------------------------------------------------- /core/src/main/golang/native/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | var appVersionName string 10 | var platformVersion int 11 | var installedAppsUid = map[int]string{} 12 | 13 | func ApplyVersionName(versionName string) { 14 | appVersionName = versionName 15 | } 16 | 17 | func ApplyPlatformVersion(version int) { 18 | platformVersion = version 19 | } 20 | 21 | func VersionName() string { 22 | return appVersionName 23 | } 24 | 25 | func PlatformVersion() int { 26 | return platformVersion 27 | } 28 | 29 | func NotifyInstallAppsChanged(uidList string) { 30 | uids := map[int]string{} 31 | 32 | for _, item := range strings.Split(uidList, ",") { 33 | kv := strings.Split(item, ":") 34 | if len(kv) == 2 { 35 | uid, err := strconv.Atoi(kv[0]) 36 | if err != nil { 37 | continue 38 | } 39 | 40 | uids[uid] = kv[1] 41 | } 42 | } 43 | 44 | installedAppsUid = uids 45 | } 46 | 47 | func QueryAppByUid(uid int) string { 48 | return installedAppsUid[uid] 49 | } 50 | 51 | func NotifyTimeZoneChanged(name string, offset int) { 52 | time.Local = time.FixedZone(name, offset) 53 | } -------------------------------------------------------------------------------- /core/src/main/golang/native/app/content.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "syscall" 7 | ) 8 | 9 | var openContentImpl = func(url string) (int, error) { 10 | return -1, errors.New("not implement") 11 | } 12 | 13 | func OpenContent(url string) (*os.File, error) { 14 | fd, err := openContentImpl(url) 15 | 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | _ = syscall.SetNonblock(fd, true) 21 | 22 | return os.NewFile(uintptr(fd), "fd"), nil 23 | } 24 | 25 | func ApplyContentContext(openContent func(string) (int, error)) { 26 | openContentImpl = openContent 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/golang/native/app/dns.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/Dreamacro/clash/dns" 7 | ) 8 | 9 | func NotifyDnsChanged(dnsList string) { 10 | dL := strings.Split(dnsList, ",") 11 | 12 | ns := make([]dns.NameServer, 0, len(dnsList)) 13 | for _, d := range dL { 14 | ns = append(ns, dns.NameServer{Addr: d}) 15 | } 16 | 17 | dns.UpdateSystemDNS(dL) 18 | dns.FlushCacheWithDefaultResolver() 19 | } 20 | 21 | -------------------------------------------------------------------------------- /core/src/main/golang/native/app/tun.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | 7 | "cfa/native/platform" 8 | ) 9 | 10 | var markSocketImpl func(fd int) 11 | var querySocketUidImpl func(protocol int, source, target string) int 12 | 13 | func MarkSocket(fd int) { 14 | markSocketImpl(fd) 15 | } 16 | 17 | func QuerySocketUid(source, target net.Addr) int { 18 | var protocol int 19 | 20 | switch source.Network() { 21 | case "udp", "udp4", "udp6": 22 | protocol = syscall.IPPROTO_UDP 23 | case "tcp", "tcp4", "tcp6": 24 | protocol = syscall.IPPROTO_TCP 25 | default: 26 | return -1 27 | } 28 | 29 | if PlatformVersion() < 29 { 30 | return platform.QuerySocketUidFromProcFs(source, target) 31 | } 32 | 33 | return querySocketUidImpl(protocol, source.String(), target.String()) 34 | } 35 | 36 | func ApplyTunContext(markSocket func(fd int), querySocketUid func(int, string, string) int) { 37 | if markSocket == nil { 38 | markSocket = func(fd int) {} 39 | } 40 | 41 | if querySocketUid == nil { 42 | querySocketUid = func(int, string, string) int { return -1 } 43 | } 44 | 45 | markSocketImpl = markSocket 46 | querySocketUidImpl = querySocketUid 47 | } 48 | 49 | func init() { 50 | ApplyTunContext(nil, nil) 51 | } 52 | -------------------------------------------------------------------------------- /core/src/main/golang/native/app/ui.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/dlclark/regexp2" 5 | 6 | "github.com/Dreamacro/clash/log" 7 | ) 8 | 9 | var uiSubtitlePattern *regexp2.Regexp 10 | 11 | func ApplySubtitlePattern(pattern string) { 12 | if pattern == "" { 13 | uiSubtitlePattern = nil 14 | 15 | return 16 | } 17 | 18 | if o := uiSubtitlePattern; o != nil && o.String() == pattern { 19 | return 20 | } 21 | 22 | reg, err := regexp2.Compile(pattern, regexp2.IgnoreCase|regexp2.Compiled) 23 | if err == nil { 24 | uiSubtitlePattern = reg 25 | } else { 26 | uiSubtitlePattern = nil 27 | 28 | log.Warnln("Compile ui-subtitle-pattern: %s", err.Error()) 29 | } 30 | } 31 | 32 | func SubtitlePattern() *regexp2.Regexp { 33 | return uiSubtitlePattern 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/golang/native/common/path.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "strings" 4 | 5 | func ResolveAsRoot(path string) string { 6 | directories := strings.Split(path, "/") 7 | result := make([]string, 0, len(directories)) 8 | 9 | for _, directory := range directories { 10 | switch directory { 11 | case "", ".": 12 | continue 13 | case "..": 14 | if len(result) > 0 { 15 | result = result[:len(result)-1] 16 | } 17 | default: 18 | result = append(result, directory) 19 | } 20 | } 21 | 22 | return strings.Join(result, "/") 23 | } 24 | -------------------------------------------------------------------------------- /core/src/main/golang/native/config/defaults.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | var ( 4 | defaultNameServers = []string{ 5 | "223.5.5.5", 6 | "119.29.29.29", 7 | "8.8.4.4", 8 | "1.0.0.1", 9 | } 10 | defaultFakeIPFilter = []string{ 11 | // Stun Services 12 | "+.stun.*.*", 13 | "+.stun.*.*.*", 14 | "+.stun.*.*.*.*", 15 | "+.stun.*.*.*.*.*", 16 | 17 | // Google Voices 18 | "lens.l.google.com", 19 | 20 | // Nintendo Switch STUN 21 | "*.n.n.srv.nintendo.net", 22 | 23 | // PlayStation STUN 24 | "+.stun.playstation.net", 25 | 26 | // XBox 27 | "xbox.*.*.microsoft.com", 28 | "*.*.xboxlive.com", 29 | 30 | // Microsoft Captive Portal 31 | "*.msftncsi.com", 32 | "*.msftconnecttest.com", 33 | 34 | // Bilibili CDN 35 | "*.mcdn.bilivideo.cn", 36 | 37 | // Windows Default LAN WorkGroup 38 | "WORKGROUP", 39 | } 40 | defaultFakeIPRange = "28.0.0.0/8" 41 | ) 42 | -------------------------------------------------------------------------------- /core/src/main/golang/native/config/process_open.go: -------------------------------------------------------------------------------- 1 | //go:build !premium 2 | 3 | package config 4 | 5 | import "github.com/Dreamacro/clash/config" 6 | 7 | func patchTun(cfg *config.RawConfig, _ string) error { 8 | return nil 9 | } 10 | -------------------------------------------------------------------------------- /core/src/main/golang/native/config/process_premium.go: -------------------------------------------------------------------------------- 1 | //go:build premium 2 | 3 | package config 4 | 5 | import "github.com/Dreamacro/clash/config" 6 | 7 | func patchTun(cfg *config.RawConfig, _ string) error { 8 | cfg.Tun.Enable = false 9 | 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /core/src/main/golang/native/config/provider_open.go: -------------------------------------------------------------------------------- 1 | //go:build !premium 2 | 3 | package config 4 | 5 | import ( 6 | "io" 7 | 8 | "github.com/Dreamacro/clash/config" 9 | ) 10 | 11 | func forEachProviders(rawCfg *config.RawConfig, fun func(index int, total int, key string, provider map[string]any)) { 12 | total := len(rawCfg.ProxyProvider) 13 | index := 0 14 | 15 | for k, v := range rawCfg.ProxyProvider { 16 | fun(index, total, k, v) 17 | 18 | index++ 19 | } 20 | } 21 | 22 | func destroyProviders(cfg *config.Config) { 23 | for _, p := range cfg.Providers { 24 | _ = p.(io.Closer).Close() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/golang/native/config/provider_premium.go: -------------------------------------------------------------------------------- 1 | //go:build premium 2 | 3 | package config 4 | 5 | import ( 6 | "io" 7 | 8 | "github.com/Dreamacro/clash/config" 9 | ) 10 | 11 | func forEachProviders(rawCfg *config.RawConfig, fun func(index int, total int, key string, provider map[string]any)) { 12 | total := len(rawCfg.ProxyProvider) + len(rawCfg.RuleProvider) 13 | index := 0 14 | 15 | for k, v := range rawCfg.ProxyProvider { 16 | fun(index, total, k, v) 17 | 18 | index++ 19 | } 20 | 21 | for k, v := range rawCfg.RuleProvider { 22 | fun(index, total, k, v) 23 | 24 | index++ 25 | } 26 | } 27 | 28 | func destroyProviders(cfg *config.Config) { 29 | for _, p := range cfg.ProxyProviders { 30 | _ = p.(io.Closer).Close() 31 | } 32 | 33 | for _, p := range cfg.RuleProviders { 34 | _ = p.(io.Closer).Close() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/golang/native/debug.go: -------------------------------------------------------------------------------- 1 | // +build debug 2 | 3 | package main 4 | 5 | import ( 6 | "net/http" 7 | _ "net/http/pprof" 8 | 9 | "github.com/Dreamacro/clash/log" 10 | ) 11 | 12 | func init() { 13 | go func() { 14 | log.Debugln("pprof service listen at: 0.0.0.0:8888") 15 | 16 | _ = http.ListenAndServe("0.0.0.0:8888", nil) 17 | }() 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/golang/native/delegate/init.go: -------------------------------------------------------------------------------- 1 | package delegate 2 | 3 | import ( 4 | "errors" 5 | "syscall" 6 | 7 | "cfa/blob" 8 | 9 | "github.com/Dreamacro/clash/component/process" 10 | "github.com/Dreamacro/clash/log" 11 | 12 | "cfa/native/app" 13 | "cfa/native/platform" 14 | 15 | "github.com/Dreamacro/clash/component/dialer" 16 | "github.com/Dreamacro/clash/component/mmdb" 17 | "github.com/Dreamacro/clash/constant" 18 | ) 19 | 20 | var errBlocked = errors.New("blocked") 21 | 22 | func Init(home, versionName string, platformVersion int) { 23 | mmdb.LoadFromBytes(blob.GeoipDatabase) 24 | constant.SetHomeDir(home) 25 | app.ApplyVersionName(versionName) 26 | app.ApplyPlatformVersion(platformVersion) 27 | 28 | process.DefaultPackageNameResolver = func(metadata *constant.Metadata) (string, error) { 29 | src, dst := metadata.RawSrcAddr, metadata.RawDstAddr 30 | 31 | if src == nil || dst == nil { 32 | return "", process.ErrInvalidNetwork 33 | } 34 | 35 | uid := app.QuerySocketUid(metadata.RawSrcAddr, metadata.RawDstAddr) 36 | pkg := app.QueryAppByUid(uid) 37 | 38 | log.Debugln("[PKG] %s --> %s by %d[%s]", metadata.SourceAddress(), metadata.RemoteAddress(), uid, pkg) 39 | 40 | return pkg, nil 41 | } 42 | 43 | dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error { 44 | if platform.ShouldBlockConnection() { 45 | return errBlocked 46 | } 47 | 48 | return conn.Control(func(fd uintptr) { 49 | app.MarkSocket(int(fd)) 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/golang/native/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | #cgo LDFLAGS: -llog 5 | 6 | #include "bridge.h" 7 | */ 8 | import "C" 9 | 10 | import ( 11 | "runtime" 12 | 13 | "cfa/native/config" 14 | "cfa/native/delegate" 15 | "cfa/native/tunnel" 16 | 17 | "github.com/Dreamacro/clash/log" 18 | ) 19 | 20 | func main() { 21 | panic("Stub!") 22 | } 23 | 24 | //export coreInit 25 | func coreInit(home, versionName C.c_string, sdkVersion C.int) { 26 | h := C.GoString(home) 27 | v := C.GoString(versionName) 28 | s := int(sdkVersion) 29 | 30 | delegate.Init(h, v, s) 31 | 32 | reset() 33 | } 34 | 35 | //export reset 36 | func reset() { 37 | config.LoadDefault() 38 | tunnel.ResetStatistic() 39 | tunnel.CloseAllConnections() 40 | 41 | runtime.GC() 42 | } 43 | 44 | //export forceGc 45 | func forceGc() { 46 | go func() { 47 | log.Infoln("[APP] request force GC") 48 | 49 | runtime.GC() 50 | }() 51 | } 52 | -------------------------------------------------------------------------------- /core/src/main/golang/native/platform/limit.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package platform 4 | 5 | import "syscall" 6 | 7 | var nullFd int 8 | var maxFdCount int 9 | 10 | func init() { 11 | fd, err := syscall.Open("/dev/null", syscall.O_WRONLY, 0644) 12 | if err != nil { 13 | panic(err.Error()) 14 | } 15 | 16 | nullFd = fd 17 | 18 | var limit syscall.Rlimit 19 | 20 | if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { 21 | maxFdCount = 1024 22 | } else { 23 | maxFdCount = int(limit.Cur) 24 | } 25 | 26 | maxFdCount = maxFdCount / 4 * 3 27 | } 28 | 29 | func ShouldBlockConnection() bool { 30 | fd, err := syscall.Dup(nullFd) 31 | if err != nil { 32 | return true 33 | } 34 | 35 | _ = syscall.Close(fd) 36 | 37 | if fd > maxFdCount { 38 | return true 39 | } 40 | 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /core/src/main/golang/native/proxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //#include "bridge.h" 4 | import "C" 5 | 6 | import ( 7 | "cfa/native/proxy" 8 | ) 9 | 10 | //export startHttp 11 | func startHttp(listenAt C.c_string) *C.char { 12 | l := C.GoString(listenAt) 13 | 14 | listen, err := proxy.Start(l) 15 | if err != nil { 16 | return nil 17 | } 18 | 19 | return C.CString(listen) 20 | } 21 | 22 | //export stopHttp 23 | func stopHttp() { 24 | proxy.Stop() 25 | } -------------------------------------------------------------------------------- /core/src/main/golang/native/proxy/http.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/Dreamacro/clash/listener/http" 7 | "github.com/Dreamacro/clash/tunnel" 8 | ) 9 | 10 | var listener *http.Listener 11 | var lock sync.Mutex 12 | 13 | func Start(listen string) (listenAt string, err error) { 14 | lock.Lock() 15 | defer lock.Unlock() 16 | 17 | stopLocked() 18 | 19 | listener, err = http.NewWithAuthenticate(listen, tunnel.TCPIn(), false) 20 | if err == nil { 21 | listenAt = listener.Listener().Addr().String() 22 | } 23 | 24 | return 25 | } 26 | 27 | func Stop() { 28 | lock.Lock() 29 | defer lock.Unlock() 30 | 31 | stopLocked() 32 | } 33 | 34 | func stopLocked() { 35 | if listener != nil { 36 | listener.Close() 37 | } 38 | 39 | listener = nil 40 | } 41 | -------------------------------------------------------------------------------- /core/src/main/golang/native/trace.c: -------------------------------------------------------------------------------- 1 | #include "trace.h" 2 | 3 | #if ENABLE_TRACE 4 | 5 | void trace_method_exit(const char **name) { 6 | __android_log_print(ANDROID_LOG_VERBOSE, TAG, "TRACE-OUT %s", *name); 7 | } 8 | 9 | #endif -------------------------------------------------------------------------------- /core/src/main/golang/native/trace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "bridge.h" 4 | 5 | #include 6 | 7 | #define ENABLE_TRACE 0 8 | 9 | #if ENABLE_TRACE 10 | 11 | extern void trace_method_exit(const char **name); 12 | 13 | #define TRACE_METHOD() __attribute__((cleanup(trace_method_exit))) const char *__method_name = __FUNCTION__; __android_log_print(ANDROID_LOG_VERBOSE, TAG, "TRACE-IN %s", __method_name) 14 | 15 | #else 16 | 17 | #define TRACE_METHOD() 18 | 19 | #endif -------------------------------------------------------------------------------- /core/src/main/golang/native/tun/dns.go: -------------------------------------------------------------------------------- 1 | package tun 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/dns" 7 | 8 | D "github.com/miekg/dns" 9 | ) 10 | 11 | func shouldHijackDns(dns net.IP, target net.IP, targetPort int) bool { 12 | if targetPort != 53 { 13 | return false 14 | } 15 | 16 | return net.IPv4zero.Equal(dns) || target.Equal(dns) 17 | } 18 | 19 | func relayDns(payload []byte) ([]byte, error) { 20 | msg := &D.Msg{} 21 | if err := msg.Unpack(payload); err != nil { 22 | return nil, err 23 | } 24 | 25 | r, err := dns.ServeDNSWithDefaultServer(msg) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | r.SetRcode(msg, r.Rcode) 31 | 32 | return r.Pack() 33 | } 34 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tun/metadata_open.go: -------------------------------------------------------------------------------- 1 | //go:build !premium 2 | 3 | package tun 4 | 5 | import ( 6 | "net" 7 | "strconv" 8 | 9 | C "github.com/Dreamacro/clash/constant" 10 | ) 11 | 12 | func createMetadata(lAddr, rAddr *net.TCPAddr) *C.Metadata { 13 | return &C.Metadata{ 14 | NetWork: C.TCP, 15 | Type: C.SOCKS5, 16 | SrcIP: lAddr.IP, 17 | DstIP: rAddr.IP, 18 | SrcPort: strconv.Itoa(lAddr.Port), 19 | DstPort: strconv.Itoa(rAddr.Port), 20 | AddrType: C.AtypIPv4, 21 | Host: "", 22 | RawSrcAddr: lAddr, 23 | RawDstAddr: rAddr, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tun/metadata_premium.go: -------------------------------------------------------------------------------- 1 | //go:build premium 2 | 3 | package tun 4 | 5 | import ( 6 | "net" 7 | "net/netip" 8 | "strconv" 9 | 10 | C "github.com/Dreamacro/clash/constant" 11 | ) 12 | 13 | func createMetadata(lAddr, rAddr *net.TCPAddr) *C.Metadata { 14 | srcAddr, _ := netip.AddrFromSlice(lAddr.IP) 15 | dstAddr, _ := netip.AddrFromSlice(rAddr.IP) 16 | 17 | return &C.Metadata{ 18 | NetWork: C.TCP, 19 | Type: C.SOCKS5, 20 | SrcIP: srcAddr, 21 | DstIP: dstAddr, 22 | SrcPort: strconv.Itoa(lAddr.Port), 23 | DstPort: strconv.Itoa(rAddr.Port), 24 | AddrType: C.AtypIPv4, 25 | Host: "", 26 | RawSrcAddr: lAddr, 27 | RawDstAddr: rAddr, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tun/udp.go: -------------------------------------------------------------------------------- 1 | package tun 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | type packet struct { 8 | local *net.UDPAddr 9 | data []byte 10 | writeBack func(b []byte, addr net.Addr) (int, error) 11 | drop func() 12 | } 13 | 14 | func (pkt *packet) Data() []byte { 15 | return pkt.data 16 | } 17 | 18 | func (pkt *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { 19 | return pkt.writeBack(b, addr) 20 | } 21 | 22 | func (pkt *packet) Drop() { 23 | pkt.drop() 24 | } 25 | 26 | func (pkt *packet) LocalAddr() net.Addr { 27 | return pkt.local 28 | } 29 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/conn.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | C "github.com/Dreamacro/clash/constant" 5 | "github.com/Dreamacro/clash/tunnel/statistic" 6 | ) 7 | 8 | func CloseAllConnections() { 9 | for _, c := range statistic.DefaultManager.Snapshot().Connections { 10 | _ = c.Close() 11 | } 12 | } 13 | 14 | func closeMatch(filter func(conn C.Conn) bool) { 15 | for _, c := range statistic.DefaultManager.Snapshot().Connections { 16 | if cc, ok := c.(C.Conn); ok { 17 | if filter(cc) { 18 | _ = cc.Close() 19 | } 20 | } 21 | } 22 | } 23 | 24 | func closeConnByGroup(name string) { 25 | closeMatch(func(conn C.Conn) bool { 26 | for _, c := range conn.Chains() { 27 | if c == name { 28 | return true 29 | } 30 | } 31 | 32 | return false 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/connectivity.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/Dreamacro/clash/adapter" 7 | "github.com/Dreamacro/clash/adapter/outboundgroup" 8 | "github.com/Dreamacro/clash/constant/provider" 9 | "github.com/Dreamacro/clash/log" 10 | "github.com/Dreamacro/clash/tunnel" 11 | ) 12 | 13 | func HealthCheck(name string) { 14 | p := tunnel.Proxies()[name] 15 | 16 | if p == nil { 17 | log.Warnln("Request health check for `%s`: not found", name) 18 | 19 | return 20 | } 21 | 22 | g, ok := p.(*adapter.Proxy).ProxyAdapter.(outboundgroup.ProxyGroup) 23 | if !ok { 24 | log.Warnln("Request health check for `%s`: invalid type %s", name, p.Type().String()) 25 | 26 | return 27 | } 28 | 29 | wg := &sync.WaitGroup{} 30 | 31 | for _, pr := range g.Providers() { 32 | wg.Add(1) 33 | 34 | go func(provider provider.ProxyProvider) { 35 | provider.HealthCheck() 36 | 37 | wg.Done() 38 | }(pr) 39 | } 40 | 41 | wg.Wait() 42 | } 43 | 44 | func HealthCheckAll() { 45 | for _, g := range QueryProxyGroupNames(false) { 46 | go func(group string) { 47 | HealthCheck(group) 48 | }(g) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/geoip.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/oschwald/geoip2-golang" 7 | 8 | "github.com/Dreamacro/clash/component/mmdb" 9 | ) 10 | 11 | func InstallSideloadGeoip(block []byte) error { 12 | if block == nil { 13 | mmdb.InstallOverride(nil) 14 | 15 | return nil 16 | } 17 | 18 | db, err := geoip2.FromBytes(block) 19 | if err != nil { 20 | return fmt.Errorf("load sideload geoip mmdb: %s", err.Error()) 21 | } 22 | 23 | mmdb.InstallOverride(db) 24 | 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/init.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "strings" 7 | 8 | "github.com/Dreamacro/clash/component/dialer" 9 | C "github.com/Dreamacro/clash/constant" 10 | CTX "github.com/Dreamacro/clash/context" 11 | "github.com/Dreamacro/clash/tunnel" 12 | ) 13 | 14 | func init() { 15 | dialer.DefaultTunnelDialer = func(context context.Context, network, address string) (net.Conn, error) { 16 | if !strings.HasPrefix(network, "tcp") { 17 | return nil, net.UnknownNetworkError("unsupported network") 18 | } 19 | 20 | host, port, err := net.SplitHostPort(address) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | left, right := net.Pipe() 26 | 27 | metadata := &C.Metadata{ 28 | NetWork: C.TCP, 29 | Type: C.HTTPCONNECT, 30 | SrcIP: loopback, 31 | SrcPort: "65535", 32 | DstPort: port, 33 | AddrType: C.AtypDomainName, 34 | Host: host, 35 | RawSrcAddr: left.RemoteAddr(), 36 | RawDstAddr: left.LocalAddr(), 37 | } 38 | 39 | tunnel.TCPIn() <- CTX.NewConnContext(right, metadata) 40 | 41 | return left, nil 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/loopback_open.go: -------------------------------------------------------------------------------- 1 | //go:build !premium 2 | 3 | package tunnel 4 | 5 | import "net" 6 | 7 | var loopback = net.ParseIP("127.0.0.1") 8 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/loopback_premium.go: -------------------------------------------------------------------------------- 1 | //go:build premium 2 | 3 | package tunnel 4 | 5 | import "net/netip" 6 | 7 | var loopback = netip.MustParseAddr("127.0.0.1") 8 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/providers_open.go: -------------------------------------------------------------------------------- 1 | //go:build !premium 2 | 3 | package tunnel 4 | 5 | import ( 6 | "fmt" 7 | "time" 8 | 9 | P "github.com/Dreamacro/clash/adapter/provider" 10 | "github.com/Dreamacro/clash/constant/provider" 11 | "github.com/Dreamacro/clash/tunnel" 12 | ) 13 | 14 | type Provider struct { 15 | Name string `json:"name"` 16 | VehicleType string `json:"vehicleType"` 17 | Type string `json:"type"` 18 | UpdatedAt int64 `json:"updatedAt"` 19 | } 20 | 21 | func QueryProviders() []*Provider { 22 | p := tunnel.Providers() 23 | 24 | providers := make([]provider.Provider, 0, len(p)) 25 | 26 | for _, proxy := range p { 27 | if proxy.VehicleType() == provider.Compatible { 28 | continue 29 | } 30 | 31 | providers = append(providers, proxy) 32 | } 33 | 34 | result := make([]*Provider, 0, len(providers)) 35 | 36 | for _, p := range providers { 37 | updatedAt := time.Time{} 38 | 39 | if s, ok := p.(P.UpdatableProvider); ok { 40 | updatedAt = s.UpdatedAt() 41 | } 42 | 43 | result = append(result, &Provider{ 44 | Name: p.Name(), 45 | VehicleType: p.VehicleType().String(), 46 | Type: p.Type().String(), 47 | UpdatedAt: updatedAt.UnixNano() / 1000 / 1000, 48 | }) 49 | } 50 | 51 | return result 52 | } 53 | 54 | func UpdateProvider(_ string, name string) error { 55 | p, ok := tunnel.Providers()[name] 56 | if !ok { 57 | return fmt.Errorf("%s not found", name) 58 | } 59 | 60 | return p.Update() 61 | } 62 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/state.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/tunnel" 5 | ) 6 | 7 | func QueryMode() string { 8 | return tunnel.Mode().String() 9 | } 10 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/statistic.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/tunnel/statistic" 5 | ) 6 | 7 | func ResetStatistic() { 8 | statistic.DefaultManager.ResetStatistic() 9 | } 10 | 11 | func Now() (up int64, down int64) { 12 | return statistic.DefaultManager.Now() 13 | } 14 | 15 | func Total() (up int64, down int64) { 16 | return statistic.DefaultManager.Total() 17 | } 18 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/suspend.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import "github.com/Dreamacro/clash/adapter/provider" 4 | 5 | func Suspend(s bool) { 6 | provider.Suspend(s) 7 | } 8 | -------------------------------------------------------------------------------- /core/src/main/golang/native/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "C" 4 | 5 | import ( 6 | "encoding/json" 7 | "reflect" 8 | ) 9 | 10 | func marshalJson(obj any) *C.char { 11 | res, err := json.Marshal(obj) 12 | if err != nil { 13 | panic(err.Error()) 14 | } 15 | 16 | return C.CString(string(res)) 17 | } 18 | 19 | func marshalString(obj any) *C.char { 20 | if obj == nil { 21 | return nil 22 | } 23 | 24 | switch o := obj.(type) { 25 | case error: 26 | return C.CString(o.Error()) 27 | case string: 28 | return C.CString(o) 29 | } 30 | 31 | panic("invalid marshal type " + reflect.TypeOf(obj).Name()) 32 | } 33 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/bridge/ClashException.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.bridge 2 | 3 | import androidx.annotation.Keep 4 | 5 | @Keep 6 | class ClashException(msg: String) : IllegalArgumentException(msg) -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/bridge/Content.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.bridge 2 | 3 | import android.net.Uri 4 | import androidx.annotation.Keep 5 | import yos.clash.material.common.Global 6 | import java.io.FileNotFoundException 7 | 8 | @Keep 9 | object Content { 10 | @JvmStatic 11 | fun open(url: String): Int { 12 | val uri = Uri.parse(url) 13 | 14 | if (uri.scheme != "content") { 15 | throw UnsupportedOperationException("Unsupported scheme ${uri.scheme}") 16 | } 17 | 18 | return Global.application.contentResolver.openFileDescriptor(uri, "r")?.detachFd() 19 | ?: throw FileNotFoundException("$uri not found") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/bridge/FetchCallback.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.bridge 2 | 3 | import androidx.annotation.Keep 4 | 5 | @Keep 6 | interface FetchCallback { 7 | fun report(statusJson: String) 8 | fun complete(error: String?) 9 | } -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/bridge/LogcatInterface.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.bridge 2 | 3 | import androidx.annotation.Keep 4 | 5 | @Keep 6 | interface LogcatInterface { 7 | fun received(jsonPayload: String) 8 | } -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/bridge/TunInterface.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.bridge 2 | 3 | import androidx.annotation.Keep 4 | 5 | @Keep 6 | interface TunInterface { 7 | fun markSocket(fd: Int) 8 | fun querySocketUid(protocol: Int, source: String, target: String): Int 9 | } -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/FetchStatus.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.core.util.Parcelizer 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | data class FetchStatus( 10 | val action: Action, 11 | val args: List, 12 | val progress: Int, 13 | val max: Int 14 | ) : Parcelable { 15 | enum class Action { 16 | FetchConfiguration, 17 | FetchProviders, 18 | Verifying, 19 | } 20 | 21 | override fun describeContents(): Int { 22 | return 0 23 | } 24 | 25 | override fun writeToParcel(dest: Parcel, flags: Int) { 26 | Parcelizer.encodeToParcel(serializer(), dest, this) 27 | } 28 | 29 | companion object CREATOR : Parcelable.Creator { 30 | override fun createFromParcel(parcel: Parcel): FetchStatus { 31 | return Parcelizer.decodeFromParcel(serializer(), parcel) 32 | } 33 | 34 | override fun newArray(size: Int): Array { 35 | return arrayOfNulls(size) 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/Provider.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.core.util.Parcelizer 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | data class Provider( 10 | val name: String, 11 | val type: Type, 12 | val vehicleType: VehicleType, 13 | val updatedAt: Long 14 | ) : Parcelable, Comparable { 15 | enum class Type { 16 | Proxy, Rule 17 | } 18 | 19 | enum class VehicleType { 20 | HTTP, File, Compatible 21 | } 22 | 23 | override fun writeToParcel(parcel: Parcel, flags: Int) { 24 | Parcelizer.encodeToParcel(serializer(), parcel, this) 25 | } 26 | 27 | override fun describeContents(): Int { 28 | return 0 29 | } 30 | 31 | override fun compareTo(other: Provider): Int { 32 | return compareValuesBy(this, other, Provider::type, Provider::name) 33 | } 34 | 35 | companion object CREATOR : Parcelable.Creator { 36 | override fun createFromParcel(parcel: Parcel): Provider { 37 | return Parcelizer.decodeFromParcel(serializer(), parcel) 38 | } 39 | 40 | override fun newArray(size: Int): Array { 41 | return arrayOfNulls(size) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/ProviderList.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.model 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import yos.clash.material.common.util.createListFromParcelSlice 6 | import yos.clash.material.common.util.writeToParcelSlice 7 | 8 | class ProviderList(data: List) : List by data, Parcelable { 9 | constructor(parcel: Parcel) : this(Provider.createListFromParcelSlice(parcel, 0, 20)) 10 | 11 | override fun describeContents(): Int { 12 | return 0 13 | } 14 | 15 | override fun writeToParcel(parcel: Parcel, flags: Int) { 16 | return writeToParcelSlice(parcel, flags) 17 | } 18 | 19 | companion object CREATOR : Parcelable.Creator { 20 | override fun createFromParcel(parcel: Parcel): ProviderList { 21 | return ProviderList(parcel) 22 | } 23 | 24 | override fun newArray(size: Int): Array { 25 | return arrayOfNulls(size) 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/ProxySort.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.model 2 | 3 | enum class ProxySort { 4 | Default, Title, Delay 5 | } 6 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/Traffic.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.model 2 | 3 | typealias Traffic = Long -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/TunnelState.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.core.util.Parcelizer 6 | import kotlinx.serialization.SerialName 7 | import kotlinx.serialization.Serializable 8 | 9 | @Serializable 10 | data class TunnelState( 11 | val mode: Mode, 12 | ) : Parcelable { 13 | @Serializable 14 | enum class Mode { 15 | @SerialName("direct") 16 | Direct, 17 | 18 | @SerialName("global") 19 | Global, 20 | 21 | @SerialName("rule") 22 | Rule, 23 | 24 | @SerialName("script") 25 | Script, 26 | } 27 | 28 | override fun writeToParcel(parcel: Parcel, flags: Int) { 29 | Parcelizer.encodeToParcel(serializer(), parcel, this) 30 | } 31 | 32 | override fun describeContents(): Int { 33 | return 0 34 | } 35 | 36 | companion object CREATOR : Parcelable.Creator { 37 | override fun createFromParcel(parcel: Parcel): TunnelState { 38 | return Parcelizer.decodeFromParcel(serializer(), parcel) 39 | } 40 | 41 | override fun newArray(size: Int): Array { 42 | return arrayOfNulls(size) 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/UiConfiguration.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.core.util.Parcelizer 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | class UiConfiguration : Parcelable { 10 | override fun writeToParcel(parcel: Parcel, flags: Int) { 11 | Parcelizer.encodeToParcel(serializer(), parcel, this) 12 | } 13 | 14 | override fun describeContents(): Int { 15 | return 0 16 | } 17 | 18 | companion object CREATOR : Parcelable.Creator { 19 | override fun createFromParcel(parcel: Parcel): UiConfiguration { 20 | return Parcelizer.decodeFromParcel(serializer(), parcel) 21 | } 22 | 23 | override fun newArray(size: Int): Array { 24 | return arrayOfNulls(size) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/util/Net.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.util 2 | 3 | import java.net.InetAddress 4 | import java.net.InetSocketAddress 5 | import java.net.URL 6 | 7 | fun parseInetSocketAddress(address: String): InetSocketAddress { 8 | val url = URL("https://$address") 9 | 10 | return InetSocketAddress(InetAddress.getByName(url.host), url.port) 11 | } -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/util/Serializers.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.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 | object DateSerializer : KSerializer { 12 | override val descriptor: SerialDescriptor 13 | get() = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) 14 | 15 | override fun deserialize(decoder: Decoder): Date { 16 | return Date(decoder.decodeLong()) 17 | } 18 | 19 | override fun serialize(encoder: Encoder, value: Date) { 20 | encoder.encodeLong(value.time) 21 | } 22 | } -------------------------------------------------------------------------------- /core/src/premium/golang/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import _ "cfa/native/all" 4 | -------------------------------------------------------------------------------- /design/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("android") 3 | kotlin("kapt") 4 | id("com.android.library") 5 | } 6 | 7 | dependencies { 8 | repositories { 9 | mavenLocal() 10 | mavenCentral() 11 | gradlePluginPortal() 12 | google() 13 | maven("https://jitpack.io") 14 | maven("https://oss.sonatype.org/content/repositories/snapshots/") 15 | maven("https://maven.kr328.app/releases") 16 | } 17 | implementation(project(":common")) 18 | implementation(project(":core")) 19 | implementation(project(":service")) 20 | 21 | implementation(libs.kotlin.coroutine) 22 | implementation(libs.androidx.core) 23 | implementation(libs.androidx.appcompat) 24 | implementation(libs.androidx.activity) 25 | implementation(libs.androidx.coordinator) 26 | implementation(libs.androidx.recyclerview) 27 | implementation(libs.androidx.fragment) 28 | implementation(libs.androidx.viewpager) 29 | implementation(libs.google.material) 30 | implementation(libs.getactivity.xxpermission) 31 | implementation(libs.androidx.splashscreen) 32 | } 33 | -------------------------------------------------------------------------------- /design/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/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/yos/clash/material/design/AppCrashedDesign.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import yos.clash.material.design.databinding.DesignAppCrashedBinding 6 | import yos.clash.material.design.util.applyFrom 7 | import yos.clash.material.design.util.bindAppBarElevation 8 | import yos.clash.material.design.util.layoutInflater 9 | import yos.clash.material.design.util.root 10 | 11 | class AppCrashedDesign(context: Context) : Design(context) { 12 | private val binding = DesignAppCrashedBinding 13 | .inflate(context.layoutInflater, context.root, false) 14 | 15 | override val root: View 16 | get() = binding.root 17 | 18 | fun setAppLogs(logs: String) { 19 | binding.logsView.text = logs 20 | } 21 | 22 | init { 23 | binding.self = this 24 | 25 | binding.activityBarLayout.applyFrom(context) 26 | 27 | binding.scrollRoot.bindAppBarElevation(binding.activityBarLayout) 28 | } 29 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/SettingsDesign.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import yos.clash.material.design.databinding.DesignSettingsBinding 6 | import yos.clash.material.design.util.applyFrom 7 | import yos.clash.material.design.util.bindAppBarElevation 8 | import yos.clash.material.design.util.layoutInflater 9 | import yos.clash.material.design.util.root 10 | 11 | class SettingsDesign(context: Context) : Design(context) { 12 | enum class Request { 13 | StartApp, StartNetwork, StartOverride, 14 | } 15 | 16 | private val binding = DesignSettingsBinding 17 | .inflate(context.layoutInflater, context.root, false) 18 | 19 | override val root: View 20 | get() = binding.root 21 | 22 | init { 23 | binding.self = this 24 | 25 | binding.activityBarLayout.applyFrom(context) 26 | 27 | binding.scrollRoot.bindAppBarElevation(binding.activityBarLayout) 28 | } 29 | 30 | fun request(request: Request) { 31 | requests.trySend(request) 32 | } 33 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/YosConfigAchieve.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design 2 | 3 | open class YosConfigAchieve { 4 | companion object { 5 | fun getIfPremium(): Boolean { 6 | return true 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/adapter/LogFileAdapter.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.adapter 2 | 3 | import android.content.Context 4 | import android.view.ViewGroup 5 | import android.view.ViewGroup.LayoutParams 6 | import androidx.recyclerview.widget.RecyclerView 7 | import yos.clash.material.design.model.LogFile 8 | import yos.clash.material.design.util.format 9 | import yos.clash.material.design.view.ActionLabel 10 | 11 | class LogFileAdapter( 12 | private val context: Context, 13 | private val open: (LogFile) -> Unit, 14 | ) : RecyclerView.Adapter() { 15 | class Holder(val label: ActionLabel) : RecyclerView.ViewHolder(label) 16 | 17 | var logs: List = emptyList() 18 | 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { 20 | return Holder(ActionLabel(context).apply { 21 | layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) 22 | }) 23 | } 24 | 25 | override fun onBindViewHolder(holder: Holder, position: Int) { 26 | val current = logs[position] 27 | 28 | holder.label.text = current.fileName 29 | holder.label.subtext = current.date.format(context) 30 | holder.label.setOnClickListener { 31 | open(current) 32 | } 33 | holder.label.icon = null 34 | } 35 | 36 | override fun getItemCount(): Int { 37 | return logs.size 38 | } 39 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/adapter/LogMessageAdapter.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.adapter 2 | 3 | import android.content.Context 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.github.kr328.clash.core.model.LogMessage 7 | import yos.clash.material.design.databinding.AdapterLogMessageBinding 8 | import yos.clash.material.design.util.layoutInflater 9 | 10 | class LogMessageAdapter( 11 | private val context: Context, 12 | private val copy: (LogMessage) -> Unit, 13 | ) : 14 | RecyclerView.Adapter() { 15 | class Holder(val binding: AdapterLogMessageBinding) : RecyclerView.ViewHolder(binding.root) 16 | 17 | var messages: List = emptyList() 18 | 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { 20 | return Holder( 21 | AdapterLogMessageBinding 22 | .inflate(context.layoutInflater, parent, false) 23 | ) 24 | } 25 | 26 | override fun onBindViewHolder(holder: Holder, position: Int) { 27 | val current = messages[position] 28 | 29 | holder.binding.message = current 30 | holder.binding.root.setOnLongClickListener { 31 | copy(current) 32 | 33 | true 34 | } 35 | } 36 | 37 | override fun getItemCount(): Int { 38 | return messages.size 39 | } 40 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/adapter/ProxyAdapter.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.adapter 2 | 3 | import android.view.ViewGroup 4 | import androidx.recyclerview.widget.RecyclerView 5 | import yos.clash.material.design.component.ProxyView 6 | import yos.clash.material.design.component.ProxyViewConfig 7 | import yos.clash.material.design.component.ProxyViewState 8 | 9 | class ProxyAdapter( 10 | private val config: ProxyViewConfig, 11 | private val clicked: (String) -> Unit, 12 | ) : RecyclerView.Adapter() { 13 | class Holder(val view: ProxyView) : RecyclerView.ViewHolder(view) 14 | 15 | var selectable: Boolean = false 16 | var states: List = emptyList() 17 | 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { 19 | return Holder(ProxyView(config.context, config)) 20 | } 21 | 22 | override fun onBindViewHolder(holder: Holder, position: Int) { 23 | val current = states[position] 24 | 25 | holder.view.apply { 26 | state = current 27 | 28 | setOnClickListener { 29 | clicked(current.proxy.name) 30 | } 31 | 32 | val isSelector = selectable 33 | 34 | isFocusable = isSelector 35 | isClickable = isSelector 36 | 37 | current.update(true) 38 | } 39 | } 40 | 41 | override fun getItemCount(): Int { 42 | return states.size 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/model/AppInfo.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.model 2 | 3 | import android.graphics.drawable.Drawable 4 | 5 | data class AppInfo( 6 | val packageName: String, 7 | val label: String, 8 | val icon: Drawable, 9 | val installTime: Long, 10 | val updateDate: Long, 11 | ) -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/model/AppInfoSort.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.model 2 | 3 | enum class AppInfoSort(comparator: Comparator) : Comparator by comparator { 4 | Label(compareBy(AppInfo::label)), 5 | PackageName(compareBy(AppInfo::packageName)), 6 | InstallTime(compareBy(AppInfo::installTime)), 7 | UpdateTime(compareBy(AppInfo::updateDate)), 8 | } 9 | -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/model/Behavior.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.model 2 | 3 | interface Behavior { 4 | var autoRestart: Boolean 5 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/model/DarkMode.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.model 2 | 3 | enum class DarkMode { 4 | Auto, ForceLight, ForceDark 5 | } 6 | -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/model/File.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.model 2 | 3 | data class File( 4 | val id: String, 5 | val name: String, 6 | val size: Long, 7 | val lastModified: Long, 8 | val isDirectory: Boolean 9 | ) -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/model/LogFile.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.model 2 | 3 | import java.util.* 4 | 5 | data class LogFile(val fileName: String, val date: Date) { 6 | companion object { 7 | private val REGEX_FILE = Regex("clash-(\\d+).log") 8 | private const val FORMAT_FILE_NAME = "clash-%d.log" 9 | 10 | fun parseFromFileName(fileName: String): LogFile? { 11 | return REGEX_FILE.matchEntire(fileName)?.run { 12 | LogFile(fileName, Date(groupValues[1].toLong())) 13 | } 14 | } 15 | 16 | fun generate(): LogFile { 17 | val current = Date() 18 | val fileName = FORMAT_FILE_NAME.format(current.time) 19 | 20 | return LogFile(fileName, current) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/model/ProviderState.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.model 2 | 3 | import androidx.databinding.BaseObservable 4 | import androidx.databinding.Bindable 5 | import com.github.kr328.clash.core.model.Provider 6 | import yos.clash.material.design.BR 7 | 8 | class ProviderState( 9 | val provider: Provider, 10 | updatedAt: Long, 11 | updating: Boolean, 12 | ) : BaseObservable() { 13 | var updatedAt: Long = updatedAt 14 | @Bindable get 15 | set(value) { 16 | field = value 17 | 18 | notifyPropertyChanged(BR.updatedAt) 19 | } 20 | 21 | var updating: Boolean = updating 22 | @Bindable get 23 | set(value) { 24 | field = value 25 | 26 | notifyPropertyChanged(BR.updating) 27 | } 28 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/model/ProxyPageState.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.model 2 | 3 | class ProxyPageState { 4 | var bottom = false 5 | var urlTesting = false 6 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/model/ProxyState.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.model 2 | 3 | data class ProxyState(var now: String) -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/preference/Category.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.preference 2 | 3 | import android.view.View 4 | import androidx.annotation.StringRes 5 | import yos.clash.material.design.databinding.PreferenceCategoryBinding 6 | import yos.clash.material.design.util.layoutInflater 7 | 8 | fun PreferenceScreen.category( 9 | @StringRes text: Int, 10 | ) { 11 | val binding = PreferenceCategoryBinding 12 | .inflate(context.layoutInflater, root, false) 13 | 14 | binding.textView.text = context.getString(text) 15 | 16 | addElement(object : Preference { 17 | override val view: View 18 | get() = binding.root 19 | override var enabled: Boolean 20 | get() = binding.root.isEnabled 21 | set(value) { 22 | binding.root.isEnabled = value 23 | } 24 | }) 25 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/preference/Preference.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.preference 2 | 3 | import android.view.View 4 | 5 | fun interface OnChangedListener { 6 | fun onChanged() 7 | } 8 | 9 | interface Preference { 10 | val view: View 11 | 12 | var enabled: Boolean 13 | get() = view.isEnabled 14 | set(value) { 15 | view.isEnabled = value 16 | view.isClickable = value 17 | view.isFocusable = value 18 | view.alpha = if (value) 1.0f else 0.33f 19 | } 20 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/preference/Screen.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.preference 2 | 3 | import android.content.Context 4 | import android.view.ViewGroup 5 | import android.widget.LinearLayout 6 | import android.widget.LinearLayout.LayoutParams 7 | import android.widget.LinearLayout.LayoutParams.MATCH_PARENT 8 | import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT 9 | import kotlinx.coroutines.CoroutineScope 10 | 11 | interface PreferenceScreen : CoroutineScope { 12 | val context: Context 13 | val root: ViewGroup 14 | } 15 | 16 | fun CoroutineScope.preferenceScreen( 17 | context: Context, 18 | configure: PreferenceScreen.() -> Unit 19 | ): PreferenceScreen { 20 | val root = LinearLayout(context).apply { 21 | orientation = LinearLayout.VERTICAL 22 | } 23 | 24 | val impl = object : PreferenceScreen, CoroutineScope by this { 25 | override val context: Context 26 | get() = context 27 | override val root: ViewGroup 28 | get() = root 29 | } 30 | 31 | impl.configure() 32 | 33 | return impl 34 | } 35 | 36 | fun PreferenceScreen.addElement(preference: Preference) { 37 | root.addView(preference.view, LayoutParams(MATCH_PARENT, WRAP_CONTENT)) 38 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/preference/Tips.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.preference 2 | 3 | import android.view.View 4 | import androidx.annotation.StringRes 5 | import yos.clash.material.design.databinding.PreferenceTipsBinding 6 | import yos.clash.material.design.util.getHtml 7 | import yos.clash.material.design.util.layoutInflater 8 | import yos.clash.material.design.util.root 9 | 10 | interface TipsPreference : Preference { 11 | var text: CharSequence? 12 | } 13 | 14 | fun PreferenceScreen.tips( 15 | @StringRes text: Int, 16 | configure: TipsPreference.() -> Unit = {}, 17 | ): TipsPreference { 18 | val binding = PreferenceTipsBinding 19 | .inflate(context.layoutInflater, context.root, false) 20 | val impl = object : TipsPreference { 21 | override var text: CharSequence? 22 | get() = binding.tips.text 23 | set(value) { 24 | binding.tips.text = value 25 | } 26 | override val view: View 27 | get() = binding.root 28 | override var enabled: Boolean 29 | get() = binding.root.isEnabled 30 | set(value) { 31 | binding.root.isEnabled = value 32 | } 33 | } 34 | 35 | binding.tips.text = context.getHtml(text) 36 | 37 | impl.configure() 38 | 39 | addElement(impl) 40 | 41 | return impl 42 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/preference/Value.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.preference 2 | 3 | interface NullableTextAdapter { 4 | fun from(value: T): String? 5 | fun to(text: String?): T 6 | 7 | companion object { 8 | val Port = object : NullableTextAdapter { 9 | override fun from(value: Int?): String? { 10 | if (value == null) return null 11 | 12 | return if (value > 0) value.toString() else "" 13 | } 14 | 15 | override fun to(text: String?): Int? { 16 | if (text == null) return null 17 | 18 | return text.toIntOrNull() ?: 0 19 | } 20 | } 21 | 22 | val String = object : NullableTextAdapter { 23 | override fun from(value: String?): String? { 24 | return value 25 | } 26 | 27 | override fun to(text: String?): String? { 28 | return text 29 | } 30 | } 31 | } 32 | } 33 | 34 | interface TextAdapter { 35 | fun from(value: T): String 36 | fun to(text: String): T 37 | 38 | companion object { 39 | val String = object : TextAdapter { 40 | override fun from(value: String): String { 41 | return value 42 | } 43 | 44 | override fun to(text: String): String { 45 | return text 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/ui/DayNight.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.ui 2 | 3 | enum class DayNight { 4 | Day, Night 5 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/ui/Insets.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.ui 2 | 3 | data class Insets(val start: Int, val top: Int, val end: Int, val bottom: Int) { 4 | companion object { 5 | val EMPTY = Insets(0, 0, 0, 0) 6 | } 7 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/ui/ObservableCurrentTime.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.ui 2 | 3 | import androidx.databinding.BaseObservable 4 | import androidx.databinding.Bindable 5 | import androidx.databinding.library.baseAdapters.BR 6 | 7 | class ObservableCurrentTime : BaseObservable() { 8 | var value: Long = System.currentTimeMillis() 9 | @Bindable get 10 | private set(value) { 11 | field = value 12 | 13 | notifyPropertyChanged(BR.value) 14 | } 15 | 16 | fun update() { 17 | value = System.currentTimeMillis() 18 | } 19 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/ui/Surface.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.ui 2 | 3 | import androidx.databinding.BaseObservable 4 | import androidx.databinding.Bindable 5 | import yos.clash.material.design.BR 6 | 7 | class Surface : BaseObservable() { 8 | var insets: Insets = Insets.EMPTY 9 | @Bindable get 10 | set(value) { 11 | field = value 12 | 13 | notifyPropertyChanged(BR.insets) 14 | } 15 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/ui/ToastDuration.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.ui 2 | 3 | enum class ToastDuration { 4 | Short, Long, Indefinite 5 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/util/ActivityBar.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.util 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.widget.ImageView 6 | import android.widget.TextView 7 | import yos.clash.material.design.R 8 | import yos.clash.material.design.view.ActivityBarLayout 9 | 10 | fun ActivityBarLayout.applyFrom(context: Context) { 11 | if (context is Activity) { 12 | findViewById(R.id.activity_bar_close_view)?.apply { 13 | setOnClickListener { 14 | context.onBackPressed() 15 | } 16 | } 17 | findViewById(R.id.activity_bar_title_view)?.apply { 18 | text = context.title 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/util/App.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.util 2 | 3 | import android.content.pm.PackageInfo 4 | import android.content.pm.PackageManager 5 | import yos.clash.material.common.compat.foreground 6 | import yos.clash.material.design.model.AppInfo 7 | 8 | fun PackageInfo.toAppInfo(pm: PackageManager): AppInfo { 9 | return AppInfo( 10 | packageName = packageName, 11 | icon = applicationInfo.loadIcon(pm).foreground(), 12 | label = applicationInfo.loadLabel(pm).toString(), 13 | installTime = firstInstallTime, 14 | updateDate = lastUpdateTime, 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/util/Binding.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.util 2 | 3 | import android.view.View 4 | import androidx.databinding.BindingAdapter 5 | 6 | @BindingAdapter("android:minHeight") 7 | fun bindMinHeight(view: View, value: Float) { 8 | view.minimumHeight = value.toInt() 9 | } 10 | -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/util/Context.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.util 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.text.Spanned 6 | import android.view.LayoutInflater 7 | import android.view.ViewGroup 8 | import androidx.annotation.DimenRes 9 | import androidx.annotation.StringRes 10 | import yos.clash.material.common.compat.fromHtmlCompat 11 | 12 | val Context.layoutInflater: LayoutInflater 13 | get() = LayoutInflater.from(this) 14 | 15 | val Context.root: ViewGroup? 16 | get() { 17 | return when (this) { 18 | is Activity -> { 19 | findViewById(android.R.id.content) 20 | } 21 | else -> { 22 | null 23 | } 24 | } 25 | } 26 | 27 | fun Context.getPixels(@DimenRes resId: Int): Int { 28 | return resources.getDimensionPixelSize(resId) 29 | } 30 | 31 | fun Context.getHtml(@StringRes resId: Int): Spanned { 32 | return fromHtmlCompat(getString(resId)) 33 | } 34 | -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/util/Diff.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.util 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | 5 | fun List.diffWith( 6 | newList: List, 7 | detectMove: Boolean = false, 8 | id: (T) -> Any? = { it } 9 | ): DiffUtil.DiffResult { 10 | val oldList = this 11 | 12 | return DiffUtil.calculateDiff(object : DiffUtil.Callback() { 13 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 14 | return id(oldList[oldItemPosition]) == id(newList[newItemPosition]) 15 | } 16 | 17 | override fun getOldListSize(): Int { 18 | return oldList.size 19 | } 20 | 21 | override fun getNewListSize(): Int { 22 | return newList.size 23 | } 24 | 25 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 26 | return oldList[oldItemPosition] == newList[newItemPosition] 27 | } 28 | }, detectMove) 29 | } 30 | -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/util/Inserts.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.util 2 | 3 | import android.view.View 4 | import androidx.core.view.ViewCompat 5 | import androidx.core.view.WindowInsetsCompat 6 | import yos.clash.material.design.ui.Insets 7 | 8 | fun View.setOnInsertsChangedListener(adaptLandscape: Boolean = true, listener: (Insets) -> Unit) { 9 | setOnApplyWindowInsetsListener { v, ins -> 10 | val compat = WindowInsetsCompat.toWindowInsetsCompat(ins) 11 | val insets = compat.getInsets(WindowInsetsCompat.Type.systemBars()) 12 | 13 | val rInsets = if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) { 14 | Insets( 15 | insets.left, 16 | insets.top, 17 | insets.right, 18 | insets.bottom, 19 | ) 20 | } else { 21 | Insets( 22 | insets.right, 23 | insets.top, 24 | insets.left, 25 | insets.bottom, 26 | ) 27 | } 28 | 29 | listener(if (adaptLandscape) rInsets.landscape(v.context) else rInsets) 30 | 31 | compat.toWindowInsets()!! 32 | } 33 | 34 | requestApplyInsets() 35 | } 36 | -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/util/Interval.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.util 2 | 3 | import android.content.Context 4 | import yos.clash.material.design.R 5 | import java.util.concurrent.TimeUnit 6 | 7 | fun Long.elapsedIntervalString(context: Context): String { 8 | val day = TimeUnit.MILLISECONDS.toDays(this) 9 | val hour = TimeUnit.MILLISECONDS.toHours(this) 10 | val minute = TimeUnit.MILLISECONDS.toMinutes(this) 11 | 12 | return when { 13 | day > 0 -> context.getString(R.string.format_days_ago, day) 14 | hour > 0 -> context.getString(R.string.format_hours_ago, hour) 15 | minute > 0 -> context.getString(R.string.format_minutes_ago, minute) 16 | else -> context.getString(R.string.recently) 17 | } 18 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/util/Landscape.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.util 2 | 3 | import android.content.Context 4 | import yos.clash.material.design.R 5 | import yos.clash.material.design.ui.Insets 6 | 7 | fun Insets.landscape(context: Context): Insets { 8 | val displayMetrics = context.resources.displayMetrics 9 | val minWidth = context.getPixels(R.dimen.surface_landscape_min_width) 10 | 11 | val width = displayMetrics.widthPixels 12 | val height = displayMetrics.heightPixels 13 | 14 | return if (width > height && width > minWidth) { 15 | val expectedWidth = width.coerceAtMost(height.coerceAtLeast(minWidth)) 16 | 17 | val padding = (width - expectedWidth).coerceAtLeast(start + end) / 2 18 | 19 | copy(start = padding.coerceAtLeast(start), end = padding.coerceAtLeast(end)) 20 | } else { 21 | this 22 | } 23 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/util/ListView.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.util 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import android.view.View.MeasureSpec 6 | import android.widget.FrameLayout 7 | import android.widget.ListAdapter 8 | 9 | fun ListAdapter.measureWidth(context: Context): Int { 10 | val parent = FrameLayout(context) 11 | 12 | val widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) 13 | val heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) 14 | 15 | var itemView: View? = null 16 | var maxWidth = 0 17 | var itemType = 0 18 | 19 | for (i in 0 until count) { 20 | val positionType = getItemViewType(i) 21 | if (positionType != itemType) { 22 | itemType = positionType 23 | itemView = null 24 | } 25 | 26 | itemView = getView(i, itemView, parent) 27 | itemView.measure(widthMeasureSpec, heightMeasureSpec) 28 | 29 | val itemWidth: Int = itemView.measuredWidth 30 | 31 | if (itemWidth > maxWidth) { 32 | maxWidth = itemWidth 33 | } 34 | } 35 | 36 | return maxWidth 37 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/util/ScrollView.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.util 2 | 3 | import yos.clash.material.design.view.ObservableScrollView 4 | 5 | val ObservableScrollView.isTop: Boolean 6 | get() = scrollX == 0 && scrollY == 0 7 | -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/util/Toast.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.util 2 | 3 | import yos.clash.material.design.Design 4 | import yos.clash.material.design.R 5 | import yos.clash.material.design.ui.ToastDuration 6 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 7 | 8 | suspend fun Design<*>.showExceptionToast(message: CharSequence) { 9 | showToast(message, ToastDuration.Long) { 10 | setAction(R.string.detail) { 11 | MaterialAlertDialogBuilder(it.context) 12 | .setTitle(R.string.error) 13 | .setMessage(message) 14 | .setCancelable(true) 15 | .setPositiveButton(R.string.ok) { _, _ -> } 16 | .show() 17 | } 18 | } 19 | } 20 | 21 | suspend fun Design<*>.showExceptionToast(exception: Exception) { 22 | showExceptionToast(exception.message ?: "Unknown") 23 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/util/Validator.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.util 2 | 3 | import yos.clash.material.common.util.PatternFileName 4 | 5 | typealias Validator = (String) -> Boolean 6 | 7 | val ValidatorAcceptAll: Validator = { 8 | true 9 | } 10 | 11 | val ValidatorFileName: Validator = { 12 | PatternFileName.matches(it) && it.isNotBlank() 13 | } 14 | 15 | val ValidatorNotBlank: Validator = { 16 | it.isNotBlank() 17 | } 18 | 19 | val ValidatorHttpUrl: Validator = { 20 | it.startsWith("https://", ignoreCase = true) || it.startsWith("http://", ignoreCase = true) 21 | } 22 | 23 | val ValidatorAutoUpdateInterval: Validator = { 24 | it.isEmpty() || (it.toLongOrNull() ?: 0) >= 15 25 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/util/View.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.util 2 | 3 | import android.view.View 4 | import android.view.inputmethod.InputMethodManager 5 | import androidx.core.content.getSystemService 6 | 7 | fun View.requestTextInput() { 8 | post { 9 | requestFocus() 10 | 11 | postDelayed({ 12 | context.getSystemService() 13 | ?.showSoftInput(this, 0) 14 | }, 300) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/view/ActivityBarLayout.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.MotionEvent 6 | import android.widget.FrameLayout 7 | import androidx.annotation.AttrRes 8 | import androidx.annotation.StyleRes 9 | import yos.clash.material.design.util.resolveThemedColor 10 | 11 | class ActivityBarLayout @JvmOverloads constructor( 12 | context: Context, 13 | attributeSet: AttributeSet? = null, 14 | @AttrRes defStyleAttr: Int = 0, 15 | @StyleRes defStyleRes: Int = 0 16 | ) : FrameLayout(context, attributeSet, defStyleAttr, defStyleRes) { 17 | init { 18 | alpha = 0.96f 19 | 20 | setBackgroundColor(context.resolveThemedColor(android.R.attr.windowBackground)) 21 | } 22 | 23 | override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { 24 | super.dispatchTouchEvent(ev) 25 | 26 | return true 27 | } 28 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/view/AppRecyclerView.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.view 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.util.AttributeSet 6 | import androidx.annotation.AttrRes 7 | import androidx.recyclerview.widget.RecyclerView 8 | 9 | class AppRecyclerView @JvmOverloads constructor( 10 | context: Context, 11 | attributeSet: AttributeSet? = null, 12 | @AttrRes defStyleAttr: Int = 0 13 | ) : RecyclerView(context, attributeSet, defStyleAttr) { 14 | init { 15 | isFocusable = false 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/view/ObservableScrollView.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.ScrollView 6 | import androidx.annotation.AttrRes 7 | import androidx.annotation.StyleRes 8 | 9 | class ObservableScrollView @JvmOverloads constructor( 10 | context: Context, 11 | attributeSet: AttributeSet? = null, 12 | @AttrRes defStyleAttr: Int = 0, 13 | @StyleRes defStyleRes: Int = 0 14 | ) : ScrollView(context, attributeSet, defStyleAttr, defStyleRes) { 15 | fun interface OnScrollChangedListener { 16 | fun onChanged(scrollView: ObservableScrollView, x: Int, y: Int, oldl: Int, oldt: Int) 17 | } 18 | 19 | private val scrollChangedListeners: MutableSet = mutableSetOf() 20 | 21 | override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { 22 | super.onScrollChanged(l, t, oldl, oldt) 23 | 24 | scrollChangedListeners.forEach { 25 | it.onChanged(this, l, t, oldl, oldt) 26 | } 27 | } 28 | 29 | fun addOnScrollChangedListener(listener: OnScrollChangedListener) { 30 | scrollChangedListeners.add(listener) 31 | } 32 | } -------------------------------------------------------------------------------- /design/src/main/java/yos/clash/material/design/view/VerticalScrollableHost.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.design.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.MotionEvent 6 | import android.widget.FrameLayout 7 | import kotlin.math.absoluteValue 8 | import kotlin.math.tan 9 | 10 | class VerticalScrollableHost @JvmOverloads constructor( 11 | context: Context, 12 | attributeSet: AttributeSet? = null, 13 | defStyleAttr: Int = 0, 14 | defStyleRes: Int = 0 15 | ) : FrameLayout(context, attributeSet, defStyleAttr, defStyleRes) { 16 | private var initialX = 0f 17 | private var initialY = 0f 18 | 19 | private val degree = tan(Math.toRadians(15.0)) 20 | 21 | override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { 22 | val parentView = parent ?: return super.onInterceptTouchEvent(ev) 23 | 24 | if (ev.action == MotionEvent.ACTION_DOWN) { 25 | initialX = ev.x 26 | initialY = ev.y 27 | parentView.requestDisallowInterceptTouchEvent(true) 28 | } else if (ev.action == MotionEvent.ACTION_MOVE) { 29 | val dx = ev.x - initialX 30 | val dy = ev.y - initialY 31 | 32 | val t = dy.absoluteValue / dx.absoluteValue 33 | 34 | if (t < degree) { 35 | parentView.requestDisallowInterceptTouchEvent(false) 36 | } 37 | } 38 | 39 | return super.onInterceptTouchEvent(ev) 40 | } 41 | } -------------------------------------------------------------------------------- /design/src/main/res/drawable/bg_bottom_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_adb.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_add.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_apps.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_assignment.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_attach_file.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_brightness_4.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_clear_all.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_close.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_cloud_download.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_content_copy.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_delete.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_dns.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_domain.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_edit.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_extension.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_flash_on.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_get_app.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_help_center.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_info.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_more_vert.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_publish.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_replay.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_restore.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_save.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_search.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_settings.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_stop.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_swap_vert.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_swap_vertical_circle.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_sync.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_update.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_view_list.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_vpn_lock.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_work.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_article.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_check_circle.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_delete.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_folder.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_inbox.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_info.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_label.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_not_interested.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_update.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/yos_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/yos_shape_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /design/src/main/res/layout/common_activity_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 30 | -------------------------------------------------------------------------------- /design/src/main/res/layout/common_recycler_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 16 | -------------------------------------------------------------------------------- /design/src/main/res/layout/design_new_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 15 | 16 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /design/src/main/res/layout/dialog_fetch_status.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 15 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /design/src/main/res/layout/dialog_text_field.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | 11 | 17 | 18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /design/src/main/res/layout/preference_category.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | -------------------------------------------------------------------------------- /design/src/main/res/layout/preference_tips.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 15 | 16 | 22 | 23 | -------------------------------------------------------------------------------- /design/src/main/res/values-v31/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/system_accent1_600 4 | @android:color/system_accent1_400 5 | #501F1F23 6 | #FAF9FD 7 | #121316 8 | #EFEDF1 9 | #292A2D 10 | #FF808080 11 | #FFD3D3D3 12 | #FF808080 13 | #C00003 14 | -------------------------------------------------------------------------------- /design/src/main/res/values-v34/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @android:color/system_error_light 4 | @android:color/system_primary_light 5 | @android:color/system_primary_dark 6 | @android:color/system_surface_container_dark 7 | @android:color/system_background_light 8 | @android:color/system_background_dark 9 | @android:color/system_surface_container_high_dark 10 | #FF808080 11 | #FFD3D3D3 12 | #FF808080 13 | @android:color/system_surface_container_light 14 | -------------------------------------------------------------------------------- /design/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #00668B 4 | #219BCC 5 | #501F1F23 6 | #FAF9FD 7 | #121316 8 | #292A2D 9 | #EFEDF1 10 | #FF808080 11 | #FFD3D3D3 12 | #FF808080 13 | #C00003 14 | 15 | -------------------------------------------------------------------------------- /design/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /design/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | -------------------------------------------------------------------------------- /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:+UseParallelGC -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/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionSha256Sum=e6d864e3b5bc05cc62041842b306383fc1fefcec359e70cebb1d470a6094ca82 -------------------------------------------------------------------------------- /hideapi/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | } 4 | -------------------------------------------------------------------------------- /hideapi/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/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 | -------------------------------------------------------------------------------- /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 | } 23 | 24 | afterEvaluate { 25 | android { 26 | libraryVariants.forEach { 27 | sourceSets[it.name].kotlin.srcDir(buildDir.resolve("generated/ksp/${it.name}/kotlin")) 28 | sourceSets[it.name].java.srcDir(buildDir.resolve("generated/ksp/${it.name}/java")) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /service/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yos-X/ClashYou/367b3292d3e1f21013eab582f4d33729a56b9cab/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/yos/clash/material/service/BaseService.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.service 2 | 3 | import android.app.Service 4 | import yos.clash.material.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/yos/clash/material/service/PreferenceProvider.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.service 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import yos.clash.material.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/yos/clash/material/service/clash/module/CloseModule.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.service.clash.module 2 | 3 | import android.app.Service 4 | import yos.clash.material.common.constants.Intents 5 | import yos.clash.material.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/yos/clash/material/service/clash/module/TimeZoneModule.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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) 18 | 19 | timeZones.receive() 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /service/src/main/java/yos/clash/material/service/data/Converters.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.service.data 2 | 3 | import androidx.room.TypeConverter 4 | import yos.clash.material.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/yos/clash/material/service/data/Daos.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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 | } 14 | 15 | fun ProviderMoreInfoDao(): ProviderMoreInfoDao { 16 | return Database.database.openSubscriptionUserInfoDao() 17 | } -------------------------------------------------------------------------------- /service/src/main/java/yos/clash/material/service/data/Imported.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.service.data 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.TypeConverters 6 | import yos.clash.material.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 = "createdAt") val createdAt: Long, 18 | ) -------------------------------------------------------------------------------- /service/src/main/java/yos/clash/material/service/data/ImportedDao.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/data/Pending.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.service.data 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.TypeConverters 6 | import yos.clash.material.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 = "createdAt") val createdAt: Long = System.currentTimeMillis(), 18 | ) -------------------------------------------------------------------------------- /service/src/main/java/yos/clash/material/service/data/PendingDao.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/data/ProviderMoreInfo.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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 = "provider_more_info", 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"] 19 | ) 20 | @TypeConverters(Converters::class) 21 | data class ProviderMoreInfo( 22 | @ColumnInfo(name = "uuid") val uuid: UUID, 23 | @ColumnInfo(name = "upload") val upload: Long, 24 | @ColumnInfo(name = "download") val download: Long, 25 | @ColumnInfo(name = "total") val total: Long, 26 | @ColumnInfo(name = "expire") val expire: Long, 27 | ) -------------------------------------------------------------------------------- /service/src/main/java/yos/clash/material/service/data/ProviderMoreInfoDao.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.service.data 2 | 3 | import androidx.room.* 4 | import java.util.* 5 | 6 | @Dao 7 | @TypeConverters(Converters::class) 8 | interface ProviderMoreInfoDao { 9 | @Query("SELECT * FROM provider_more_info WHERE uuid = :uuid") 10 | suspend fun queryByUUID(uuid: UUID): ProviderMoreInfo? 11 | 12 | @Insert(onConflict = OnConflictStrategy.REPLACE) 13 | suspend fun setInfo(info: ProviderMoreInfo) 14 | } -------------------------------------------------------------------------------- /service/src/main/java/yos/clash/material/service/data/Selection.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/data/SelectionDao.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/data/migrations/Migrations.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.service.data.migrations 2 | 3 | import androidx.room.migration.Migration 4 | import androidx.sqlite.db.SupportSQLiteDatabase 5 | 6 | private val MIGRATION_1_2 = object : Migration(1, 2) { 7 | override fun migrate(database: SupportSQLiteDatabase) { 8 | database.execSQL("CREATE TABLE `provider_more_info` (`uuid` TEXT NOT NULL, `upload` INTEGER NOT NULL, `download` INTEGER NOT NULL, `total` INTEGER NOT NULL, `expire` INTEGER NOT NULL, PRIMARY KEY(`uuid`), FOREIGN KEY(`uuid`) REFERENCES `imported`(`uuid`) ON UPDATE CASCADE ON DELETE CASCADE )") 9 | } 10 | } 11 | 12 | val MIGRATIONS: Array = arrayOf(MIGRATION_1_2) 13 | 14 | val LEGACY_MIGRATION = ::migrationFromLegacy -------------------------------------------------------------------------------- /service/src/main/java/yos/clash/material/service/document/Document.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/document/FileDocument.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/document/Flag.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.service.document 2 | 3 | enum class Flag { 4 | Writable, Deletable, Virtual 5 | } -------------------------------------------------------------------------------- /service/src/main/java/yos/clash/material/service/document/Path.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/document/VirtualDocument.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/model/AccessControlMode.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.service.model 2 | 3 | enum class AccessControlMode { 4 | AcceptAll, AcceptSelected, DenySelected 5 | } 6 | -------------------------------------------------------------------------------- /service/src/main/java/yos/clash/material/service/remote/IClashManager.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.service.remote 2 | 3 | import com.github.kr328.clash.core.model.UiConfiguration 4 | import com.github.kr328.clash.core.Clash 5 | import com.github.kr328.clash.core.model.ConfigurationOverride 6 | import com.github.kr328.clash.core.model.Provider 7 | import com.github.kr328.clash.core.model.ProviderList 8 | import com.github.kr328.clash.core.model.ProxyGroup 9 | import com.github.kr328.clash.core.model.ProxySort 10 | import com.github.kr328.clash.core.model.TunnelState 11 | import com.github.kr328.kaidl.BinderInterface 12 | 13 | @BinderInterface 14 | interface IClashManager { 15 | fun queryTunnelState(): TunnelState 16 | fun queryTrafficTotal(): Long 17 | fun queryProxyGroupNames(excludeNotSelectable: Boolean): List 18 | fun queryProxyGroup(name: String, proxySort: ProxySort): ProxyGroup 19 | fun queryConfiguration(): UiConfiguration 20 | fun queryProviders(): ProviderList 21 | 22 | fun patchSelector(group: String, name: String): Boolean 23 | 24 | suspend fun healthCheck(group: String) 25 | suspend fun updateProvider(type: Provider.Type, name: String) 26 | 27 | fun queryOverride(slot: Clash.OverrideSlot): ConfigurationOverride 28 | fun patchOverride(slot: Clash.OverrideSlot, configuration: ConfigurationOverride) 29 | fun clearOverride(slot: Clash.OverrideSlot) 30 | 31 | fun setLogObserver(observer: ILogObserver?) 32 | } -------------------------------------------------------------------------------- /service/src/main/java/yos/clash/material/service/remote/IFetchObserver.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/remote/ILogObserver.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/remote/IProfileManager.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.service.remote 2 | 3 | import yos.clash.material.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/yos/clash/material/service/remote/IRemoteService.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/sideload/ExternalGeoip.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.service.sideload 2 | 3 | import android.content.Context 4 | import android.content.pm.PackageManager 5 | import yos.clash.material.common.constants.Metadata 6 | import yos.clash.material.common.log.Log 7 | import java.io.InputStream 8 | 9 | fun Context.readGeoipDatabaseFrom(packageName: String): ByteArray? { 10 | return try { 11 | val appInfo = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA) 12 | val path = appInfo.metaData.getString(Metadata.GEOIP_FILE_NAME) ?: return null 13 | 14 | createPackageContext(packageName, 0) 15 | .resources.assets.open(path).use(InputStream::readBytes) 16 | } catch (e: PackageManager.NameNotFoundException) { 17 | Log.w("Sideload geoip: $packageName not found", e) 18 | 19 | null 20 | } 21 | } -------------------------------------------------------------------------------- /service/src/main/java/yos/clash/material/service/util/Address.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/util/Connectivity.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.service.util 2 | 3 | import android.net.ConnectivityManager 4 | import android.net.Network 5 | 6 | fun ConnectivityManager.resolvePrimaryDns(network: Network?): String? { 7 | val properties = getLinkProperties(network) ?: return null 8 | 9 | return properties.dnsServers.firstOrNull()?.asSocketAddressText(53) 10 | } 11 | -------------------------------------------------------------------------------- /service/src/main/java/yos/clash/material/service/util/Coroutine.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/util/Database.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.service.util 2 | 3 | import yos.clash.material.service.data.ImportedDao 4 | import yos.clash.material.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/yos/clash/material/service/util/Files.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/util/Intent.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/util/Net.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/yos/clash/material/service/util/Serializers.kt: -------------------------------------------------------------------------------- 1 | package yos.clash.material.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/drawable/ic_logo_service.xml: -------------------------------------------------------------------------------- 1 | 8 | 17 | 18 | -------------------------------------------------------------------------------- /service/src/main/res/values-zh-rHK/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clash You 狀態 4 | 正在運行 5 | 更新 %s 成功 6 | "更新 %1$s: %2$s " 7 | Clash You 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 You 狀態 4 | 正在運作 5 | 更新 %s 成功 6 | "更新 %1$s: %2$s " 7 | Clash You 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 You 状态 4 | 正在运行 5 | 更新 %s 成功 6 | "更新 %1$s: %2$s " 7 | Clash You 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 You 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 You 16 | Profiles and Providers 17 | Configuration.yaml 18 | Provider Files 19 | Profile Updater 20 | Profile Updating 21 | 22 | --------------------------------------------------------------------------------