├── .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-debug.yaml │ ├── build-pre-release.yaml │ ├── build-release.yaml │ └── update-dependencies.yaml ├── .gitignore ├── .gitmodules ├── .idea └── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── PRIVACY_POLICY.md ├── README.md ├── app ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── ic_launcher-web.png │ ├── java │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ ├── AccessControlActivity.kt │ │ ├── ApkBrokenActivity.kt │ │ ├── AppCrashedActivity.kt │ │ ├── AppSettingsActivity.kt │ │ ├── BaseActivity.kt │ │ ├── DialerReceiver.kt │ │ ├── ExternalControlActivity.kt │ │ ├── FilesActivity.kt │ │ ├── HelpActivity.kt │ │ ├── LogcatActivity.kt │ │ ├── LogcatService.kt │ │ ├── LogsActivity.kt │ │ ├── MainActivity.kt │ │ ├── MainApplication.kt │ │ ├── MetaFeatureSettingsActivity.kt │ │ ├── NetworkSettingsActivity.kt │ │ ├── NewProfileActivity.kt │ │ ├── OverrideSettingsActivity.kt │ │ ├── ProfilesActivity.kt │ │ ├── PropertiesActivity.kt │ │ ├── ProvidersActivity.kt │ │ ├── ProxyActivity.kt │ │ ├── RestartReceiver.kt │ │ ├── SettingsActivity.kt │ │ ├── TileService.kt │ │ ├── log │ │ ├── LogcatCache.kt │ │ ├── LogcatFilter.kt │ │ ├── LogcatReader.kt │ │ ├── LogcatWriter.kt │ │ └── SystemLogcat.kt │ │ ├── remote │ │ ├── Broadcasts.kt │ │ ├── FilesClient.kt │ │ ├── Remote.kt │ │ ├── Resource.kt │ │ ├── Service.kt │ │ └── StatusClient.kt │ │ ├── store │ │ ├── AppStore.kt │ │ └── TipsStore.kt │ │ └── util │ │ ├── Activity.kt │ │ ├── Application.kt │ │ ├── Clash.kt │ │ ├── Content.kt │ │ ├── Files.kt │ │ ├── Remote.kt │ │ ├── Service.kt │ │ └── Uri.kt │ └── res │ ├── drawable │ └── ic_launcher_foreground.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_banner.png │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── values-night │ └── themes.xml │ ├── values │ ├── colors.xml │ ├── ic_banner_background.xml │ ├── ic_launcher_background.xml │ ├── ids.xml │ └── themes.xml │ └── xml │ ├── full_backup_content.xml │ └── network_security_config.xml ├── build.gradle.kts ├── common ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ └── common │ │ ├── Global.kt │ │ ├── compat │ │ ├── App.kt │ │ ├── Context.kt │ │ ├── Html.kt │ │ ├── Intents.kt │ │ ├── Package.kt │ │ ├── Resource.kt │ │ ├── Services.kt │ │ ├── UI.kt │ │ └── View.kt │ │ ├── constants │ │ ├── Authorities.kt │ │ ├── Components.kt │ │ ├── Intents.kt │ │ ├── Metadata.kt │ │ └── Permissions.kt │ │ ├── id │ │ └── UndefinedIds.kt │ │ ├── log │ │ └── Log.kt │ │ ├── store │ │ ├── Providers.kt │ │ ├── Store.kt │ │ └── StoreProvider.kt │ │ └── util │ │ ├── Components.kt │ │ ├── Global.kt │ │ ├── Intent.kt │ │ ├── Parcelable.kt │ │ ├── Patterns.kt │ │ └── Ticker.kt │ └── res │ ├── values-ru │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ └── values │ └── strings.xml ├── core ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── foss │ └── golang │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ └── main │ ├── AndroidManifest.xml │ ├── cpp │ ├── CMakeLists.txt │ ├── bridge_helper.c │ ├── bridge_helper.h │ ├── jni_helper.c │ ├── jni_helper.h │ ├── main.c │ └── version.h.in │ ├── golang │ ├── .idea │ │ └── codeStyles │ │ │ ├── Project.xml │ │ │ └── codeStyleConfig.xml │ ├── go.mod │ ├── go.sum │ └── native │ │ ├── all │ │ └── imports.go │ │ ├── app.go │ │ ├── app │ │ ├── app.go │ │ ├── content.go │ │ ├── dns.go │ │ ├── tun.go │ │ └── ui.go │ │ ├── bridge.c │ │ ├── bridge.h │ │ ├── common │ │ └── path.go │ │ ├── config.go │ │ ├── config │ │ ├── defaults.go │ │ ├── fetch.go │ │ ├── load.go │ │ ├── override.go │ │ ├── process.go │ │ └── provider.go │ │ ├── debug.go │ │ ├── delegate │ │ └── init.go │ │ ├── log.go │ │ ├── main.go │ │ ├── platform │ │ ├── limit.go │ │ └── procfs.go │ │ ├── proxy.go │ │ ├── proxy │ │ └── http.go │ │ ├── trace.c │ │ ├── trace.h │ │ ├── tun.go │ │ ├── tun │ │ └── tun.go │ │ ├── tunnel.go │ │ ├── tunnel │ │ ├── conn.go │ │ ├── connectivity.go │ │ ├── loopback.go │ │ ├── providers.go │ │ ├── proxies.go │ │ ├── state.go │ │ ├── statistic.go │ │ └── suspend.go │ │ └── utils.go │ └── java │ └── com │ └── github │ └── kr328 │ └── clash │ └── core │ ├── Clash.kt │ ├── bridge │ ├── Bridge.kt │ ├── ClashException.kt │ ├── Content.kt │ ├── FetchCallback.kt │ ├── LogcatInterface.kt │ └── TunInterface.kt │ ├── model │ ├── ConfigurationOverride.kt │ ├── FetchStatus.kt │ ├── LogMessage.kt │ ├── Provider.kt │ ├── ProviderList.kt │ ├── Proxy.kt │ ├── ProxyGroup.kt │ ├── ProxySort.kt │ ├── Traffic.kt │ ├── TunnelState.kt │ └── UiConfiguration.kt │ └── util │ ├── Net.kt │ ├── Parcelizer.kt │ ├── Serializers.kt │ └── Traffic.kt ├── design ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ └── design │ │ ├── AccessControlDesign.kt │ │ ├── ApkBrokenDesign.kt │ │ ├── AppCrashedDesign.kt │ │ ├── AppSettingsDesign.kt │ │ ├── Design.kt │ │ ├── FilesDesign.kt │ │ ├── HelpDesign.kt │ │ ├── LogcatDesign.kt │ │ ├── LogsDesign.kt │ │ ├── MainDesign.kt │ │ ├── MetaFeatureSettingsDesign.kt │ │ ├── NetworkSettingsDesign.kt │ │ ├── NewProfileDesign.kt │ │ ├── OverrideSettingsDesign.kt │ │ ├── ProfilesDesign.kt │ │ ├── PropertiesDesign.kt │ │ ├── ProvidersDesign.kt │ │ ├── ProxyDesign.kt │ │ ├── SettingsDesign.kt │ │ ├── adapter │ │ ├── AppAdapter.kt │ │ ├── EditableTextListAdapter.kt │ │ ├── EditableTextMapAdapter.kt │ │ ├── FileAdapter.kt │ │ ├── LogFileAdapter.kt │ │ ├── LogMessageAdapter.kt │ │ ├── PopupListAdapter.kt │ │ ├── ProfileAdapter.kt │ │ ├── ProfileProviderAdapter.kt │ │ ├── ProviderAdapter.kt │ │ ├── ProxyAdapter.kt │ │ └── ProxyPageAdapter.kt │ │ ├── component │ │ ├── AccessControlMenu.kt │ │ ├── ProxyMenu.kt │ │ ├── ProxyPageFactory.kt │ │ ├── ProxyView.kt │ │ ├── ProxyViewConfig.kt │ │ └── ProxyViewState.kt │ │ ├── dialog │ │ ├── Dialogs.kt │ │ ├── Input.kt │ │ └── Progress.kt │ │ ├── model │ │ ├── AppInfo.kt │ │ ├── AppInfoSort.kt │ │ ├── Behavior.kt │ │ ├── DarkMode.kt │ │ ├── File.kt │ │ ├── LogFile.kt │ │ ├── ProfilePageState.kt │ │ ├── ProfileProvider.kt │ │ ├── ProviderState.kt │ │ ├── ProxyPageState.kt │ │ └── ProxyState.kt │ │ ├── preference │ │ ├── Category.kt │ │ ├── Clickable.kt │ │ ├── EditableText.kt │ │ ├── EditableTextList.kt │ │ ├── EditableTextMap.kt │ │ ├── Overlay.kt │ │ ├── Preference.kt │ │ ├── Screen.kt │ │ ├── SelectableList.kt │ │ ├── Switch.kt │ │ ├── Tips.kt │ │ └── Value.kt │ │ ├── store │ │ └── UiStore.kt │ │ ├── ui │ │ ├── DayNight.kt │ │ ├── Insets.kt │ │ ├── ObservableCurrentTime.kt │ │ ├── Surface.kt │ │ └── ToastDuration.kt │ │ ├── util │ │ ├── ActivityBar.kt │ │ ├── App.kt │ │ ├── Binding.kt │ │ ├── Context.kt │ │ ├── Diff.kt │ │ ├── Elevation.kt │ │ ├── I18n.kt │ │ ├── Inserts.kt │ │ ├── Interval.kt │ │ ├── Landscape.kt │ │ ├── ListView.kt │ │ ├── RecyclerView.kt │ │ ├── ScrollView.kt │ │ ├── Theme.kt │ │ ├── Toast.kt │ │ ├── Validator.kt │ │ └── View.kt │ │ └── view │ │ ├── ActionLabel.kt │ │ ├── ActionTextField.kt │ │ ├── ActivityBarLayout.kt │ │ ├── AppRecyclerView.kt │ │ ├── LargeActionCard.kt │ │ ├── LargeActionLabel.kt │ │ ├── ObservableScrollView.kt │ │ └── VerticalScrollableHost.kt │ └── res │ ├── anim │ └── rotate_infinite.xml │ ├── drawable │ ├── bg_b.xml │ ├── bg_bottom_sheet.xml │ ├── ic_baseline_adb.xml │ ├── ic_baseline_add.xml │ ├── ic_baseline_apps.xml │ ├── ic_baseline_arrow_back.xml │ ├── ic_baseline_assignment.xml │ ├── ic_baseline_attach_file.xml │ ├── ic_baseline_brightness_4.xml │ ├── ic_baseline_clear_all.xml │ ├── ic_baseline_close.xml │ ├── ic_baseline_cloud_download.xml │ ├── ic_baseline_content_copy.xml │ ├── ic_baseline_delete.xml │ ├── ic_baseline_dns.xml │ ├── ic_baseline_domain.xml │ ├── ic_baseline_edit.xml │ ├── ic_baseline_extension.xml │ ├── ic_baseline_flash_on.xml │ ├── ic_baseline_get_app.xml │ ├── ic_baseline_help_center.xml │ ├── ic_baseline_hide.xml │ ├── ic_baseline_info.xml │ ├── ic_baseline_meta.xml │ ├── ic_baseline_more_vert.xml │ ├── ic_baseline_publish.xml │ ├── ic_baseline_replay.xml │ ├── ic_baseline_restore.xml │ ├── ic_baseline_save.xml │ ├── ic_baseline_search.xml │ ├── ic_baseline_settings.xml │ ├── ic_baseline_stop.xml │ ├── ic_baseline_swap_vert.xml │ ├── ic_baseline_swap_vertical_circle.xml │ ├── ic_baseline_sync.xml │ ├── ic_baseline_update.xml │ ├── ic_baseline_view_list.xml │ ├── ic_baseline_vpn_lock.xml │ ├── ic_baseline_work.xml │ ├── ic_clash.xml │ ├── ic_outline_article.xml │ ├── ic_outline_check_circle.xml │ ├── ic_outline_delete.xml │ ├── ic_outline_folder.xml │ ├── ic_outline_inbox.xml │ ├── ic_outline_info.xml │ ├── ic_outline_label.xml │ ├── ic_outline_not_interested.xml │ └── ic_outline_update.xml │ ├── layout │ ├── adapter_app.xml │ ├── adapter_editable_text_list.xml │ ├── adapter_editable_text_map.xml │ ├── adapter_file.xml │ ├── adapter_log_message.xml │ ├── adapter_profile.xml │ ├── adapter_profile_provider.xml │ ├── adapter_provider.xml │ ├── adapter_sideload_provider.xml │ ├── common_activity_bar.xml │ ├── common_recycler_list.xml │ ├── component_action_label.xml │ ├── component_action_text_field.xml │ ├── component_large_action_label.xml │ ├── design_about.xml │ ├── design_access_control.xml │ ├── design_app_crashed.xml │ ├── design_files.xml │ ├── design_logcat.xml │ ├── design_logs.xml │ ├── design_main.xml │ ├── design_new_profile.xml │ ├── design_profiles.xml │ ├── design_properties.xml │ ├── design_providers.xml │ ├── design_proxy.xml │ ├── design_settings.xml │ ├── design_settings_common.xml │ ├── design_settings_meta_feature.xml │ ├── design_settings_overide.xml │ ├── dialog_editable_map_text_field.xml │ ├── dialog_fetch_status.xml │ ├── dialog_files_menu.xml │ ├── dialog_preference_list.xml │ ├── dialog_profiles_menu.xml │ ├── dialog_search.xml │ ├── dialog_text_field.xml │ ├── preference_category.xml │ ├── preference_clickable.xml │ ├── preference_switch.xml │ └── preference_tips.xml │ ├── menu │ ├── menu_access_control.xml │ └── menu_proxy.xml │ ├── values-ja-rJP │ └── strings.xml │ ├── values-ko-rKR │ └── strings.xml │ ├── values-ru │ └── strings.xml │ ├── values-v23 │ └── themes.xml │ ├── values-v27 │ └── themes.xml │ ├── values-v29 │ └── themes.xml │ ├── values-vi │ └── strings.xml │ ├── values-zh-rHK │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── ids.xml │ ├── strings.xml │ ├── styles.xml │ └── themes.xml ├── fastlane └── metadata │ └── android │ ├── en-US │ ├── full_description.txt │ └── short_description.txt │ └── zh-CN │ ├── full_description.txt │ └── short_description.txt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hideapi ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── android │ └── app │ └── ActivityThread.java ├── release.keystore ├── renovate.json ├── service ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ └── service │ │ ├── BaseService.kt │ │ ├── ClashManager.kt │ │ ├── ClashService.kt │ │ ├── FilesProvider.kt │ │ ├── PreferenceProvider.kt │ │ ├── ProfileManager.kt │ │ ├── ProfileProcessor.kt │ │ ├── ProfileReceiver.kt │ │ ├── ProfileWorker.kt │ │ ├── RemoteService.kt │ │ ├── StatusProvider.kt │ │ ├── TunService.kt │ │ ├── clash │ │ ├── ClashRuntime.kt │ │ └── module │ │ │ ├── AppListCacheModule.kt │ │ │ ├── CloseModule.kt │ │ │ ├── ConfigurationModule.kt │ │ │ ├── DynamicNotificationModule.kt │ │ │ ├── Module.kt │ │ │ ├── NetworkObserveModule.kt │ │ │ ├── StaticNotificationModule.kt │ │ │ ├── SuspendModule.kt │ │ │ ├── TimeZoneModule.kt │ │ │ └── TunModule.kt │ │ ├── data │ │ ├── Converters.kt │ │ ├── Daos.kt │ │ ├── Database.kt │ │ ├── Imported.kt │ │ ├── ImportedDao.kt │ │ ├── Pending.kt │ │ ├── PendingDao.kt │ │ ├── Selection.kt │ │ ├── SelectionDao.kt │ │ └── migrations │ │ │ ├── LegacyMigration.kt │ │ │ └── Migrations.kt │ │ ├── document │ │ ├── Document.kt │ │ ├── FileDocument.kt │ │ ├── Flag.kt │ │ ├── Path.kt │ │ ├── Paths.kt │ │ ├── Picker.kt │ │ └── VirtualDocument.kt │ │ ├── model │ │ ├── AccessControlMode.kt │ │ └── Profile.kt │ │ ├── remote │ │ ├── IClashManager.kt │ │ ├── IFetchObserver.kt │ │ ├── ILogObserver.kt │ │ ├── IProfileManager.kt │ │ └── IRemoteService.kt │ │ ├── store │ │ └── ServiceStore.kt │ │ └── util │ │ ├── Address.kt │ │ ├── Broadcast.kt │ │ ├── Connectivity.kt │ │ ├── Coroutine.kt │ │ ├── Database.kt │ │ ├── Files.kt │ │ ├── Intent.kt │ │ ├── Net.kt │ │ └── Serializers.kt │ └── res │ ├── drawable │ └── ic_logo_service.xml │ ├── values-ja-rJP │ └── strings.xml │ ├── values-ko-rKR │ └── strings.xml │ ├── values-ru │ └── strings.xml │ ├── values-zh-rHK │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ └── values │ ├── arrays.xml │ ├── colors.xml │ ├── ids.xml │ └── strings.xml └── settings.gradle.kts /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.bat text eol=crlf 4 | *.jar binary 5 | -------------------------------------------------------------------------------- /.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 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this feature request! 10 | 11 | NOTE: Be sure to put a clear and concise title **AFTER** `[Feature Request]` in the text box above. 12 | 13 | <!-- template --> 14 | - type: textarea 15 | id: "description" 16 | attributes: 17 | label: "Feature Description" 18 | description: | 19 | A clear and concise description of the feature. 20 | validations: 21 | required: true 22 | - type: textarea 23 | id: "additional" 24 | attributes: 25 | label: "Additional" 26 | description: | 27 | Add any other context or screenshots about the feature request here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/04-feature-request-zh-cn.yml: -------------------------------------------------------------------------------- 1 | name: "[简体中文] 功能请求" 2 | description: "您希望的能够在应用中增加功能" 3 | title: "[Feature Request] " 4 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 感谢您在百忙之中填写此功能请求报告。 10 | 11 | 注意: 请务必在上方文本框的 `[Feature Request]` **之后**填写清晰明了的标题。 12 | 13 | <!-- template --> 14 | - type: textarea 15 | id: "description" 16 | attributes: 17 | label: "功能描述" 18 | description: | 19 | 简介明了的描述此功能。 20 | validations: 21 | required: true 22 | - type: textarea 23 | id: "additional" 24 | attributes: 25 | label: "附加信息" 26 | description: | 27 | 与此功能相关的其他附加信息。 28 | -------------------------------------------------------------------------------- /.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 | !/.idea/codeStyles 23 | /core/src/main/golang/.idea/* 24 | !/core/src/main/golang/.idea/codeStyles 25 | /core/src/foss/golang/.idea/* 26 | !/core/src/foss/golang/.idea/codeStyles 27 | /core/src/premium/golang/.idea/* 28 | !/core/src/premium/golang/.idea/codeStyles 29 | 30 | # Ignore builtin geofiles 31 | app/src/main/assets 32 | 33 | # KeyStore 34 | signing.properties 35 | *.keystore 36 | *.jks 37 | 38 | # clion cmake build 39 | cmake-build-* 40 | 41 | # local.properties 42 | local.properties 43 | 44 | 45 | # tracker 46 | tracker.properties 47 | 48 | # vscode 49 | .vscode 50 | 51 | # cxx 52 | .cxx 53 | 54 | *.hprof 55 | 56 | # firebase 57 | google-services.json 58 | 59 | # Dolphin 60 | .directory 61 | 62 | # logs 63 | *.log 64 | 65 | # MacOS 66 | .DS_Store 67 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "clash-foss"] 2 | path = core/src/foss/golang/clash 3 | url = https://github.com/MetaCubeX/mihomo 4 | branch = Alpha 5 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | <component name="ProjectCodeStyleConfiguration"> 2 | <state> 3 | <option name="USE_PER_PROJECT_SETTINGS" value="true" /> 4 | </state> 5 | </component> -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to Clash for Android 2 | 3 | #### Code Style 4 | 5 | Please use `Android Studio` or `Intellij IDEA` to open the project and use the project code style profile. 6 | 7 | `File` -> `Settings` -> `Editor` -> `Code Style` -> `C/C++ and Kotlin` -> `Scheme` -> `Project` 8 | 9 | 10 | 11 | #### License 12 | 13 | Contributing to Clash for Android that assumes you allow code to be merged into closed-source branch of Clash for Android. Other terms follow the [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/ApkBrokenActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import com.github.kr328.clash.design.ApkBrokenDesign 6 | import kotlinx.coroutines.isActive 7 | 8 | class ApkBrokenActivity : BaseActivity<ApkBrokenDesign>() { 9 | override suspend fun main() { 10 | val design = ApkBrokenDesign(this) 11 | 12 | setContentDesign(design) 13 | 14 | while (isActive) { 15 | val req = design.requests.receive() 16 | 17 | startActivity(Intent(Intent.ACTION_VIEW).setData(Uri.parse(req.url))) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/AppCrashedActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import com.github.kr328.clash.common.compat.versionCodeCompat 4 | import com.github.kr328.clash.common.log.Log 5 | import com.github.kr328.clash.design.AppCrashedDesign 6 | import com.github.kr328.clash.log.SystemLogcat 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.isActive 9 | import kotlinx.coroutines.withContext 10 | 11 | class AppCrashedActivity : BaseActivity<AppCrashedDesign>() { 12 | override suspend fun main() { 13 | val design = AppCrashedDesign(this) 14 | 15 | setContentDesign(design) 16 | 17 | val packageInfo = withContext(Dispatchers.IO) { 18 | packageManager.getPackageInfo(packageName, 0) 19 | } 20 | 21 | Log.i("App version: versionName = ${packageInfo.versionName} versionCode = ${packageInfo.versionCodeCompat}") 22 | 23 | val logs = withContext(Dispatchers.IO) { 24 | SystemLogcat.dumpCrash() 25 | } 26 | 27 | design.setAppLogs(logs) 28 | 29 | while (isActive) { 30 | events.receive() 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/DialerReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | 7 | class DialerReceiver : BroadcastReceiver() { 8 | override fun onReceive(context: Context, intent: Intent) { 9 | val intent = Intent(context, MainActivity::class.java) 10 | .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 11 | context.startActivity(intent) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/HelpActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.content.Intent 4 | import com.github.kr328.clash.design.HelpDesign 5 | import kotlinx.coroutines.isActive 6 | 7 | class HelpActivity : BaseActivity<HelpDesign>() { 8 | override suspend fun main() { 9 | val design = HelpDesign(this) { 10 | startActivity(Intent(Intent.ACTION_VIEW).setData(it)) 11 | } 12 | 13 | setContentDesign(design) 14 | 15 | while (isActive) { 16 | events.receive() 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/NetworkSettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import com.github.kr328.clash.common.util.intent 4 | import com.github.kr328.clash.design.NetworkSettingsDesign 5 | import com.github.kr328.clash.service.store.ServiceStore 6 | import kotlinx.coroutines.isActive 7 | import kotlinx.coroutines.selects.select 8 | 9 | class NetworkSettingsActivity : BaseActivity<NetworkSettingsDesign>() { 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<Unit> { 22 | events.onReceive { 23 | when (it) { 24 | Event.ClashStart, Event.ClashStop, Event.ServiceRecreated -> 25 | recreate() 26 | else -> Unit 27 | } 28 | } 29 | design.requests.onReceive { 30 | when (it) { 31 | NetworkSettingsDesign.Request.StartAccessControlList -> 32 | startActivity(AccessControlActivity::class.intent) 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/RestartReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.github.kr328.clash.service.StatusProvider 7 | import com.github.kr328.clash.util.startClashService 8 | 9 | class RestartReceiver : BroadcastReceiver() { 10 | override fun onReceive(context: Context, intent: Intent) { 11 | when (intent.action) { 12 | Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_MY_PACKAGE_REPLACED -> { 13 | if (StatusProvider.shouldStartClashOnBoot) 14 | context.startClashService() 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import com.github.kr328.clash.common.util.intent 4 | import com.github.kr328.clash.design.SettingsDesign 5 | import kotlinx.coroutines.isActive 6 | import kotlinx.coroutines.selects.select 7 | 8 | class SettingsActivity : BaseActivity<SettingsDesign>() { 9 | override suspend fun main() { 10 | val design = SettingsDesign(this) 11 | 12 | setContentDesign(design) 13 | 14 | while (isActive) { 15 | select<Unit> { 16 | events.onReceive { 17 | 18 | } 19 | design.requests.onReceive { 20 | when (it) { 21 | SettingsDesign.Request.StartApp -> 22 | startActivity(AppSettingsActivity::class.intent) 23 | SettingsDesign.Request.StartNetwork -> 24 | startActivity(NetworkSettingsActivity::class.intent) 25 | SettingsDesign.Request.StartOverride -> 26 | startActivity(OverrideSettingsActivity::class.intent) 27 | SettingsDesign.Request.StartMetaFeature -> 28 | startActivity(MetaFeatureSettingsActivity::class.intent) 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/log/LogcatCache.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.log 2 | 3 | import androidx.collection.CircularArray 4 | import com.github.kr328.clash.core.model.LogMessage 5 | import kotlinx.coroutines.sync.Mutex 6 | import kotlinx.coroutines.sync.withLock 7 | 8 | class LogcatCache { 9 | data class Snapshot(val messages: List<LogMessage>, val removed: Int, val appended: Int) 10 | 11 | private val array = CircularArray<LogMessage>(CAPACITY) 12 | private val lock = Mutex() 13 | 14 | private var removed: Int = 0 15 | private var appended: Int = 0 16 | 17 | suspend fun append(msg: LogMessage) { 18 | lock.withLock { 19 | if (array.size() >= CAPACITY) { 20 | array.removeFromStart(1) 21 | 22 | removed++ 23 | appended-- 24 | } 25 | 26 | array.addLast(msg) 27 | 28 | appended++ 29 | } 30 | } 31 | 32 | suspend fun snapshot(full: Boolean): Snapshot? { 33 | return lock.withLock { 34 | if (!full && removed == 0 && appended == 0) { 35 | return@withLock null 36 | } 37 | 38 | Snapshot( 39 | List(array.size()) { array[it] }, 40 | removed, 41 | if (full) array.size() + appended else appended 42 | ).also { 43 | removed = 0 44 | appended = 0 45 | } 46 | } 47 | } 48 | 49 | companion object { 50 | const val CAPACITY = 128 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/log/LogcatFilter.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.log 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.core.model.LogMessage 5 | import com.github.kr328.clash.design.util.format 6 | import java.io.BufferedWriter 7 | import java.io.Writer 8 | import java.util.* 9 | 10 | class LogcatFilter(output: Writer, private val context: Context) : BufferedWriter(output) { 11 | fun writeHeader(time: Date) { 12 | appendLine("# Capture on ${time.format(context)}") 13 | } 14 | 15 | fun writeMessage(message: LogMessage) { 16 | val time = message.time.format(context, includeDate = false) 17 | val level = message.level.name 18 | 19 | appendLine(FORMAT.format(time, level, message.message)) 20 | } 21 | 22 | companion object { 23 | private const val FORMAT = "%12s %7s: %s" 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/log/LogcatWriter.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.log 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.core.model.LogMessage 5 | import com.github.kr328.clash.design.model.LogFile 6 | import com.github.kr328.clash.util.logsDir 7 | import java.io.BufferedWriter 8 | import java.io.FileWriter 9 | 10 | class LogcatWriter(context: Context) : AutoCloseable { 11 | private val file = LogFile.generate() 12 | private val writer = BufferedWriter(FileWriter(context.logsDir.resolve(file.fileName))) 13 | 14 | override fun close() { 15 | writer.close() 16 | } 17 | 18 | fun appendMessage(message: LogMessage) { 19 | writer.appendLine(FORMAT.format(message.time.time, message.level.name, message.message)) 20 | } 21 | 22 | companion object { 23 | private const val FORMAT = "%d:%s:%s" 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/log/SystemLogcat.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.log 2 | 3 | object SystemLogcat { 4 | private val command = arrayOf( 5 | "logcat", 6 | "-d", 7 | "-s", 8 | "Go", 9 | "DEBUG", 10 | "AndroidRuntime", 11 | "ClashMetaForAndroid", 12 | "LwIP", 13 | ) 14 | 15 | fun dumpCrash(): String { 16 | return try { 17 | val process = Runtime.getRuntime().exec(command) 18 | 19 | val result = process.inputStream.use { stream -> 20 | stream.reader().readLines() 21 | .filterNot { it.startsWith("------") } 22 | .joinToString("\n") 23 | } 24 | 25 | process.waitFor() 26 | 27 | result.trim() 28 | } catch (e: Exception) { 29 | "" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/remote/StatusClient.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.remote 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | import com.github.kr328.clash.common.constants.Authorities 6 | import com.github.kr328.clash.common.log.Log 7 | import com.github.kr328.clash.service.StatusProvider 8 | 9 | class StatusClient(private val context: Context) { 10 | private val uri: Uri 11 | get() { 12 | return Uri.Builder() 13 | .scheme("content") 14 | .authority(Authorities.STATUS_PROVIDER) 15 | .build() 16 | } 17 | 18 | fun currentProfile(): String? { 19 | return try { 20 | val result = context.contentResolver.call( 21 | uri, 22 | StatusProvider.METHOD_CURRENT_PROFILE, 23 | null, 24 | null 25 | ) 26 | 27 | result?.getString("name") 28 | } catch (e: Exception) { 29 | Log.w("Query current profile: $e", e) 30 | 31 | null 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/store/AppStore.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.store 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.common.store.Store 5 | import com.github.kr328.clash.common.store.asStoreProvider 6 | 7 | class AppStore(context: Context) { 8 | private val store = Store( 9 | context 10 | .getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE) 11 | .asStoreProvider() 12 | ) 13 | 14 | var updatedAt: Long by store.long( 15 | key = "updated_at", 16 | defaultValue = -1, 17 | ) 18 | 19 | companion object { 20 | private const val FILE_NAME = "app" 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/store/TipsStore.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.store 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.common.store.Store 5 | import com.github.kr328.clash.common.store.asStoreProvider 6 | 7 | class TipsStore(context: Context) { 8 | private val store = Store( 9 | context 10 | .getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE) 11 | .asStoreProvider() 12 | ) 13 | 14 | var requestDonate: Boolean by store.boolean( 15 | key = "request_donate", 16 | defaultValue = true, 17 | ) 18 | 19 | var primaryVersion: Int by store.int( 20 | key = "primary_version", 21 | defaultValue = -1, 22 | ) 23 | 24 | companion object { 25 | const val CURRENT_PRIMARY_VERSION = 1 26 | 27 | private const val FILE_NAME = "tips" 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Activity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.lifecycle.LifecycleOwner 5 | import androidx.lifecycle.LifecycleRegistry 6 | import kotlinx.coroutines.NonCancellable 7 | import kotlinx.coroutines.withContext 8 | 9 | class ActivityResultLifecycle : LifecycleOwner { 10 | override val lifecycle = LifecycleRegistry(this) 11 | 12 | init { 13 | lifecycle.currentState = Lifecycle.State.INITIALIZED 14 | } 15 | 16 | suspend fun <T> use(block: suspend (lifecycle: ActivityResultLifecycle, start: () -> Unit) -> T): T { 17 | return try { 18 | markCreated() 19 | 20 | block(this, this::markStarted) 21 | } finally { 22 | withContext(NonCancellable) { 23 | markDestroy() 24 | } 25 | } 26 | } 27 | 28 | private fun markCreated() { 29 | lifecycle.currentState = Lifecycle.State.CREATED 30 | } 31 | 32 | private fun markStarted() { 33 | lifecycle.currentState = Lifecycle.State.STARTED 34 | lifecycle.currentState = Lifecycle.State.RESUMED 35 | } 36 | 37 | private fun markDestroy() { 38 | lifecycle.currentState = Lifecycle.State.DESTROYED 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Clash.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.VpnService 6 | import com.github.kr328.clash.common.compat.startForegroundServiceCompat 7 | import com.github.kr328.clash.common.constants.Intents 8 | import com.github.kr328.clash.common.util.intent 9 | import com.github.kr328.clash.design.store.UiStore 10 | import com.github.kr328.clash.service.ClashService 11 | import com.github.kr328.clash.service.TunService 12 | import com.github.kr328.clash.service.util.sendBroadcastSelf 13 | 14 | fun Context.startClashService(): Intent? { 15 | val startTun = UiStore(this).enableVpn 16 | 17 | if (startTun) { 18 | val vpnRequest = VpnService.prepare(this) 19 | if (vpnRequest != null) 20 | return vpnRequest 21 | 22 | startForegroundServiceCompat(TunService::class.intent) 23 | } else { 24 | startForegroundServiceCompat(ClashService::class.intent) 25 | } 26 | 27 | return null 28 | } 29 | 30 | fun Context.stopClashService() { 31 | sendBroadcastSelf(Intent(Intents.ACTION_CLASH_REQUEST_STOP)) 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Content.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.content.ContentResolver 4 | import android.net.Uri 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import java.io.FileNotFoundException 8 | 9 | private fun fileNotFound(file: Uri): FileNotFoundException { 10 | return FileNotFoundException("$file not found") 11 | } 12 | 13 | @Suppress("BlockingMethodInNonBlockingContext") 14 | suspend fun ContentResolver.copyContentTo( 15 | source: Uri, 16 | target: Uri 17 | ) { 18 | withContext(Dispatchers.IO) { 19 | (openInputStream(source) ?: throw fileNotFound(source)).use { input -> 20 | (openOutputStream(target, "rwt") ?: throw fileNotFound(target)).use { output -> 21 | input.copyTo(output) 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Files.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.content.Context 4 | import java.io.File 5 | 6 | val Context.logsDir: File 7 | get() = cacheDir.resolve("logs") 8 | 9 | val Context.clashDir: File 10 | get() = filesDir.resolve("clash") -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Remote.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.os.DeadObjectException 4 | import com.github.kr328.clash.common.log.Log 5 | import com.github.kr328.clash.remote.Remote 6 | import com.github.kr328.clash.service.remote.IClashManager 7 | import com.github.kr328.clash.service.remote.IProfileManager 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.withContext 10 | import kotlin.coroutines.CoroutineContext 11 | 12 | suspend fun <T> withClash( 13 | context: CoroutineContext = Dispatchers.IO, 14 | block: suspend IClashManager.() -> T 15 | ): T { 16 | while (true) { 17 | val remote = Remote.service.remote.get() 18 | val client = remote.clash() 19 | 20 | try { 21 | return withContext(context) { client.block() } 22 | } catch (e: DeadObjectException) { 23 | Log.w("Remote services panic") 24 | 25 | Remote.service.remote.reset(remote) 26 | } 27 | } 28 | } 29 | 30 | suspend fun <T> withProfile( 31 | context: CoroutineContext = Dispatchers.IO, 32 | block: suspend IProfileManager.() -> T 33 | ): T { 34 | while (true) { 35 | val remote = Remote.service.remote.get() 36 | val client = remote.profile() 37 | 38 | try { 39 | return withContext(context) { client.block() } 40 | } catch (e: DeadObjectException) { 41 | Log.w("Remote services panic") 42 | 43 | Remote.service.remote.reset(remote) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Service.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.content.Context 4 | import android.content.ServiceConnection 5 | 6 | fun Context.unbindServiceSilent(connection: ServiceConnection) { 7 | try { 8 | unbindService(connection) 9 | } catch (e: Exception) { 10 | // ignore 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Uri.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.net.Uri 4 | 5 | val Uri.fileName: String? 6 | get() = schemeSpecificPart.split("/").lastOrNull() -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> 3 | <background android:drawable="@color/ic_launcher_background"/> 4 | <foreground android:drawable="@drawable/ic_launcher_foreground"/> 5 | <monochrome android:drawable="@drawable/ic_launcher_foreground" /> 6 | </adaptive-icon> -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> 3 | <background android:drawable="@color/ic_launcher_background"/> 4 | <foreground android:drawable="@drawable/ic_launcher_foreground"/> 5 | <monochrome android:drawable="@drawable/ic_launcher_foreground" /> 6 | </adaptive-icon> -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/app/src/main/res/mipmap-xhdpi/ic_banner.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <style name="BootstrapTheme" parent="AppThemeDark" /> 4 | </resources> -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <color name="color_launcher_background">#FFFFFF</color> 4 | </resources> -------------------------------------------------------------------------------- /app/src/main/res/values/ic_banner_background.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <color name="ic_banner_background">#FFFFFF</color> 4 | </resources> -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <color name="ic_launcher_background">#FFFFFF</color> 4 | </resources> -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <item name="nf_logcat_status" type="id" /> 4 | </resources> -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <style name="BootstrapTheme" parent="AppThemeLight" /> 4 | </resources> 5 | -------------------------------------------------------------------------------- /app/src/main/res/xml/full_backup_content.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <full-backup-content> 3 | <include 4 | domain="sharedpref" 5 | path="." /> 6 | <include 7 | domain="database" 8 | path="." /> 9 | <include 10 | domain="file" 11 | path="imported" /> 12 | <include 13 | domain="file" 14 | path="pending" /> 15 | <include 16 | domain="file" 17 | path="clash/override.json" /> 18 | </full-backup-content> -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <network-security-config xmlns:tools="http://schemas.android.com/tools" 3 | tools:ignore="AcceptsUserCertificates"> 4 | <base-config> 5 | <trust-anchors> 6 | <certificates src="system" /> 7 | <certificates src="user" /> 8 | </trust-anchors> 9 | </base-config> 10 | </network-security-config> -------------------------------------------------------------------------------- /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/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/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 | <manifest xmlns:android="http://schemas.android.com/apk/res/android"> 2 | 3 | <permission 4 | android:name="${applicationId}.permission.RECEIVE_BROADCASTS" 5 | android:description="@string/receive_broadcasts_of_clash" 6 | android:label="@string/receive_clash_broadcasts" 7 | android:protectionLevel="privileged|signature" /> 8 | <uses-permission android:name="${applicationId}.permission.RECEIVE_BROADCASTS" /> 9 | </manifest> 10 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/Global.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common 2 | 3 | import android.app.Application 4 | import 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/com/github/kr328/clash/common/compat/App.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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 com.github.kr328.clash.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/com/github/kr328/clash/common/compat/Context.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package com.github.kr328.clash.common.compat 4 | 5 | import android.annotation.SuppressLint 6 | import android.content.BroadcastReceiver 7 | import android.content.Context 8 | import android.content.IntentFilter 9 | import android.graphics.drawable.Drawable 10 | import android.os.Build 11 | import android.os.Handler 12 | import androidx.annotation.ColorRes 13 | import androidx.annotation.DrawableRes 14 | import androidx.core.content.ContextCompat 15 | 16 | fun Context.getColorCompat(@ColorRes id: Int): Int { 17 | return ContextCompat.getColor(this, id) 18 | } 19 | 20 | fun Context.getDrawableCompat(@DrawableRes id: Int): Drawable? { 21 | return ContextCompat.getDrawable(this, id) 22 | } 23 | 24 | @SuppressLint("UnspecifiedRegisterReceiverFlag") 25 | fun Context.registerReceiverCompat( 26 | receiver: BroadcastReceiver, 27 | filter: IntentFilter, 28 | permission: String? = null, 29 | handler: Handler? = null 30 | ) = 31 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) 32 | registerReceiver(receiver, filter, permission, handler, 33 | if (permission == null) Context.RECEIVER_EXPORTED else Context.RECEIVER_NOT_EXPORTED 34 | ) 35 | else 36 | registerReceiver(receiver, filter, permission, handler) 37 | 38 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/compat/Html.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package com.github.kr328.clash.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/com/github/kr328/clash/common/compat/Intents.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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/com/github/kr328/clash/common/compat/Package.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package com.github.kr328.clash.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/com/github/kr328/clash/common/compat/Resource.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package com.github.kr328.clash.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/com/github/kr328/clash/common/compat/Services.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.compat 2 | 3 | import android.app.Notification 4 | import android.app.Service 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.content.pm.ServiceInfo 8 | import android.os.Build 9 | 10 | fun Context.startForegroundServiceCompat(intent: Intent) { 11 | if (Build.VERSION.SDK_INT >= 26) { 12 | startForegroundService(intent) 13 | } else { 14 | startService(intent) 15 | } 16 | } 17 | 18 | fun Service.startForegroundCompat(id: Int, notification: Notification) { 19 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { 20 | startForeground(id, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE) 21 | } else { 22 | startForeground(id, notification) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/compat/View.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package com.github.kr328.clash.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/com/github/kr328/clash/common/constants/Authorities.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.constants 2 | 3 | import com.github.kr328.clash.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/com/github/kr328/clash/common/constants/Components.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.constants 2 | 3 | import android.content.ComponentName 4 | import com.github.kr328.clash.common.util.packageName 5 | 6 | object Components { 7 | private const val componentsPackageName = "com.github.kr328.clash" 8 | 9 | val MAIN_ACTIVITY = ComponentName(packageName, "$componentsPackageName.MainActivity") 10 | val PROPERTIES_ACTIVITY = ComponentName(packageName, "$componentsPackageName.PropertiesActivity") 11 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/constants/Metadata.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.constants 2 | 3 | import com.github.kr328.clash.common.util.packageName 4 | 5 | object Metadata { 6 | val GEOIP_FILE_NAME = "$packageName.GEOIP_FILE_NAME" 7 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/constants/Permissions.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.constants 2 | 3 | import com.github.kr328.clash.common.util.packageName 4 | 5 | object Permissions { 6 | val RECEIVE_SELF_BROADCASTS = "$packageName.permission.RECEIVE_BROADCASTS" 7 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/id/UndefinedIds.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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/com/github/kr328/clash/common/log/Log.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.log 2 | 3 | object Log { 4 | private const val TAG = "ClashMetaForAndroid" 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/com/github/kr328/clash/common/store/StoreProvider.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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<String>): Set<String> 14 | fun setStringSet(key: String, value: Set<String>) 15 | 16 | fun getBoolean(key: String, defaultValue: Boolean): Boolean 17 | fun setBoolean(key: String, value: Boolean) 18 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/util/Components.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.util 2 | 3 | import android.content.ComponentName 4 | import android.content.Intent 5 | import com.github.kr328.clash.common.Global 6 | import kotlin.reflect.KClass 7 | 8 | val KClass<*>.componentName: ComponentName 9 | get() = ComponentName(Global.application.packageName, this.java.name) 10 | 11 | val KClass<*>.intent: Intent 12 | get() = Intent(Global.application, this.java) -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/util/Global.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.util 2 | 3 | import com.github.kr328.clash.common.Global 4 | 5 | val packageName: String = Global.application.packageName -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/util/Intent.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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/com/github/kr328/clash/common/util/Patterns.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.util 2 | 3 | val PatternFileName = Regex("[^*&%\\n\\r/]+") -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/util/Ticker.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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<Long> { 10 | val channel = Channel<Long>(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-ru/strings.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <string name="receive_clash_broadcasts">Получать оповещения от Clash</string> 4 | <string name="receive_broadcasts_of_clash">Получать оповещения от сервисов Clash</string> 5 | </resources> 6 | -------------------------------------------------------------------------------- /common/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <string name="receive_clash_broadcasts">接收 Clash 廣播</string> 4 | <string name="receive_broadcasts_of_clash">接收來自 Clash 內部的廣播</string> 5 | </resources> 6 | -------------------------------------------------------------------------------- /common/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <string name="receive_clash_broadcasts">接收 Clash 广播</string> 4 | <string name="receive_broadcasts_of_clash">接收来自 Clash 内部的广播</string> 5 | </resources> -------------------------------------------------------------------------------- /common/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <string name="receive_clash_broadcasts">Receive Clash Broadcasts</string> 4 | <string name="receive_broadcasts_of_clash">Receive broadcasts of clash services</string> 5 | </resources> 6 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | /src/main/cpp/version.h 2 | -------------------------------------------------------------------------------- /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 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" > 2 | 3 | <uses-permission android:name="android.permission.INTERNET" /> 4 | </manifest> 5 | -------------------------------------------------------------------------------- /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 <stdint.h> 4 | 5 | uint64_t down_scale_traffic(uint64_t value); -------------------------------------------------------------------------------- /core/src/main/cpp/jni_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include <jni.h> 4 | #include <stdint.h> 5 | #include <stdlib.h> 6 | #include <malloc.h> 7 | #include <android/log.h> 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/cpp/version.h.in: -------------------------------------------------------------------------------- 1 | #ifndef VERSION_H_IN 2 | #define VERSION_H_IN 3 | 4 | /** 5 | * 当前编译core版本号 6 | */ 7 | 8 | #define GIT_VERSION @GIT_VERSION@ 9 | #define make_Str(x) #x 10 | #define make_String(x) make_Str(x) 11 | 12 | #endif -------------------------------------------------------------------------------- /core/src/main/golang/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | <component name="ProjectCodeStyleConfiguration"> 2 | <code_scheme name="Project" version="173"> 3 | <GoCodeStyleSettings> 4 | <option name="IMPORT_SORTING" value="GOIMPORTS" /> 5 | <option name="MOVE_ALL_IMPORTS_IN_ONE_DECLARATION" value="true" /> 6 | <option name="MOVE_ALL_STDLIB_IMPORTS_IN_ONE_GROUP" value="true" /> 7 | <option name="GROUP_STDLIB_IMPORTS" value="true" /> 8 | </GoCodeStyleSettings> 9 | </code_scheme> 10 | </component> -------------------------------------------------------------------------------- /core/src/main/golang/.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | <component name="ProjectCodeStyleConfiguration"> 2 | <state> 3 | <option name="USE_PER_PROJECT_SETTINGS" value="true" /> 4 | </state> 5 | </component> -------------------------------------------------------------------------------- /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/metacubex/mihomo/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/metacubex/mihomo/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/metacubex/mihomo/dns" 7 | ) 8 | 9 | func NotifyDnsChanged(dnsList string) { 10 | var addr []string 11 | if len(dnsList) > 0 { 12 | addr = strings.Split(dnsList, ",") 13 | } 14 | dns.UpdateSystemDNS(addr) 15 | dns.FlushCacheWithDefaultResolver() 16 | } 17 | -------------------------------------------------------------------------------- /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/metacubex/mihomo/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.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //#include "bridge.h" 4 | import "C" 5 | 6 | import ( 7 | "runtime" 8 | "unsafe" 9 | 10 | "cfa/native/config" 11 | ) 12 | 13 | type remoteValidCallback struct { 14 | callback unsafe.Pointer 15 | } 16 | 17 | func (r *remoteValidCallback) reportStatus(json string) { 18 | C.fetch_report(r.callback, marshalString(json)) 19 | } 20 | 21 | //export fetchAndValid 22 | func fetchAndValid(callback unsafe.Pointer, path, url C.c_string, force C.int) { 23 | go func(path, url string, callback unsafe.Pointer) { 24 | cb := &remoteValidCallback{callback: callback} 25 | 26 | err := config.FetchAndValid(path, url, force != 0, cb.reportStatus) 27 | 28 | C.fetch_complete(callback, marshalString(err)) 29 | 30 | C.release_object(callback) 31 | 32 | runtime.GC() 33 | }(C.GoString(path), C.GoString(url), callback) 34 | } 35 | 36 | //export load 37 | func load(completable unsafe.Pointer, path C.c_string) { 38 | go func(path string) { 39 | C.complete(completable, marshalString(config.Load(path))) 40 | 41 | C.release_object(completable) 42 | 43 | runtime.GC() 44 | }(C.GoString(path)) 45 | } 46 | 47 | //export readOverride 48 | func readOverride(slot C.int) *C.char { 49 | return C.CString(config.ReadOverride(config.OverrideSlot(slot))) 50 | } 51 | 52 | //export writeOverride 53 | func writeOverride(slot C.int, content C.c_string) { 54 | c := C.GoString(content) 55 | 56 | config.WriteOverride(config.OverrideSlot(slot), c) 57 | } 58 | 59 | //export clearOverride 60 | func clearOverride(slot C.int) { 61 | config.ClearOverride(config.OverrideSlot(slot)) 62 | } -------------------------------------------------------------------------------- /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/provider.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/metacubex/mihomo/config" 7 | ) 8 | 9 | const ( 10 | PROXIES = "proxies" 11 | RULES = "rules" 12 | ) 13 | 14 | func forEachProviders(rawCfg *config.RawConfig, fun func(index int, total int, key string, provider map[string]any, prefix string)) { 15 | total := len(rawCfg.ProxyProvider) + len(rawCfg.RuleProvider) 16 | index := 0 17 | 18 | for k, v := range rawCfg.ProxyProvider { 19 | fun(index, total, k, v, PROXIES) 20 | 21 | index++ 22 | } 23 | 24 | for k, v := range rawCfg.RuleProvider { 25 | fun(index, total, k, v, RULES) 26 | 27 | index++ 28 | } 29 | } 30 | 31 | func destroyProviders(cfg *config.Config) { 32 | for _, p := range cfg.Providers { 33 | if p, ok := p.(io.Closer); ok { 34 | _ = p.Close() 35 | } 36 | } 37 | 38 | for _, p := range cfg.RuleProviders { 39 | if p, ok := p.(io.Closer); ok { 40 | _ = p.Close() 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /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/metacubex/mihomo/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/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 | "runtime/debug" 13 | 14 | "cfa/native/config" 15 | "cfa/native/delegate" 16 | "cfa/native/tunnel" 17 | 18 | "github.com/metacubex/mihomo/log" 19 | ) 20 | 21 | func main() { 22 | panic("Stub!") 23 | } 24 | 25 | //export coreInit 26 | func coreInit(home, versionName, gitVersion C.c_string, sdkVersion C.int) { 27 | h := C.GoString(home) 28 | v := C.GoString(versionName) 29 | g := C.GoString(gitVersion) 30 | s := int(sdkVersion) 31 | 32 | delegate.Init(h, v, g, s) 33 | 34 | reset() 35 | } 36 | 37 | //export reset 38 | func reset() { 39 | config.LoadDefault() 40 | tunnel.ResetStatistic() 41 | tunnel.CloseAllConnections() 42 | 43 | runtime.GC() 44 | debug.FreeOSMemory() 45 | } 46 | 47 | //export forceGc 48 | func forceGc() { 49 | go func() { 50 | log.Infoln("[APP] request force GC") 51 | 52 | runtime.GC() 53 | debug.FreeOSMemory() 54 | }() 55 | } 56 | -------------------------------------------------------------------------------- /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/metacubex/mihomo/listener/http" 7 | "github.com/metacubex/mihomo/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.Tunnel, false) 20 | if err == nil { 21 | listenAt = listener.Address() 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 <android/log.h> 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/tunnel/conn.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | C "github.com/metacubex/mihomo/constant" 5 | "github.com/metacubex/mihomo/tunnel/statistic" 6 | ) 7 | 8 | func CloseAllConnections() { 9 | statistic.DefaultManager.Range(func(c statistic.Tracker) bool { 10 | _ = c.Close() 11 | return true 12 | }) 13 | } 14 | 15 | func closeMatch(filter func(conn C.Connection) bool) { 16 | statistic.DefaultManager.Range(func(c statistic.Tracker) bool { 17 | if filter(c) { 18 | _ = c.Close() 19 | } 20 | return true 21 | }) 22 | } 23 | 24 | func closeConnByGroup(name string) { 25 | closeMatch(func(conn C.Connection) 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/metacubex/mihomo/adapter/outboundgroup" 7 | "github.com/metacubex/mihomo/constant/provider" 8 | "github.com/metacubex/mihomo/log" 9 | "github.com/metacubex/mihomo/tunnel" 10 | ) 11 | 12 | func HealthCheck(name string) { 13 | p := tunnel.Proxies()[name] 14 | 15 | if p == nil { 16 | log.Warnln("Request health check for `%s`: not found", name) 17 | 18 | return 19 | } 20 | 21 | g, ok := p.Adapter().(outboundgroup.ProxyGroup) 22 | if !ok { 23 | log.Warnln("Request health check for `%s`: invalid type %s", name, p.Type().String()) 24 | 25 | return 26 | } 27 | 28 | wg := &sync.WaitGroup{} 29 | 30 | for _, pr := range g.Providers() { 31 | wg.Add(1) 32 | 33 | go func(provider provider.ProxyProvider) { 34 | provider.HealthCheck() 35 | 36 | wg.Done() 37 | }(pr) 38 | } 39 | 40 | wg.Wait() 41 | } 42 | 43 | func HealthCheckAll() { 44 | for _, g := range QueryProxyGroupNames(false) { 45 | go func(group string) { 46 | HealthCheck(group) 47 | }(g) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/loopback.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "net/netip" 5 | ) 6 | 7 | var loopback = netip.MustParseAddr("127.0.0.1") 8 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/state.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "github.com/metacubex/mihomo/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/metacubex/mihomo/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 | func Suspend(s bool) { 4 | // cause by ACTION_SCREEN_OFF/ACTION_SCREEN_ON, 5 | // but we don't know what should do so just ignored. 6 | // 7 | // WARNING: don't call core's Tunnel.OnSuspend/OnRunning at here, 8 | // this will cause the core to stop processing new incoming connections when the screen is locked. 9 | } 10 | -------------------------------------------------------------------------------- /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 com.github.kr328.clash.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<String>, 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<FetchStatus> { 30 | override fun createFromParcel(parcel: Parcel): FetchStatus { 31 | return Parcelizer.decodeFromParcel(serializer(), parcel) 32 | } 33 | 34 | override fun newArray(size: Int): Array<FetchStatus?> { 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<Provider> { 15 | enum class Type { 16 | Proxy, Rule 17 | } 18 | 19 | enum class VehicleType { 20 | HTTP, File, Inline, 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<Provider> { 36 | override fun createFromParcel(parcel: Parcel): Provider { 37 | return Parcelizer.decodeFromParcel(serializer(), parcel) 38 | } 39 | 40 | override fun newArray(size: Int): Array<Provider?> { 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 com.github.kr328.clash.common.util.createListFromParcelSlice 6 | import com.github.kr328.clash.common.util.writeToParcelSlice 7 | 8 | class ProviderList(data: List<Provider>) : List<Provider> 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<ProviderList> { 20 | override fun createFromParcel(parcel: Parcel): ProviderList { 21 | return ProviderList(parcel) 22 | } 23 | 24 | override fun newArray(size: Int): Array<ProviderList?> { 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<TunnelState> { 37 | override fun createFromParcel(parcel: Parcel): TunnelState { 38 | return Parcelizer.decodeFromParcel(serializer(), parcel) 39 | } 40 | 41 | override fun newArray(size: Int): Array<TunnelState?> { 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<UiConfiguration> { 19 | override fun createFromParcel(parcel: Parcel): UiConfiguration { 20 | return Parcelizer.decodeFromParcel(serializer(), parcel) 21 | } 22 | 23 | override fun newArray(size: Int): Array<UiConfiguration?> { 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<Date> { 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 | } -------------------------------------------------------------------------------- /design/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("android") 3 | kotlin("kapt") 4 | id("com.android.library") 5 | } 6 | 7 | dependencies { 8 | implementation(project(":common")) 9 | implementation(project(":core")) 10 | implementation(project(":service")) 11 | 12 | implementation(libs.kotlin.coroutine) 13 | implementation(libs.androidx.core) 14 | implementation(libs.androidx.appcompat) 15 | implementation(libs.androidx.activity) 16 | implementation(libs.androidx.coordinator) 17 | implementation(libs.androidx.recyclerview) 18 | implementation(libs.androidx.fragment) 19 | implementation(libs.androidx.viewpager) 20 | implementation(libs.google.material) 21 | } 22 | -------------------------------------------------------------------------------- /design/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/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 | <manifest /> 2 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/AppCrashedDesign.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import com.github.kr328.clash.design.databinding.DesignAppCrashedBinding 6 | import com.github.kr328.clash.design.util.applyFrom 7 | import com.github.kr328.clash.design.util.bindAppBarElevation 8 | import com.github.kr328.clash.design.util.layoutInflater 9 | import com.github.kr328.clash.design.util.root 10 | 11 | class AppCrashedDesign(context: Context) : Design<Unit>(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/com/github/kr328/clash/design/SettingsDesign.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import com.github.kr328.clash.design.databinding.DesignSettingsBinding 6 | import com.github.kr328.clash.design.util.applyFrom 7 | import com.github.kr328.clash.design.util.bindAppBarElevation 8 | import com.github.kr328.clash.design.util.layoutInflater 9 | import com.github.kr328.clash.design.util.root 10 | 11 | class SettingsDesign(context: Context) : Design<SettingsDesign.Request>(context) { 12 | enum class Request { 13 | StartApp, StartNetwork, StartOverride, StartMetaFeature, 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/com/github/kr328/clash/design/adapter/LogFileAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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 com.github.kr328.clash.design.model.LogFile 8 | import com.github.kr328.clash.design.util.format 9 | import com.github.kr328.clash.design.view.ActionLabel 10 | 11 | class LogFileAdapter( 12 | private val context: Context, 13 | private val open: (LogFile) -> Unit, 14 | ) : RecyclerView.Adapter<LogFileAdapter.Holder>() { 15 | class Holder(val label: ActionLabel) : RecyclerView.ViewHolder(label) 16 | 17 | var logs: List<LogFile> = 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 | } 34 | 35 | override fun getItemCount(): Int { 36 | return logs.size 37 | } 38 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/adapter/LogMessageAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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 com.github.kr328.clash.design.databinding.AdapterLogMessageBinding 8 | import com.github.kr328.clash.design.util.layoutInflater 9 | 10 | class LogMessageAdapter( 11 | private val context: Context, 12 | private val copy: (LogMessage) -> Unit, 13 | ) : 14 | RecyclerView.Adapter<LogMessageAdapter.Holder>() { 15 | class Holder(val binding: AdapterLogMessageBinding) : RecyclerView.ViewHolder(binding.root) 16 | 17 | var messages: List<LogMessage> = 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/com/github/kr328/clash/design/adapter/ProxyAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.adapter 2 | 3 | import android.view.ViewGroup 4 | import androidx.recyclerview.widget.RecyclerView 5 | import com.github.kr328.clash.design.component.ProxyView 6 | import com.github.kr328.clash.design.component.ProxyViewConfig 7 | import com.github.kr328.clash.design.component.ProxyViewState 8 | 9 | class ProxyAdapter( 10 | private val config: ProxyViewConfig, 11 | private val clicked: (String) -> Unit, 12 | ) : RecyclerView.Adapter<ProxyAdapter.Holder>() { 13 | class Holder(val view: ProxyView) : RecyclerView.ViewHolder(view) 14 | 15 | var selectable: Boolean = false 16 | var states: List<ProxyViewState> = 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/com/github/kr328/clash/design/model/AppInfo.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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/com/github/kr328/clash/design/model/AppInfoSort.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.model 2 | 3 | enum class AppInfoSort(comparator: Comparator<AppInfo>) : Comparator<AppInfo> 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/com/github/kr328/clash/design/model/Behavior.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.model 2 | 3 | interface Behavior { 4 | var autoRestart: Boolean 5 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/model/DarkMode.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.model 2 | 3 | enum class DarkMode { 4 | Auto, ForceLight, ForceDark 5 | } 6 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/model/File.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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/com/github/kr328/clash/design/model/LogFile.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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/com/github/kr328/clash/design/model/ProfilePageState.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.model 2 | 3 | class ProfilePageState { 4 | var allUpdating = false 5 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/model/ProfileProvider.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.model 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.graphics.drawable.Drawable 6 | import com.github.kr328.clash.common.compat.getDrawableCompat 7 | import com.github.kr328.clash.design.R 8 | 9 | sealed class ProfileProvider { 10 | class File(private val context: Context) : ProfileProvider() { 11 | override val name: String 12 | get() = context.getString(R.string.file) 13 | override val summary: String 14 | get() = context.getString(R.string.import_from_file) 15 | override val icon: Drawable? 16 | get() = context.getDrawableCompat(R.drawable.ic_baseline_attach_file) 17 | } 18 | 19 | class Url(private val context: Context) : ProfileProvider() { 20 | override val name: String 21 | get() = context.getString(R.string.url) 22 | override val summary: String 23 | get() = context.getString(R.string.import_from_url) 24 | override val icon: Drawable? 25 | get() = context.getDrawableCompat(R.drawable.ic_baseline_cloud_download) 26 | } 27 | 28 | class External( 29 | override val name: String, 30 | override val summary: String, 31 | override val icon: Drawable?, 32 | val intent: Intent, 33 | ) : ProfileProvider() 34 | 35 | abstract val name: String 36 | abstract val summary: String 37 | abstract val icon: Drawable? 38 | } 39 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/model/ProviderState.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.model 2 | 3 | import androidx.databinding.BaseObservable 4 | import androidx.databinding.Bindable 5 | import com.github.kr328.clash.core.model.Provider 6 | import com.github.kr328.clash.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/com/github/kr328/clash/design/model/ProxyPageState.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.model 2 | 3 | class ProxyPageState { 4 | var bottom = false 5 | var urlTesting = false 6 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/model/ProxyState.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.model 2 | 3 | data class ProxyState(var now: String) -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/preference/Category.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.preference 2 | 3 | import android.view.View 4 | import androidx.annotation.StringRes 5 | import com.github.kr328.clash.design.databinding.PreferenceCategoryBinding 6 | import com.github.kr328.clash.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/com/github/kr328/clash/design/preference/Preference.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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/com/github/kr328/clash/design/preference/Screen.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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/com/github/kr328/clash/design/preference/Tips.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.preference 2 | 3 | import android.view.View 4 | import androidx.annotation.StringRes 5 | import com.github.kr328.clash.design.databinding.PreferenceTipsBinding 6 | import com.github.kr328.clash.design.util.getHtml 7 | import com.github.kr328.clash.design.util.layoutInflater 8 | import com.github.kr328.clash.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/com/github/kr328/clash/design/preference/Value.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.preference 2 | 3 | interface NullableTextAdapter<T> { 4 | fun from(value: T): String? 5 | fun to(text: String?): T 6 | 7 | companion object { 8 | val Port = object : NullableTextAdapter<Int?> { 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<String?> { 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<T> { 35 | fun from(value: T): String 36 | fun to(text: String): T 37 | 38 | companion object { 39 | val String = object : TextAdapter<String> { 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/com/github/kr328/clash/design/ui/DayNight.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.ui 2 | 3 | enum class DayNight { 4 | Day, Night 5 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/ui/Insets.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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/com/github/kr328/clash/design/ui/ObservableCurrentTime.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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/com/github/kr328/clash/design/ui/Surface.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.ui 2 | 3 | import androidx.databinding.BaseObservable 4 | import androidx.databinding.Bindable 5 | import com.github.kr328.clash.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/com/github/kr328/clash/design/ui/ToastDuration.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.ui 2 | 3 | enum class ToastDuration { 4 | Short, Long, Indefinite 5 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/util/ActivityBar.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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 com.github.kr328.clash.design.R 8 | import com.github.kr328.clash.design.view.ActivityBarLayout 9 | 10 | fun ActivityBarLayout.applyFrom(context: Context) { 11 | if (context is Activity) { 12 | findViewById<ImageView>(R.id.activity_bar_close_view)?.apply { 13 | setOnClickListener { 14 | context.onBackPressed() 15 | } 16 | } 17 | findViewById<TextView>(R.id.activity_bar_title_view)?.apply { 18 | text = context.title 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/util/App.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import android.content.pm.PackageInfo 4 | import android.content.pm.PackageManager 5 | import com.github.kr328.clash.common.compat.foreground 6 | import com.github.kr328.clash.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/com/github/kr328/clash/design/util/Binding.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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/com/github/kr328/clash/design/util/Context.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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 com.github.kr328.clash.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/com/github/kr328/clash/design/util/Diff.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | 5 | fun <T> List<T>.diffWith( 6 | newList: List<T>, 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/com/github/kr328/clash/design/util/Inserts.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import android.view.View 4 | import androidx.core.view.ViewCompat 5 | import androidx.core.view.WindowInsetsCompat 6 | import com.github.kr328.clash.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/com/github/kr328/clash/design/util/Interval.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.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/com/github/kr328/clash/design/util/Landscape.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.design.R 5 | import com.github.kr328.clash.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/com/github/kr328/clash/design/util/ListView.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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/com/github/kr328/clash/design/util/ScrollView.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import com.github.kr328.clash.design.view.ObservableScrollView 4 | 5 | val ObservableScrollView.isTop: Boolean 6 | get() = scrollX == 0 && scrollY == 0 7 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/util/Toast.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import com.github.kr328.clash.design.Design 4 | import com.github.kr328.clash.design.R 5 | import com.github.kr328.clash.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/com/github/kr328/clash/design/util/Validator.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import com.github.kr328.clash.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/com/github/kr328/clash/design/util/View.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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<InputMethodManager>() 13 | ?.showSoftInput(this, 0) 14 | }, 300) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/view/ActivityBarLayout.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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 com.github.kr328.clash.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/com/github/kr328/clash/design/view/AppRecyclerView.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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/com/github/kr328/clash/design/view/ObservableScrollView.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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<OnScrollChangedListener> = 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/com/github/kr328/clash/design/view/VerticalScrollableHost.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.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/anim/rotate_infinite.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <set xmlns:android="http://schemas.android.com/apk/res/android"> 3 | <rotate xmlns:android="http://schemas.android.com/apk/res/android" 4 | android:duration="1000" 5 | android:fromDegrees="360" 6 | android:pivotX="50%" 7 | android:pivotY="50%" 8 | android:toDegrees="0" 9 | android:repeatCount="infinite" 10 | android:interpolator="@android:anim/cycle_interpolator" /> 11 | </set> -------------------------------------------------------------------------------- /design/src/main/res/drawable/bg_b.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> 3 | <item> 4 | <shape android:shape="rectangle"> 5 | <solid android:color="?attr/colorSurface" /> 6 | <corners 7 | android:radius="8dp" 8 | /> 9 | </shape> 10 | </item> 11 | 12 | </layer-list> -------------------------------------------------------------------------------- /design/src/main/res/drawable/bg_bottom_sheet.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> 3 | <item> 4 | <shape android:shape="rectangle"> 5 | <solid android:color="?attr/colorSurface" /> 6 | <corners 7 | android:topLeftRadius="16dp" 8 | android:topRightRadius="16dp" /> 9 | </shape> 10 | </item> 11 | <item 12 | android:gravity="top|center_horizontal" 13 | android:top="@dimen/bottom_sheet_background_padding_top"> 14 | <shape android:shape="rectangle"> 15 | <size 16 | android:width="40dp" 17 | android:height="@dimen/bottom_sheet_header_height" /> 18 | <solid android:color="?attr/colorControlHighlight" /> 19 | <corners android:radius="5dp" /> 20 | </shape> 21 | </item> 22 | </layer-list> -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_adb.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M5,16c0,3.87 3.13,7 7,7s7,-3.13 7,-7v-4L5,12v4zM16.12,4.37l2.1,-2.1 -0.82,-0.83 -2.3,2.31C14.16,3.28 13.12,3 12,3s-2.16,0.28 -3.09,0.75L6.6,1.44l-0.82,0.83 2.1,2.1C6.14,5.64 5,7.68 5,10v1h14v-1c0,-2.32 -1.14,-4.36 -2.88,-5.63zM9,9c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM15,9c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_add.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_apps.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_arrow_back.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_assignment.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM14,17L7,17v-2h7v2zM17,13L7,13v-2h10v2zM17,9L7,9L7,7h10v2z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_attach_file.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M16.5,6v11.5c0,2.21 -1.79,4 -4,4s-4,-1.79 -4,-4V5c0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5v10.5c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1V6H10v9.5c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5V5c0,-2.21 -1.79,-4 -4,-4S7,2.79 7,5v12.5c0,3.04 2.46,5.5 5.5,5.5s5.5,-2.46 5.5,-5.5V6h-1.5z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_brightness_4.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69L23.31,12 20,8.69zM12,18c-0.89,0 -1.74,-0.2 -2.5,-0.55C11.56,16.5 13,14.42 13,12s-1.44,-4.5 -3.5,-5.45C10.26,6.2 11.11,6 12,6c3.31,0 6,2.69 6,6s-2.69,6 -6,6z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_clear_all.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M5,13h14v-2L5,11v2zM3,17h14v-2L3,15v2zM7,7v2h14L21,7L7,7z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_close.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_cloud_download.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_content_copy.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_delete.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_dns.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M20,13H4c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1v-6c0,-0.55 -0.45,-1 -1,-1zM7,19c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM20,3H4c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1V4c0,-0.55 -0.45,-1 -1,-1zM7,9c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_domain.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M12,7V3H2v18h20V7H12zM6,19H4v-2h2V19zM6,15H4v-2h2V15zM6,11H4V9h2V11zM6,7H4V5h2V7zM10,19H8v-2h2V19zM10,15H8v-2h2V15zM10,11H8V9h2V11zM10,7H8V5h2V7zM20,19h-8v-2h2v-2h-2v-2h2v-2h-2V9h8V19zM18,11h-2v2h2V11zM18,15h-2v2h2V15z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_edit.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_extension.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M20.5,11H19V7c0,-1.1 -0.9,-2 -2,-2h-4V3.5C13,2.12 11.88,1 10.5,1S8,2.12 8,3.5V5H4c-1.1,0 -1.99,0.9 -1.99,2v3.8H3.5c1.49,0 2.7,1.21 2.7,2.7s-1.21,2.7 -2.7,2.7H2V20c0,1.1 0.9,2 2,2h3.8v-1.5c0,-1.49 1.21,-2.7 2.7,-2.7 1.49,0 2.7,1.21 2.7,2.7V22H17c1.1,0 2,-0.9 2,-2v-4h1.5c1.38,0 2.5,-1.12 2.5,-2.5S21.88,11 20.5,11z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_flash_on.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M7,2v11h3v9l7,-12h-4l4,-8z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_get_app.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:viewportWidth="24" 5 | android:viewportHeight="24" 6 | android:tint="?attr/colorControlNormal"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_help_center.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM12.01,18c-0.7,0 -1.26,-0.56 -1.26,-1.26c0,-0.71 0.56,-1.25 1.26,-1.25c0.71,0 1.25,0.54 1.25,1.25C13.25,17.43 12.72,18 12.01,18zM15.02,10.6c-0.76,1.11 -1.48,1.46 -1.87,2.17c-0.16,0.29 -0.22,0.48 -0.22,1.41h-1.82c0,-0.49 -0.08,-1.29 0.31,-1.98c0.49,-0.87 1.42,-1.39 1.96,-2.16c0.57,-0.81 0.25,-2.33 -1.37,-2.33c-1.06,0 -1.58,0.8 -1.8,1.48L8.56,8.49C9.01,7.15 10.22,6 11.99,6c1.48,0 2.49,0.67 3.01,1.52C15.44,8.24 15.7,9.59 15.02,10.6z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_hide.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="1024" 6 | android:viewportHeight="1024"> 7 | 8 | <path 9 | android:fillColor="@android:color/white" 10 | android:pathData="M825.9,134.2l51.7,51.7 -655.1,655.1 -51.7,-51.7 655.1,-655.1zM804.4,325.8c41.3,39.5 81.3,87.9 120,145.3a73.1,73.1 0,0 1,2.8 77.4l-2.8,4.4 -6.9,10.1C795.2,740.3 660,829 512,829c-58.4,0 -114.9,-13.8 -169.3,-41.4l55.1,-55.1c37.4,15.7 75.5,23.4 114.2,23.4 120.9,0 235.5,-75.1 345.1,-234l6.7,-9.8 -6.7,-9.8c-34.3,-49.7 -69,-91.2 -104.4,-124.7l51.7,-51.7zM512,195c51.4,0 101.3,10.7 149.7,32.1l-56.5,56.5A289.4,289.4 0,0 0,512 268.2c-120.9,0 -235.5,75.1 -345.1,234L160.2,512l6.7,9.8c29.5,42.8 59.4,79.5 89.7,110.3l-51.7,51.7c-36.1,-36.7 -71.3,-80.3 -105.4,-130.9a73.1,73.1 0,0 1,-2.8 -77.4l2.8,-4.4 6.9,-10.1C228.8,283.7 364,195 512,195zM664.8,465.4a161.7,161.7 0,0 1,-205.8 205.8l65.1,-65.1a88.6,88.6 0,0 0,75.6 -75.6l65.1,-65.1zM512,356.7c6.4,0 12.8,0.4 19,1.1l-179.5,179.6A161.7,161.7 0,0 1,512 356.7z" /> 11 | 12 | </vector> 13 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_info.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_more_vert.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_publish.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M5,4v2h14L19,4L5,4zM5,14h4v6h6v-6h4l-7,-7 -7,7z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_replay.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M12,5V1L7,6l5,5V7c3.31,0 6,2.69 6,6s-2.69,6 -6,6 -6,-2.69 -6,-6H4c0,4.42 3.58,8 8,8s8,-3.58 8,-8 -3.58,-8 -8,-8z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_restore.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_save.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_search.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_settings.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_stop.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M6,6h12v12H6z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_swap_vert.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M16,17.01V10h-2v7.01h-3L15,21l4,-3.99h-3zM9,3L5,6.99h3V14h2V6.99h3L9,3z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_swap_vertical_circle.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM6.5,9L10,5.5 13.5,9L11,9v4L9,13L9,9L6.5,9zM17.5,15L14,18.5 10.5,15L13,15v-4h2v4h2.5z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_sync.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_update.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M21,10.12h-6.78l2.74,-2.82c-2.73,-2.7 -7.15,-2.8 -9.88,-0.1c-2.73,2.71 -2.73,7.08 0,9.79s7.15,2.71 9.88,0C18.32,15.65 19,14.08 19,12.1h2c0,1.98 -0.88,4.55 -2.64,6.29c-3.51,3.48 -9.21,3.48 -12.72,0c-3.5,-3.47 -3.53,-9.11 -0.02,-12.58s9.14,-3.47 12.65,0L21,3V10.12zM12.5,8v4.25l3.5,2.08l-0.72,1.21L11,13V8H12.5z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_view_list.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M4,14h4v-4L4,10v4zM4,19h4v-4L4,15v4zM4,9h4L8,5L4,5v4zM9,14h12v-4L9,10v4zM9,19h12v-4L9,15v4zM9,5v4h12L21,5L9,5z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_vpn_lock.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M22,4v-0.5C22,2.12 20.88,1 19.5,1S17,2.12 17,3.5L17,4c-0.55,0 -1,0.45 -1,1v4c0,0.55 0.45,1 1,1h5c0.55,0 1,-0.45 1,-1L23,5c0,-0.55 -0.45,-1 -1,-1zM21.2,4h-3.4v-0.5c0,-0.94 0.76,-1.7 1.7,-1.7s1.7,0.76 1.7,1.7L21.2,4zM18.92,12c0.04,0.33 0.08,0.66 0.08,1 0,2.08 -0.8,3.97 -2.1,5.39 -0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L7,13v-2h2c0.55,0 1,-0.45 1,-1L10,8h2c1.1,0 2,-0.9 2,-2L14,3.46c-0.95,-0.3 -1.95,-0.46 -3,-0.46C5.48,3 1,7.48 1,13s4.48,10 10,10 10,-4.48 10,-10c0,-0.34 -0.02,-0.67 -0.05,-1h-2.03zM10,20.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L8,16v1c0,1.1 0.9,2 2,2v1.93z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_work.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M20,6h-4L16,4c0,-1.11 -0.89,-2 -2,-2h-4c-1.11,0 -2,0.89 -2,2v2L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM14,6h-4L10,4h4v2z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_article.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M19,5v14H5V5H19M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3L19,3z" /> 10 | <path 11 | android:fillColor="@android:color/white" 12 | android:pathData="M14,17H7v-2h7V17zM17,13H7v-2h10V13zM17,9H7V7h10V9z" /> 13 | </vector> 14 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_check_circle.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM16.59,7.58L10,14.17l-2.59,-2.58L6,13l4,4 8,-8z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_delete.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M16,9v10H8V9h8m-1.5,-6h-5l-1,1H5v2h14V4h-3.5l-1,-1zM18,7H6v12c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_folder.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M9.17,6l2,2H20v10H4V6h5.17M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_inbox.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19L5,19v-3h3.56c0.69,1.19 1.97,2 3.45,2s2.75,-0.81 3.45,-2L19,16v3zM19,14h-4.99c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2L5,14L5,5h14v9z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_info.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M11,7h2v2h-2zM11,11h2v6h-2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_label.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M17.63,5.84C17.27,5.33 16.67,5 16,5L5,5.01C3.9,5.01 3,5.9 3,7v10c0,1.1 0.9,1.99 2,1.99L16,19c0.67,0 1.27,-0.33 1.63,-0.84L22,12l-4.37,-6.16zM16,17H5V7h11l3.55,5L16,17z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_not_interested.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8 0,-1.85 0.63,-3.55 1.69,-4.9L16.9,18.31C15.55,19.37 13.85,20 12,20zM18.31,16.9L7.1,5.69C8.45,4.63 10.15,4 12,4c4.42,0 8,3.58 8,8 0,1.85 -0.63,3.55 -1.69,4.9z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_update.xml: -------------------------------------------------------------------------------- 1 | <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 | android:width="24dp" 3 | android:height="24dp" 4 | android:tint="?attr/colorControlNormal" 5 | android:viewportWidth="24" 6 | android:viewportHeight="24"> 7 | <path 8 | android:fillColor="@android:color/white" 9 | android:pathData="M11,8v5l4.25,2.52l0.77,-1.28l-3.52,-2.09V8H11zM21,10V3l-2.64,2.64C16.74,4.01 14.49,3 12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9s9,-4.03 9,-9h-2c0,3.86 -3.14,7 -7,7s-7,-3.14 -7,-7s3.14,-7 7,-7c1.93,0 3.68,0.79 4.95,2.05L14,10H21z" /> 10 | </vector> 11 | -------------------------------------------------------------------------------- /design/src/main/res/layout/adapter_editable_text_list.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <layout xmlns:android="http://schemas.android.com/apk/res/android"> 3 | <RelativeLayout 4 | android:layout_width="match_parent" 5 | android:layout_height="wrap_content" 6 | android:paddingVertical="@dimen/item_padding_vertical" 7 | android:paddingStart="@dimen/item_header_margin" 8 | android:paddingEnd="@dimen/item_tailing_margin"> 9 | 10 | <TextView 11 | android:id="@+id/text_view" 12 | android:layout_width="wrap_content" 13 | android:layout_height="wrap_content" 14 | android:layout_alignParentStart="true" 15 | android:layout_centerVertical="true" 16 | android:layout_toStartOf="@id/delete_view" 17 | android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" /> 18 | 19 | <ImageView 20 | android:id="@+id/delete_view" 21 | android:layout_width="@dimen/item_tailing_component_size" 22 | android:layout_height="@dimen/item_tailing_component_size" 23 | android:layout_alignParentEnd="true" 24 | android:layout_centerVertical="true" 25 | android:background="?attr/selectableItemBackgroundBorderless" 26 | android:clickable="true" 27 | android:contentDescription="@string/delete" 28 | android:focusable="true" 29 | android:padding="@dimen/toolbar_image_action_padding" 30 | android:src="@drawable/ic_baseline_close" /> 31 | </RelativeLayout> 32 | </layout> -------------------------------------------------------------------------------- /design/src/main/res/layout/common_activity_bar.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 | android:layout_width="wrap_content" 4 | android:layout_height="wrap_content" 5 | android:gravity="center_vertical" 6 | android:minHeight="@dimen/toolbar_height" 7 | android:orientation="horizontal"> 8 | 9 | <ImageView 10 | android:id="@+id/activity_bar_close_view" 11 | android:layout_width="@dimen/item_header_component_size" 12 | android:layout_height="@dimen/item_header_component_size" 13 | android:layout_marginHorizontal="@dimen/item_header_margin" 14 | android:background="?attr/selectableItemBackgroundBorderless" 15 | android:clickable="true" 16 | android:contentDescription="@string/close" 17 | android:focusable="true" 18 | android:paddingHorizontal="@dimen/toolbar_image_action_padding" 19 | android:src="@drawable/ic_baseline_arrow_back" /> 20 | 21 | <TextView 22 | android:id="@+id/activity_bar_title_view" 23 | android:layout_width="wrap_content" 24 | android:layout_height="wrap_content" 25 | android:ellipsize="end" 26 | android:maxLines="1" 27 | android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" /> 28 | </LinearLayout> -------------------------------------------------------------------------------- /design/src/main/res/layout/common_recycler_list.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <layout xmlns:android="http://schemas.android.com/apk/res/android"> 3 | <data> 4 | <variable 5 | name="insets" 6 | type="com.github.kr328.clash.design.ui.Insets" /> 7 | </data> 8 | 9 | <com.github.kr328.clash.design.view.AppRecyclerView 10 | android:id="@+id/recycler_list" 11 | android:layout_width="match_parent" 12 | android:layout_height="match_parent" 13 | android:clipToPadding="false" 14 | android:paddingTop="@{(float) insets.top + @dimen/toolbar_height}" 15 | android:paddingBottom="@{insets.bottom}" /> 16 | </layout> -------------------------------------------------------------------------------- /design/src/main/res/layout/design_new_profile.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <layout xmlns:android="http://schemas.android.com/apk/res/android" 3 | xmlns:app="http://schemas.android.com/apk/res-auto"> 4 | <data> 5 | <variable 6 | name="self" 7 | type="com.github.kr328.clash.design.NewProfileDesign" /> 8 | </data> 9 | 10 | <androidx.coordinatorlayout.widget.CoordinatorLayout 11 | android:layout_width="match_parent" 12 | android:layout_height="match_parent" 13 | android:paddingStart="@{self.surface.insets.start}" 14 | android:paddingEnd="@{self.surface.insets.end}"> 15 | 16 | <include 17 | android:id="@+id/main_list" 18 | layout="@layout/common_recycler_list" 19 | app:insets="@{self.surface.insets}" /> 20 | 21 | <com.github.kr328.clash.design.view.ActivityBarLayout 22 | android:id="@+id/activity_bar_layout" 23 | android:layout_width="match_parent" 24 | android:layout_height="wrap_content" 25 | android:paddingTop="@{self.surface.insets.top}" 26 | android:paddingEnd="@dimen/item_tailing_margin"> 27 | 28 | <include layout="@layout/common_activity_bar" /> 29 | </com.github.kr328.clash.design.view.ActivityBarLayout> 30 | </androidx.coordinatorlayout.widget.CoordinatorLayout> 31 | </layout> -------------------------------------------------------------------------------- /design/src/main/res/layout/dialog_fetch_status.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <layout xmlns:android="http://schemas.android.com/apk/res/android"> 3 | <LinearLayout 4 | android:layout_width="match_parent" 5 | android:layout_height="match_parent" 6 | android:gravity="center_vertical" 7 | android:minHeight="@dimen/item_min_height" 8 | android:orientation="vertical" 9 | android:padding="@dimen/dialog_padding"> 10 | 11 | <com.google.android.material.progressindicator.LinearProgressIndicator 12 | android:id="@+id/progress_indicator" 13 | android:layout_width="match_parent" 14 | android:layout_height="wrap_content" /> 15 | 16 | <TextView 17 | android:id="@+id/text" 18 | android:layout_width="match_parent" 19 | android:layout_height="wrap_content" 20 | android:layout_marginTop="@dimen/item_text_margin" /> 21 | </LinearLayout> 22 | </layout> -------------------------------------------------------------------------------- /design/src/main/res/layout/dialog_text_field.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <layout xmlns:android="http://schemas.android.com/apk/res/android" 3 | xmlns:app="http://schemas.android.com/apk/res-auto"> 4 | <LinearLayout 5 | android:layout_width="match_parent" 6 | android:layout_height="wrap_content" 7 | android:paddingHorizontal="@dimen/dialog_padding" 8 | android:paddingTop="@dimen/dialog_padding" 9 | android:paddingBottom="@dimen/dialog_button_margin"> 10 | 11 | <com.google.android.material.textfield.TextInputLayout 12 | android:id="@+id/text_layout" 13 | style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" 14 | android:layout_width="match_parent" 15 | android:layout_height="wrap_content" 16 | app:errorEnabled="true"> 17 | 18 | <com.google.android.material.textfield.TextInputEditText 19 | android:id="@+id/text_field" 20 | android:layout_width="match_parent" 21 | android:layout_height="wrap_content" /> 22 | </com.google.android.material.textfield.TextInputLayout> 23 | </LinearLayout> 24 | </layout> -------------------------------------------------------------------------------- /design/src/main/res/layout/preference_category.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <layout xmlns:android="http://schemas.android.com/apk/res/android"> 3 | <TextView 4 | android:id="@+id/text_view" 5 | android:layout_width="match_parent" 6 | android:layout_height="wrap_content" 7 | android:paddingVertical="@dimen/item_padding_vertical" 8 | android:paddingStart="@{@dimen/item_header_component_size + @dimen/item_header_margin * 2}" 9 | android:paddingEnd="@dimen/item_tailing_margin" 10 | android:textColor="?attr/colorControlActivated" /> 11 | </layout> -------------------------------------------------------------------------------- /design/src/main/res/layout/preference_tips.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <layout xmlns:android="http://schemas.android.com/apk/res/android"> 3 | <LinearLayout 4 | android:layout_width="wrap_content" 5 | android:layout_height="wrap_content" 6 | android:gravity="center_vertical" 7 | android:orientation="horizontal" 8 | android:paddingVertical="@dimen/item_padding_vertical"> 9 | 10 | <View 11 | android:layout_width="@dimen/tips_icon_size" 12 | android:layout_height="@dimen/tips_icon_size" 13 | android:layout_marginHorizontal="@dimen/tips_icon_margin" 14 | android:background="@drawable/ic_outline_info" /> 15 | 16 | <TextView 17 | android:id="@+id/tips" 18 | android:layout_width="wrap_content" 19 | android:layout_height="wrap_content" 20 | android:layout_marginEnd="@dimen/item_tailing_margin" 21 | android:textAppearance="@style/TextAppearance.MaterialComponents.Body2" /> 22 | </LinearLayout> 23 | </layout> -------------------------------------------------------------------------------- /design/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <color name="color_clash_light">#1e4376</color> 4 | <color name="color_clash_dark">#1976d2</color> 5 | <color name="color_system_ui_overlay">#50000000</color> 6 | <color name="color_light_background">#FFFAFAFA</color> 7 | <color name="color_dark_background">#FF121212</color> 8 | <color name="color_dark_surface">#FF202020</color> 9 | <color name="color_light_clash_stopped">#FF808080</color> 10 | <color name="color_light_control_disabled">#FFD3D3D3</color> 11 | <color name="color_dark_control_disabled">#FF808080</color> 12 | <color name="color_error">#FFB00020</color> 13 | </resources> 14 | -------------------------------------------------------------------------------- /design/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <item name="close_button" type="id" /> 4 | </resources> -------------------------------------------------------------------------------- /design/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | 4 | <style name="ThemeOverlay" /> 5 | 6 | <style name="ThemeOverlay.ControlColorError"> 7 | <item name="colorControlNormal">@color/color_error</item> 8 | <item name="android:textColor">@color/color_error</item> 9 | </style> 10 | </resources> -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | A Graphical user interface of Clash.Meta for Android. 2 | 3 | <h3>Features</h3> 4 | <ul> 5 | <li>Local HTTP/HTTPS/SOCKS server</li> 6 | <li>VMess, Shadowsocks, Trojan, Snell protocol support for remote connections</li> 7 | <li>Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP</li> 8 | <li>Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes</li> 9 | <li>Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency</li> 10 | <li>Remote providers, allowing users to get node lists remotely instead of hardcoding in config</li> 11 | </ul> -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | A rule-based tunnel -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/full_description.txt: -------------------------------------------------------------------------------- 1 | Clash.Meta 的 Android 图形界面。 2 | 3 | <h3>功能</h3> 4 | <ul> 5 | <li>本地 HTTP/HTTPS/SOCKS 服务器</li> 6 | <li>支持 VMess, Shadowsocks, Trojan, Snell 协议远程连接</li> 7 | <li>内置 DNS 服务器,旨在最小化 DNS 污染攻击影响,支持 DoH/DoT 上游和 fake IP</li> 8 | <li>基于规则根据域名,GEOIP, IPCIDR 或进程转发数据包到不同节点</li> 9 | <li>远程分组允许用户实现强大的规则。支持自动回退,负载均衡或基于延迟自动选择节点</li> 10 | <li>远程提供者,允许用户远程获取节点列表而不是在配置中硬编码</li> 11 | </ul> -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/short_description.txt: -------------------------------------------------------------------------------- 1 | 基于规则的隧道 -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx4g -XX:+UseZGC -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | # Gradle parallel build 23 | org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jan 14 14:06:42 CST 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /hideapi/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | } 4 | -------------------------------------------------------------------------------- /hideapi/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/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 | <?xml version="1.0" encoding="utf-8"?> 2 | <manifest /> -------------------------------------------------------------------------------- /hideapi/src/main/java/android/app/ActivityThread.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | public class ActivityThread { 4 | public static String currentProcessName() { 5 | throw new IllegalArgumentException("Stub!"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /release.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/release.keystore -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /service/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("android") 3 | id("kotlinx-serialization") 4 | id("com.android.library") 5 | id("com.google.devtools.ksp") 6 | } 7 | 8 | dependencies { 9 | implementation(project(":core")) 10 | implementation(project(":common")) 11 | 12 | ksp(libs.kaidl.compiler) 13 | ksp(libs.androidx.room.compiler) 14 | 15 | implementation(libs.kotlin.coroutine) 16 | implementation(libs.kotlin.serialization.json) 17 | implementation(libs.androidx.core) 18 | implementation(libs.androidx.room.runtime) 19 | implementation(libs.androidx.room.ktx) 20 | implementation(libs.kaidl.runtime) 21 | implementation(libs.rikkax.multiprocess) 22 | implementation(platform("com.squareup.okhttp3:okhttp-bom:4.12.0")) 23 | 24 | // define any required OkHttp artifacts without version 25 | implementation("com.squareup.okhttp3:okhttp") 26 | implementation("com.squareup.okhttp3:logging-interceptor") 27 | } 28 | 29 | afterEvaluate { 30 | android { 31 | libraryVariants.forEach { 32 | sourceSets[it.name].kotlin.srcDir(buildDir.resolve("generated/ksp/${it.name}/kotlin")) 33 | sourceSets[it.name].java.srcDir(buildDir.resolve("generated/ksp/${it.name}/java")) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /service/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MetaCubeX/ClashMetaForAndroid/61093eb8d655cbd89dee761bbb79634481c3f5b6/service/consumer-rules.pro -------------------------------------------------------------------------------- /service/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/BaseService.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service 2 | 3 | import android.app.Service 4 | import com.github.kr328.clash.service.util.cancelAndJoinBlocking 5 | import kotlinx.coroutines.CoroutineScope 6 | import kotlinx.coroutines.Dispatchers 7 | 8 | abstract class BaseService : Service(), CoroutineScope by CoroutineScope(Dispatchers.Default) { 9 | override fun onDestroy() { 10 | super.onDestroy() 11 | 12 | cancelAndJoinBlocking() 13 | } 14 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/PreferenceProvider.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import com.github.kr328.clash.common.constants.Authorities 6 | import rikka.preference.MultiProcessPreference 7 | import rikka.preference.PreferenceProvider 8 | 9 | class PreferenceProvider : PreferenceProvider() { 10 | override fun onCreatePreference(context: Context): SharedPreferences { 11 | return context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE) 12 | } 13 | 14 | companion object { 15 | private const val FILE_NAME = "service" 16 | 17 | fun createSharedPreferencesFromContext(context: Context): SharedPreferences { 18 | return when (context) { 19 | is BaseService, is TunService -> 20 | context.getSharedPreferences( 21 | FILE_NAME, 22 | Context.MODE_PRIVATE 23 | ) 24 | else -> 25 | MultiProcessPreference( 26 | context, 27 | Authorities.SETTINGS_PROVIDER 28 | ) 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/RemoteService.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service 2 | 3 | import android.content.Intent 4 | import android.os.IBinder 5 | import com.github.kr328.clash.service.remote.IClashManager 6 | import com.github.kr328.clash.service.remote.IRemoteService 7 | import com.github.kr328.clash.service.remote.IProfileManager 8 | import com.github.kr328.clash.service.remote.wrap 9 | import com.github.kr328.clash.service.util.cancelAndJoinBlocking 10 | 11 | class RemoteService : BaseService(), IRemoteService { 12 | private val binder = this.wrap() 13 | 14 | private var clash: ClashManager? = null 15 | private var profile: ProfileManager? = null 16 | private var clashBinder: IClashManager? = null 17 | private var profileBinder: IProfileManager? = null 18 | 19 | override fun onCreate() { 20 | super.onCreate() 21 | 22 | clash = ClashManager(this) 23 | profile = ProfileManager(this) 24 | clashBinder = clash?.wrap() as IClashManager? 25 | profileBinder = profile?.wrap() as IProfileManager? 26 | } 27 | 28 | override fun onDestroy() { 29 | super.onDestroy() 30 | 31 | clash?.cancelAndJoinBlocking() 32 | profile?.cancelAndJoinBlocking() 33 | } 34 | 35 | override fun onBind(intent: Intent?): IBinder { 36 | return binder 37 | } 38 | 39 | override fun clash(): IClashManager { 40 | return clashBinder!! 41 | } 42 | 43 | override fun profile(): IProfileManager { 44 | return profileBinder!! 45 | } 46 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/clash/module/CloseModule.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.clash.module 2 | 3 | import android.app.Service 4 | import com.github.kr328.clash.common.constants.Intents 5 | import com.github.kr328.clash.common.log.Log 6 | 7 | class CloseModule(service: Service) : Module<CloseModule.RequestClose>(service) { 8 | object RequestClose 9 | 10 | override suspend fun run() { 11 | val broadcasts = receiveBroadcast { 12 | addAction(Intents.ACTION_CLASH_REQUEST_STOP) 13 | } 14 | 15 | broadcasts.receive() 16 | 17 | Log.d("User request close") 18 | 19 | return enqueueEvent(RequestClose) 20 | } 21 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/clash/module/TimeZoneModule.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.clash.module 2 | 3 | import android.app.Service 4 | import android.content.Intent 5 | import com.github.kr328.clash.core.Clash 6 | import java.util.* 7 | 8 | class TimeZoneModule(service: Service) : Module<Unit>(service) { 9 | override suspend fun run() { 10 | val timeZones = receiveBroadcast { 11 | addAction(Intent.ACTION_TIMEZONE_CHANGED) 12 | } 13 | 14 | while (true) { 15 | val timeZone = TimeZone.getDefault() 16 | 17 | Clash.notifyTimeZoneChanged(timeZone.id, timeZone.rawOffset / 1000) 18 | 19 | timeZones.receive() 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Converters.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.TypeConverter 4 | import com.github.kr328.clash.service.model.Profile 5 | import java.util.* 6 | 7 | class Converters { 8 | @TypeConverter 9 | fun fromUUID(uuid: UUID): String { 10 | return uuid.toString() 11 | } 12 | 13 | @TypeConverter 14 | fun toUUID(uuid: String): UUID { 15 | return UUID.fromString(uuid) 16 | } 17 | 18 | @TypeConverter 19 | fun fromProfileType(type: Profile.Type): String { 20 | return type.name 21 | } 22 | 23 | @TypeConverter 24 | fun toProfileType(type: String): Profile.Type { 25 | return Profile.Type.valueOf(type) 26 | } 27 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Daos.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | fun ImportedDao(): ImportedDao { 4 | return Database.database.openImportedDao() 5 | } 6 | 7 | fun PendingDao(): PendingDao { 8 | return Database.database.openPendingDao() 9 | } 10 | 11 | fun SelectionDao(): SelectionDao { 12 | return Database.database.openSelectionProxyDao() 13 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Imported.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.TypeConverters 6 | import com.github.kr328.clash.service.model.Profile 7 | import java.util.* 8 | 9 | @Entity(tableName = "imported", primaryKeys = ["uuid"]) 10 | @TypeConverters(Converters::class) 11 | data class Imported( 12 | @ColumnInfo(name = "uuid") val uuid: UUID, 13 | @ColumnInfo(name = "name") val name: String, 14 | @ColumnInfo(name = "type") val type: Profile.Type, 15 | @ColumnInfo(name = "source") val source: String, 16 | @ColumnInfo(name = "interval") val interval: Long, 17 | @ColumnInfo(name = "upload") val upload: Long, 18 | @ColumnInfo(name = "download") val download: Long, 19 | @ColumnInfo(name = "total") val total: Long, 20 | @ColumnInfo(name = "expire") val expire: Long, 21 | @ColumnInfo(name = "createdAt") val createdAt: Long, 22 | ) -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/ImportedDao.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.* 4 | import java.util.* 5 | 6 | @Dao 7 | @TypeConverters(Converters::class) 8 | interface ImportedDao { 9 | @Query("SELECT * FROM imported WHERE uuid = :uuid") 10 | suspend fun queryByUUID(uuid: UUID): Imported? 11 | 12 | @Query("SELECT uuid FROM imported ORDER BY createdAt") 13 | suspend fun queryAllUUIDs(): List<UUID> 14 | 15 | @Insert(onConflict = OnConflictStrategy.ABORT) 16 | suspend fun insert(imported: Imported): Long 17 | 18 | @Update(onConflict = OnConflictStrategy.ABORT) 19 | suspend fun update(imported: Imported) 20 | 21 | @Query("DELETE FROM imported WHERE uuid = :uuid") 22 | suspend fun remove(uuid: UUID) 23 | 24 | @Query("SELECT EXISTS(SELECT 1 FROM imported WHERE uuid = :uuid)") 25 | suspend fun exists(uuid: UUID): Boolean 26 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Pending.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.TypeConverters 6 | import com.github.kr328.clash.service.model.Profile 7 | import java.util.* 8 | 9 | @Entity(tableName = "pending", primaryKeys = ["uuid"]) 10 | @TypeConverters(Converters::class) 11 | data class Pending( 12 | @ColumnInfo(name = "uuid") val uuid: UUID, 13 | @ColumnInfo(name = "name") val name: String, 14 | @ColumnInfo(name = "type") val type: Profile.Type, 15 | @ColumnInfo(name = "source") val source: String, 16 | @ColumnInfo(name = "interval") val interval: Long, 17 | @ColumnInfo(name = "upload") val upload: Long, 18 | @ColumnInfo(name = "download") val download: Long, 19 | @ColumnInfo(name = "total") val total: Long, 20 | @ColumnInfo(name = "expire") val expire: Long, 21 | @ColumnInfo(name = "createdAt") val createdAt: Long = System.currentTimeMillis(), 22 | ) -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/PendingDao.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.* 4 | import java.util.* 5 | 6 | @Dao 7 | @TypeConverters(Converters::class) 8 | interface PendingDao { 9 | @Query("SELECT * FROM pending WHERE uuid = :uuid") 10 | suspend fun queryByUUID(uuid: UUID): Pending? 11 | 12 | @Query("DELETE FROM pending WHERE uuid = :uuid") 13 | suspend fun remove(uuid: UUID) 14 | 15 | @Query("SELECT EXISTS(SELECT 1 FROM pending WHERE uuid = :uuid)") 16 | suspend fun exists(uuid: UUID): Boolean 17 | 18 | @Query("SELECT uuid FROM pending ORDER BY createdAt") 19 | suspend fun queryAllUUIDs(): List<UUID> 20 | 21 | @Insert(onConflict = OnConflictStrategy.REPLACE) 22 | suspend fun insert(pending: Pending) 23 | 24 | @Update(onConflict = OnConflictStrategy.REPLACE) 25 | suspend fun update(pending: Pending) 26 | } 27 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Selection.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | import androidx.room.TypeConverters 7 | import java.util.* 8 | 9 | @Entity( 10 | tableName = "selections", 11 | foreignKeys = [ForeignKey( 12 | entity = Imported::class, 13 | childColumns = ["uuid"], 14 | parentColumns = ["uuid"], 15 | onDelete = ForeignKey.CASCADE, 16 | onUpdate = ForeignKey.CASCADE 17 | )], 18 | primaryKeys = ["uuid", "proxy"] 19 | ) 20 | @TypeConverters(Converters::class) 21 | data class Selection( 22 | @ColumnInfo(name = "uuid") val uuid: UUID, 23 | @ColumnInfo(name = "proxy") val proxy: String, 24 | @ColumnInfo(name = "selected") val selected: String, 25 | ) -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/SelectionDao.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.* 4 | import java.util.* 5 | 6 | @Dao 7 | @TypeConverters(Converters::class) 8 | interface SelectionDao { 9 | @Insert(onConflict = OnConflictStrategy.REPLACE) 10 | fun setSelected(selection: Selection) 11 | 12 | @Query("DELETE FROM selections WHERE uuid = :uuid AND proxy = :proxy") 13 | fun removeSelected(uuid: UUID, proxy: String) 14 | 15 | @Query("SELECT * FROM selections WHERE uuid = :uuid") 16 | suspend fun querySelections(uuid: UUID): List<Selection> 17 | 18 | @Query("DELETE FROM selections WHERE uuid = :uuid AND proxy in (:proxies)") 19 | suspend fun removeSelections(uuid: UUID, proxies: List<String>) 20 | } 21 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/migrations/Migrations.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data.migrations 2 | 3 | import androidx.room.migration.Migration 4 | 5 | val MIGRATIONS: Array<Migration> = arrayOf() 6 | 7 | val LEGACY_MIGRATION = ::migrationFromLegacy -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/document/Document.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.document 2 | 3 | interface Document { 4 | val id: String 5 | val name: String 6 | val mimeType: String 7 | val size: Long 8 | val updatedAt: Long 9 | val flags: Set<Flag> 10 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/document/FileDocument.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.document 2 | 3 | import android.provider.DocumentsContract 4 | import java.io.File 5 | 6 | class FileDocument( 7 | val file: File, 8 | override val flags: Set<Flag>, 9 | private val idOverride: String? = null, 10 | private val nameOverride: String? = null, 11 | ) : Document { 12 | override val id: String 13 | get() = idOverride ?: file.name 14 | override val name: String 15 | get() = nameOverride ?: file.name 16 | override val mimeType: String 17 | get() = if (file.isDirectory) DocumentsContract.Document.MIME_TYPE_DIR else "text/plain" 18 | override val size: Long 19 | get() = file.length() 20 | override val updatedAt: Long 21 | get() = file.lastModified() 22 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/document/Flag.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.document 2 | 3 | enum class Flag { 4 | Writable, Deletable, Virtual 5 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/document/Path.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.document 2 | 3 | import java.util.* 4 | 5 | data class Path( 6 | val uuid: UUID?, 7 | val scope: Scope?, 8 | val relative: List<String>? 9 | ) { 10 | enum class Scope { 11 | Configuration, Providers 12 | } 13 | 14 | override fun toString(): String { 15 | if (uuid == null) 16 | return "/" 17 | 18 | if (scope == null) 19 | return "/$uuid" 20 | 21 | val sc = when (scope) { 22 | Scope.Configuration -> Paths.CONFIGURATION_ID 23 | Scope.Providers -> Paths.PROVIDERS_ID 24 | } 25 | 26 | if (relative == null) 27 | return "/$uuid/$sc" 28 | 29 | return "/$uuid/$sc/${relative.joinToString(separator = "/")}" 30 | } 31 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/document/VirtualDocument.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.document 2 | 3 | class VirtualDocument( 4 | override val id: String, 5 | override val name: String, 6 | override val mimeType: String, 7 | override val size: Long, 8 | override val updatedAt: Long, 9 | override val flags: Set<Flag>, 10 | ) : Document 11 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/model/AccessControlMode.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.model 2 | 3 | enum class AccessControlMode { 4 | AcceptAll, AcceptSelected, DenySelected 5 | } 6 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/model/Profile.kt: -------------------------------------------------------------------------------- 1 | @file:UseSerializers(UUIDSerializer::class) 2 | 3 | package com.github.kr328.clash.service.model 4 | 5 | import android.os.Parcel 6 | import android.os.Parcelable 7 | import com.github.kr328.clash.core.util.Parcelizer 8 | import com.github.kr328.clash.service.util.UUIDSerializer 9 | import kotlinx.serialization.Serializable 10 | import kotlinx.serialization.UseSerializers 11 | import java.util.* 12 | 13 | @Serializable 14 | data class Profile( 15 | val uuid: UUID, 16 | val name: String, 17 | val type: Type, 18 | val source: String, 19 | val active: Boolean, 20 | val interval: Long, 21 | val upload: Long, 22 | var download: Long, 23 | val total: Long, 24 | val expire: Long, 25 | 26 | 27 | val updatedAt: Long, 28 | val imported: Boolean, 29 | val pending: Boolean, 30 | ) : Parcelable { 31 | enum class Type { 32 | File, Url, External 33 | } 34 | 35 | override fun writeToParcel(parcel: Parcel, flags: Int) { 36 | Parcelizer.encodeToParcel(serializer(), parcel, this) 37 | } 38 | 39 | override fun describeContents(): Int { 40 | return 0 41 | } 42 | 43 | companion object CREATOR : Parcelable.Creator<Profile> { 44 | override fun createFromParcel(parcel: Parcel): Profile { 45 | return Parcelizer.decodeFromParcel(serializer(), parcel) 46 | } 47 | 48 | override fun newArray(size: Int): Array<Profile?> { 49 | return arrayOfNulls(size) 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/remote/IClashManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.remote 2 | 3 | import com.github.kr328.clash.core.Clash 4 | import com.github.kr328.clash.core.model.* 5 | import com.github.kr328.kaidl.BinderInterface 6 | 7 | @BinderInterface 8 | interface IClashManager { 9 | fun queryTunnelState(): TunnelState 10 | fun queryTrafficTotal(): Long 11 | fun queryProxyGroupNames(excludeNotSelectable: Boolean): List<String> 12 | fun queryProxyGroup(name: String, proxySort: ProxySort): ProxyGroup 13 | fun queryConfiguration(): UiConfiguration 14 | fun queryProviders(): ProviderList 15 | 16 | fun patchSelector(group: String, name: String): Boolean 17 | 18 | suspend fun healthCheck(group: String) 19 | suspend fun updateProvider(type: Provider.Type, name: String) 20 | 21 | fun queryOverride(slot: Clash.OverrideSlot): ConfigurationOverride 22 | fun patchOverride(slot: Clash.OverrideSlot, configuration: ConfigurationOverride) 23 | fun clearOverride(slot: Clash.OverrideSlot) 24 | 25 | fun setLogObserver(observer: ILogObserver?) 26 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/remote/IFetchObserver.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.remote 2 | 3 | import com.github.kr328.clash.core.model.FetchStatus 4 | import com.github.kr328.kaidl.BinderInterface 5 | 6 | @BinderInterface 7 | fun interface IFetchObserver { 8 | fun updateStatus(status: FetchStatus) 9 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/remote/ILogObserver.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.remote 2 | 3 | import com.github.kr328.clash.core.model.LogMessage 4 | import com.github.kr328.kaidl.BinderInterface 5 | 6 | @BinderInterface 7 | interface ILogObserver { 8 | fun newItem(log: LogMessage) 9 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/remote/IProfileManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.remote 2 | 3 | import com.github.kr328.clash.service.model.Profile 4 | import com.github.kr328.kaidl.BinderInterface 5 | import java.util.* 6 | 7 | @BinderInterface 8 | interface IProfileManager { 9 | suspend fun create(type: Profile.Type, name: String, source: String = ""): UUID 10 | suspend fun clone(uuid: UUID): UUID 11 | suspend fun commit(uuid: UUID, callback: IFetchObserver? = null) 12 | suspend fun release(uuid: UUID) 13 | suspend fun delete(uuid: UUID) 14 | suspend fun patch(uuid: UUID, name: String, source: String, interval: Long) 15 | suspend fun update(uuid: UUID) 16 | suspend fun queryByUUID(uuid: UUID): Profile? 17 | suspend fun queryAll(): List<Profile> 18 | suspend fun queryActive(): Profile? 19 | suspend fun setActive(profile: Profile) 20 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/remote/IRemoteService.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.remote 2 | 3 | import com.github.kr328.kaidl.BinderInterface 4 | 5 | @BinderInterface 6 | interface IRemoteService { 7 | fun clash(): IClashManager 8 | fun profile(): IProfileManager 9 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Connectivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import android.net.ConnectivityManager 4 | import android.net.Network 5 | 6 | fun ConnectivityManager.resolveDns(network: Network?): List<String> { 7 | val properties = getLinkProperties(network) ?: return listOf() 8 | return properties.dnsServers.map { it.asSocketAddressText(53) } 9 | } 10 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Coroutine.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.job 5 | import kotlinx.coroutines.runBlocking 6 | 7 | fun CoroutineScope.cancelAndJoinBlocking() { 8 | val scope = this 9 | 10 | runBlocking { 11 | scope.coroutineContext.job.cancel() 12 | scope.coroutineContext.job.join() 13 | } 14 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Database.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import com.github.kr328.clash.service.data.ImportedDao 4 | import com.github.kr328.clash.service.data.PendingDao 5 | import java.util.* 6 | 7 | suspend fun generateProfileUUID(): UUID { 8 | var result = UUID.randomUUID() 9 | 10 | while (ImportedDao().exists(result) || PendingDao().exists(result)) { 11 | result = UUID.randomUUID() 12 | } 13 | 14 | return result 15 | } 16 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Files.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import android.content.Context 4 | import java.io.File 5 | 6 | val Context.importedDir: File 7 | get() = filesDir.resolve("imported") 8 | 9 | val Context.pendingDir: File 10 | get() = filesDir.resolve("pending") 11 | 12 | val Context.processingDir: File 13 | get() = filesDir.resolve("processing") 14 | 15 | val File.directoryLastModified: Long? 16 | get() { 17 | return walk().map { it.lastModified() }.maxOrNull() 18 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Intent.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import android.content.Intent 4 | 5 | val Intent.packageName: String? 6 | get() { 7 | return data?.takeIf { it.scheme == "package" }?.schemeSpecificPart 8 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Net.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | data class IPNet(val ip: String, val prefix: Int) 4 | 5 | fun parseCIDR(cidr: String): IPNet { 6 | val s = cidr.split("/", limit = 2) 7 | 8 | if (s.size != 2) 9 | throw IllegalArgumentException("Invalid address") 10 | 11 | val address = s[0] 12 | val prefix = s[1].toInt() 13 | 14 | return IPNet(address, prefix) 15 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Serializers.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import kotlinx.serialization.KSerializer 4 | import kotlinx.serialization.descriptors.PrimitiveKind 5 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 6 | import kotlinx.serialization.descriptors.SerialDescriptor 7 | import kotlinx.serialization.encoding.Decoder 8 | import kotlinx.serialization.encoding.Encoder 9 | import java.util.* 10 | 11 | class UUIDSerializer : KSerializer<UUID> { 12 | override val descriptor: SerialDescriptor = 13 | PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING) 14 | 15 | override fun deserialize(decoder: Decoder): UUID { 16 | return UUID.fromString(decoder.decodeString()) 17 | } 18 | 19 | override fun serialize(encoder: Encoder, value: UUID) { 20 | encoder.encodeString(value.toString()) 21 | } 22 | } -------------------------------------------------------------------------------- /service/src/main/res/values-ja-rJP/strings.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <string name="clash_service_status_channel">Clashステータス</string> 4 | <string name="profile_service_status">プロファイルサービスのステータス</string> 5 | <string name="profile_process_status">プロファイルの処理状況</string> 6 | <string name="profile_process_result">プロファイルプロセスの結果</string> 7 | <string name="update_successfully">正常に更新されました</string> 8 | <string name="update_failure">更新に失敗しました</string> 9 | <string name="format_update_complete">%sを更新しました</string> 10 | <string name="format_update_failure">更新 %1$s: %2$s </string> 11 | <string name="running">実行中</string> 12 | <string name="loading">読み込み中</string> 13 | <string name="clash_meta_for_android">Clash Meta for Android</string> 14 | <string name="profiles_and_providers">プロファイルと外部リソース</string> 15 | <string name="configuration_yaml">コンフィグ.yaml</string> 16 | <string name="provider_files">外部リソースファイル</string> 17 | <string name="profile_updater">プロファイルの更新</string> 18 | <string name="profile_updating">プロファイルを更新中</string> 19 | </resources> -------------------------------------------------------------------------------- /service/src/main/res/values-ko-rKR/strings.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <string name="clash_service_status_channel">Clash 상태</string> 4 | <string name="profile_service_status">구성 파일 서비스 상태</string> 5 | <string name="profile_process_status">구성 파일 처리 상태</string> 6 | <string name="profile_process_result">구성 파일 처리 결과</string> 7 | <string name="update_successfully">업데이트 성공</string> 8 | <string name="update_failure">업데이트 실패</string> 9 | <string name="format_update_complete">%s 업데이트 성공</string> 10 | <string name="format_update_failure">업데이트 %1$s: %2$s</string> 11 | <string name="running">연결됨</string> 12 | <string name="loading">로딩중</string> 13 | <string name="clash_meta_for_android">Clash Meta for Android</string> 14 | <string name="profiles_and_providers">구성 파일과 외부 리소스</string> 15 | <string name="configuration_yaml">구성 파일.yaml</string> 16 | <string name="provider_files">외부 리소스 파일</string> 17 | <string name="profile_updater">구성 파일 업데이트</string> 18 | <string name="profile_updating">구성 파일 업데이트 중</string> 19 | </resources> -------------------------------------------------------------------------------- /service/src/main/res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | <resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="PluralsCandidate"> 2 | <!-- from https://github.com/shadowsocks/shadowsocks-android/blob/master/core/src/main/res/values/strings.xml --> 3 | <string name="clash_notification_content" translatable="false">"%1$s↑\t%2$s↓"</string> 4 | 5 | <string name="clash_service_status_channel">Статус Clash</string> 6 | <string name="profile_service_status">Статус сервиса профиля</string> 7 | <string name="profile_process_status">Статус обработки профиля</string> 8 | <string name="profile_process_result">Результат обработки профиля</string> 9 | <string name="update_successfully">Успешно обновлено</string> 10 | <string name="update_failure">Не удалось обновить</string> 11 | <string name="format_update_complete">Обновление %s завершено</string> 12 | <string name="format_update_failure">Обновление %1$s: %2$s</string> 13 | <string name="running">Работает</string> 14 | <string name="loading">Загружается</string> 15 | <string name="clash_meta_for_android">Clash Meta для Android</string> 16 | <string name="profiles_and_providers">Профили и провайдеры</string> 17 | <string name="configuration_yaml">Configuration.yaml</string> 18 | <string name="provider_files">Файлы провайдера</string> 19 | <string name="profile_updater">Обновление профиля</string> 20 | <string name="profile_updating">Профиль обновляется</string> 21 | </resources> 22 | -------------------------------------------------------------------------------- /service/src/main/res/values-zh-rHK/strings.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <string name="clash_service_status_channel">Clash 狀態</string> 4 | <string name="running">正在運行</string> 5 | <string name="format_update_complete">更新 %s 成功</string> 6 | <string name="format_update_failure">"更新 %1$s: %2$s "</string> 7 | <string name="clash_meta_for_android">Clash Meta for Android</string> 8 | <string name="profiles_and_providers">配置文件和外部資源</string> 9 | <string name="configuration_yaml">配置文件.yaml</string> 10 | <string name="provider_files">外部資源文件列表</string> 11 | <string name="loading">載入中</string> 12 | <string name="profile_process_status">配置文件處理狀態</string> 13 | <string name="update_successfully">更新成功</string> 14 | <string name="update_failure">更新失敗</string> 15 | <string name="profile_updater">配置更新服務</string> 16 | <string name="profile_updating">配置更新中</string> 17 | <string name="profile_service_status">配置文件服務狀態</string> 18 | <string name="profile_process_result">配置文件處理結果</string> 19 | </resources> -------------------------------------------------------------------------------- /service/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <string name="clash_service_status_channel">Clash 狀態</string> 4 | <string name="running">正在運作</string> 5 | <string name="format_update_complete">更新 %s 成功</string> 6 | <string name="format_update_failure">"更新 %1$s: %2$s "</string> 7 | <string name="clash_meta_for_android">Clash Meta for Android</string> 8 | <string name="profiles_and_providers">設定檔和外部資源</string> 9 | <string name="configuration_yaml">設定檔.yaml</string> 10 | <string name="provider_files">外部資源文件列表</string> 11 | <string name="loading">載入中</string> 12 | <string name="profile_process_status">設定檔處理狀態</string> 13 | <string name="update_successfully">更新成功</string> 14 | <string name="update_failure">更新失敗</string> 15 | <string name="profile_updater">設定檔更新服務</string> 16 | <string name="profile_updating">設定檔更新中</string> 17 | <string name="profile_service_status">設定檔服務狀態</string> 18 | <string name="profile_process_result">設定檔處理結果</string> 19 | </resources> 20 | -------------------------------------------------------------------------------- /service/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <string name="clash_service_status_channel">Clash 状态</string> 4 | <string name="running">正在运行</string> 5 | <string name="format_update_complete">更新 %s 成功</string> 6 | <string name="format_update_failure">"更新 %1$s: %2$s "</string> 7 | <string name="clash_meta_for_android">Clash Meta for Android</string> 8 | <string name="profiles_and_providers">配置文件和外部资源</string> 9 | <string name="configuration_yaml">配置文件.yaml</string> 10 | <string name="provider_files">外部资源文件列表</string> 11 | <string name="loading">载入中</string> 12 | <string name="profile_process_status">配置文件处理状态</string> 13 | <string name="update_successfully">更新成功</string> 14 | <string name="update_failure">更新失败</string> 15 | <string name="profile_updater">配置更新服务</string> 16 | <string name="profile_updating">配置更新中</string> 17 | <string name="profile_service_status">配置文件服务状态</string> 18 | <string name="profile_process_result">配置文件处理结果</string> 19 | </resources> -------------------------------------------------------------------------------- /service/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <color name="color_clash">#1E4376</color> 4 | </resources> -------------------------------------------------------------------------------- /service/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="utf-8"?> 2 | <resources> 3 | <item name="nf_clash_status" type="id" /> 4 | <item name="nf_vpn_status" type="id" /> 5 | <item name="nf_profile_worker" type="id" /> 6 | </resources> -------------------------------------------------------------------------------- /service/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | <resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="PluralsCandidate"> 2 | <!-- from https://github.com/shadowsocks/shadowsocks-android/blob/master/core/src/main/res/values/strings.xml --> 3 | <string name="clash_notification_content" translatable="false">"%1$s↑\t%2$s↓"</string> 4 | 5 | <string name="clash_service_status_channel">Clash Status</string> 6 | <string name="profile_service_status">Profile Service Status</string> 7 | <string name="profile_process_status">Profile Processing Status</string> 8 | <string name="profile_process_result">Profile Process Result</string> 9 | <string name="update_successfully">Update Successfully</string> 10 | <string name="update_failure">Update Failure</string> 11 | <string name="format_update_complete">Update %s completed</string> 12 | <string name="format_update_failure">Update %1$s: %2$s</string> 13 | <string name="running">Running</string> 14 | <string name="loading">Loading</string> 15 | <string name="clash_meta_for_android">Clash Meta for Android</string> 16 | <string name="profiles_and_providers">Profiles and Providers</string> 17 | <string name="configuration_yaml">Configuration.yaml</string> 18 | <string name="provider_files">Provider Files</string> 19 | <string name="profile_updater">Profile Updater</string> 20 | <string name="profile_updating">Profile Updating</string> 21 | </resources> 22 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "ClashMetaForAndroid" 2 | 3 | include(":app") 4 | include(":core") 5 | include(":service") 6 | include(":design") 7 | include(":common") 8 | include(":hideapi") 9 | 10 | pluginManagement { 11 | repositories { 12 | mavenLocal() 13 | mavenCentral() 14 | gradlePluginPortal() 15 | } 16 | } 17 | --------------------------------------------------------------------------------