├── .gitattributes
├── .github
└── workflows
│ ├── Build Release2.yml
│ ├── Sync_Fork.yml
│ ├── build-debug.yaml
│ ├── build-pre-release.yaml1
│ ├── build-release.yaml
│ └── update-dependencies.yaml
├── .gitignore
├── .gitmodules
├── .idea
└── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── CONTRIBUTING.md
├── LICENSE
├── NOTICE
├── PRIVACY_POLICY.md
├── README.md
├── app
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── ic_launcher-web.png
│ ├── java
│ └── com
│ │ └── github
│ │ └── kr328
│ │ └── clash
│ │ ├── AccessControlActivity.kt
│ │ ├── ApkBrokenActivity.kt
│ │ ├── AppCrashedActivity.kt
│ │ ├── AppSettingsActivity.kt
│ │ ├── BaseActivity.kt
│ │ ├── DialerReceiver.kt
│ │ ├── ExternalControlActivity.kt
│ │ ├── FilesActivity.kt
│ │ ├── HelpActivity.kt
│ │ ├── LogcatActivity.kt
│ │ ├── LogcatService.kt
│ │ ├── LogsActivity.kt
│ │ ├── MainActivity.kt
│ │ ├── MainApplication.kt
│ │ ├── MetaFeatureSettingsActivity.kt
│ │ ├── NetworkSettingsActivity.kt
│ │ ├── NewProfileActivity.kt
│ │ ├── OverrideSettingsActivity.kt
│ │ ├── ProfilesActivity.kt
│ │ ├── PropertiesActivity.kt
│ │ ├── ProvidersActivity.kt
│ │ ├── ProxyActivity.kt
│ │ ├── RestartReceiver.kt
│ │ ├── SettingsActivity.kt
│ │ ├── TileService.kt
│ │ ├── log
│ │ ├── LogcatCache.kt
│ │ ├── LogcatFilter.kt
│ │ ├── LogcatReader.kt
│ │ ├── LogcatWriter.kt
│ │ └── SystemLogcat.kt
│ │ ├── remote
│ │ ├── Broadcasts.kt
│ │ ├── FilesClient.kt
│ │ ├── Remote.kt
│ │ ├── Resource.kt
│ │ ├── Service.kt
│ │ └── StatusClient.kt
│ │ ├── store
│ │ ├── AppStore.kt
│ │ └── TipsStore.kt
│ │ └── util
│ │ ├── Activity.kt
│ │ ├── Application.kt
│ │ ├── Clash.kt
│ │ ├── Content.kt
│ │ ├── Files.kt
│ │ ├── Remote.kt
│ │ ├── Service.kt
│ │ └── Uri.kt
│ └── res
│ ├── drawable
│ └── ic_launcher_foreground.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_banner.png
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── values-night
│ └── themes.xml
│ ├── values
│ ├── colors.xml
│ ├── ic_banner_background.xml
│ ├── ic_launcher_background.xml
│ ├── ids.xml
│ └── themes.xml
│ └── xml
│ ├── full_backup_content.xml
│ └── network_security_config.xml
├── build.gradle.kts
├── common
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── github
│ │ └── kr328
│ │ └── clash
│ │ └── common
│ │ ├── Global.kt
│ │ ├── compat
│ │ ├── App.kt
│ │ ├── Context.kt
│ │ ├── Html.kt
│ │ ├── Intents.kt
│ │ ├── Package.kt
│ │ ├── Resource.kt
│ │ ├── Services.kt
│ │ ├── UI.kt
│ │ └── View.kt
│ │ ├── constants
│ │ ├── Authorities.kt
│ │ ├── Components.kt
│ │ ├── Intents.kt
│ │ ├── Metadata.kt
│ │ └── Permissions.kt
│ │ ├── id
│ │ └── UndefinedIds.kt
│ │ ├── log
│ │ └── Log.kt
│ │ ├── store
│ │ ├── Providers.kt
│ │ ├── Store.kt
│ │ └── StoreProvider.kt
│ │ └── util
│ │ ├── Components.kt
│ │ ├── Global.kt
│ │ ├── Intent.kt
│ │ ├── Parcelable.kt
│ │ ├── Patterns.kt
│ │ └── Ticker.kt
│ └── res
│ ├── values-ru
│ └── strings.xml
│ ├── values-zh-rTW
│ └── strings.xml
│ ├── values-zh
│ └── strings.xml
│ └── values
│ └── strings.xml
├── core
├── .gitignore
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── foss
│ └── golang
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── main.go
│ └── main
│ ├── AndroidManifest.xml
│ ├── cpp
│ ├── CMakeLists.txt
│ ├── bridge_helper.c
│ ├── bridge_helper.h
│ ├── jni_helper.c
│ ├── jni_helper.h
│ ├── main.c
│ └── version.h.in
│ ├── golang
│ ├── .idea
│ │ └── codeStyles
│ │ │ ├── Project.xml
│ │ │ └── codeStyleConfig.xml
│ ├── go.mod
│ ├── go.sum
│ └── native
│ │ ├── all
│ │ └── imports.go
│ │ ├── app.go
│ │ ├── app
│ │ ├── app.go
│ │ ├── content.go
│ │ ├── dns.go
│ │ ├── tun.go
│ │ └── ui.go
│ │ ├── bridge.c
│ │ ├── bridge.h
│ │ ├── common
│ │ └── path.go
│ │ ├── config.go
│ │ ├── config
│ │ ├── defaults.go
│ │ ├── fetch.go
│ │ ├── load.go
│ │ ├── override.go
│ │ ├── process.go
│ │ └── provider.go
│ │ ├── debug.go
│ │ ├── delegate
│ │ └── init.go
│ │ ├── log.go
│ │ ├── main.go
│ │ ├── platform
│ │ ├── limit.go
│ │ └── procfs.go
│ │ ├── proxy.go
│ │ ├── proxy
│ │ └── http.go
│ │ ├── trace.c
│ │ ├── trace.h
│ │ ├── tun.go
│ │ ├── tun
│ │ └── tun.go
│ │ ├── tunnel.go
│ │ ├── tunnel
│ │ ├── conn.go
│ │ ├── connectivity.go
│ │ ├── loopback.go
│ │ ├── providers.go
│ │ ├── proxies.go
│ │ ├── state.go
│ │ ├── statistic.go
│ │ └── suspend.go
│ │ └── utils.go
│ └── java
│ └── com
│ └── github
│ └── kr328
│ └── clash
│ └── core
│ ├── Clash.kt
│ ├── bridge
│ ├── Bridge.kt
│ ├── ClashException.kt
│ ├── Content.kt
│ ├── FetchCallback.kt
│ ├── LogcatInterface.kt
│ └── TunInterface.kt
│ ├── model
│ ├── ConfigurationOverride.kt
│ ├── FetchStatus.kt
│ ├── LogMessage.kt
│ ├── Provider.kt
│ ├── ProviderList.kt
│ ├── Proxy.kt
│ ├── ProxyGroup.kt
│ ├── ProxySort.kt
│ ├── Traffic.kt
│ ├── TunnelState.kt
│ └── UiConfiguration.kt
│ └── util
│ ├── Net.kt
│ ├── Parcelizer.kt
│ ├── Serializers.kt
│ └── Traffic.kt
├── design
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── github
│ │ └── kr328
│ │ └── clash
│ │ └── design
│ │ ├── AccessControlDesign.kt
│ │ ├── ApkBrokenDesign.kt
│ │ ├── AppCrashedDesign.kt
│ │ ├── AppSettingsDesign.kt
│ │ ├── Design.kt
│ │ ├── FilesDesign.kt
│ │ ├── HelpDesign.kt
│ │ ├── LogcatDesign.kt
│ │ ├── LogsDesign.kt
│ │ ├── MainDesign.kt
│ │ ├── MetaFeatureSettingsDesign.kt
│ │ ├── NetworkSettingsDesign.kt
│ │ ├── NewProfileDesign.kt
│ │ ├── OverrideSettingsDesign.kt
│ │ ├── ProfilesDesign.kt
│ │ ├── PropertiesDesign.kt
│ │ ├── ProvidersDesign.kt
│ │ ├── ProxyDesign.kt
│ │ ├── SettingsDesign.kt
│ │ ├── adapter
│ │ ├── AppAdapter.kt
│ │ ├── EditableTextListAdapter.kt
│ │ ├── EditableTextMapAdapter.kt
│ │ ├── FileAdapter.kt
│ │ ├── LogFileAdapter.kt
│ │ ├── LogMessageAdapter.kt
│ │ ├── PopupListAdapter.kt
│ │ ├── ProfileAdapter.kt
│ │ ├── ProfileProviderAdapter.kt
│ │ ├── ProviderAdapter.kt
│ │ ├── ProxyAdapter.kt
│ │ └── ProxyPageAdapter.kt
│ │ ├── component
│ │ ├── AccessControlMenu.kt
│ │ ├── ProxyMenu.kt
│ │ ├── ProxyPageFactory.kt
│ │ ├── ProxyView.kt
│ │ ├── ProxyViewConfig.kt
│ │ └── ProxyViewState.kt
│ │ ├── dialog
│ │ ├── Dialogs.kt
│ │ ├── Input.kt
│ │ └── Progress.kt
│ │ ├── model
│ │ ├── AppInfo.kt
│ │ ├── AppInfoSort.kt
│ │ ├── Behavior.kt
│ │ ├── DarkMode.kt
│ │ ├── File.kt
│ │ ├── LogFile.kt
│ │ ├── ProfilePageState.kt
│ │ ├── ProfileProvider.kt
│ │ ├── ProviderState.kt
│ │ ├── ProxyPageState.kt
│ │ └── ProxyState.kt
│ │ ├── preference
│ │ ├── Category.kt
│ │ ├── Clickable.kt
│ │ ├── EditableText.kt
│ │ ├── EditableTextList.kt
│ │ ├── EditableTextMap.kt
│ │ ├── Overlay.kt
│ │ ├── Preference.kt
│ │ ├── Screen.kt
│ │ ├── SelectableList.kt
│ │ ├── Switch.kt
│ │ ├── Tips.kt
│ │ └── Value.kt
│ │ ├── store
│ │ └── UiStore.kt
│ │ ├── ui
│ │ ├── DayNight.kt
│ │ ├── Insets.kt
│ │ ├── ObservableCurrentTime.kt
│ │ ├── Surface.kt
│ │ └── ToastDuration.kt
│ │ ├── util
│ │ ├── ActivityBar.kt
│ │ ├── App.kt
│ │ ├── Binding.kt
│ │ ├── Context.kt
│ │ ├── Diff.kt
│ │ ├── Elevation.kt
│ │ ├── I18n.kt
│ │ ├── Inserts.kt
│ │ ├── Interval.kt
│ │ ├── Landscape.kt
│ │ ├── ListView.kt
│ │ ├── RecyclerView.kt
│ │ ├── ScrollView.kt
│ │ ├── Theme.kt
│ │ ├── Toast.kt
│ │ ├── Validator.kt
│ │ └── View.kt
│ │ └── view
│ │ ├── ActionLabel.kt
│ │ ├── ActionTextField.kt
│ │ ├── ActivityBarLayout.kt
│ │ ├── AppRecyclerView.kt
│ │ ├── LargeActionCard.kt
│ │ ├── LargeActionLabel.kt
│ │ ├── ObservableScrollView.kt
│ │ └── VerticalScrollableHost.kt
│ └── res
│ ├── anim
│ └── rotate_infinite.xml
│ ├── drawable
│ ├── bg_b.xml
│ ├── bg_bottom_sheet.xml
│ ├── ic_baseline_adb.xml
│ ├── ic_baseline_add.xml
│ ├── ic_baseline_apps.xml
│ ├── ic_baseline_arrow_back.xml
│ ├── ic_baseline_assignment.xml
│ ├── ic_baseline_attach_file.xml
│ ├── ic_baseline_brightness_4.xml
│ ├── ic_baseline_clear_all.xml
│ ├── ic_baseline_close.xml
│ ├── ic_baseline_cloud_download.xml
│ ├── ic_baseline_content_copy.xml
│ ├── ic_baseline_delete.xml
│ ├── ic_baseline_dns.xml
│ ├── ic_baseline_domain.xml
│ ├── ic_baseline_edit.xml
│ ├── ic_baseline_extension.xml
│ ├── ic_baseline_flash_on.xml
│ ├── ic_baseline_get_app.xml
│ ├── ic_baseline_help_center.xml
│ ├── ic_baseline_hide.xml
│ ├── ic_baseline_info.xml
│ ├── ic_baseline_meta.xml
│ ├── ic_baseline_more_vert.xml
│ ├── ic_baseline_publish.xml
│ ├── ic_baseline_replay.xml
│ ├── ic_baseline_restore.xml
│ ├── ic_baseline_save.xml
│ ├── ic_baseline_search.xml
│ ├── ic_baseline_settings.xml
│ ├── ic_baseline_stop.xml
│ ├── ic_baseline_swap_vert.xml
│ ├── ic_baseline_swap_vertical_circle.xml
│ ├── ic_baseline_sync.xml
│ ├── ic_baseline_update.xml
│ ├── ic_baseline_view_list.xml
│ ├── ic_baseline_vpn_lock.xml
│ ├── ic_baseline_work.xml
│ ├── ic_clash.xml
│ ├── ic_outline_article.xml
│ ├── ic_outline_check_circle.xml
│ ├── ic_outline_delete.xml
│ ├── ic_outline_folder.xml
│ ├── ic_outline_inbox.xml
│ ├── ic_outline_info.xml
│ ├── ic_outline_label.xml
│ ├── ic_outline_not_interested.xml
│ └── ic_outline_update.xml
│ ├── layout
│ ├── adapter_app.xml
│ ├── adapter_editable_text_list.xml
│ ├── adapter_editable_text_map.xml
│ ├── adapter_file.xml
│ ├── adapter_log_message.xml
│ ├── adapter_profile.xml
│ ├── adapter_profile_provider.xml
│ ├── adapter_provider.xml
│ ├── adapter_sideload_provider.xml
│ ├── common_activity_bar.xml
│ ├── common_recycler_list.xml
│ ├── component_action_label.xml
│ ├── component_action_text_field.xml
│ ├── component_large_action_label.xml
│ ├── design_about.xml
│ ├── design_access_control.xml
│ ├── design_app_crashed.xml
│ ├── design_files.xml
│ ├── design_logcat.xml
│ ├── design_logs.xml
│ ├── design_main.xml
│ ├── design_new_profile.xml
│ ├── design_profiles.xml
│ ├── design_properties.xml
│ ├── design_providers.xml
│ ├── design_proxy.xml
│ ├── design_settings.xml
│ ├── design_settings_common.xml
│ ├── design_settings_meta_feature.xml
│ ├── design_settings_overide.xml
│ ├── dialog_editable_map_text_field.xml
│ ├── dialog_fetch_status.xml
│ ├── dialog_files_menu.xml
│ ├── dialog_preference_list.xml
│ ├── dialog_profiles_menu.xml
│ ├── dialog_search.xml
│ ├── dialog_text_field.xml
│ ├── preference_category.xml
│ ├── preference_clickable.xml
│ ├── preference_switch.xml
│ └── preference_tips.xml
│ ├── menu
│ ├── menu_access_control.xml
│ └── menu_proxy.xml
│ ├── values-ja-rJP
│ └── strings.xml
│ ├── values-ko-rKR
│ └── strings.xml
│ ├── values-ru
│ └── strings.xml
│ ├── values-v23
│ └── themes.xml
│ ├── values-v27
│ └── themes.xml
│ ├── values-v29
│ └── themes.xml
│ ├── values-vi
│ └── strings.xml
│ ├── values-zh-rHK
│ └── strings.xml
│ ├── values-zh-rTW
│ └── strings.xml
│ ├── values-zh
│ └── strings.xml
│ └── values
│ ├── attrs.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── ids.xml
│ ├── strings.xml
│ ├── styles.xml
│ └── themes.xml
├── fastlane
└── metadata
│ └── android
│ ├── en-US
│ ├── full_description.txt
│ └── short_description.txt
│ └── zh-CN
│ ├── full_description.txt
│ └── short_description.txt
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── hideapi
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── android
│ └── app
│ └── ActivityThread.java
├── release.keystore
├── renovate.json
├── service
├── build.gradle.kts
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── github
│ │ └── kr328
│ │ └── clash
│ │ └── service
│ │ ├── BaseService.kt
│ │ ├── ClashManager.kt
│ │ ├── ClashService.kt
│ │ ├── FilesProvider.kt
│ │ ├── PreferenceProvider.kt
│ │ ├── ProfileManager.kt
│ │ ├── ProfileProcessor.kt
│ │ ├── ProfileReceiver.kt
│ │ ├── ProfileWorker.kt
│ │ ├── RemoteService.kt
│ │ ├── StatusProvider.kt
│ │ ├── TunService.kt
│ │ ├── clash
│ │ ├── ClashRuntime.kt
│ │ └── module
│ │ │ ├── AppListCacheModule.kt
│ │ │ ├── CloseModule.kt
│ │ │ ├── ConfigurationModule.kt
│ │ │ ├── DynamicNotificationModule.kt
│ │ │ ├── Module.kt
│ │ │ ├── NetworkObserveModule.kt
│ │ │ ├── StaticNotificationModule.kt
│ │ │ ├── SuspendModule.kt
│ │ │ ├── TimeZoneModule.kt
│ │ │ └── TunModule.kt
│ │ ├── data
│ │ ├── Converters.kt
│ │ ├── Daos.kt
│ │ ├── Database.kt
│ │ ├── Imported.kt
│ │ ├── ImportedDao.kt
│ │ ├── Pending.kt
│ │ ├── PendingDao.kt
│ │ ├── Selection.kt
│ │ ├── SelectionDao.kt
│ │ └── migrations
│ │ │ ├── LegacyMigration.kt
│ │ │ └── Migrations.kt
│ │ ├── document
│ │ ├── Document.kt
│ │ ├── FileDocument.kt
│ │ ├── Flag.kt
│ │ ├── Path.kt
│ │ ├── Paths.kt
│ │ ├── Picker.kt
│ │ └── VirtualDocument.kt
│ │ ├── model
│ │ ├── AccessControlMode.kt
│ │ └── Profile.kt
│ │ ├── remote
│ │ ├── IClashManager.kt
│ │ ├── IFetchObserver.kt
│ │ ├── ILogObserver.kt
│ │ ├── IProfileManager.kt
│ │ └── IRemoteService.kt
│ │ ├── store
│ │ └── ServiceStore.kt
│ │ └── util
│ │ ├── Address.kt
│ │ ├── Broadcast.kt
│ │ ├── Connectivity.kt
│ │ ├── Coroutine.kt
│ │ ├── Database.kt
│ │ ├── Files.kt
│ │ ├── Intent.kt
│ │ ├── Net.kt
│ │ └── Serializers.kt
│ └── res
│ ├── drawable
│ └── ic_logo_service.xml
│ ├── values-ja-rJP
│ └── strings.xml
│ ├── values-ko-rKR
│ └── strings.xml
│ ├── values-ru
│ └── strings.xml
│ ├── values-zh-rHK
│ └── strings.xml
│ ├── values-zh-rTW
│ └── strings.xml
│ ├── values-zh
│ └── strings.xml
│ └── values
│ ├── arrays.xml
│ ├── colors.xml
│ ├── ids.xml
│ └── strings.xml
└── settings.gradle.kts
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
3 | *.bat text eol=crlf
4 | *.jar binary
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 | /app/foss/release
4 | /app/premium/release
5 | /captures
6 |
7 | # Ignore Gradle GUI config
8 | gradle-app.setting
9 |
10 | # Avoid ignoring Gradle wrapper jar targetFile (.jar files are usually ignored)
11 | !gradle-wrapper.jar
12 |
13 | # Cache of project
14 | .gradletasknamecache
15 |
16 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
17 | # gradle/wrapper/gradle-wrapper.properties
18 |
19 | # Ignore IDEA config
20 | *.iml
21 | /.idea/*
22 | !/.idea/codeStyles
23 | /core/src/main/golang/.idea/*
24 | !/core/src/main/golang/.idea/codeStyles
25 | /core/src/foss/golang/.idea/*
26 | !/core/src/foss/golang/.idea/codeStyles
27 | /core/src/premium/golang/.idea/*
28 | !/core/src/premium/golang/.idea/codeStyles
29 |
30 | # Ignore builtin geofiles
31 | app/src/main/assets
32 |
33 | # KeyStore
34 | signing.properties
35 | *.keystore
36 | *.jks
37 |
38 | # clion cmake build
39 | cmake-build-*
40 |
41 | # local.properties
42 | local.properties
43 |
44 |
45 | # tracker
46 | tracker.properties
47 |
48 | # vscode
49 | .vscode
50 |
51 | # cxx
52 | .cxx
53 |
54 | *.hprof
55 |
56 | # firebase
57 | google-services.json
58 |
59 | # Dolphin
60 | .directory
61 |
62 | # logs
63 | *.log
64 |
65 | # MacOS
66 | .DS_Store
67 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "clash-foss"]
2 | path = core/src/foss/golang/clash
3 | url = https://github.com/MetaCubeX/mihomo
4 | branch = Alpha
5 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing to Clash for Android
2 |
3 | #### Code Style
4 |
5 | Please use `Android Studio` or `Intellij IDEA` to open the project and use the project code style profile.
6 |
7 | `File` -> `Settings` -> `Editor` -> `Code Style` -> `C/C++ and Kotlin` -> `Scheme` -> `Project`
8 |
9 |
10 |
11 | #### License
12 |
13 | Contributing to Clash for Android that assumes you allow code to be merged into closed-source branch of Clash for Android. Other terms follow the [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html)
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/ApkBrokenActivity.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash
2 |
3 | import android.content.Intent
4 | import android.net.Uri
5 | import com.github.kr328.clash.design.ApkBrokenDesign
6 | import kotlinx.coroutines.isActive
7 |
8 | class ApkBrokenActivity : BaseActivity() {
9 | override suspend fun main() {
10 | val design = ApkBrokenDesign(this)
11 |
12 | setContentDesign(design)
13 |
14 | while (isActive) {
15 | val req = design.requests.receive()
16 |
17 | startActivity(Intent(Intent.ACTION_VIEW).setData(Uri.parse(req.url)))
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/AppCrashedActivity.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash
2 |
3 | import com.github.kr328.clash.common.compat.versionCodeCompat
4 | import com.github.kr328.clash.common.log.Log
5 | import com.github.kr328.clash.design.AppCrashedDesign
6 | import com.github.kr328.clash.log.SystemLogcat
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.isActive
9 | import kotlinx.coroutines.withContext
10 |
11 | class AppCrashedActivity : BaseActivity() {
12 | override suspend fun main() {
13 | val design = AppCrashedDesign(this)
14 |
15 | setContentDesign(design)
16 |
17 | val packageInfo = withContext(Dispatchers.IO) {
18 | packageManager.getPackageInfo(packageName, 0)
19 | }
20 |
21 | Log.i("App version: versionName = ${packageInfo.versionName} versionCode = ${packageInfo.versionCodeCompat}")
22 |
23 | val logs = withContext(Dispatchers.IO) {
24 | SystemLogcat.dumpCrash()
25 | }
26 |
27 | design.setAppLogs(logs)
28 |
29 | while (isActive) {
30 | events.receive()
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/DialerReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 |
7 | class DialerReceiver : BroadcastReceiver() {
8 | override fun onReceive(context: Context, intent: Intent) {
9 | val intent = Intent(context, MainActivity::class.java)
10 | .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
11 | context.startActivity(intent)
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/HelpActivity.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash
2 |
3 | import android.content.Intent
4 | import com.github.kr328.clash.design.HelpDesign
5 | import kotlinx.coroutines.isActive
6 |
7 | class HelpActivity : BaseActivity() {
8 | override suspend fun main() {
9 | val design = HelpDesign(this) {
10 | startActivity(Intent(Intent.ACTION_VIEW).setData(it))
11 | }
12 |
13 | setContentDesign(design)
14 |
15 | while (isActive) {
16 | events.receive()
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/NetworkSettingsActivity.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash
2 |
3 | import com.github.kr328.clash.common.util.intent
4 | import com.github.kr328.clash.design.NetworkSettingsDesign
5 | import com.github.kr328.clash.service.store.ServiceStore
6 | import kotlinx.coroutines.isActive
7 | import kotlinx.coroutines.selects.select
8 |
9 | class NetworkSettingsActivity : BaseActivity() {
10 | override suspend fun main() {
11 | val design = NetworkSettingsDesign(
12 | this,
13 | uiStore,
14 | ServiceStore(this),
15 | clashRunning,
16 | )
17 |
18 | setContentDesign(design)
19 |
20 | while (isActive) {
21 | select {
22 | events.onReceive {
23 | when (it) {
24 | Event.ClashStart, Event.ClashStop, Event.ServiceRecreated ->
25 | recreate()
26 | else -> Unit
27 | }
28 | }
29 | design.requests.onReceive {
30 | when (it) {
31 | NetworkSettingsDesign.Request.StartAccessControlList ->
32 | startActivity(AccessControlActivity::class.intent)
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/RestartReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import com.github.kr328.clash.service.StatusProvider
7 | import com.github.kr328.clash.util.startClashService
8 |
9 | class RestartReceiver : BroadcastReceiver() {
10 | override fun onReceive(context: Context, intent: Intent) {
11 | when (intent.action) {
12 | Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_MY_PACKAGE_REPLACED -> {
13 | if (StatusProvider.shouldStartClashOnBoot)
14 | context.startClashService()
15 | }
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/SettingsActivity.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash
2 |
3 | import com.github.kr328.clash.common.util.intent
4 | import com.github.kr328.clash.design.SettingsDesign
5 | import kotlinx.coroutines.isActive
6 | import kotlinx.coroutines.selects.select
7 |
8 | class SettingsActivity : BaseActivity() {
9 | override suspend fun main() {
10 | val design = SettingsDesign(this)
11 |
12 | setContentDesign(design)
13 |
14 | while (isActive) {
15 | select {
16 | events.onReceive {
17 |
18 | }
19 | design.requests.onReceive {
20 | when (it) {
21 | SettingsDesign.Request.StartApp ->
22 | startActivity(AppSettingsActivity::class.intent)
23 | SettingsDesign.Request.StartNetwork ->
24 | startActivity(NetworkSettingsActivity::class.intent)
25 | SettingsDesign.Request.StartOverride ->
26 | startActivity(OverrideSettingsActivity::class.intent)
27 | SettingsDesign.Request.StartMetaFeature ->
28 | startActivity(MetaFeatureSettingsActivity::class.intent)
29 | }
30 | }
31 | }
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/log/LogcatCache.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.log
2 |
3 | import androidx.collection.CircularArray
4 | import com.github.kr328.clash.core.model.LogMessage
5 | import kotlinx.coroutines.sync.Mutex
6 | import kotlinx.coroutines.sync.withLock
7 |
8 | class LogcatCache {
9 | data class Snapshot(val messages: List, val removed: Int, val appended: Int)
10 |
11 | private val array = CircularArray(CAPACITY)
12 | private val lock = Mutex()
13 |
14 | private var removed: Int = 0
15 | private var appended: Int = 0
16 |
17 | suspend fun append(msg: LogMessage) {
18 | lock.withLock {
19 | if (array.size() >= CAPACITY) {
20 | array.removeFromStart(1)
21 |
22 | removed++
23 | appended--
24 | }
25 |
26 | array.addLast(msg)
27 |
28 | appended++
29 | }
30 | }
31 |
32 | suspend fun snapshot(full: Boolean): Snapshot? {
33 | return lock.withLock {
34 | if (!full && removed == 0 && appended == 0) {
35 | return@withLock null
36 | }
37 |
38 | Snapshot(
39 | List(array.size()) { array[it] },
40 | removed,
41 | if (full) array.size() + appended else appended
42 | ).also {
43 | removed = 0
44 | appended = 0
45 | }
46 | }
47 | }
48 |
49 | companion object {
50 | const val CAPACITY = 128
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/log/LogcatFilter.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.log
2 |
3 | import android.content.Context
4 | import com.github.kr328.clash.core.model.LogMessage
5 | import com.github.kr328.clash.design.util.format
6 | import java.io.BufferedWriter
7 | import java.io.Writer
8 | import java.util.*
9 |
10 | class LogcatFilter(output: Writer, private val context: Context) : BufferedWriter(output) {
11 | fun writeHeader(time: Date) {
12 | appendLine("# Capture on ${time.format(context)}")
13 | }
14 |
15 | fun writeMessage(message: LogMessage) {
16 | val time = message.time.format(context, includeDate = false)
17 | val level = message.level.name
18 |
19 | appendLine(FORMAT.format(time, level, message.message))
20 | }
21 |
22 | companion object {
23 | private const val FORMAT = "%12s %7s: %s"
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/log/LogcatWriter.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.log
2 |
3 | import android.content.Context
4 | import com.github.kr328.clash.core.model.LogMessage
5 | import com.github.kr328.clash.design.model.LogFile
6 | import com.github.kr328.clash.util.logsDir
7 | import java.io.BufferedWriter
8 | import java.io.FileWriter
9 |
10 | class LogcatWriter(context: Context) : AutoCloseable {
11 | private val file = LogFile.generate()
12 | private val writer = BufferedWriter(FileWriter(context.logsDir.resolve(file.fileName)))
13 |
14 | override fun close() {
15 | writer.close()
16 | }
17 |
18 | fun appendMessage(message: LogMessage) {
19 | writer.appendLine(FORMAT.format(message.time.time, message.level.name, message.message))
20 | }
21 |
22 | companion object {
23 | private const val FORMAT = "%d:%s:%s"
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/log/SystemLogcat.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.log
2 |
3 | object SystemLogcat {
4 | private val command = arrayOf(
5 | "logcat",
6 | "-d",
7 | "-s",
8 | "Go",
9 | "DEBUG",
10 | "AndroidRuntime",
11 | "ClashMetaForAndroid",
12 | "LwIP",
13 | )
14 |
15 | fun dumpCrash(): String {
16 | return try {
17 | val process = Runtime.getRuntime().exec(command)
18 |
19 | val result = process.inputStream.use { stream ->
20 | stream.reader().readLines()
21 | .filterNot { it.startsWith("------") }
22 | .joinToString("\n")
23 | }
24 |
25 | process.waitFor()
26 |
27 | result.trim()
28 | } catch (e: Exception) {
29 | ""
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/remote/StatusClient.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.remote
2 |
3 | import android.content.Context
4 | import android.net.Uri
5 | import com.github.kr328.clash.common.constants.Authorities
6 | import com.github.kr328.clash.common.log.Log
7 | import com.github.kr328.clash.service.StatusProvider
8 |
9 | class StatusClient(private val context: Context) {
10 | private val uri: Uri
11 | get() {
12 | return Uri.Builder()
13 | .scheme("content")
14 | .authority(Authorities.STATUS_PROVIDER)
15 | .build()
16 | }
17 |
18 | fun currentProfile(): String? {
19 | return try {
20 | val result = context.contentResolver.call(
21 | uri,
22 | StatusProvider.METHOD_CURRENT_PROFILE,
23 | null,
24 | null
25 | )
26 |
27 | result?.getString("name")
28 | } catch (e: Exception) {
29 | Log.w("Query current profile: $e", e)
30 |
31 | null
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/store/AppStore.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.store
2 |
3 | import android.content.Context
4 | import com.github.kr328.clash.common.store.Store
5 | import com.github.kr328.clash.common.store.asStoreProvider
6 |
7 | class AppStore(context: Context) {
8 | private val store = Store(
9 | context
10 | .getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)
11 | .asStoreProvider()
12 | )
13 |
14 | var updatedAt: Long by store.long(
15 | key = "updated_at",
16 | defaultValue = -1,
17 | )
18 |
19 | companion object {
20 | private const val FILE_NAME = "app"
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/store/TipsStore.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.store
2 |
3 | import android.content.Context
4 | import com.github.kr328.clash.common.store.Store
5 | import com.github.kr328.clash.common.store.asStoreProvider
6 |
7 | class TipsStore(context: Context) {
8 | private val store = Store(
9 | context
10 | .getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)
11 | .asStoreProvider()
12 | )
13 |
14 | var requestDonate: Boolean by store.boolean(
15 | key = "request_donate",
16 | defaultValue = true,
17 | )
18 |
19 | var primaryVersion: Int by store.int(
20 | key = "primary_version",
21 | defaultValue = -1,
22 | )
23 |
24 | companion object {
25 | const val CURRENT_PRIMARY_VERSION = 1
26 |
27 | private const val FILE_NAME = "tips"
28 | }
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/util/Activity.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.util
2 |
3 | import androidx.lifecycle.Lifecycle
4 | import androidx.lifecycle.LifecycleOwner
5 | import androidx.lifecycle.LifecycleRegistry
6 | import kotlinx.coroutines.NonCancellable
7 | import kotlinx.coroutines.withContext
8 |
9 | class ActivityResultLifecycle : LifecycleOwner {
10 | override val lifecycle = LifecycleRegistry(this)
11 |
12 | init {
13 | lifecycle.currentState = Lifecycle.State.INITIALIZED
14 | }
15 |
16 | suspend fun use(block: suspend (lifecycle: ActivityResultLifecycle, start: () -> Unit) -> T): T {
17 | return try {
18 | markCreated()
19 |
20 | block(this, this::markStarted)
21 | } finally {
22 | withContext(NonCancellable) {
23 | markDestroy()
24 | }
25 | }
26 | }
27 |
28 | private fun markCreated() {
29 | lifecycle.currentState = Lifecycle.State.CREATED
30 | }
31 |
32 | private fun markStarted() {
33 | lifecycle.currentState = Lifecycle.State.STARTED
34 | lifecycle.currentState = Lifecycle.State.RESUMED
35 | }
36 |
37 | private fun markDestroy() {
38 | lifecycle.currentState = Lifecycle.State.DESTROYED
39 | }
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/util/Clash.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.util
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.net.VpnService
6 | import com.github.kr328.clash.common.compat.startForegroundServiceCompat
7 | import com.github.kr328.clash.common.constants.Intents
8 | import com.github.kr328.clash.common.util.intent
9 | import com.github.kr328.clash.design.store.UiStore
10 | import com.github.kr328.clash.service.ClashService
11 | import com.github.kr328.clash.service.TunService
12 | import com.github.kr328.clash.service.util.sendBroadcastSelf
13 |
14 | fun Context.startClashService(): Intent? {
15 | val startTun = UiStore(this).enableVpn
16 |
17 | if (startTun) {
18 | val vpnRequest = VpnService.prepare(this)
19 | if (vpnRequest != null)
20 | return vpnRequest
21 |
22 | startForegroundServiceCompat(TunService::class.intent)
23 | } else {
24 | startForegroundServiceCompat(ClashService::class.intent)
25 | }
26 |
27 | return null
28 | }
29 |
30 | fun Context.stopClashService() {
31 | sendBroadcastSelf(Intent(Intents.ACTION_CLASH_REQUEST_STOP))
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/util/Content.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.util
2 |
3 | import android.content.ContentResolver
4 | import android.net.Uri
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.withContext
7 | import java.io.FileNotFoundException
8 |
9 | private fun fileNotFound(file: Uri): FileNotFoundException {
10 | return FileNotFoundException("$file not found")
11 | }
12 |
13 | @Suppress("BlockingMethodInNonBlockingContext")
14 | suspend fun ContentResolver.copyContentTo(
15 | source: Uri,
16 | target: Uri
17 | ) {
18 | withContext(Dispatchers.IO) {
19 | (openInputStream(source) ?: throw fileNotFound(source)).use { input ->
20 | (openOutputStream(target, "rwt") ?: throw fileNotFound(target)).use { output ->
21 | input.copyTo(output)
22 | }
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/util/Files.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.util
2 |
3 | import android.content.Context
4 | import java.io.File
5 |
6 | val Context.logsDir: File
7 | get() = cacheDir.resolve("logs")
8 |
9 | val Context.clashDir: File
10 | get() = filesDir.resolve("clash")
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/util/Remote.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.util
2 |
3 | import android.os.DeadObjectException
4 | import com.github.kr328.clash.common.log.Log
5 | import com.github.kr328.clash.remote.Remote
6 | import com.github.kr328.clash.service.remote.IClashManager
7 | import com.github.kr328.clash.service.remote.IProfileManager
8 | import kotlinx.coroutines.Dispatchers
9 | import kotlinx.coroutines.withContext
10 | import kotlin.coroutines.CoroutineContext
11 |
12 | suspend fun withClash(
13 | context: CoroutineContext = Dispatchers.IO,
14 | block: suspend IClashManager.() -> T
15 | ): T {
16 | while (true) {
17 | val remote = Remote.service.remote.get()
18 | val client = remote.clash()
19 |
20 | try {
21 | return withContext(context) { client.block() }
22 | } catch (e: DeadObjectException) {
23 | Log.w("Remote services panic")
24 |
25 | Remote.service.remote.reset(remote)
26 | }
27 | }
28 | }
29 |
30 | suspend fun withProfile(
31 | context: CoroutineContext = Dispatchers.IO,
32 | block: suspend IProfileManager.() -> T
33 | ): T {
34 | while (true) {
35 | val remote = Remote.service.remote.get()
36 | val client = remote.profile()
37 |
38 | try {
39 | return withContext(context) { client.block() }
40 | } catch (e: DeadObjectException) {
41 | Log.w("Remote services panic")
42 |
43 | Remote.service.remote.reset(remote)
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/util/Service.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.util
2 |
3 | import android.content.Context
4 | import android.content.ServiceConnection
5 |
6 | fun Context.unbindServiceSilent(connection: ServiceConnection) {
7 | try {
8 | unbindService(connection)
9 | } catch (e: Exception) {
10 | // ignore
11 | }
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/kr328/clash/util/Uri.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.util
2 |
3 | import android.net.Uri
4 |
5 | val Uri.fileName: String?
6 | get() = schemeSpecificPart.split("/").lastOrNull()
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-xhdpi/ic_banner.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_banner_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/full_backup_content.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
9 |
12 |
15 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/common/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("android")
3 | id("com.android.library")
4 | }
5 |
6 | dependencies {
7 | compileOnly(project(":hideapi"))
8 |
9 | implementation(libs.kotlin.coroutine)
10 | implementation(libs.androidx.core)
11 | }
12 |
--------------------------------------------------------------------------------
/common/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/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 |
2 |
3 |
8 |
9 |
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/Intents.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.common.constants
2 |
3 | import com.github.kr328.clash.common.util.packageName
4 |
5 | object Intents {
6 | // Public
7 | val ACTION_PROVIDE_URL = "$packageName.action.PROVIDE_URL"
8 | val ACTION_START_CLASH = "$packageName.action.START_CLASH"
9 | val ACTION_STOP_CLASH = "$packageName.action.STOP_CLASH"
10 | val ACTION_TOGGLE_CLASH = "$packageName.action.TOGGLE_CLASH"
11 |
12 | const val EXTRA_NAME = "name"
13 |
14 | // Self
15 | val ACTION_SERVICE_RECREATED = "$packageName.intent.action.CLASH_RECREATED"
16 | val ACTION_CLASH_STARTED = "$packageName.intent.action.CLASH_STARTED"
17 | val ACTION_CLASH_STOPPED = "$packageName.intent.action.CLASH_STOPPED"
18 | val ACTION_CLASH_REQUEST_STOP = "$packageName.intent.action.CLASH_REQUEST_STOP"
19 | val ACTION_PROFILE_CHANGED = "$packageName.intent.action.PROFILE_CHANGED"
20 | val ACTION_PROFILE_UPDATE_COMPLETED = "$packageName.intent.action.PROFILE_UPDATE_COMPLETED"
21 | val ACTION_PROFILE_UPDATE_FAILED = "$packageName.intent.action.PROFILE_UPDATE_FAILED"
22 | val ACTION_PROFILE_REQUEST_UPDATE = "$packageName.intent.action.REQUEST_UPDATE"
23 | val ACTION_PROFILE_SCHEDULE_UPDATES = "$packageName.intent.action.SCHEDULE_UPDATES"
24 | val ACTION_PROFILE_LOADED = "$packageName.intent.action.PROFILE_LOADED"
25 | val ACTION_OVERRIDE_CHANGED = "$packageName.intent.action.OVERRIDE_CHANGED"
26 |
27 | const val EXTRA_STOP_REASON = "stop_reason"
28 | const val EXTRA_UUID = "uuid"
29 | const val EXTRA_FAIL_REASON = "fail_reason"
30 | }
--------------------------------------------------------------------------------
/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): Set
14 | fun setStringSet(key: String, value: Set)
15 |
16 | fun getBoolean(key: String, defaultValue: Boolean): Boolean
17 | fun setBoolean(key: String, value: Boolean)
18 | }
--------------------------------------------------------------------------------
/common/src/main/java/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 {
10 | val channel = Channel(Channel.RENDEZVOUS)
11 |
12 | launch {
13 | try {
14 | while (isActive) {
15 | channel.send(System.currentTimeMillis())
16 |
17 | delay(period)
18 | }
19 | } catch (ignored: Exception) {
20 |
21 | }
22 | }
23 |
24 | return channel
25 | }
--------------------------------------------------------------------------------
/common/src/main/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Получать оповещения от Clash
4 | Получать оповещения от сервисов Clash
5 |
6 |
--------------------------------------------------------------------------------
/common/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 接收 Clash 廣播
4 | 接收來自 Clash 內部的廣播
5 |
6 |
--------------------------------------------------------------------------------
/common/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 接收 Clash 广播
4 | 接收来自 Clash 内部的广播
5 |
--------------------------------------------------------------------------------
/common/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Receive Clash Broadcasts
4 | Receive broadcasts of clash services
5 |
6 |
--------------------------------------------------------------------------------
/core/.gitignore:
--------------------------------------------------------------------------------
1 | /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 |
2 |
3 |
4 |
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
4 |
5 | uint64_t down_scale_traffic(uint64_t value);
--------------------------------------------------------------------------------
/core/src/main/cpp/jni_helper.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | struct _scoped_jni {
10 | JNIEnv *env;
11 | int require_release;
12 | };
13 |
14 | extern void initialize_jni(JavaVM *vm, JNIEnv *env);
15 | extern jstring jni_new_string(JNIEnv *env, const char *str);
16 | extern char *jni_get_string(JNIEnv *env, jstring str);
17 | extern int jni_catch_exception(JNIEnv *env);
18 | extern void jni_attach_thread(struct _scoped_jni *jni);
19 | extern void jni_detach_thread(struct _scoped_jni *env);
20 | extern void release_string(char **str);
21 |
22 | #define ATTACH_JNI() __attribute__((unused, cleanup(jni_detach_thread))) \
23 | struct _scoped_jni _jni; \
24 | jni_attach_thread(&_jni); \
25 | JNIEnv *env = _jni.env
26 |
27 | #define scoped_string __attribute__((cleanup(release_string))) char*
28 |
29 | #define find_class(name) (*env)->FindClass(env, name)
30 | #define find_method(cls, name, signature) (*env)->GetMethodID(env, cls, name, signature)
31 | #define new_global(obj) (*env)->NewGlobalRef(env, obj)
32 | #define del_global(obj) (*env)->DeleteGlobalRef(env, obj)
33 | #define get_string(jstr) jni_get_string(env, jstr)
34 | #define new_string(cstr) jni_new_string(env, cstr)
--------------------------------------------------------------------------------
/core/src/main/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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/core/src/main/golang/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/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/override.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "io"
5 | "os"
6 |
7 | "github.com/metacubex/mihomo/constant"
8 | )
9 |
10 | type OverrideSlot int
11 |
12 | const (
13 | OverrideSlotPersist OverrideSlot = iota
14 | OverrideSlotSession
15 | )
16 |
17 | const defaultPersistOverride = `{}`
18 | const defaultSessionOverride = `{}`
19 |
20 | var sessionOverride = defaultSessionOverride
21 |
22 | func overridePersistPath() string {
23 | return constant.Path.Resolve("override.json")
24 | }
25 |
26 | func ReadOverride(slot OverrideSlot) string {
27 | switch slot {
28 | case OverrideSlotPersist:
29 | file, err := os.OpenFile(overridePersistPath(), os.O_RDONLY, 0600)
30 | if err != nil {
31 | return defaultPersistOverride
32 | }
33 |
34 | buf, err := io.ReadAll(file)
35 | if err != nil {
36 | return defaultPersistOverride
37 | }
38 |
39 | return string(buf)
40 | case OverrideSlotSession:
41 | return sessionOverride
42 | }
43 |
44 | return ""
45 | }
46 |
47 | func WriteOverride(slot OverrideSlot, content string) {
48 | switch slot {
49 | case OverrideSlotPersist:
50 | file, err := os.OpenFile(overridePersistPath(), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600)
51 | if err != nil {
52 | return
53 | }
54 |
55 | _, err = file.Write([]byte(content))
56 | case OverrideSlotSession:
57 | sessionOverride = content
58 | }
59 | }
60 |
61 | func ClearOverride(slot OverrideSlot) {
62 | switch slot {
63 | case OverrideSlotPersist:
64 | _ = os.Remove(overridePersistPath())
65 | case OverrideSlotSession:
66 | sessionOverride = defaultSessionOverride
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/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
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,
12 | val progress: Int,
13 | val max: Int
14 | ) : Parcelable {
15 | enum class Action {
16 | FetchConfiguration,
17 | FetchProviders,
18 | Verifying,
19 | }
20 |
21 | override fun describeContents(): Int {
22 | return 0
23 | }
24 |
25 | override fun writeToParcel(dest: Parcel, flags: Int) {
26 | Parcelizer.encodeToParcel(serializer(), dest, this)
27 | }
28 |
29 | companion object CREATOR : Parcelable.Creator {
30 | override fun createFromParcel(parcel: Parcel): FetchStatus {
31 | return Parcelizer.decodeFromParcel(serializer(), parcel)
32 | }
33 |
34 | override fun newArray(size: Int): Array {
35 | return arrayOfNulls(size)
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/github/kr328/clash/core/model/Provider.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.core.model
2 |
3 | import android.os.Parcel
4 | import android.os.Parcelable
5 | import com.github.kr328.clash.core.util.Parcelizer
6 | import kotlinx.serialization.Serializable
7 |
8 | @Serializable
9 | data class Provider(
10 | val name: String,
11 | val type: Type,
12 | val vehicleType: VehicleType,
13 | val updatedAt: Long
14 | ) : Parcelable, Comparable {
15 | enum class Type {
16 | Proxy, Rule
17 | }
18 |
19 | enum class VehicleType {
20 | HTTP, File, 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 {
36 | override fun createFromParcel(parcel: Parcel): Provider {
37 | return Parcelizer.decodeFromParcel(serializer(), parcel)
38 | }
39 |
40 | override fun newArray(size: Int): Array {
41 | return arrayOfNulls(size)
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/core/src/main/java/com/github/kr328/clash/core/model/ProviderList.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.core.model
2 |
3 | import android.os.Parcel
4 | import android.os.Parcelable
5 | import com.github.kr328.clash.common.util.createListFromParcelSlice
6 | import com.github.kr328.clash.common.util.writeToParcelSlice
7 |
8 | class ProviderList(data: List) : List by data, Parcelable {
9 | constructor(parcel: Parcel) : this(Provider.createListFromParcelSlice(parcel, 0, 20))
10 |
11 | override fun describeContents(): Int {
12 | return 0
13 | }
14 |
15 | override fun writeToParcel(parcel: Parcel, flags: Int) {
16 | return writeToParcelSlice(parcel, flags)
17 | }
18 |
19 | companion object CREATOR : Parcelable.Creator {
20 | override fun createFromParcel(parcel: Parcel): ProviderList {
21 | return ProviderList(parcel)
22 | }
23 |
24 | override fun newArray(size: Int): Array {
25 | return arrayOfNulls(size)
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/github/kr328/clash/core/model/ProxySort.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.core.model
2 |
3 | enum class ProxySort {
4 | Default, Title, Delay
5 | }
6 |
--------------------------------------------------------------------------------
/core/src/main/java/com/github/kr328/clash/core/model/Traffic.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.core.model
2 |
3 | typealias Traffic = Long
--------------------------------------------------------------------------------
/core/src/main/java/com/github/kr328/clash/core/model/TunnelState.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.core.model
2 |
3 | import android.os.Parcel
4 | import android.os.Parcelable
5 | import com.github.kr328.clash.core.util.Parcelizer
6 | import kotlinx.serialization.SerialName
7 | import kotlinx.serialization.Serializable
8 |
9 | @Serializable
10 | data class TunnelState(
11 | val mode: Mode,
12 | ) : Parcelable {
13 | @Serializable
14 | enum class Mode {
15 | @SerialName("direct")
16 | Direct,
17 |
18 | @SerialName("global")
19 | Global,
20 |
21 | @SerialName("rule")
22 | Rule,
23 |
24 | @SerialName("script")
25 | Script,
26 | }
27 |
28 | override fun writeToParcel(parcel: Parcel, flags: Int) {
29 | Parcelizer.encodeToParcel(serializer(), parcel, this)
30 | }
31 |
32 | override fun describeContents(): Int {
33 | return 0
34 | }
35 |
36 | companion object CREATOR : Parcelable.Creator {
37 | override fun createFromParcel(parcel: Parcel): TunnelState {
38 | return Parcelizer.decodeFromParcel(serializer(), parcel)
39 | }
40 |
41 | override fun newArray(size: Int): Array {
42 | return arrayOfNulls(size)
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/github/kr328/clash/core/model/UiConfiguration.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.core.model
2 |
3 | import android.os.Parcel
4 | import android.os.Parcelable
5 | import com.github.kr328.clash.core.util.Parcelizer
6 | import kotlinx.serialization.Serializable
7 |
8 | @Serializable
9 | class UiConfiguration : Parcelable {
10 | override fun writeToParcel(parcel: Parcel, flags: Int) {
11 | Parcelizer.encodeToParcel(serializer(), parcel, this)
12 | }
13 |
14 | override fun describeContents(): Int {
15 | return 0
16 | }
17 |
18 | companion object CREATOR : Parcelable.Creator {
19 | override fun createFromParcel(parcel: Parcel): UiConfiguration {
20 | return Parcelizer.decodeFromParcel(serializer(), parcel)
21 | }
22 |
23 | override fun newArray(size: Int): Array {
24 | return arrayOfNulls(size)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/core/src/main/java/com/github/kr328/clash/core/util/Net.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.core.util
2 |
3 | import java.net.InetAddress
4 | import java.net.InetSocketAddress
5 | import java.net.URL
6 |
7 | fun parseInetSocketAddress(address: String): InetSocketAddress {
8 | val url = URL("https://$address")
9 |
10 | return InetSocketAddress(InetAddress.getByName(url.host), url.port)
11 | }
--------------------------------------------------------------------------------
/core/src/main/java/com/github/kr328/clash/core/util/Serializers.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.core.util
2 |
3 | import kotlinx.serialization.KSerializer
4 | import kotlinx.serialization.descriptors.PrimitiveKind
5 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
6 | import kotlinx.serialization.descriptors.SerialDescriptor
7 | import kotlinx.serialization.encoding.Decoder
8 | import kotlinx.serialization.encoding.Encoder
9 | import java.util.*
10 |
11 | object DateSerializer : KSerializer {
12 | override val descriptor: SerialDescriptor
13 | get() = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG)
14 |
15 | override fun deserialize(decoder: Decoder): Date {
16 | return Date(decoder.decodeLong())
17 | }
18 |
19 | override fun serialize(encoder: Encoder, value: Date) {
20 | encoder.encodeLong(value.time)
21 | }
22 | }
--------------------------------------------------------------------------------
/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/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/design/consumer-rules.pro
--------------------------------------------------------------------------------
/design/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/design/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/design/src/main/java/com/github/kr328/clash/design/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(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(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() {
15 | class Holder(val label: ActionLabel) : RecyclerView.ViewHolder(label)
16 |
17 | var logs: List = emptyList()
18 |
19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
20 | return Holder(ActionLabel(context).apply {
21 | layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
22 | })
23 | }
24 |
25 | override fun onBindViewHolder(holder: Holder, position: Int) {
26 | val current = logs[position]
27 |
28 | holder.label.text = current.fileName
29 | holder.label.subtext = current.date.format(context)
30 | holder.label.setOnClickListener {
31 | open(current)
32 | }
33 | }
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() {
15 | class Holder(val binding: AdapterLogMessageBinding) : RecyclerView.ViewHolder(binding.root)
16 |
17 | var messages: List = emptyList()
18 |
19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
20 | return Holder(
21 | AdapterLogMessageBinding
22 | .inflate(context.layoutInflater, parent, false)
23 | )
24 | }
25 |
26 | override fun onBindViewHolder(holder: Holder, position: Int) {
27 | val current = messages[position]
28 |
29 | holder.binding.message = current
30 | holder.binding.root.setOnLongClickListener {
31 | copy(current)
32 |
33 | true
34 | }
35 | }
36 |
37 | override fun getItemCount(): Int {
38 | return messages.size
39 | }
40 | }
--------------------------------------------------------------------------------
/design/src/main/java/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() {
13 | class Holder(val view: ProxyView) : RecyclerView.ViewHolder(view)
14 |
15 | var selectable: Boolean = false
16 | var states: List = emptyList()
17 |
18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
19 | return Holder(ProxyView(config.context, config))
20 | }
21 |
22 | override fun onBindViewHolder(holder: Holder, position: Int) {
23 | val current = states[position]
24 |
25 | holder.view.apply {
26 | state = current
27 |
28 | setOnClickListener {
29 | clicked(current.proxy.name)
30 | }
31 |
32 | val isSelector = selectable
33 |
34 | isFocusable = isSelector
35 | isClickable = isSelector
36 |
37 | current.update(true)
38 | }
39 | }
40 |
41 | override fun getItemCount(): Int {
42 | return states.size
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/design/src/main/java/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) : Comparator by comparator {
4 | Label(compareBy(AppInfo::label)),
5 | PackageName(compareBy(AppInfo::packageName)),
6 | InstallTime(compareBy(AppInfo::installTime)),
7 | UpdateTime(compareBy(AppInfo::updateDate)),
8 | }
9 |
--------------------------------------------------------------------------------
/design/src/main/java/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 {
4 | fun from(value: T): String?
5 | fun to(text: String?): T
6 |
7 | companion object {
8 | val Port = object : NullableTextAdapter {
9 | override fun from(value: Int?): String? {
10 | if (value == null) return null
11 |
12 | return if (value > 0) value.toString() else ""
13 | }
14 |
15 | override fun to(text: String?): Int? {
16 | if (text == null) return null
17 |
18 | return text.toIntOrNull() ?: 0
19 | }
20 | }
21 |
22 | val String = object : NullableTextAdapter {
23 | override fun from(value: String?): String? {
24 | return value
25 | }
26 |
27 | override fun to(text: String?): String? {
28 | return text
29 | }
30 | }
31 | }
32 | }
33 |
34 | interface TextAdapter {
35 | fun from(value: T): String
36 | fun to(text: String): T
37 |
38 | companion object {
39 | val String = object : TextAdapter {
40 | override fun from(value: String): String {
41 | return value
42 | }
43 |
44 | override fun to(text: String): String {
45 | return text
46 | }
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/design/src/main/java/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(R.id.activity_bar_close_view)?.apply {
13 | setOnClickListener {
14 | context.onBackPressed()
15 | }
16 | }
17 | findViewById(R.id.activity_bar_title_view)?.apply {
18 | text = context.title
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/design/src/main/java/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 List.diffWith(
6 | newList: List,
7 | detectMove: Boolean = false,
8 | id: (T) -> Any? = { it }
9 | ): DiffUtil.DiffResult {
10 | val oldList = this
11 |
12 | return DiffUtil.calculateDiff(object : DiffUtil.Callback() {
13 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
14 | return id(oldList[oldItemPosition]) == id(newList[newItemPosition])
15 | }
16 |
17 | override fun getOldListSize(): Int {
18 | return oldList.size
19 | }
20 |
21 | override fun getNewListSize(): Int {
22 | return newList.size
23 | }
24 |
25 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
26 | return oldList[oldItemPosition] == newList[newItemPosition]
27 | }
28 | }, detectMove)
29 | }
30 |
--------------------------------------------------------------------------------
/design/src/main/java/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()
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 = 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 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/bg_b.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/bg_bottom_sheet.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
9 |
10 |
11 | -
14 |
15 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_adb.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_add.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_apps.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_arrow_back.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_assignment.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_attach_file.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_brightness_4.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_clear_all.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_close.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_cloud_download.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_content_copy.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_delete.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_dns.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_domain.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_edit.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_extension.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_flash_on.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_get_app.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_help_center.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_hide.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_info.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_more_vert.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_publish.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_replay.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_restore.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_save.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_search.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_settings.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_stop.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_swap_vert.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_swap_vertical_circle.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_sync.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_update.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_view_list.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_vpn_lock.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_baseline_work.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_outline_article.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_outline_check_circle.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_outline_delete.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_outline_folder.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_outline_inbox.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_outline_info.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_outline_label.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_outline_not_interested.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/drawable/ic_outline_update.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/layout/adapter_editable_text_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
18 |
19 |
31 |
32 |
--------------------------------------------------------------------------------
/design/src/main/res/layout/common_activity_bar.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
28 |
--------------------------------------------------------------------------------
/design/src/main/res/layout/common_recycler_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
16 |
--------------------------------------------------------------------------------
/design/src/main/res/layout/design_new_profile.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
10 |
15 |
16 |
20 |
21 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/design/src/main/res/layout/dialog_fetch_status.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
15 |
16 |
21 |
22 |
--------------------------------------------------------------------------------
/design/src/main/res/layout/dialog_text_field.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
10 |
11 |
17 |
18 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/design/src/main/res/layout/preference_category.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/design/src/main/res/layout/preference_tips.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
15 |
16 |
22 |
23 |
--------------------------------------------------------------------------------
/design/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #1e4376
4 | #1976d2
5 | #50000000
6 | #FFFAFAFA
7 | #FF121212
8 | #FF202020
9 | #FF808080
10 | #FFD3D3D3
11 | #FF808080
12 | #FFB00020
13 |
14 |
--------------------------------------------------------------------------------
/design/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/design/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | A Graphical user interface of Clash.Meta for Android.
2 |
3 | Features
4 |
5 | - Local HTTP/HTTPS/SOCKS server
6 | - VMess, Shadowsocks, Trojan, Snell protocol support for remote connections
7 | - Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP
8 | - Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes
9 | - Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency
10 | - Remote providers, allowing users to get node lists remotely instead of hardcoding in config
11 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | A rule-based tunnel
--------------------------------------------------------------------------------
/fastlane/metadata/android/zh-CN/full_description.txt:
--------------------------------------------------------------------------------
1 | Clash.Meta 的 Android 图形界面。
2 |
3 | 功能
4 |
5 | - 本地 HTTP/HTTPS/SOCKS 服务器
6 | - 支持 VMess, Shadowsocks, Trojan, Snell 协议远程连接
7 | - 内置 DNS 服务器,旨在最小化 DNS 污染攻击影响,支持 DoH/DoT 上游和 fake IP
8 | - 基于规则根据域名,GEOIP, IPCIDR 或进程转发数据包到不同节点
9 | - 远程分组允许用户实现强大的规则。支持自动回退,负载均衡或基于延迟自动选择节点
10 | - 远程提供者,允许用户远程获取节点列表而不是在配置中硬编码
11 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/zh-CN/short_description.txt:
--------------------------------------------------------------------------------
1 | 基于规则的隧道
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx4g -XX:+UseZGC -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 | # Gradle parallel build
23 | org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jan 14 14:06:42 CST 2025
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/hideapi/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | }
4 |
--------------------------------------------------------------------------------
/hideapi/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/hideapi/consumer-rules.pro
--------------------------------------------------------------------------------
/hideapi/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.kts.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/hideapi/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/hideapi/src/main/java/android/app/ActivityThread.java:
--------------------------------------------------------------------------------
1 | package android.app;
2 |
3 | public class ActivityThread {
4 | public static String currentProcessName() {
5 | throw new IllegalArgumentException("Stub!");
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/release.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/release.keystore
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:recommended"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/service/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("android")
3 | id("kotlinx-serialization")
4 | id("com.android.library")
5 | id("com.google.devtools.ksp")
6 | }
7 |
8 | dependencies {
9 | implementation(project(":core"))
10 | implementation(project(":common"))
11 |
12 | ksp(libs.kaidl.compiler)
13 | ksp(libs.androidx.room.compiler)
14 |
15 | implementation(libs.kotlin.coroutine)
16 | implementation(libs.kotlin.serialization.json)
17 | implementation(libs.androidx.core)
18 | implementation(libs.androidx.room.runtime)
19 | implementation(libs.androidx.room.ktx)
20 | implementation(libs.kaidl.runtime)
21 | implementation(libs.rikkax.multiprocess)
22 | implementation(platform("com.squareup.okhttp3:okhttp-bom:4.12.0"))
23 |
24 | // define any required OkHttp artifacts without version
25 | implementation("com.squareup.okhttp3:okhttp")
26 | implementation("com.squareup.okhttp3:logging-interceptor")
27 | }
28 |
29 | afterEvaluate {
30 | android {
31 | libraryVariants.forEach {
32 | sourceSets[it.name].kotlin.srcDir(buildDir.resolve("generated/ksp/${it.name}/kotlin"))
33 | sourceSets[it.name].java.srcDir(buildDir.resolve("generated/ksp/${it.name}/java"))
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/service/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jacky-Bruse/CMFA/c7bd47c7e3f6ccb02da82be9b4fbb21c6f308b60/service/consumer-rules.pro
--------------------------------------------------------------------------------
/service/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/BaseService.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service
2 |
3 | import android.app.Service
4 | import com.github.kr328.clash.service.util.cancelAndJoinBlocking
5 | import kotlinx.coroutines.CoroutineScope
6 | import kotlinx.coroutines.Dispatchers
7 |
8 | abstract class BaseService : Service(), CoroutineScope by CoroutineScope(Dispatchers.Default) {
9 | override fun onDestroy() {
10 | super.onDestroy()
11 |
12 | cancelAndJoinBlocking()
13 | }
14 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/PreferenceProvider.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import com.github.kr328.clash.common.constants.Authorities
6 | import rikka.preference.MultiProcessPreference
7 | import rikka.preference.PreferenceProvider
8 |
9 | class PreferenceProvider : PreferenceProvider() {
10 | override fun onCreatePreference(context: Context): SharedPreferences {
11 | return context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)
12 | }
13 |
14 | companion object {
15 | private const val FILE_NAME = "service"
16 |
17 | fun createSharedPreferencesFromContext(context: Context): SharedPreferences {
18 | return when (context) {
19 | is BaseService, is TunService ->
20 | context.getSharedPreferences(
21 | FILE_NAME,
22 | Context.MODE_PRIVATE
23 | )
24 | else ->
25 | MultiProcessPreference(
26 | context,
27 | Authorities.SETTINGS_PROVIDER
28 | )
29 | }
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/RemoteService.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service
2 |
3 | import android.content.Intent
4 | import android.os.IBinder
5 | import com.github.kr328.clash.service.remote.IClashManager
6 | import com.github.kr328.clash.service.remote.IRemoteService
7 | import com.github.kr328.clash.service.remote.IProfileManager
8 | import com.github.kr328.clash.service.remote.wrap
9 | import com.github.kr328.clash.service.util.cancelAndJoinBlocking
10 |
11 | class RemoteService : BaseService(), IRemoteService {
12 | private val binder = this.wrap()
13 |
14 | private var clash: ClashManager? = null
15 | private var profile: ProfileManager? = null
16 | private var clashBinder: IClashManager? = null
17 | private var profileBinder: IProfileManager? = null
18 |
19 | override fun onCreate() {
20 | super.onCreate()
21 |
22 | clash = ClashManager(this)
23 | profile = ProfileManager(this)
24 | clashBinder = clash?.wrap() as IClashManager?
25 | profileBinder = profile?.wrap() as IProfileManager?
26 | }
27 |
28 | override fun onDestroy() {
29 | super.onDestroy()
30 |
31 | clash?.cancelAndJoinBlocking()
32 | profile?.cancelAndJoinBlocking()
33 | }
34 |
35 | override fun onBind(intent: Intent?): IBinder {
36 | return binder
37 | }
38 |
39 | override fun clash(): IClashManager {
40 | return clashBinder!!
41 | }
42 |
43 | override fun profile(): IProfileManager {
44 | return profileBinder!!
45 | }
46 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/clash/module/CloseModule.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.clash.module
2 |
3 | import android.app.Service
4 | import com.github.kr328.clash.common.constants.Intents
5 | import com.github.kr328.clash.common.log.Log
6 |
7 | class CloseModule(service: Service) : Module(service) {
8 | object RequestClose
9 |
10 | override suspend fun run() {
11 | val broadcasts = receiveBroadcast {
12 | addAction(Intents.ACTION_CLASH_REQUEST_STOP)
13 | }
14 |
15 | broadcasts.receive()
16 |
17 | Log.d("User request close")
18 |
19 | return enqueueEvent(RequestClose)
20 | }
21 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/clash/module/TimeZoneModule.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.clash.module
2 |
3 | import android.app.Service
4 | import android.content.Intent
5 | import com.github.kr328.clash.core.Clash
6 | import java.util.*
7 |
8 | class TimeZoneModule(service: Service) : Module(service) {
9 | override suspend fun run() {
10 | val timeZones = receiveBroadcast {
11 | addAction(Intent.ACTION_TIMEZONE_CHANGED)
12 | }
13 |
14 | while (true) {
15 | val timeZone = TimeZone.getDefault()
16 |
17 | Clash.notifyTimeZoneChanged(timeZone.id, timeZone.rawOffset / 1000)
18 |
19 | timeZones.receive()
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/data/Converters.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.data
2 |
3 | import androidx.room.TypeConverter
4 | import com.github.kr328.clash.service.model.Profile
5 | import java.util.*
6 |
7 | class Converters {
8 | @TypeConverter
9 | fun fromUUID(uuid: UUID): String {
10 | return uuid.toString()
11 | }
12 |
13 | @TypeConverter
14 | fun toUUID(uuid: String): UUID {
15 | return UUID.fromString(uuid)
16 | }
17 |
18 | @TypeConverter
19 | fun fromProfileType(type: Profile.Type): String {
20 | return type.name
21 | }
22 |
23 | @TypeConverter
24 | fun toProfileType(type: String): Profile.Type {
25 | return Profile.Type.valueOf(type)
26 | }
27 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/data/Daos.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.data
2 |
3 | fun ImportedDao(): ImportedDao {
4 | return Database.database.openImportedDao()
5 | }
6 |
7 | fun PendingDao(): PendingDao {
8 | return Database.database.openPendingDao()
9 | }
10 |
11 | fun SelectionDao(): SelectionDao {
12 | return Database.database.openSelectionProxyDao()
13 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/data/Imported.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.data
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.TypeConverters
6 | import com.github.kr328.clash.service.model.Profile
7 | import java.util.*
8 |
9 | @Entity(tableName = "imported", primaryKeys = ["uuid"])
10 | @TypeConverters(Converters::class)
11 | data class Imported(
12 | @ColumnInfo(name = "uuid") val uuid: UUID,
13 | @ColumnInfo(name = "name") val name: String,
14 | @ColumnInfo(name = "type") val type: Profile.Type,
15 | @ColumnInfo(name = "source") val source: String,
16 | @ColumnInfo(name = "interval") val interval: Long,
17 | @ColumnInfo(name = "upload") val upload: Long,
18 | @ColumnInfo(name = "download") val download: Long,
19 | @ColumnInfo(name = "total") val total: Long,
20 | @ColumnInfo(name = "expire") val expire: Long,
21 | @ColumnInfo(name = "createdAt") val createdAt: Long,
22 | )
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/data/ImportedDao.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.data
2 |
3 | import androidx.room.*
4 | import java.util.*
5 |
6 | @Dao
7 | @TypeConverters(Converters::class)
8 | interface ImportedDao {
9 | @Query("SELECT * FROM imported WHERE uuid = :uuid")
10 | suspend fun queryByUUID(uuid: UUID): Imported?
11 |
12 | @Query("SELECT uuid FROM imported ORDER BY createdAt")
13 | suspend fun queryAllUUIDs(): List
14 |
15 | @Insert(onConflict = OnConflictStrategy.ABORT)
16 | suspend fun insert(imported: Imported): Long
17 |
18 | @Update(onConflict = OnConflictStrategy.ABORT)
19 | suspend fun update(imported: Imported)
20 |
21 | @Query("DELETE FROM imported WHERE uuid = :uuid")
22 | suspend fun remove(uuid: UUID)
23 |
24 | @Query("SELECT EXISTS(SELECT 1 FROM imported WHERE uuid = :uuid)")
25 | suspend fun exists(uuid: UUID): Boolean
26 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/data/Pending.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.data
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.TypeConverters
6 | import com.github.kr328.clash.service.model.Profile
7 | import java.util.*
8 |
9 | @Entity(tableName = "pending", primaryKeys = ["uuid"])
10 | @TypeConverters(Converters::class)
11 | data class Pending(
12 | @ColumnInfo(name = "uuid") val uuid: UUID,
13 | @ColumnInfo(name = "name") val name: String,
14 | @ColumnInfo(name = "type") val type: Profile.Type,
15 | @ColumnInfo(name = "source") val source: String,
16 | @ColumnInfo(name = "interval") val interval: Long,
17 | @ColumnInfo(name = "upload") val upload: Long,
18 | @ColumnInfo(name = "download") val download: Long,
19 | @ColumnInfo(name = "total") val total: Long,
20 | @ColumnInfo(name = "expire") val expire: Long,
21 | @ColumnInfo(name = "createdAt") val createdAt: Long = System.currentTimeMillis(),
22 | )
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/data/PendingDao.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.data
2 |
3 | import androidx.room.*
4 | import java.util.*
5 |
6 | @Dao
7 | @TypeConverters(Converters::class)
8 | interface PendingDao {
9 | @Query("SELECT * FROM pending WHERE uuid = :uuid")
10 | suspend fun queryByUUID(uuid: UUID): Pending?
11 |
12 | @Query("DELETE FROM pending WHERE uuid = :uuid")
13 | suspend fun remove(uuid: UUID)
14 |
15 | @Query("SELECT EXISTS(SELECT 1 FROM pending WHERE uuid = :uuid)")
16 | suspend fun exists(uuid: UUID): Boolean
17 |
18 | @Query("SELECT uuid FROM pending ORDER BY createdAt")
19 | suspend fun queryAllUUIDs(): List
20 |
21 | @Insert(onConflict = OnConflictStrategy.REPLACE)
22 | suspend fun insert(pending: Pending)
23 |
24 | @Update(onConflict = OnConflictStrategy.REPLACE)
25 | suspend fun update(pending: Pending)
26 | }
27 |
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/data/Selection.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.data
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.ForeignKey
6 | import androidx.room.TypeConverters
7 | import java.util.*
8 |
9 | @Entity(
10 | tableName = "selections",
11 | foreignKeys = [ForeignKey(
12 | entity = Imported::class,
13 | childColumns = ["uuid"],
14 | parentColumns = ["uuid"],
15 | onDelete = ForeignKey.CASCADE,
16 | onUpdate = ForeignKey.CASCADE
17 | )],
18 | primaryKeys = ["uuid", "proxy"]
19 | )
20 | @TypeConverters(Converters::class)
21 | data class Selection(
22 | @ColumnInfo(name = "uuid") val uuid: UUID,
23 | @ColumnInfo(name = "proxy") val proxy: String,
24 | @ColumnInfo(name = "selected") val selected: String,
25 | )
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/data/SelectionDao.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.data
2 |
3 | import androidx.room.*
4 | import java.util.*
5 |
6 | @Dao
7 | @TypeConverters(Converters::class)
8 | interface SelectionDao {
9 | @Insert(onConflict = OnConflictStrategy.REPLACE)
10 | fun setSelected(selection: Selection)
11 |
12 | @Query("DELETE FROM selections WHERE uuid = :uuid AND proxy = :proxy")
13 | fun removeSelected(uuid: UUID, proxy: String)
14 |
15 | @Query("SELECT * FROM selections WHERE uuid = :uuid")
16 | suspend fun querySelections(uuid: UUID): List
17 |
18 | @Query("DELETE FROM selections WHERE uuid = :uuid AND proxy in (:proxies)")
19 | suspend fun removeSelections(uuid: UUID, proxies: List)
20 | }
21 |
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/data/migrations/Migrations.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.data.migrations
2 |
3 | import androidx.room.migration.Migration
4 |
5 | val MIGRATIONS: Array = arrayOf()
6 |
7 | val LEGACY_MIGRATION = ::migrationFromLegacy
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/document/Document.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.document
2 |
3 | interface Document {
4 | val id: String
5 | val name: String
6 | val mimeType: String
7 | val size: Long
8 | val updatedAt: Long
9 | val flags: Set
10 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/document/FileDocument.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.document
2 |
3 | import android.provider.DocumentsContract
4 | import java.io.File
5 |
6 | class FileDocument(
7 | val file: File,
8 | override val flags: Set,
9 | private val idOverride: String? = null,
10 | private val nameOverride: String? = null,
11 | ) : Document {
12 | override val id: String
13 | get() = idOverride ?: file.name
14 | override val name: String
15 | get() = nameOverride ?: file.name
16 | override val mimeType: String
17 | get() = if (file.isDirectory) DocumentsContract.Document.MIME_TYPE_DIR else "text/plain"
18 | override val size: Long
19 | get() = file.length()
20 | override val updatedAt: Long
21 | get() = file.lastModified()
22 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/document/Flag.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.document
2 |
3 | enum class Flag {
4 | Writable, Deletable, Virtual
5 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/document/Path.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.document
2 |
3 | import java.util.*
4 |
5 | data class Path(
6 | val uuid: UUID?,
7 | val scope: Scope?,
8 | val relative: List?
9 | ) {
10 | enum class Scope {
11 | Configuration, Providers
12 | }
13 |
14 | override fun toString(): String {
15 | if (uuid == null)
16 | return "/"
17 |
18 | if (scope == null)
19 | return "/$uuid"
20 |
21 | val sc = when (scope) {
22 | Scope.Configuration -> Paths.CONFIGURATION_ID
23 | Scope.Providers -> Paths.PROVIDERS_ID
24 | }
25 |
26 | if (relative == null)
27 | return "/$uuid/$sc"
28 |
29 | return "/$uuid/$sc/${relative.joinToString(separator = "/")}"
30 | }
31 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/document/VirtualDocument.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.document
2 |
3 | class VirtualDocument(
4 | override val id: String,
5 | override val name: String,
6 | override val mimeType: String,
7 | override val size: Long,
8 | override val updatedAt: Long,
9 | override val flags: Set,
10 | ) : Document
11 |
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/model/AccessControlMode.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.model
2 |
3 | enum class AccessControlMode {
4 | AcceptAll, AcceptSelected, DenySelected
5 | }
6 |
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/model/Profile.kt:
--------------------------------------------------------------------------------
1 | @file:UseSerializers(UUIDSerializer::class)
2 |
3 | package com.github.kr328.clash.service.model
4 |
5 | import android.os.Parcel
6 | import android.os.Parcelable
7 | import com.github.kr328.clash.core.util.Parcelizer
8 | import com.github.kr328.clash.service.util.UUIDSerializer
9 | import kotlinx.serialization.Serializable
10 | import kotlinx.serialization.UseSerializers
11 | import java.util.*
12 |
13 | @Serializable
14 | data class Profile(
15 | val uuid: UUID,
16 | val name: String,
17 | val type: Type,
18 | val source: String,
19 | val active: Boolean,
20 | val interval: Long,
21 | val upload: Long,
22 | var download: Long,
23 | val total: Long,
24 | val expire: Long,
25 |
26 |
27 | val updatedAt: Long,
28 | val imported: Boolean,
29 | val pending: Boolean,
30 | ) : Parcelable {
31 | enum class Type {
32 | File, Url, External
33 | }
34 |
35 | override fun writeToParcel(parcel: Parcel, flags: Int) {
36 | Parcelizer.encodeToParcel(serializer(), parcel, this)
37 | }
38 |
39 | override fun describeContents(): Int {
40 | return 0
41 | }
42 |
43 | companion object CREATOR : Parcelable.Creator {
44 | override fun createFromParcel(parcel: Parcel): Profile {
45 | return Parcelizer.decodeFromParcel(serializer(), parcel)
46 | }
47 |
48 | override fun newArray(size: Int): Array {
49 | return arrayOfNulls(size)
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/remote/IClashManager.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.remote
2 |
3 | import com.github.kr328.clash.core.Clash
4 | import com.github.kr328.clash.core.model.*
5 | import com.github.kr328.kaidl.BinderInterface
6 |
7 | @BinderInterface
8 | interface IClashManager {
9 | fun queryTunnelState(): TunnelState
10 | fun queryTrafficTotal(): Long
11 | fun queryProxyGroupNames(excludeNotSelectable: Boolean): List
12 | fun queryProxyGroup(name: String, proxySort: ProxySort): ProxyGroup
13 | fun queryConfiguration(): UiConfiguration
14 | fun queryProviders(): ProviderList
15 |
16 | fun patchSelector(group: String, name: String): Boolean
17 |
18 | suspend fun healthCheck(group: String)
19 | suspend fun updateProvider(type: Provider.Type, name: String)
20 |
21 | fun queryOverride(slot: Clash.OverrideSlot): ConfigurationOverride
22 | fun patchOverride(slot: Clash.OverrideSlot, configuration: ConfigurationOverride)
23 | fun clearOverride(slot: Clash.OverrideSlot)
24 |
25 | fun setLogObserver(observer: ILogObserver?)
26 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/remote/IFetchObserver.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.remote
2 |
3 | import com.github.kr328.clash.core.model.FetchStatus
4 | import com.github.kr328.kaidl.BinderInterface
5 |
6 | @BinderInterface
7 | fun interface IFetchObserver {
8 | fun updateStatus(status: FetchStatus)
9 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/remote/ILogObserver.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.remote
2 |
3 | import com.github.kr328.clash.core.model.LogMessage
4 | import com.github.kr328.kaidl.BinderInterface
5 |
6 | @BinderInterface
7 | interface ILogObserver {
8 | fun newItem(log: LogMessage)
9 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/remote/IProfileManager.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.remote
2 |
3 | import com.github.kr328.clash.service.model.Profile
4 | import com.github.kr328.kaidl.BinderInterface
5 | import java.util.*
6 |
7 | @BinderInterface
8 | interface IProfileManager {
9 | suspend fun create(type: Profile.Type, name: String, source: String = ""): UUID
10 | suspend fun clone(uuid: UUID): UUID
11 | suspend fun commit(uuid: UUID, callback: IFetchObserver? = null)
12 | suspend fun release(uuid: UUID)
13 | suspend fun delete(uuid: UUID)
14 | suspend fun patch(uuid: UUID, name: String, source: String, interval: Long)
15 | suspend fun update(uuid: UUID)
16 | suspend fun queryByUUID(uuid: UUID): Profile?
17 | suspend fun queryAll(): List
18 | suspend fun queryActive(): Profile?
19 | suspend fun setActive(profile: Profile)
20 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/remote/IRemoteService.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.remote
2 |
3 | import com.github.kr328.kaidl.BinderInterface
4 |
5 | @BinderInterface
6 | interface IRemoteService {
7 | fun clash(): IClashManager
8 | fun profile(): IProfileManager
9 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/util/Connectivity.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.util
2 |
3 | import android.net.ConnectivityManager
4 | import android.net.Network
5 |
6 | fun ConnectivityManager.resolveDns(network: Network?): List {
7 | val properties = getLinkProperties(network) ?: return listOf()
8 | return properties.dnsServers.map { it.asSocketAddressText(53) }
9 | }
10 |
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/util/Coroutine.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.util
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.job
5 | import kotlinx.coroutines.runBlocking
6 |
7 | fun CoroutineScope.cancelAndJoinBlocking() {
8 | val scope = this
9 |
10 | runBlocking {
11 | scope.coroutineContext.job.cancel()
12 | scope.coroutineContext.job.join()
13 | }
14 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/util/Database.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.util
2 |
3 | import com.github.kr328.clash.service.data.ImportedDao
4 | import com.github.kr328.clash.service.data.PendingDao
5 | import java.util.*
6 |
7 | suspend fun generateProfileUUID(): UUID {
8 | var result = UUID.randomUUID()
9 |
10 | while (ImportedDao().exists(result) || PendingDao().exists(result)) {
11 | result = UUID.randomUUID()
12 | }
13 |
14 | return result
15 | }
16 |
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/util/Files.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.util
2 |
3 | import android.content.Context
4 | import java.io.File
5 |
6 | val Context.importedDir: File
7 | get() = filesDir.resolve("imported")
8 |
9 | val Context.pendingDir: File
10 | get() = filesDir.resolve("pending")
11 |
12 | val Context.processingDir: File
13 | get() = filesDir.resolve("processing")
14 |
15 | val File.directoryLastModified: Long?
16 | get() {
17 | return walk().map { it.lastModified() }.maxOrNull()
18 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/util/Intent.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.util
2 |
3 | import android.content.Intent
4 |
5 | val Intent.packageName: String?
6 | get() {
7 | return data?.takeIf { it.scheme == "package" }?.schemeSpecificPart
8 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/util/Net.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.util
2 |
3 | data class IPNet(val ip: String, val prefix: Int)
4 |
5 | fun parseCIDR(cidr: String): IPNet {
6 | val s = cidr.split("/", limit = 2)
7 |
8 | if (s.size != 2)
9 | throw IllegalArgumentException("Invalid address")
10 |
11 | val address = s[0]
12 | val prefix = s[1].toInt()
13 |
14 | return IPNet(address, prefix)
15 | }
--------------------------------------------------------------------------------
/service/src/main/java/com/github/kr328/clash/service/util/Serializers.kt:
--------------------------------------------------------------------------------
1 | package com.github.kr328.clash.service.util
2 |
3 | import kotlinx.serialization.KSerializer
4 | import kotlinx.serialization.descriptors.PrimitiveKind
5 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
6 | import kotlinx.serialization.descriptors.SerialDescriptor
7 | import kotlinx.serialization.encoding.Decoder
8 | import kotlinx.serialization.encoding.Encoder
9 | import java.util.*
10 |
11 | class UUIDSerializer : KSerializer {
12 | override val descriptor: SerialDescriptor =
13 | PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
14 |
15 | override fun deserialize(decoder: Decoder): UUID {
16 | return UUID.fromString(decoder.decodeString())
17 | }
18 |
19 | override fun serialize(encoder: Encoder, value: UUID) {
20 | encoder.encodeString(value.toString())
21 | }
22 | }
--------------------------------------------------------------------------------
/service/src/main/res/values-ja-rJP/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Clashステータス
4 | プロファイルサービスのステータス
5 | プロファイルの処理状況
6 | プロファイルプロセスの結果
7 | 正常に更新されました
8 | 更新に失敗しました
9 | %sを更新しました
10 | 更新 %1$s: %2$s
11 | 実行中
12 | 読み込み中
13 | Clash Meta for Android
14 | プロファイルと外部リソース
15 | コンフィグ.yaml
16 | 外部リソースファイル
17 | プロファイルの更新
18 | プロファイルを更新中
19 |
--------------------------------------------------------------------------------
/service/src/main/res/values-ko-rKR/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Clash 상태
4 | 구성 파일 서비스 상태
5 | 구성 파일 처리 상태
6 | 구성 파일 처리 결과
7 | 업데이트 성공
8 | 업데이트 실패
9 | %s 업데이트 성공
10 | 업데이트 %1$s: %2$s
11 | 연결됨
12 | 로딩중
13 | Clash Meta for Android
14 | 구성 파일과 외부 리소스
15 | 구성 파일.yaml
16 | 외부 리소스 파일
17 | 구성 파일 업데이트
18 | 구성 파일 업데이트 중
19 |
--------------------------------------------------------------------------------
/service/src/main/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | "%1$s↑\t%2$s↓"
4 |
5 | Статус Clash
6 | Статус сервиса профиля
7 | Статус обработки профиля
8 | Результат обработки профиля
9 | Успешно обновлено
10 | Не удалось обновить
11 | Обновление %s завершено
12 | Обновление %1$s: %2$s
13 | Работает
14 | Загружается
15 | Clash Meta для Android
16 | Профили и провайдеры
17 | Configuration.yaml
18 | Файлы провайдера
19 | Обновление профиля
20 | Профиль обновляется
21 |
22 |
--------------------------------------------------------------------------------
/service/src/main/res/values-zh-rHK/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Clash 狀態
4 | 正在運行
5 | 更新 %s 成功
6 | "更新 %1$s: %2$s "
7 | Clash Meta for Android
8 | 配置文件和外部資源
9 | 配置文件.yaml
10 | 外部資源文件列表
11 | 載入中
12 | 配置文件處理狀態
13 | 更新成功
14 | 更新失敗
15 | 配置更新服務
16 | 配置更新中
17 | 配置文件服務狀態
18 | 配置文件處理結果
19 |
--------------------------------------------------------------------------------
/service/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Clash 狀態
4 | 正在運作
5 | 更新 %s 成功
6 | "更新 %1$s: %2$s "
7 | Clash Meta for Android
8 | 設定檔和外部資源
9 | 設定檔.yaml
10 | 外部資源文件列表
11 | 載入中
12 | 設定檔處理狀態
13 | 更新成功
14 | 更新失敗
15 | 設定檔更新服務
16 | 設定檔更新中
17 | 設定檔服務狀態
18 | 設定檔處理結果
19 |
20 |
--------------------------------------------------------------------------------
/service/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Clash 状态
4 | 正在运行
5 | 更新 %s 成功
6 | "更新 %1$s: %2$s "
7 | Clash Meta for Android
8 | 配置文件和外部资源
9 | 配置文件.yaml
10 | 外部资源文件列表
11 | 载入中
12 | 配置文件处理状态
13 | 更新成功
14 | 更新失败
15 | 配置更新服务
16 | 配置更新中
17 | 配置文件服务状态
18 | 配置文件处理结果
19 |
--------------------------------------------------------------------------------
/service/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #1E4376
4 |
--------------------------------------------------------------------------------
/service/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/service/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | "%1$s↑\t%2$s↓"
4 |
5 | Clash Status
6 | Profile Service Status
7 | Profile Processing Status
8 | Profile Process Result
9 | Update Successfully
10 | Update Failure
11 | Update %s completed
12 | Update %1$s: %2$s
13 | Running
14 | Loading
15 | Clash Meta for Android
16 | Profiles and Providers
17 | Configuration.yaml
18 | Provider Files
19 | Profile Updater
20 | Profile Updating
21 |
22 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "ClashMetaForAndroid"
2 |
3 | include(":app")
4 | include(":core")
5 | include(":service")
6 | include(":design")
7 | include(":common")
8 | include(":hideapi")
9 |
10 | pluginManagement {
11 | repositories {
12 | mavenLocal()
13 | mavenCentral()
14 | gradlePluginPortal()
15 | }
16 | }
17 |
--------------------------------------------------------------------------------