├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── build-unsigned.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 │ ├── foss │ └── java │ │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ └── Tracker.kt │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── kr328 │ │ │ └── clash │ │ │ ├── AccessControlActivity.kt │ │ │ ├── ApkBrokenActivity.kt │ │ │ ├── AppCrashedActivity.kt │ │ │ ├── AppSettingsActivity.kt │ │ │ ├── BaseActivity.kt │ │ │ ├── ExternalImportActivity.kt │ │ │ ├── FilesActivity.kt │ │ │ ├── HelpActivity.kt │ │ │ ├── LogcatActivity.kt │ │ │ ├── LogcatService.kt │ │ │ ├── LogsActivity.kt │ │ │ ├── MainActivity.kt │ │ │ ├── MainApplication.kt │ │ │ ├── NetworkSettingsActivity.kt │ │ │ ├── NewProfileActivity.kt │ │ │ ├── OverrideSettingsActivity.kt │ │ │ ├── ProfilesActivity.kt │ │ │ ├── PropertiesActivity.kt │ │ │ ├── ProvidersActivity.kt │ │ │ ├── ProxyActivity.kt │ │ │ ├── RestartReceiver.kt │ │ │ ├── SettingsActivity.kt │ │ │ ├── TileService.kt │ │ │ ├── log │ │ │ ├── LogcatCache.kt │ │ │ ├── LogcatFilter.kt │ │ │ ├── LogcatReader.kt │ │ │ ├── LogcatWriter.kt │ │ │ └── SystemLogcat.kt │ │ │ ├── remote │ │ │ ├── Broadcasts.kt │ │ │ ├── FilesClient.kt │ │ │ ├── Remote.kt │ │ │ ├── Resource.kt │ │ │ ├── Service.kt │ │ │ └── StatusClient.kt │ │ │ ├── store │ │ │ ├── AppStore.kt │ │ │ └── TipsStore.kt │ │ │ └── util │ │ │ ├── Activity.kt │ │ │ ├── Application.kt │ │ │ ├── Clash.kt │ │ │ ├── Content.kt │ │ │ ├── Files.kt │ │ │ ├── Remote.kt │ │ │ ├── Service.kt │ │ │ └── Uri.kt │ └── res │ │ ├── drawable │ │ └── ic_launcher_foreground.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_banner.png │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── ids.xml │ │ └── themes.xml │ │ └── xml │ │ ├── full_backup_content.xml │ │ └── network_security_config.xml │ └── premium │ └── java │ └── com │ └── github │ └── kr328 │ └── clash │ └── Tracker.kt ├── 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-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ └── values │ └── strings.xml ├── core ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── foss │ └── golang │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go │ ├── main │ ├── AndroidManifest.xml │ ├── cpp │ │ ├── CMakeLists.txt │ │ ├── bridge_helper.c │ │ ├── bridge_helper.h │ │ ├── jni_helper.c │ │ ├── jni_helper.h │ │ └── main.c │ ├── golang │ │ ├── .idea │ │ │ └── codeStyles │ │ │ │ ├── Project.xml │ │ │ │ └── codeStyleConfig.xml │ │ ├── go.mod │ │ ├── go.sum │ │ └── native │ │ │ ├── all │ │ │ └── imports.go │ │ │ ├── app.go │ │ │ ├── app │ │ │ ├── app.go │ │ │ ├── content.go │ │ │ ├── dns.go │ │ │ ├── tun.go │ │ │ └── ui.go │ │ │ ├── bridge.c │ │ │ ├── bridge.h │ │ │ ├── common │ │ │ └── path.go │ │ │ ├── config.go │ │ │ ├── config │ │ │ ├── defaults.go │ │ │ ├── fetch.go │ │ │ ├── load.go │ │ │ ├── override.go │ │ │ ├── process.go │ │ │ ├── provider_open.go │ │ │ └── provider_premium.go │ │ │ ├── debug.go │ │ │ ├── delegate │ │ │ └── init.go │ │ │ ├── log_open.go │ │ │ ├── log_premium.go │ │ │ ├── main.go │ │ │ ├── platform │ │ │ ├── limit.go │ │ │ └── procfs.go │ │ │ ├── proxy.go │ │ │ ├── proxy │ │ │ └── http.go │ │ │ ├── trace.c │ │ │ ├── trace.h │ │ │ ├── tun.go │ │ │ ├── tun │ │ │ ├── dns.go │ │ │ ├── metadata_open.go │ │ │ ├── metadata_premium.go │ │ │ ├── tun.go │ │ │ └── udp.go │ │ │ ├── tunnel.go │ │ │ ├── tunnel │ │ │ ├── conn.go │ │ │ ├── connectivity.go │ │ │ ├── geoip.go │ │ │ ├── init.go │ │ │ ├── loopback_open.go │ │ │ ├── loopback_premium.go │ │ │ ├── providers_open.go │ │ │ ├── providers_premium.go │ │ │ ├── proxies.go │ │ │ ├── state.go │ │ │ ├── statistic.go │ │ │ └── suspend.go │ │ │ └── utils.go │ └── java │ │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ └── core │ │ ├── Clash.kt │ │ ├── bridge │ │ ├── Bridge.kt │ │ ├── ClashException.kt │ │ ├── Content.kt │ │ ├── FetchCallback.kt │ │ ├── LogcatInterface.kt │ │ └── TunInterface.kt │ │ ├── model │ │ ├── ConfigurationOverride.kt │ │ ├── FetchStatus.kt │ │ ├── LogMessage.kt │ │ ├── Provider.kt │ │ ├── ProviderList.kt │ │ ├── Proxy.kt │ │ ├── ProxyGroup.kt │ │ ├── ProxySort.kt │ │ ├── Traffic.kt │ │ ├── TunnelState.kt │ │ └── UiConfiguration.kt │ │ └── util │ │ ├── Net.kt │ │ ├── Parcelizer.kt │ │ ├── Serializers.kt │ │ └── Traffic.kt │ └── premium │ └── golang │ ├── go.mod │ ├── go.sum │ └── main.go ├── design ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── 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 │ │ ├── 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 │ │ └── SideloadProviderAdapter.kt │ │ ├── component │ │ ├── AccessControlMenu.kt │ │ ├── ProxyMenu.kt │ │ ├── ProxyPageFactory.kt │ │ ├── ProxyView.kt │ │ ├── ProxyViewConfig.kt │ │ └── ProxyViewState.kt │ │ ├── dialog │ │ ├── Dialogs.kt │ │ ├── Input.kt │ │ └── Progress.kt │ │ ├── model │ │ ├── AppInfo.kt │ │ ├── AppInfoSort.kt │ │ ├── Behavior.kt │ │ ├── DarkMode.kt │ │ ├── File.kt │ │ ├── LogFile.kt │ │ ├── ProfileProvider.kt │ │ ├── ProviderState.kt │ │ ├── ProxyPageState.kt │ │ └── ProxyState.kt │ │ ├── preference │ │ ├── Category.kt │ │ ├── Clickable.kt │ │ ├── EditableText.kt │ │ ├── EditableTextList.kt │ │ ├── EditableTextMap.kt │ │ ├── Overlay.kt │ │ ├── Preference.kt │ │ ├── Screen.kt │ │ ├── SelectableList.kt │ │ ├── Switch.kt │ │ ├── Tips.kt │ │ └── Value.kt │ │ ├── store │ │ └── UiStore.kt │ │ ├── ui │ │ ├── DayNight.kt │ │ ├── Insets.kt │ │ ├── ObservableCurrentTime.kt │ │ ├── Surface.kt │ │ └── ToastDuration.kt │ │ ├── util │ │ ├── ActivityBar.kt │ │ ├── App.kt │ │ ├── Binding.kt │ │ ├── Context.kt │ │ ├── Diff.kt │ │ ├── Elevation.kt │ │ ├── I18n.kt │ │ ├── Inserts.kt │ │ ├── Interval.kt │ │ ├── Landscape.kt │ │ ├── ListView.kt │ │ ├── RecyclerView.kt │ │ ├── ScrollView.kt │ │ ├── Theme.kt │ │ ├── Toast.kt │ │ ├── Validator.kt │ │ └── View.kt │ │ └── view │ │ ├── ActionLabel.kt │ │ ├── ActionTextField.kt │ │ ├── ActivityBarLayout.kt │ │ ├── AppRecyclerView.kt │ │ ├── LargeActionCard.kt │ │ ├── LargeActionLabel.kt │ │ ├── ObservableScrollView.kt │ │ └── VerticalScrollableHost.kt │ └── res │ ├── drawable │ ├── bg_bottom_sheet.xml │ ├── ic_baseline_adb.xml │ ├── ic_baseline_add.xml │ ├── ic_baseline_apps.xml │ ├── ic_baseline_arrow_back.xml │ ├── ic_baseline_assignment.xml │ ├── ic_baseline_attach_file.xml │ ├── ic_baseline_brightness_4.xml │ ├── ic_baseline_clear_all.xml │ ├── ic_baseline_close.xml │ ├── ic_baseline_cloud_download.xml │ ├── ic_baseline_content_copy.xml │ ├── ic_baseline_delete.xml │ ├── ic_baseline_dns.xml │ ├── ic_baseline_domain.xml │ ├── ic_baseline_edit.xml │ ├── ic_baseline_extension.xml │ ├── ic_baseline_flash_on.xml │ ├── ic_baseline_get_app.xml │ ├── ic_baseline_help_center.xml │ ├── ic_baseline_info.xml │ ├── ic_baseline_more_vert.xml │ ├── ic_baseline_publish.xml │ ├── ic_baseline_replay.xml │ ├── ic_baseline_restore.xml │ ├── ic_baseline_save.xml │ ├── ic_baseline_search.xml │ ├── ic_baseline_settings.xml │ ├── ic_baseline_stop.xml │ ├── ic_baseline_swap_vert.xml │ ├── ic_baseline_swap_vertical_circle.xml │ ├── ic_baseline_sync.xml │ ├── ic_baseline_update.xml │ ├── ic_baseline_view_list.xml │ ├── ic_baseline_vpn_lock.xml │ ├── ic_baseline_work.xml │ ├── ic_clash.xml │ ├── ic_outline_article.xml │ ├── ic_outline_check_circle.xml │ ├── ic_outline_delete.xml │ ├── ic_outline_folder.xml │ ├── ic_outline_inbox.xml │ ├── ic_outline_info.xml │ ├── ic_outline_label.xml │ ├── ic_outline_not_interested.xml │ └── ic_outline_update.xml │ ├── layout │ ├── adapter_app.xml │ ├── adapter_editable_text_list.xml │ ├── adapter_editable_text_map.xml │ ├── adapter_file.xml │ ├── adapter_log_message.xml │ ├── adapter_profile.xml │ ├── adapter_profile_provider.xml │ ├── adapter_provider.xml │ ├── adapter_sideload_provider.xml │ ├── common_activity_bar.xml │ ├── common_recycler_list.xml │ ├── component_action_label.xml │ ├── component_action_text_field.xml │ ├── component_large_action_label.xml │ ├── design_about.xml │ ├── design_access_control.xml │ ├── design_app_crashed.xml │ ├── design_files.xml │ ├── design_logcat.xml │ ├── design_logs.xml │ ├── design_main.xml │ ├── design_new_profile.xml │ ├── design_profiles.xml │ ├── design_properties.xml │ ├── design_providers.xml │ ├── design_proxy.xml │ ├── design_settings.xml │ ├── design_settings_common.xml │ ├── design_settings_overide.xml │ ├── dialog_editable_map_text_field.xml │ ├── dialog_fetch_status.xml │ ├── dialog_files_menu.xml │ ├── dialog_preference_list.xml │ ├── dialog_profiles_menu.xml │ ├── dialog_search.xml │ ├── dialog_text_field.xml │ ├── preference_category.xml │ ├── preference_clickable.xml │ ├── preference_switch.xml │ └── preference_tips.xml │ ├── menu │ ├── menu_access_control.xml │ └── menu_proxy.xml │ ├── values-v23 │ └── themes.xml │ ├── values-v27 │ └── themes.xml │ ├── values-v29 │ └── themes.xml │ ├── values-zh-rHK │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── ids.xml │ ├── strings.xml │ ├── styles.xml │ └── themes.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hideapi ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── android │ └── app │ └── ActivityThread.java ├── service ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── 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 │ │ │ ├── SideloadDatabaseModule.kt │ │ │ ├── StaticNotificationModule.kt │ │ │ ├── SuspendModule.kt │ │ │ ├── TimeZoneModule.kt │ │ │ └── TunModule.kt │ │ ├── data │ │ ├── Converters.kt │ │ ├── Daos.kt │ │ ├── Database.kt │ │ ├── Imported.kt │ │ ├── ImportedDao.kt │ │ ├── Pending.kt │ │ ├── PendingDao.kt │ │ ├── Selection.kt │ │ ├── SelectionDao.kt │ │ └── migrations │ │ │ ├── LegacyMigration.kt │ │ │ └── Migrations.kt │ │ ├── document │ │ ├── Document.kt │ │ ├── FileDocument.kt │ │ ├── Flag.kt │ │ ├── Path.kt │ │ ├── Paths.kt │ │ ├── Picker.kt │ │ └── VirtualDocument.kt │ │ ├── model │ │ ├── AccessControlMode.kt │ │ └── Profile.kt │ │ ├── remote │ │ ├── IClashManager.kt │ │ ├── IFetchObserver.kt │ │ ├── ILogObserver.kt │ │ ├── IProfileManager.kt │ │ └── IRemoteService.kt │ │ ├── sideload │ │ └── ExternalGeoip.kt │ │ ├── store │ │ └── ServiceStore.kt │ │ └── util │ │ ├── Address.kt │ │ ├── Broadcast.kt │ │ ├── Connectivity.kt │ │ ├── Coroutine.kt │ │ ├── Database.kt │ │ ├── Files.kt │ │ ├── Intent.kt │ │ ├── Net.kt │ │ └── Serializers.kt │ └── res │ ├── drawable │ └── ic_logo_service.xml │ ├── values-zh-rHK │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ ├── values-zh │ └── strings.xml │ └── values │ ├── arrays.xml │ ├── colors.xml │ ├── ids.xml │ └── strings.xml ├── settings.gradle.kts └── update.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.bat text eol=crlf 4 | *.jar binary 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [Kr328] 4 | -------------------------------------------------------------------------------- /.github/workflows/build-unsigned.yaml: -------------------------------------------------------------------------------- 1 | name: Build Unsigned 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - '.github/**' 8 | - '.idea/**' 9 | - '.gitattributes' 10 | - '.gitignore' 11 | - '.gitmodules' 12 | - '**.md' 13 | - 'LICENSE' 14 | - 'NOTICE' 15 | pull_request: 16 | paths-ignore: 17 | - '.github/**' 18 | - '.idea/**' 19 | - '.gitattributes' 20 | - '.gitignore' 21 | - '.gitmodules' 22 | - '**.md' 23 | - 'LICENSE' 24 | - 'NOTICE' 25 | 26 | jobs: 27 | BuildUnsigned: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Checkout Repository 31 | uses: actions/checkout@v3 32 | with: 33 | submodules: recursive 34 | - name: Setup Java 35 | uses: actions/setup-java@v3 36 | with: 37 | distribution: 'zulu' 38 | java-version: 17 39 | - name: Setup Go 40 | uses: actions/setup-go@v3 41 | with: 42 | go-version: 1.18 43 | - uses: actions/cache@v3 44 | with: 45 | path: | 46 | ~/.cache/go-build 47 | ~/go/pkg/mod 48 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 49 | restore-keys: | 50 | ${{ runner.os }}-go- 51 | - name: Build 52 | uses: gradle/gradle-build-action@v2 53 | with: 54 | arguments: --no-daemon app:assembleFossRelease 55 | -------------------------------------------------------------------------------- /.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 | # KeyStore 31 | signing.properties 32 | *.keystore 33 | *.jks 34 | 35 | # clion cmake build 36 | cmake-build-* 37 | 38 | # local.properties 39 | local.properties 40 | 41 | 42 | # tracker 43 | tracker.properties 44 | 45 | # vscode 46 | .vscode 47 | 48 | # cxx 49 | .cxx 50 | 51 | *.hprof 52 | 53 | # firebase 54 | google-services.json 55 | 56 | # Dolphin 57 | .directory 58 | 59 | # logs 60 | *.log 61 | 62 | # MacOS 63 | .DS_Store 64 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "clash-foss"] 2 | path = core/src/foss/golang/clash 3 | url = https://github.com/SagerNet/clash.git 4 | [submodule "clash-premium"] 5 | path = core/src/premium/golang/clash 6 | url = https://github.com/Kr328/clash.git 7 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to Clash for Android 2 | 3 | #### Code Style 4 | 5 | Please use `Android Studio` or `Intellij IDEA` to open the project and use the project code style profile. 6 | 7 | `File` -> `Settings` -> `Editor` -> `Code Style` -> `C/C++ and Kotlin` -> `Scheme` -> `Project` 8 | 9 | 10 | 11 | #### License 12 | 13 | Contributing to Clash for Android that assumes you allow code to be merged into closed-source branch of Clash for Android. Other terms follow the [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ClashForAndroid for SagerNet 2 | 3 | ### Changes 4 | 5 | * See https://github.com/SagerNet/clash -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("android") 3 | kotlin("kapt") 4 | id("com.android.application") 5 | } 6 | 7 | dependencies { 8 | compileOnly(project(":hideapi")) 9 | 10 | implementation(project(":core")) 11 | implementation(project(":service")) 12 | implementation(project(":design")) 13 | implementation(project(":common")) 14 | 15 | implementation(libs.kotlin.coroutine) 16 | implementation(libs.androidx.core) 17 | implementation(libs.androidx.activity) 18 | implementation(libs.androidx.fragment) 19 | implementation(libs.androidx.appcompat) 20 | implementation(libs.androidx.coordinator) 21 | implementation(libs.androidx.recyclerview) 22 | implementation(libs.google.material) 23 | 24 | val premiumImplementation by configurations 25 | 26 | premiumImplementation(libs.appcenter.analytics) 27 | premiumImplementation(libs.appcenter.crashes) 28 | } 29 | 30 | tasks.getByName("clean", type = Delete::class) { 31 | delete(file("release")) 32 | } 33 | -------------------------------------------------------------------------------- /app/src/foss/java/com/github/kr328/clash/Tracker.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.app.Application 4 | 5 | @Suppress("UNUSED_PARAMETER") 6 | object Tracker { 7 | fun initialize(application: Application) { 8 | // do nothing 9 | } 10 | 11 | fun uploadLogcat(logcat: String) { 12 | // do nothing 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/ClashForAndroid/950507d68461427ca4b4bba71b47f74f08c7a998/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/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/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import com.github.kr328.clash.common.Global 6 | import com.github.kr328.clash.common.compat.currentProcessName 7 | import com.github.kr328.clash.common.log.Log 8 | import com.github.kr328.clash.remote.Remote 9 | import com.github.kr328.clash.service.util.sendServiceRecreated 10 | 11 | @Suppress("unused") 12 | class MainApplication : Application() { 13 | override fun attachBaseContext(base: Context?) { 14 | super.attachBaseContext(base) 15 | 16 | Global.init(this) 17 | } 18 | 19 | override fun onCreate() { 20 | super.onCreate() 21 | 22 | // Initialize AppCenter 23 | Tracker.initialize(this) 24 | 25 | val processName = currentProcessName 26 | 27 | Log.d("Process $processName started") 28 | 29 | if (processName == packageName) { 30 | Remote.launch() 31 | } else { 32 | sendServiceRecreated() 33 | } 34 | } 35 | 36 | fun finalize() { 37 | Global.destroy() 38 | } 39 | } -------------------------------------------------------------------------------- /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 | } 28 | } 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /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/LogcatReader.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.BufferedReader 8 | import java.io.FileReader 9 | import java.util.* 10 | 11 | class LogcatReader(context: Context, file: LogFile) : AutoCloseable { 12 | private val reader = BufferedReader(FileReader(context.logsDir.resolve(file.fileName))) 13 | 14 | override fun close() { 15 | reader.close() 16 | } 17 | 18 | fun readAll(): List { 19 | return reader.lineSequence() 20 | .map { it.trim() } 21 | .filter { !it.startsWith("#") } 22 | .map { it.split(":", limit = 3) } 23 | .map { 24 | LogMessage( 25 | time = Date(it[0].toLong()), 26 | level = LogMessage.Level.valueOf(it[1]), 27 | message = it[2] 28 | ) 29 | } 30 | .toList() 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/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 | "ClashForAndroid", 12 | "LwIP", 13 | ) 14 | 15 | fun dumpCrash(): String { 16 | return try { 17 | val process = Runtime.getRuntime().exec(command) 18 | 19 | val result = process.inputStream.use { stream -> 20 | stream.reader().readLines() 21 | .filterNot { it.startsWith("------") } 22 | .joinToString("\n") 23 | } 24 | 25 | process.waitFor() 26 | 27 | result.trim() 28 | } catch (e: Exception) { 29 | "" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/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 | private val lifecycle = LifecycleRegistry(this) 11 | 12 | init { 13 | lifecycle.currentState = Lifecycle.State.INITIALIZED 14 | } 15 | 16 | override fun getLifecycle(): Lifecycle { 17 | return lifecycle 18 | } 19 | 20 | suspend fun use(block: suspend (lifecycle: ActivityResultLifecycle, start: () -> Unit) -> T): T { 21 | return try { 22 | markCreated() 23 | 24 | block(this, this::markStarted) 25 | } finally { 26 | withContext(NonCancellable) { 27 | markDestroy() 28 | } 29 | } 30 | } 31 | 32 | private fun markCreated() { 33 | lifecycle.currentState = Lifecycle.State.CREATED 34 | } 35 | 36 | private fun markStarted() { 37 | lifecycle.currentState = Lifecycle.State.STARTED 38 | lifecycle.currentState = Lifecycle.State.RESUMED 39 | } 40 | 41 | private fun markDestroy() { 42 | lifecycle.currentState = Lifecycle.State.DESTROYED 43 | } 44 | } -------------------------------------------------------------------------------- /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") -------------------------------------------------------------------------------- /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/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/ClashForAndroid/950507d68461427ca4b4bba71b47f74f08c7a998/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/ClashForAndroid/950507d68461427ca4b4bba71b47f74f08c7a998/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/ClashForAndroid/950507d68461427ca4b4bba71b47f74f08c7a998/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/ClashForAndroid/950507d68461427ca4b4bba71b47f74f08c7a998/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/ClashForAndroid/950507d68461427ca4b4bba71b47f74f08c7a998/app/src/main/res/mipmap-xhdpi/ic_banner.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/ClashForAndroid/950507d68461427ca4b4bba71b47f74f08c7a998/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/ClashForAndroid/950507d68461427ca4b4bba71b47f74f08c7a998/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/ClashForAndroid/950507d68461427ca4b4bba71b47f74f08c7a998/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/ClashForAndroid/950507d68461427ca4b4bba71b47f74f08c7a998/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/ClashForAndroid/950507d68461427ca4b4bba71b47f74f08c7a998/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/ClashForAndroid/950507d68461427ca4b4bba71b47f74f08c7a998/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | # Gradle parallel build 23 | org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/ClashForAndroid/950507d68461427ca4b4bba71b47f74f08c7a998/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionSha256Sum=e6d864e3b5bc05cc62041842b306383fc1fefcec359e70cebb1d470a6094ca82 -------------------------------------------------------------------------------- /hideapi/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | } 4 | -------------------------------------------------------------------------------- /hideapi/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/ClashForAndroid/950507d68461427ca4b4bba71b47f74f08c7a998/hideapi/consumer-rules.pro -------------------------------------------------------------------------------- /hideapi/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /hideapi/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /hideapi/src/main/java/android/app/ActivityThread.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | public class ActivityThread { 4 | public static String currentProcessName() { 5 | throw new IllegalArgumentException("Stub!"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /service/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("android") 3 | kotlin("kapt") 4 | id("kotlinx-serialization") 5 | id("com.android.library") 6 | id("com.google.devtools.ksp") 7 | } 8 | 9 | dependencies { 10 | implementation(project(":core")) 11 | implementation(project(":common")) 12 | 13 | ksp(libs.kaidl.compiler) 14 | kapt(libs.androidx.room.compiler) 15 | 16 | implementation(libs.kotlin.coroutine) 17 | implementation(libs.kotlin.serialization.json) 18 | implementation(libs.androidx.core) 19 | implementation(libs.androidx.room.runtime) 20 | implementation(libs.androidx.room.ktx) 21 | implementation(libs.kaidl.runtime) 22 | implementation(libs.rikkax.multiprocess) 23 | } 24 | 25 | afterEvaluate { 26 | android { 27 | libraryVariants.forEach { 28 | sourceSets[it.name].java.srcDir(buildDir.resolve("generated/ksp/${it.name}/kotlin")) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /service/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SagerNet/ClashForAndroid/950507d68461427ca4b4bba71b47f74f08c7a998/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/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) 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 = "createdAt") val createdAt: Long, 18 | ) -------------------------------------------------------------------------------- /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 = "createdAt") val createdAt: Long = System.currentTimeMillis(), 18 | ) -------------------------------------------------------------------------------- /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 | 22 | val updatedAt: Long, 23 | val imported: Boolean, 24 | val pending: Boolean, 25 | ) : Parcelable { 26 | enum class Type { 27 | File, Url, External 28 | } 29 | 30 | override fun writeToParcel(parcel: Parcel, flags: Int) { 31 | Parcelizer.encodeToParcel(serializer(), parcel, this) 32 | } 33 | 34 | override fun describeContents(): Int { 35 | return 0 36 | } 37 | 38 | companion object CREATOR : Parcelable.Creator { 39 | override fun createFromParcel(parcel: Parcel): Profile { 40 | return Parcelizer.decodeFromParcel(serializer(), parcel) 41 | } 42 | 43 | override fun newArray(size: Int): Array { 44 | return arrayOfNulls(size) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /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/sideload/ExternalGeoip.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.sideload 2 | 3 | import android.content.Context 4 | import android.content.pm.PackageManager 5 | import com.github.kr328.clash.common.constants.Metadata 6 | import com.github.kr328.clash.common.log.Log 7 | import java.io.InputStream 8 | 9 | fun Context.readGeoipDatabaseFrom(packageName: String): ByteArray? { 10 | return try { 11 | val appInfo = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA) 12 | val path = appInfo.metaData.getString(Metadata.GEOIP_FILE_NAME) ?: return null 13 | 14 | createPackageContext(packageName, 0) 15 | .resources.assets.open(path).use(InputStream::readBytes) 16 | } catch (e: PackageManager.NameNotFoundException) { 17 | Log.w("Sideload geoip: $packageName not found", e) 18 | 19 | null 20 | } 21 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Address.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import java.net.Inet4Address 4 | import java.net.Inet6Address 5 | import java.net.InetAddress 6 | 7 | fun InetAddress.asSocketAddressText(port: Int): String { 8 | return when (this) { 9 | is Inet6Address -> 10 | "[${numericToTextFormat(this.address)}]:$port" 11 | is Inet4Address -> 12 | "${this.hostAddress}:$port" 13 | else -> throw IllegalArgumentException("Unsupported Inet type ${this.javaClass}") 14 | } 15 | } 16 | 17 | private const val INT16SZ = 2 18 | private const val INADDRSZ = 16 19 | private fun numericToTextFormat(src: ByteArray): String { 20 | val sb = StringBuilder(39) 21 | for (i in 0 until INADDRSZ / INT16SZ) { 22 | sb.append( 23 | Integer.toHexString( 24 | src[i shl 1].toInt() shl 8 and 0xff00 25 | or (src[(i shl 1) + 1].toInt() and 0xff) 26 | ) 27 | ) 28 | if (i < INADDRSZ / INT16SZ - 1) { 29 | sb.append(":") 30 | } 31 | } 32 | return sb.toString() 33 | } 34 | 35 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/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.resolvePrimaryDns(network: Network?): String? { 7 | val properties = getLinkProperties(network) ?: return null 8 | 9 | return properties.dnsServers.firstOrNull()?.asSocketAddressText(53) 10 | } 11 | -------------------------------------------------------------------------------- /service/src/main/java/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/drawable/ic_logo_service.xml: -------------------------------------------------------------------------------- 1 | 8 | 17 | 18 | -------------------------------------------------------------------------------- /service/src/main/res/values-zh-rHK/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clash 狀態 4 | 正在運行 5 | 更新 %s 成功 6 | "更新 %1$s: %2$s " 7 | Clash 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 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 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 for Android 16 | Profiles and Providers 17 | Configuration.yaml 18 | Provider Files 19 | Profile Updater 20 | Profile Updating 21 | 22 | -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | pushd core/src/foss/golang/clash 4 | git fetch 5 | git reset origin/android-open --hard 6 | cd .. 7 | go mod tidy 8 | popd 9 | git add . --------------------------------------------------------------------------------