From 7d9798cc271a1dc28e930feb9d1ab463cb777ce6 Mon Sep 17 00:00:00 2001 From: arm64v8a <48624112+arm64v8a@users.noreply.github.com> Date: Wed, 15 Mar 2023 00:00:00 +0000 Subject: [PATCH] upload code --- .github/ISSUE_TEMPLATE/bug-report-zh_cn.md | 22 + .../ISSUE_TEMPLATE/feature_request-zh_cn.md | 12 + .github/workflows/release.yml | 171 + .gitignore | 20 + AUTHORS | 8 + LICENSE | 14 + README.md | 60 + app/.gitignore | 2 + app/build.gradle.kts | 81 + app/proguard-rules.pro | 47 + .../1.json | 330 ++ .../1.json | 46 + .../moe.matsuri.nb4a.TempDatabase/1.json | 46 + app/src/main/AndroidManifest.xml | 316 ++ .../sagernet/aidl/ISagerNetService.aidl | 13 + .../aidl/ISagerNetServiceCallback.aidl | 14 + .../sagernet/aidl/SpeedDisplayData.aidl | 3 + .../sagernet/aidl/TrafficData.aidl | 3 + app/src/main/assets/LICENSE | 19 + app/src/main/assets/analysis.txt | 5 + app/src/main/assets/yacd.version.txt | 1 + app/src/main/assets/yacd.zip | Bin 0 -> 742351 bytes app/src/main/ic_launcher-playstore.png | Bin 0 -> 165837 bytes .../com/github/shadowsocks/plugin/Utils.kt | 9 + .../plugin/fragment/AlertDialogFragment.kt | 60 + .../java/com/wireguard/crypto/Curve25519.java | 497 +++ .../java/com/wireguard/crypto/Ed25519.java | 2508 +++++++++++ .../main/java/com/wireguard/crypto/Key.java | 288 ++ .../wireguard/crypto/KeyFormatException.java | 34 + .../java/com/wireguard/crypto/KeyPair.java | 51 + .../java/io/nekohasekai/sagernet/Constants.kt | 182 + .../sagernet/QuickToggleShortcut.kt | 88 + .../java/io/nekohasekai/sagernet/SagerNet.kt | 283 ++ .../sagernet/aidl/SpeedDisplayData.kt | 18 + .../nekohasekai/sagernet/aidl/TrafficData.kt | 11 + .../sagernet/bg/AbstractInstance.kt | 9 + .../io/nekohasekai/sagernet/bg/BaseService.kt | 345 ++ .../io/nekohasekai/sagernet/bg/Executable.kt | 41 + .../sagernet/bg/GuardedProcessPool.kt | 121 + .../nekohasekai/sagernet/bg/ProxyService.kt | 27 + .../sagernet/bg/SagerConnection.kt | 167 + .../sagernet/bg/ServiceNotification.kt | 202 + .../sagernet/bg/SubscriptionUpdater.kt | 97 + .../io/nekohasekai/sagernet/bg/TileService.kt | 104 + .../io/nekohasekai/sagernet/bg/VpnService.kt | 269 ++ .../sagernet/bg/proto/BoxInstance.kt | 277 ++ .../sagernet/bg/proto/ProxyInstance.kt | 44 + .../sagernet/bg/proto/TestInstance.kt | 48 + .../sagernet/bg/proto/TrafficLooper.kt | 130 + .../sagernet/bg/proto/TrafficUpdater.kt | 69 + .../nekohasekai/sagernet/bg/proto/UrlTest.kt | 15 + .../sagernet/database/DataStore.kt | 250 ++ .../sagernet/database/GroupManager.kt | 114 + .../sagernet/database/ParcelizeBridge.java | 13 + .../sagernet/database/ProfileManager.kt | 258 ++ .../sagernet/database/ProxyEntity.kt | 489 +++ .../sagernet/database/ProxyGroup.kt | 140 + .../sagernet/database/RuleEntity.kt | 106 + .../sagernet/database/SagerDatabase.kt | 48 + .../sagernet/database/SubscriptionBean.java | 138 + .../preference/EditTextPreferenceModifiers.kt | 43 + .../database/preference/KeyValuePair.kt | 170 + .../OnPreferenceDataStoreChangeListener.kt | 7 + .../database/preference/PublicDatabase.kt | 31 + .../preference/RoomPreferenceDataStore.kt | 90 + .../sagernet/fmt/AbstractBean.java | 151 + .../nekohasekai/sagernet/fmt/ConfigBuilder.kt | 710 +++ .../sagernet/fmt/KryoConverters.java | 149 + .../nekohasekai/sagernet/fmt/PluginEntry.kt | 58 + .../nekohasekai/sagernet/fmt/Serializable.kt | 27 + .../io/nekohasekai/sagernet/fmt/TypeMap.kt | 30 + .../nekohasekai/sagernet/fmt/UniversalFmt.kt | 36 + .../sagernet/fmt/gson/GsonConverters.java | 35 + .../sagernet/fmt/http/HttpBean.java | 59 + .../nekohasekai/sagernet/fmt/http/HttpFmt.kt | 46 + .../sagernet/fmt/hysteria/HysteriaBean.java | 155 + .../sagernet/fmt/hysteria/HysteriaFmt.kt | 237 + .../sagernet/fmt/internal/ChainBean.java | 80 + .../sagernet/fmt/internal/InternalBean.java | 26 + .../sagernet/fmt/naive/NaiveBean.java | 88 + .../sagernet/fmt/naive/NaiveFmt.kt | 90 + .../fmt/shadowsocks/ShadowsocksBean.java | 71 + .../fmt/shadowsocks/ShadowsocksFmt.kt | 115 + .../sagernet/fmt/socks/SOCKSBean.java | 110 + .../sagernet/fmt/socks/SOCKSFmt.kt | 81 + .../nekohasekai/sagernet/fmt/ssh/SSHBean.java | 98 + .../io/nekohasekai/sagernet/fmt/ssh/SSHFmt.kt | 22 + .../sagernet/fmt/trojan/TrojanBean.java | 69 + .../sagernet/fmt/trojan/TrojanFmt.kt | 23 + .../sagernet/fmt/trojan_go/TrojanGoBean.java | 176 + .../sagernet/fmt/trojan_go/TrojanGoFmt.kt | 162 + .../sagernet/fmt/tuic/TuicBean.java | 97 + .../nekohasekai/sagernet/fmt/tuic/TuicFmt.kt | 64 + .../sagernet/fmt/v2ray/StandardV2RayBean.java | 194 + .../sagernet/fmt/v2ray/V2RayFmt.kt | 608 +++ .../sagernet/fmt/v2ray/VMessBean.java | 40 + .../sagernet/fmt/wireguard/WireGuardBean.java | 75 + .../sagernet/fmt/wireguard/WireGuardFmt.kt | 17 + .../sagernet/group/GroupInterfaceAdapter.kt | 102 + .../sagernet/group/GroupUpdater.kt | 171 + .../nekohasekai/sagernet/group/RawUpdater.kt | 495 +++ .../io/nekohasekai/sagernet/ktx/Asyncs.kt | 33 + .../io/nekohasekai/sagernet/ktx/Browsers.kt | 29 + .../io/nekohasekai/sagernet/ktx/Dialogs.kt | 16 + .../io/nekohasekai/sagernet/ktx/Dimens.kt | 14 + .../io/nekohasekai/sagernet/ktx/Formats.kt | 232 + .../java/io/nekohasekai/sagernet/ktx/Kryos.kt | 61 + .../io/nekohasekai/sagernet/ktx/Layouts.kt | 75 + .../java/io/nekohasekai/sagernet/ktx/Logs.kt | 64 + .../java/io/nekohasekai/sagernet/ktx/Nets.kt | 65 + .../nekohasekai/sagernet/ktx/Preferences.kt | 62 + .../java/io/nekohasekai/sagernet/ktx/Utils.kt | 315 ++ .../sagernet/plugin/PluginManager.kt | 70 + .../nekohasekai/sagernet/ui/AboutFragment.kt | 183 + .../sagernet/ui/AppListActivity.kt | 381 ++ .../sagernet/ui/AppManagerActivity.kt | 477 ++ .../nekohasekai/sagernet/ui/AssetsActivity.kt | 342 ++ .../nekohasekai/sagernet/ui/BackupFragment.kt | 313 ++ .../nekohasekai/sagernet/ui/BlankActivity.kt | 20 + .../sagernet/ui/ConfigurationFragment.kt | 1574 +++++++ .../nekohasekai/sagernet/ui/DebugFragment.kt | 28 + .../nekohasekai/sagernet/ui/GroupFragment.kt | 529 +++ .../sagernet/ui/GroupSettingsActivity.kt | 315 ++ .../nekohasekai/sagernet/ui/LogcatFragment.kt | 127 + .../nekohasekai/sagernet/ui/MainActivity.kt | 490 +++ .../nekohasekai/sagernet/ui/NamedFragment.kt | 14 + .../sagernet/ui/NetworkFragment.kt | 84 + .../sagernet/ui/ProfileSelectActivity.kt | 36 + .../nekohasekai/sagernet/ui/RouteFragment.kt | 310 ++ .../sagernet/ui/RouteSettingsActivity.kt | 371 ++ .../sagernet/ui/ScannerActivity.kt | 238 + .../sagernet/ui/SettingsFragment.kt | 22 + .../sagernet/ui/SettingsPreferenceFragment.kt | 254 ++ .../nekohasekai/sagernet/ui/StunActivity.kt | 65 + .../nekohasekai/sagernet/ui/SwitchActivity.kt | 33 + .../nekohasekai/sagernet/ui/ThemedActivity.kt | 56 + .../sagernet/ui/ToolbarFragment.kt | 29 + .../nekohasekai/sagernet/ui/ToolsFragment.kt | 42 + .../sagernet/ui/VpnRequestActivity.kt | 72 + .../sagernet/ui/WebviewFragment.kt | 77 + .../ui/profile/ChainSettingsActivity.kt | 295 ++ .../ui/profile/HttpSettingsActivity.kt | 9 + .../ui/profile/HysteriaSettingsActivity.kt | 98 + .../ui/profile/NaiveSettingsActivity.kt | 67 + .../ui/profile/ProfileSettingsActivity.kt | 374 ++ .../ui/profile/SSHSettingsActivity.kt | 77 + .../ui/profile/ShadowsocksSettingsActivity.kt | 62 + .../ui/profile/SocksSettingsActivity.kt | 60 + .../profile/StandardV2RaySettingsActivity.kt | 166 + .../ui/profile/TrojanGoSettingsActivity.kt | 130 + .../ui/profile/TrojanSettingsActivity.kt | 9 + .../ui/profile/TuicSettingsActivity.kt | 72 + .../ui/profile/VMessSettingsActivity.kt | 9 + .../ui/profile/WireGuardSettingsActivity.kt | 49 + .../nekohasekai/sagernet/utils/Cloudflare.kt | 72 + .../nekohasekai/sagernet/utils/Commandline.kt | 140 + .../sagernet/utils/CrashHandler.kt | 172 + .../sagernet/utils/DefaultNetworkListener.kt | 154 + .../sagernet/utils/DeviceStorageApp.kt | 20 + .../sagernet/utils/PackageCache.kt | 97 + .../io/nekohasekai/sagernet/utils/Subnet.kt | 86 + .../io/nekohasekai/sagernet/utils/Theme.kt | 135 + .../sagernet/utils/cf/DeviceResponse.kt | 114 + .../sagernet/utils/cf/RegisterRequest.kt | 33 + .../sagernet/utils/cf/UpdateDeviceRequest.kt | 12 + .../sagernet/widget/AppListPreference.kt | 41 + .../sagernet/widget/AutoCollapseTextView.kt | 39 + .../sagernet/widget/FabProgressBehavior.kt | 29 + .../sagernet/widget/GroupPreference.kt | 35 + .../widget/LinkOrContentPreference.kt | 64 + .../sagernet/widget/LinkPreference.kt | 93 + .../sagernet/widget/OutboundPreference.kt | 44 + .../sagernet/widget/QRCodeDialog.kt | 103 + .../sagernet/widget/ServiceButton.kt | 150 + .../nekohasekai/sagernet/widget/StatsBar.kt | 161 + .../sagernet/widget/UndoSnackbarManager.kt | 55 + .../sagernet/widget/UserAgentPreference.kt | 31 + .../sagernet/widget/WindowInsetsListeners.kt | 40 + app/src/main/java/moe/matsuri/nb4a/DNS.kt | 74 + .../main/java/moe/matsuri/nb4a/Protocols.kt | 83 + .../java/moe/matsuri/nb4a/SingBoxOptions.java | 3873 +++++++++++++++++ .../java/moe/matsuri/nb4a/TempDatabase.kt | 30 + .../moe/matsuri/nb4a/net/LocalResolverImpl.kt | 80 + .../matsuri/nb4a/plugin/NekoPluginManager.kt | 153 + .../java/moe/matsuri/nb4a/plugin/Plugins.kt | 115 + .../matsuri/nb4a/proxy/PreferenceBinding.kt | 100 + .../nb4a/proxy/PreferenceBindingManager.kt | 33 + .../matsuri/nb4a/proxy/config/ConfigBean.java | 73 + .../proxy/config/ConfigSettingActivity.kt | 67 + .../moe/matsuri/nb4a/proxy/neko/NekoBean.java | 110 + .../moe/matsuri/nb4a/proxy/neko/NekoFmt.kt | 123 + .../nb4a/proxy/neko/NekoJSInterface.kt | 388 ++ .../nb4a/proxy/neko/NekoPreferenceInflater.kt | 92 + .../nb4a/proxy/neko/NekoSettingActivity.kt | 102 + .../matsuri/nb4a/ui/ColorPickerPreference.kt | 120 + .../main/java/moe/matsuri/nb4a/ui/Dialogs.kt | 38 + .../nb4a/ui/LongClickSwitchPreference.kt | 33 + .../java/moe/matsuri/nb4a/ui/MTUPreference.kt | 51 + .../java/moe/matsuri/nb4a/utils/JavaUtil.java | 200 + .../java/moe/matsuri/nb4a/utils/KotlinUtil.kt | 53 + .../java/moe/matsuri/nb4a/utils/NGUtil.kt | 237 + .../java/moe/matsuri/nb4a/utils/SendLog.kt | 73 + .../main/java/moe/matsuri/nb4a/utils/Util.kt | 137 + .../moe/matsuri/nb4a/utils/WebViewUtil.kt | 38 + app/src/main/res/color/chip_background.xml | 11 + app/src/main/res/color/chip_ripple_color.xml | 11 + app/src/main/res/color/chip_text_color.xml | 12 + app/src/main/res/color/navigation_icon.xml | 5 + app/src/main/res/color/navigation_item.xml | 5 + .../drawable-v26/ic_qu_camera_launcher.xml | 10 + .../ic_qu_shadowsocks_launcher.xml | 10 + .../res/drawable/baseline_arrow_back_24.xml | 11 + .../res/drawable/baseline_construction_24.xml | 13 + .../res/drawable/baseline_delete_sweep_24.xml | 10 + .../drawable/baseline_developer_board_24.xml | 10 + .../drawable/baseline_flight_takeoff_24.xml | 10 + .../main/res/drawable/baseline_public_24.xml | 10 + .../main/res/drawable/baseline_save_24.xml | 10 + .../main/res/drawable/baseline_send_24.xml | 11 + .../res/drawable/baseline_translate_24.xml | 10 + .../main/res/drawable/baseline_widgets_24.xml | 10 + .../res/drawable/baseline_wrap_text_24.xml | 11 + .../main/res/drawable/ic_action_copyright.xml | 11 + .../main/res/drawable/ic_action_delete.xml | 10 + .../res/drawable/ic_action_description.xml | 10 + app/src/main/res/drawable/ic_action_dns.xml | 10 + app/src/main/res/drawable/ic_action_done.xml | 10 + app/src/main/res/drawable/ic_action_lock.xml | 10 + .../main/res/drawable/ic_action_lock_open.xml | 6 + .../main/res/drawable/ic_action_note_add.xml | 11 + .../main/res/drawable/ic_action_settings.xml | 10 + .../drawable/ic_app_shortcut_background.xml | 21 + .../main/res/drawable/ic_av_playlist_add.xml | 11 + .../res/drawable/ic_baseline_add_road_24.xml | 25 + .../ic_baseline_airplanemode_active_24.xml | 10 + .../res/drawable/ic_baseline_android_24.xml | 10 + .../drawable/ic_baseline_bug_report_24.xml | 10 + .../res/drawable/ic_baseline_camera_24.xml | 10 + .../drawable/ic_baseline_card_giftcard_24.xml | 10 + .../ic_baseline_cast_connected_24.xml | 10 + .../ic_baseline_center_focus_weak_24.xml | 10 + .../drawable/ic_baseline_color_lens_24.xml | 10 + .../ic_baseline_compare_arrows_24.xml | 11 + .../res/drawable/ic_baseline_domain_24.xml | 10 + .../res/drawable/ic_baseline_download_24.xml | 10 + .../ic_baseline_emoji_emotions_24.xml | 10 + .../drawable/ic_baseline_fast_forward_24.xml | 10 + .../ic_baseline_fiber_manual_record_24.xml | 10 + .../drawable/ic_baseline_fingerprint_24.xml | 10 + .../ic_baseline_flip_camera_android_24.xml | 16 + .../ic_baseline_format_align_left_24.xml | 11 + .../res/drawable/ic_baseline_grid_3x3_24.xml | 10 + .../main/res/drawable/ic_baseline_home_24.xml | 10 + .../main/res/drawable/ic_baseline_http_24.xml | 10 + .../res/drawable/ic_baseline_https_24.xml | 10 + .../ic_baseline_import_contacts_24.xml | 10 + .../main/res/drawable/ic_baseline_info_24.xml | 10 + .../res/drawable/ic_baseline_layers_24.xml | 10 + .../drawable/ic_baseline_legend_toggle_24.xml | 10 + .../main/res/drawable/ic_baseline_link_24.xml | 10 + .../res/drawable/ic_baseline_local_bar_24.xml | 10 + .../drawable/ic_baseline_location_on_24.xml | 10 + .../main/res/drawable/ic_baseline_lock_24.xml | 10 + .../drawable/ic_baseline_low_priority_24.xml | 10 + .../drawable/ic_baseline_manage_search_24.xml | 11 + .../res/drawable/ic_baseline_more_vert_24.xml | 10 + .../ic_baseline_multiline_chart_24.xml | 11 + .../drawable/ic_baseline_multiple_stop_24.xml | 10 + .../main/res/drawable/ic_baseline_nat_24.xml | 13 + .../main/res/drawable/ic_baseline_nfc_24.xml | 10 + ...aseline_no_encryption_gmailerrorred_24.xml | 10 + .../res/drawable/ic_baseline_person_24.xml | 10 + .../res/drawable/ic_baseline_push_pin_24.xml | 10 + .../res/drawable/ic_baseline_refresh_24.xml | 10 + .../drawable/ic_baseline_rule_folder_24.xml | 10 + .../ic_baseline_running_with_errors_24.xml | 10 + .../res/drawable/ic_baseline_sanitizer_24.xml | 10 + .../res/drawable/ic_baseline_security_24.xml | 10 + .../res/drawable/ic_baseline_shuffle_24.xml | 10 + .../drawable/ic_baseline_shutter_speed_24.xml | 10 + .../res/drawable/ic_baseline_speed_24.xml | 10 + .../res/drawable/ic_baseline_stream_24.xml | 22 + .../res/drawable/ic_baseline_texture_24.xml | 10 + .../res/drawable/ic_baseline_timelapse_24.xml | 10 + .../res/drawable/ic_baseline_transform_24.xml | 10 + .../drawable/ic_baseline_transgender_24.xml | 10 + .../res/drawable/ic_baseline_update_24.xml | 10 + .../res/drawable/ic_baseline_view_list_24.xml | 11 + .../res/drawable/ic_baseline_vpn_key_24.xml | 10 + .../res/drawable/ic_baseline_warning_24.xml | 10 + .../res/drawable/ic_baseline_wb_sunny_24.xml | 10 + .../ic_communication_phonelink_ring.xml | 6 + .../res/drawable/ic_device_data_usage.xml | 6 + .../res/drawable/ic_device_developer_mode.xml | 10 + .../main/res/drawable/ic_file_cloud_queue.xml | 10 + .../main/res/drawable/ic_file_file_upload.xml | 10 + .../main/res/drawable/ic_hardware_router.xml | 6 + .../main/res/drawable/ic_image_camera_alt.xml | 12 + app/src/main/res/drawable/ic_image_edit.xml | 10 + .../main/res/drawable/ic_image_looks_6.xml | 10 + app/src/main/res/drawable/ic_image_photo.xml | 10 + app/src/main/res/drawable/ic_maps_360.xml | 11 + .../main/res/drawable/ic_maps_directions.xml | 6 + .../res/drawable/ic_maps_directions_boat.xml | 10 + .../main/res/drawable/ic_navigation_apps.xml | 10 + .../main/res/drawable/ic_navigation_close.xml | 10 + .../main/res/drawable/ic_navigation_menu.xml | 10 + .../ic_notification_enhanced_encryption.xml | 10 + .../res/drawable/ic_qu_camera_launcher.xml | 18 + .../drawable/ic_qu_shadowsocks_foreground.xml | 11 + .../drawable/ic_qu_shadowsocks_launcher.xml | 18 + .../main/res/drawable/ic_service_active.xml | 11 + app/src/main/res/drawable/ic_service_busy.xml | 11 + .../res/drawable/ic_service_connected.xml | 17 + .../res/drawable/ic_service_connecting.xml | 28 + app/src/main/res/drawable/ic_service_idle.xml | 18 + .../main/res/drawable/ic_service_stopped.xml | 28 + .../main/res/drawable/ic_service_stopping.xml | 17 + .../res/drawable/ic_settings_password.xml | 31 + .../res/drawable/ic_social_emoji_symbols.xml | 28 + app/src/main/res/drawable/ic_social_share.xml | 11 + .../res/drawable/terminal_scroll_shape.xml | 22 + app/src/main/res/font/jetbrains_mono.ttf | Bin 0 -> 136708 bytes app/src/main/res/layout/layout_about.xml | 102 + app/src/main/res/layout/layout_add_entity.xml | 24 + app/src/main/res/layout/layout_app_list.xml | 172 + app/src/main/res/layout/layout_appbar.xml | 18 + app/src/main/res/layout/layout_apps.xml | 139 + app/src/main/res/layout/layout_apps_item.xml | 83 + app/src/main/res/layout/layout_asset_item.xml | 92 + app/src/main/res/layout/layout_assets.xml | 32 + app/src/main/res/layout/layout_backup.xml | 67 + .../main/res/layout/layout_chain_settings.xml | 40 + .../res/layout/layout_config_settings.xml | 16 + app/src/main/res/layout/layout_debug.xml | 24 + app/src/main/res/layout/layout_edit_group.xml | 89 + app/src/main/res/layout/layout_empty.xml | 6 + .../main/res/layout/layout_empty_route.xml | 24 + app/src/main/res/layout/layout_group.xml | 28 + app/src/main/res/layout/layout_group_item.xml | 132 + app/src/main/res/layout/layout_group_list.xml | 36 + .../res/layout/layout_icon_list_item_2.xml | 35 + app/src/main/res/layout/layout_import.xml | 42 + app/src/main/res/layout/layout_license.xml | 19 + .../main/res/layout/layout_link_dialog.xml | 46 + app/src/main/res/layout/layout_loading.xml | 29 + app/src/main/res/layout/layout_logcat.xml | 38 + app/src/main/res/layout/layout_main.xml | 131 + app/src/main/res/layout/layout_network.xml | 120 + .../res/layout/layout_password_dialog.xml | 48 + app/src/main/res/layout/layout_profile.xml | 179 + .../main/res/layout/layout_profile_list.xml | 18 + app/src/main/res/layout/layout_progress.xml | 22 + .../main/res/layout/layout_progress_list.xml | 28 + app/src/main/res/layout/layout_route.xml | 28 + app/src/main/res/layout/layout_route_item.xml | 139 + app/src/main/res/layout/layout_scanner.xml | 25 + .../res/layout/layout_settings_activity.xml | 21 + app/src/main/res/layout/layout_stun.xml | 132 + app/src/main/res/layout/layout_tools.xml | 35 + app/src/main/res/layout/layout_webview.xml | 16 + app/src/main/res/menu/add_group_menu.xml | 14 + app/src/main/res/menu/add_profile_menu.xml | 128 + app/src/main/res/menu/add_route_menu.xml | 18 + app/src/main/res/menu/app_list_menu.xml | 16 + app/src/main/res/menu/app_list_neko_menu.xml | 6 + app/src/main/res/menu/group_action_menu.xml | 30 + app/src/main/res/menu/import_asset_menu.xml | 10 + app/src/main/res/menu/logcat_menu.xml | 19 + app/src/main/res/menu/main_drawer_menu.xml | 60 + app/src/main/res/menu/per_app_proxy_menu.xml | 25 + app/src/main/res/menu/profile_apply_menu.xml | 9 + app/src/main/res/menu/profile_config_menu.xml | 32 + app/src/main/res/menu/profile_share_menu.xml | 46 + app/src/main/res/menu/scanner_menu.xml | 9 + app/src/main/res/menu/traffic_item_menu.xml | 31 + app/src/main/res/menu/traffic_menu.xml | 8 + app/src/main/res/menu/yacd_menu.xml | 8 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4551 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 12303 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2464 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 5968 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 7392 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 19888 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 14446 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 39470 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 23397 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 62991 bytes app/src/main/res/raw-zh-rCN/insecure.txt | 1 + app/src/main/res/raw-zh-rCN/not_encrypted.txt | 1 + .../raw-zh-rCN/shadowsocks_stream_cipher.txt | 3 + .../main/res/raw-zh-rCN/vmess_md5_auth.txt | 3 + app/src/main/res/raw/insecure.txt | 1 + app/src/main/res/raw/not_encrypted.txt | 1 + .../res/raw/shadowsocks_stream_cipher.txt | 3 + app/src/main/res/raw/vmess_md5_auth.txt | 3 + app/src/main/res/values-ar/strings.xml | 400 ++ app/src/main/res/values-be/strings.xml | 110 + app/src/main/res/values-de/strings.xml | 38 + app/src/main/res/values-es/strings.xml | 494 +++ app/src/main/res/values-fa/strings.xml | 471 ++ app/src/main/res/values-fr/strings.xml | 311 ++ app/src/main/res/values-in/strings.xml | 210 + app/src/main/res/values-it/strings.xml | 11 + app/src/main/res/values-ja/strings.xml | 103 + app/src/main/res/values-ko/strings.xml | 19 + app/src/main/res/values-nb-rNO/strings.xml | 396 ++ app/src/main/res/values-night/colors.xml | 4 + app/src/main/res/values-nl/strings.xml | 11 + app/src/main/res/values-pt-rBR/strings.xml | 21 + app/src/main/res/values-ru/strings.xml | 442 ++ app/src/main/res/values-tr/strings.xml | 419 ++ app/src/main/res/values-uk/strings.xml | 135 + app/src/main/res/values-zh-rCN/strings.xml | 479 ++ app/src/main/res/values-zh-rHK/strings.xml | 96 + app/src/main/res/values-zh-rTW/strings.xml | 447 ++ app/src/main/res/values/arrays.xml | 481 ++ app/src/main/res/values/attrs.xml | 13 + app/src/main/res/values/colors.xml | 302 ++ app/src/main/res/values/dimens.xml | 5 + .../res/values/ic_launcher_background.xml | 4 + app/src/main/res/values/strings.xml | 529 +++ app/src/main/res/values/themes.xml | 485 +++ app/src/main/res/xml/backup_descriptor.xml | 8 + app/src/main/res/xml/backup_rules.xml | 20 + app/src/main/res/xml/balancer_preferences.xml | 32 + app/src/main/res/xml/cache_paths.xml | 4 + app/src/main/res/xml/config_preferences.xml | 21 + app/src/main/res/xml/global_preferences.xml | 246 ++ app/src/main/res/xml/group_preferences.xml | 75 + app/src/main/res/xml/hysteria_preferences.xml | 97 + app/src/main/res/xml/naive_preferences.xml | 65 + app/src/main/res/xml/name_preferences.xml | 9 + app/src/main/res/xml/neko_preferences.xml | 10 + .../main/res/xml/network_security_config.xml | 4 + app/src/main/res/xml/route_preferences.xml | 59 + .../main/res/xml/shadowsocks_preferences.xml | 56 + app/src/main/res/xml/shortcuts.xml | 23 + app/src/main/res/xml/socks_preferences.xml | 41 + app/src/main/res/xml/ssh_preferences.xml | 61 + .../res/xml/standard_v2ray_preferences.xml | 124 + .../main/res/xml/trojan_go_preferences.xml | 87 + app/src/main/res/xml/tuic_preferences.xml | 82 + .../main/res/xml/wireguard_preferences.xml | 53 + build.gradle.kts | 8 + buildScript/copyLocal.sh | 3 + buildScript/fdroid/prebuild.sh | 8 + buildScript/init/action/go.sh | 15 + buildScript/init/action/gradle.sh | 18 + buildScript/init/env.sh | 37 + buildScript/init/env_ndk.sh | 24 + buildScript/lib/assets.sh | 28 + buildScript/lib/core.sh | 4 + buildScript/lib/core/build.sh | 8 + buildScript/lib/core/init.sh | 8 + buildScript/nkmr | 1 + buildScript/zipVersion/downloadZip.sh | 24 + buildSrc/build.gradle.kts | 12 + buildSrc/src/main/kotlin/Helpers.kt | 248 ++ .../android/en-US/full_description.txt | 1 + .../android/en-US/short_description.txt | 1 + .../android/zh-CN/full_description.txt | 1 + .../android/zh-CN/short_description.txt | 1 + gradle.properties | 23 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 234 + gradlew.bat | 89 + libcore/.gitignore | 7 + libcore/LICENSE | 14 + libcore/assets.go | 15 + libcore/assets_android.go | 181 + libcore/assets_other.go | 5 + libcore/box.go | 205 + libcore/build.sh | 20 + libcore/crypto.go | 17 + libcore/date.go | 19 + libcore/device/debug.go | 21 + libcore/device/device.go | 13 + libcore/dns.go | 111 + libcore/go.mod | 93 + libcore/go.sum | 266 ++ libcore/http.go | 239 + libcore/init.sh | 20 + libcore/io.go | 40 + libcore/nb4a.go | 87 + libcore/platform_box.go | 92 + libcore/platform_java.go | 29 + libcore/procfs/procfs.go | 148 + libcore/stun.go | 55 + libcore/stun/README | 1 + libcore/stun/attribute.go | 104 + libcore/stun/client.go | 145 + libcore/stun/const.go | 222 + libcore/stun/discover.go | 253 ++ libcore/stun/doc.go | 23 + libcore/stun/host.go | 68 + libcore/stun/log.go | 85 + libcore/stun/net.go | 104 + libcore/stun/packet.go | 133 + libcore/stun/response.go | 76 + libcore/stun/tests.go | 67 + libcore/stun/utils.go | 61 + libcore/update-box.sh | 2 + libcore/update-extra.sh | 2 + libcore/update-libneko.sh | 2 + lint.xml | 37 + release.keystore | Bin 0 -> 2709 bytes repositories.gradle.kts | 6 + run | 21 + sager.properties | 3 + settings.gradle.kts | 5 + 513 files changed, 46925 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-report-zh_cn.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request-zh_cn.md create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app/.gitignore create mode 100644 app/build.gradle.kts create mode 100644 app/proguard-rules.pro create mode 100644 app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/1.json create mode 100644 app/schemas/io.nekohasekai.sagernet.database.preference.PublicDatabase/1.json create mode 100644 app/schemas/moe.matsuri.nb4a.TempDatabase/1.json create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/aidl/io/nekohasekai/sagernet/aidl/ISagerNetService.aidl create mode 100644 app/src/main/aidl/io/nekohasekai/sagernet/aidl/ISagerNetServiceCallback.aidl create mode 100644 app/src/main/aidl/io/nekohasekai/sagernet/aidl/SpeedDisplayData.aidl create mode 100644 app/src/main/aidl/io/nekohasekai/sagernet/aidl/TrafficData.aidl create mode 100644 app/src/main/assets/LICENSE create mode 100644 app/src/main/assets/analysis.txt create mode 100644 app/src/main/assets/yacd.version.txt create mode 100644 app/src/main/assets/yacd.zip create mode 100644 app/src/main/ic_launcher-playstore.png create mode 100644 app/src/main/java/com/github/shadowsocks/plugin/Utils.kt create mode 100644 app/src/main/java/com/github/shadowsocks/plugin/fragment/AlertDialogFragment.kt create mode 100644 app/src/main/java/com/wireguard/crypto/Curve25519.java create mode 100644 app/src/main/java/com/wireguard/crypto/Ed25519.java create mode 100644 app/src/main/java/com/wireguard/crypto/Key.java create mode 100644 app/src/main/java/com/wireguard/crypto/KeyFormatException.java create mode 100644 app/src/main/java/com/wireguard/crypto/KeyPair.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/Constants.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/QuickToggleShortcut.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/aidl/SpeedDisplayData.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/aidl/TrafficData.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/AbstractInstance.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/Executable.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/GuardedProcessPool.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/ProxyService.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/SagerConnection.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/ServiceNotification.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/SubscriptionUpdater.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/TileService.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/proto/ProxyInstance.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/proto/TestInstance.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficLooper.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficUpdater.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/bg/proto/UrlTest.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/database/GroupManager.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/database/ParcelizeBridge.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/database/ProfileManager.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/database/ProxyGroup.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/database/RuleEntity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/database/SubscriptionBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/database/preference/EditTextPreferenceModifiers.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/database/preference/KeyValuePair.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/database/preference/OnPreferenceDataStoreChangeListener.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/database/preference/PublicDatabase.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/database/preference/RoomPreferenceDataStore.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/AbstractBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/PluginEntry.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/Serializable.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/UniversalFmt.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/gson/GsonConverters.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/http/HttpBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/http/HttpFmt.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaFmt.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/internal/ChainBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/internal/InternalBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/naive/NaiveBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/naive/NaiveFmt.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksFmt.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSFmt.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/ssh/SSHBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/ssh/SSHFmt.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/trojan/TrojanBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/trojan/TrojanFmt.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoFmt.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicFmt.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardBean.java create mode 100644 app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardFmt.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/group/GroupInterfaceAdapter.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/group/GroupUpdater.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ktx/Asyncs.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ktx/Browsers.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ktx/Dialogs.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ktx/Dimens.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ktx/Kryos.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ktx/Layouts.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ktx/Logs.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ktx/Nets.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ktx/Preferences.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/plugin/PluginManager.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/AppManagerActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/BlankActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/DebugFragment.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/GroupFragment.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/LogcatFragment.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/NamedFragment.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/NetworkFragment.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/ProfileSelectActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/RouteFragment.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/ScannerActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/SettingsFragment.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/StunActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/SwitchActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/ThemedActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/ToolbarFragment.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/ToolsFragment.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/VpnRequestActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/WebviewFragment.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/profile/ChainSettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/profile/HttpSettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/profile/HysteriaSettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/profile/NaiveSettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/profile/SSHSettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/profile/ShadowsocksSettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/profile/SocksSettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/profile/StandardV2RaySettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/profile/TrojanGoSettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/profile/TrojanSettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/profile/TuicSettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/profile/VMessSettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/ui/profile/WireGuardSettingsActivity.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/utils/Cloudflare.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/utils/Commandline.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/utils/CrashHandler.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/utils/DefaultNetworkListener.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/utils/DeviceStorageApp.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/utils/PackageCache.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/utils/Subnet.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/utils/Theme.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/utils/cf/DeviceResponse.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/utils/cf/RegisterRequest.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/utils/cf/UpdateDeviceRequest.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/widget/AppListPreference.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/widget/AutoCollapseTextView.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/widget/FabProgressBehavior.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/widget/GroupPreference.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/widget/LinkOrContentPreference.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/widget/LinkPreference.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/widget/OutboundPreference.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/widget/QRCodeDialog.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/widget/UndoSnackbarManager.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/widget/UserAgentPreference.kt create mode 100644 app/src/main/java/io/nekohasekai/sagernet/widget/WindowInsetsListeners.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/DNS.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/Protocols.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java create mode 100644 app/src/main/java/moe/matsuri/nb4a/TempDatabase.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/net/LocalResolverImpl.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/plugin/NekoPluginManager.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/plugin/Plugins.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/proxy/PreferenceBinding.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/proxy/PreferenceBindingManager.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/proxy/config/ConfigBean.java create mode 100644 app/src/main/java/moe/matsuri/nb4a/proxy/config/ConfigSettingActivity.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoBean.java create mode 100644 app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoFmt.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoJSInterface.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoPreferenceInflater.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoSettingActivity.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/ui/ColorPickerPreference.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/ui/Dialogs.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/ui/LongClickSwitchPreference.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/ui/MTUPreference.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/utils/JavaUtil.java create mode 100644 app/src/main/java/moe/matsuri/nb4a/utils/KotlinUtil.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/utils/NGUtil.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/utils/SendLog.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/utils/Util.kt create mode 100644 app/src/main/java/moe/matsuri/nb4a/utils/WebViewUtil.kt create mode 100644 app/src/main/res/color/chip_background.xml create mode 100644 app/src/main/res/color/chip_ripple_color.xml create mode 100644 app/src/main/res/color/chip_text_color.xml create mode 100644 app/src/main/res/color/navigation_icon.xml create mode 100644 app/src/main/res/color/navigation_item.xml create mode 100644 app/src/main/res/drawable-v26/ic_qu_camera_launcher.xml create mode 100644 app/src/main/res/drawable-v26/ic_qu_shadowsocks_launcher.xml create mode 100644 app/src/main/res/drawable/baseline_arrow_back_24.xml create mode 100644 app/src/main/res/drawable/baseline_construction_24.xml create mode 100644 app/src/main/res/drawable/baseline_delete_sweep_24.xml create mode 100644 app/src/main/res/drawable/baseline_developer_board_24.xml create mode 100644 app/src/main/res/drawable/baseline_flight_takeoff_24.xml create mode 100644 app/src/main/res/drawable/baseline_public_24.xml create mode 100644 app/src/main/res/drawable/baseline_save_24.xml create mode 100644 app/src/main/res/drawable/baseline_send_24.xml create mode 100644 app/src/main/res/drawable/baseline_translate_24.xml create mode 100644 app/src/main/res/drawable/baseline_widgets_24.xml create mode 100644 app/src/main/res/drawable/baseline_wrap_text_24.xml create mode 100644 app/src/main/res/drawable/ic_action_copyright.xml create mode 100644 app/src/main/res/drawable/ic_action_delete.xml create mode 100644 app/src/main/res/drawable/ic_action_description.xml create mode 100644 app/src/main/res/drawable/ic_action_dns.xml create mode 100644 app/src/main/res/drawable/ic_action_done.xml create mode 100644 app/src/main/res/drawable/ic_action_lock.xml create mode 100644 app/src/main/res/drawable/ic_action_lock_open.xml create mode 100644 app/src/main/res/drawable/ic_action_note_add.xml create mode 100644 app/src/main/res/drawable/ic_action_settings.xml create mode 100644 app/src/main/res/drawable/ic_app_shortcut_background.xml create mode 100644 app/src/main/res/drawable/ic_av_playlist_add.xml create mode 100644 app/src/main/res/drawable/ic_baseline_add_road_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_airplanemode_active_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_android_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_bug_report_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_camera_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_card_giftcard_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_cast_connected_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_center_focus_weak_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_color_lens_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_compare_arrows_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_domain_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_download_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_emoji_emotions_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_fast_forward_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_fiber_manual_record_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_fingerprint_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_flip_camera_android_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_format_align_left_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_grid_3x3_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_home_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_http_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_https_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_import_contacts_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_info_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_layers_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_legend_toggle_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_link_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_local_bar_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_location_on_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_lock_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_low_priority_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_manage_search_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_more_vert_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_multiline_chart_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_multiple_stop_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_nat_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_nfc_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_no_encryption_gmailerrorred_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_person_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_push_pin_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_refresh_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_rule_folder_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_running_with_errors_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_sanitizer_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_security_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_shuffle_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_shutter_speed_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_speed_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_stream_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_texture_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_timelapse_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_transform_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_transgender_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_update_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_view_list_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_vpn_key_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_warning_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_wb_sunny_24.xml create mode 100644 app/src/main/res/drawable/ic_communication_phonelink_ring.xml create mode 100644 app/src/main/res/drawable/ic_device_data_usage.xml create mode 100644 app/src/main/res/drawable/ic_device_developer_mode.xml create mode 100644 app/src/main/res/drawable/ic_file_cloud_queue.xml create mode 100644 app/src/main/res/drawable/ic_file_file_upload.xml create mode 100644 app/src/main/res/drawable/ic_hardware_router.xml create mode 100644 app/src/main/res/drawable/ic_image_camera_alt.xml create mode 100644 app/src/main/res/drawable/ic_image_edit.xml create mode 100644 app/src/main/res/drawable/ic_image_looks_6.xml create mode 100644 app/src/main/res/drawable/ic_image_photo.xml create mode 100644 app/src/main/res/drawable/ic_maps_360.xml create mode 100644 app/src/main/res/drawable/ic_maps_directions.xml create mode 100644 app/src/main/res/drawable/ic_maps_directions_boat.xml create mode 100644 app/src/main/res/drawable/ic_navigation_apps.xml create mode 100644 app/src/main/res/drawable/ic_navigation_close.xml create mode 100644 app/src/main/res/drawable/ic_navigation_menu.xml create mode 100644 app/src/main/res/drawable/ic_notification_enhanced_encryption.xml create mode 100644 app/src/main/res/drawable/ic_qu_camera_launcher.xml create mode 100644 app/src/main/res/drawable/ic_qu_shadowsocks_foreground.xml create mode 100755 app/src/main/res/drawable/ic_qu_shadowsocks_launcher.xml create mode 100644 app/src/main/res/drawable/ic_service_active.xml create mode 100755 app/src/main/res/drawable/ic_service_busy.xml create mode 100644 app/src/main/res/drawable/ic_service_connected.xml create mode 100644 app/src/main/res/drawable/ic_service_connecting.xml create mode 100755 app/src/main/res/drawable/ic_service_idle.xml create mode 100644 app/src/main/res/drawable/ic_service_stopped.xml create mode 100644 app/src/main/res/drawable/ic_service_stopping.xml create mode 100644 app/src/main/res/drawable/ic_settings_password.xml create mode 100644 app/src/main/res/drawable/ic_social_emoji_symbols.xml create mode 100644 app/src/main/res/drawable/ic_social_share.xml create mode 100644 app/src/main/res/drawable/terminal_scroll_shape.xml create mode 100644 app/src/main/res/font/jetbrains_mono.ttf create mode 100644 app/src/main/res/layout/layout_about.xml create mode 100644 app/src/main/res/layout/layout_add_entity.xml create mode 100644 app/src/main/res/layout/layout_app_list.xml create mode 100644 app/src/main/res/layout/layout_appbar.xml create mode 100644 app/src/main/res/layout/layout_apps.xml create mode 100644 app/src/main/res/layout/layout_apps_item.xml create mode 100644 app/src/main/res/layout/layout_asset_item.xml create mode 100644 app/src/main/res/layout/layout_assets.xml create mode 100644 app/src/main/res/layout/layout_backup.xml create mode 100644 app/src/main/res/layout/layout_chain_settings.xml create mode 100644 app/src/main/res/layout/layout_config_settings.xml create mode 100644 app/src/main/res/layout/layout_debug.xml create mode 100644 app/src/main/res/layout/layout_edit_group.xml create mode 100644 app/src/main/res/layout/layout_empty.xml create mode 100644 app/src/main/res/layout/layout_empty_route.xml create mode 100644 app/src/main/res/layout/layout_group.xml create mode 100644 app/src/main/res/layout/layout_group_item.xml create mode 100644 app/src/main/res/layout/layout_group_list.xml create mode 100644 app/src/main/res/layout/layout_icon_list_item_2.xml create mode 100644 app/src/main/res/layout/layout_import.xml create mode 100644 app/src/main/res/layout/layout_license.xml create mode 100644 app/src/main/res/layout/layout_link_dialog.xml create mode 100644 app/src/main/res/layout/layout_loading.xml create mode 100644 app/src/main/res/layout/layout_logcat.xml create mode 100644 app/src/main/res/layout/layout_main.xml create mode 100644 app/src/main/res/layout/layout_network.xml create mode 100644 app/src/main/res/layout/layout_password_dialog.xml create mode 100644 app/src/main/res/layout/layout_profile.xml create mode 100644 app/src/main/res/layout/layout_profile_list.xml create mode 100644 app/src/main/res/layout/layout_progress.xml create mode 100644 app/src/main/res/layout/layout_progress_list.xml create mode 100644 app/src/main/res/layout/layout_route.xml create mode 100644 app/src/main/res/layout/layout_route_item.xml create mode 100644 app/src/main/res/layout/layout_scanner.xml create mode 100644 app/src/main/res/layout/layout_settings_activity.xml create mode 100644 app/src/main/res/layout/layout_stun.xml create mode 100644 app/src/main/res/layout/layout_tools.xml create mode 100644 app/src/main/res/layout/layout_webview.xml create mode 100644 app/src/main/res/menu/add_group_menu.xml create mode 100644 app/src/main/res/menu/add_profile_menu.xml create mode 100644 app/src/main/res/menu/add_route_menu.xml create mode 100644 app/src/main/res/menu/app_list_menu.xml create mode 100644 app/src/main/res/menu/app_list_neko_menu.xml create mode 100644 app/src/main/res/menu/group_action_menu.xml create mode 100644 app/src/main/res/menu/import_asset_menu.xml create mode 100644 app/src/main/res/menu/logcat_menu.xml create mode 100644 app/src/main/res/menu/main_drawer_menu.xml create mode 100644 app/src/main/res/menu/per_app_proxy_menu.xml create mode 100644 app/src/main/res/menu/profile_apply_menu.xml create mode 100644 app/src/main/res/menu/profile_config_menu.xml create mode 100644 app/src/main/res/menu/profile_share_menu.xml create mode 100644 app/src/main/res/menu/scanner_menu.xml create mode 100644 app/src/main/res/menu/traffic_item_menu.xml create mode 100644 app/src/main/res/menu/traffic_menu.xml create mode 100644 app/src/main/res/menu/yacd_menu.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/raw-zh-rCN/insecure.txt create mode 100644 app/src/main/res/raw-zh-rCN/not_encrypted.txt create mode 100644 app/src/main/res/raw-zh-rCN/shadowsocks_stream_cipher.txt create mode 100644 app/src/main/res/raw-zh-rCN/vmess_md5_auth.txt create mode 100644 app/src/main/res/raw/insecure.txt create mode 100644 app/src/main/res/raw/not_encrypted.txt create mode 100644 app/src/main/res/raw/shadowsocks_stream_cipher.txt create mode 100644 app/src/main/res/raw/vmess_md5_auth.txt create mode 100644 app/src/main/res/values-ar/strings.xml create mode 100644 app/src/main/res/values-be/strings.xml create mode 100644 app/src/main/res/values-de/strings.xml create mode 100644 app/src/main/res/values-es/strings.xml create mode 100644 app/src/main/res/values-fa/strings.xml create mode 100644 app/src/main/res/values-fr/strings.xml create mode 100644 app/src/main/res/values-in/strings.xml create mode 100644 app/src/main/res/values-it/strings.xml create mode 100644 app/src/main/res/values-ja/strings.xml create mode 100644 app/src/main/res/values-ko/strings.xml create mode 100644 app/src/main/res/values-nb-rNO/strings.xml create mode 100644 app/src/main/res/values-night/colors.xml create mode 100644 app/src/main/res/values-nl/strings.xml create mode 100644 app/src/main/res/values-pt-rBR/strings.xml create mode 100644 app/src/main/res/values-ru/strings.xml create mode 100644 app/src/main/res/values-tr/strings.xml create mode 100644 app/src/main/res/values-uk/strings.xml create mode 100644 app/src/main/res/values-zh-rCN/strings.xml create mode 100644 app/src/main/res/values-zh-rHK/strings.xml create mode 100644 app/src/main/res/values-zh-rTW/strings.xml create mode 100644 app/src/main/res/values/arrays.xml create mode 100644 app/src/main/res/values/attrs.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/ic_launcher_background.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/main/res/xml/backup_descriptor.xml create mode 100644 app/src/main/res/xml/backup_rules.xml create mode 100644 app/src/main/res/xml/balancer_preferences.xml create mode 100644 app/src/main/res/xml/cache_paths.xml create mode 100644 app/src/main/res/xml/config_preferences.xml create mode 100644 app/src/main/res/xml/global_preferences.xml create mode 100644 app/src/main/res/xml/group_preferences.xml create mode 100644 app/src/main/res/xml/hysteria_preferences.xml create mode 100644 app/src/main/res/xml/naive_preferences.xml create mode 100644 app/src/main/res/xml/name_preferences.xml create mode 100644 app/src/main/res/xml/neko_preferences.xml create mode 100644 app/src/main/res/xml/network_security_config.xml create mode 100644 app/src/main/res/xml/route_preferences.xml create mode 100644 app/src/main/res/xml/shadowsocks_preferences.xml create mode 100644 app/src/main/res/xml/shortcuts.xml create mode 100644 app/src/main/res/xml/socks_preferences.xml create mode 100644 app/src/main/res/xml/ssh_preferences.xml create mode 100644 app/src/main/res/xml/standard_v2ray_preferences.xml create mode 100644 app/src/main/res/xml/trojan_go_preferences.xml create mode 100644 app/src/main/res/xml/tuic_preferences.xml create mode 100644 app/src/main/res/xml/wireguard_preferences.xml create mode 100644 build.gradle.kts create mode 100755 buildScript/copyLocal.sh create mode 100755 buildScript/fdroid/prebuild.sh create mode 100755 buildScript/init/action/go.sh create mode 100755 buildScript/init/action/gradle.sh create mode 100755 buildScript/init/env.sh create mode 100755 buildScript/init/env_ndk.sh create mode 100755 buildScript/lib/assets.sh create mode 100755 buildScript/lib/core.sh create mode 100755 buildScript/lib/core/build.sh create mode 100755 buildScript/lib/core/init.sh create mode 120000 buildScript/nkmr create mode 100644 buildScript/zipVersion/downloadZip.sh create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/kotlin/Helpers.kt create mode 100644 fastlane/metadata/android/en-US/full_description.txt create mode 100644 fastlane/metadata/android/en-US/short_description.txt create mode 100644 fastlane/metadata/android/zh-CN/full_description.txt create mode 100644 fastlane/metadata/android/zh-CN/short_description.txt create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 libcore/.gitignore create mode 100644 libcore/LICENSE create mode 100644 libcore/assets.go create mode 100644 libcore/assets_android.go create mode 100644 libcore/assets_other.go create mode 100644 libcore/box.go create mode 100755 libcore/build.sh create mode 100644 libcore/crypto.go create mode 100644 libcore/date.go create mode 100644 libcore/device/debug.go create mode 100644 libcore/device/device.go create mode 100644 libcore/dns.go create mode 100644 libcore/go.mod create mode 100644 libcore/go.sum create mode 100644 libcore/http.go create mode 100755 libcore/init.sh create mode 100644 libcore/io.go create mode 100644 libcore/nb4a.go create mode 100644 libcore/platform_box.go create mode 100644 libcore/platform_java.go create mode 100644 libcore/procfs/procfs.go create mode 100644 libcore/stun.go create mode 100644 libcore/stun/README create mode 100644 libcore/stun/attribute.go create mode 100644 libcore/stun/client.go create mode 100644 libcore/stun/const.go create mode 100644 libcore/stun/discover.go create mode 100644 libcore/stun/doc.go create mode 100644 libcore/stun/host.go create mode 100644 libcore/stun/log.go create mode 100644 libcore/stun/net.go create mode 100644 libcore/stun/packet.go create mode 100644 libcore/stun/response.go create mode 100644 libcore/stun/tests.go create mode 100644 libcore/stun/utils.go create mode 100644 libcore/update-box.sh create mode 100644 libcore/update-extra.sh create mode 100644 libcore/update-libneko.sh create mode 100644 lint.xml create mode 100644 release.keystore create mode 100644 repositories.gradle.kts create mode 100755 run create mode 100644 sager.properties create mode 100644 settings.gradle.kts diff --git a/.github/ISSUE_TEMPLATE/bug-report-zh_cn.md b/.github/ISSUE_TEMPLATE/bug-report-zh_cn.md new file mode 100644 index 0000000..d9bbafd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report-zh_cn.md @@ -0,0 +1,22 @@ +--- +name: Bug Report zh_CN +about: 问题反馈,在提出问题前请先自行排除服务器端问题和升级到最新客户端。 +title: '' +labels: '' +assignees: '' + +--- + +**描述问题** + +预期行为: + +实际行为: + +**如何复现** + +提供有帮助的截图,录像,文字说明,订阅链接等。 + +**日志** + +如果有日志,请上传。请在文档内查看导出日志的详细步骤。 diff --git a/.github/ISSUE_TEMPLATE/feature_request-zh_cn.md b/.github/ISSUE_TEMPLATE/feature_request-zh_cn.md new file mode 100644 index 0000000..b8a79e8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request-zh_cn.md @@ -0,0 +1,12 @@ +--- +name: Feature Request zh_CN +about: 功能请求,提出建议。 +title: '' +labels: '' +assignees: '' + +--- + +**描述建议** + +**建议的必要性** diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e8f6148 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,171 @@ +name: Release Build +on: + workflow_dispatch: + inputs: + tag: + description: 'Release Tag' + required: true + upload: + description: 'Upload: If want ignore' + required: false + publish: + description: 'Publish: If want ignore' + required: false + play: + description: 'Play: If want ignore' + required: false +jobs: + libcore: + name: Native Build (LibCore) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Golang Status + run: find buildScript libcore/*.sh | xargs cat | sha1sum > golang_status + - name: Libcore Status + run: git ls-files libcore | xargs cat | sha1sum > libcore_status + - name: LibCore Cache + id: cache + uses: actions/cache@v3 + with: + path: | + app/libs/libcore.aar + key: ${{ hashFiles('.github/workflows/*', 'golang_status', 'libcore_status') }} + - name: Golang Cache + if: steps.cache.outputs.cache-hit != 'true' + uses: actions/cache@v3 + with: + path: build/golang + key: go-${{ hashFiles('.github/workflows/*', 'golang_status') }} + - name: Native Build + if: steps.cache.outputs.cache-hit != 'true' + run: ./run init action go && ./run lib core + build: + name: Build OSS APK + runs-on: ubuntu-latest + needs: + - libcore + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Golang Status + run: find buildScript libcore/*.sh | xargs cat | sha1sum > golang_status + - name: Libcore Status + run: git ls-files libcore | xargs cat | sha1sum > libcore_status + - name: LibCore Cache + uses: actions/cache@v3 + with: + path: | + app/libs/libcore.aar + key: ${{ hashFiles('.github/workflows/*', 'golang_status', 'libcore_status') }} + - name: Gradle cache + uses: actions/cache@v3 + with: + path: ~/.gradle + key: gradle-oss-${{ hashFiles('**/*.gradle.kts') }} + - name: Gradle Build + env: + BUILD_PLUGIN: none + run: | + echo "sdk.dir=${ANDROID_HOME}" > local.properties + echo "ndk.dir=${ANDROID_HOME}/ndk/25.0.8775105" >> local.properties + export LOCAL_PROPERTIES="${{ secrets.LOCAL_PROPERTIES }}" + ./run init action gradle + ./gradlew app:assembleOssRelease + APK=$(find app/build/outputs/apk -name '*arm64-v8a*.apk') + APK=$(dirname $APK) + echo "APK=$APK" >> $GITHUB_ENV + - uses: actions/upload-artifact@v3 + with: + name: APKs + path: ${{ env.APK }} + publish: + name: Publish Release + if: github.event.inputs.publish != 'y' + runs-on: ubuntu-latest + needs: build + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Donwload Artifacts + uses: actions/download-artifact@v3 + with: + name: APKs + path: artifacts + - name: Release + run: | + wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.13.0/ghr_v0.13.0_linux_amd64.tar.gz + tar -xvf ghr.tar.gz + mv ghr*linux_amd64/ghr . + mkdir apks + find artifacts -name "*.apk" -exec cp {} apks \; + ./ghr -delete -t "${{ github.token }}" -n "${{ github.event.inputs.tag }}" "${{ github.event.inputs.tag }}" apks + upload: + name: Upload Release + if: github.event.inputs.upload != 'y' + runs-on: ubuntu-latest + needs: build + steps: + - name: Donwload Artifacts + uses: actions/download-artifact@v3 + with: + name: APKs + path: artifacts + - name: Release + run: | + mkdir apks + find artifacts -name "*.apk" -exec cp {} apks \; + + function upload() { + for apk in $@; do + echo ">> Uploading $apk" + curl https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendDocument \ + -X POST \ + -F chat_id="${{ secrets.TELEGRAM_CHANNEL }}" \ + -F document="@$apk" \ + --silent --show-error --fail >/dev/null & + done + for job in $(jobs -p); do + wait $job || exit 1 + done + } + upload apks/* + play: + name: Build Play Bundle + if: github.event.inputs.play != 'y' + runs-on: ubuntu-latest + needs: + - libcore + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Golang Status + run: find buildScript libcore/*.sh | xargs cat | sha1sum > golang_status + - name: Libcore Status + run: git ls-files libcore | xargs cat | sha1sum > libcore_status + - name: LibCore Cache + uses: actions/cache@v3 + with: + path: | + app/libs/libcore.aar + key: ${{ hashFiles('.github/workflows/*', 'golang_status', 'libcore_status') }} + - name: Gradle cache + uses: actions/cache@v3 + with: + path: ~/.gradle + key: gradle-play-${{ hashFiles('**/*.gradle.kts') }} + - name: Checkout Library + run: | + git submodule update --init 'app/*' + - name: Gradle Build + run: | + echo "sdk.dir=${ANDROID_HOME}" > local.properties + echo "ndk.dir=${ANDROID_HOME}/ndk/25.0.8775105" >> local.properties + export LOCAL_PROPERTIES="${{ secrets.LOCAL_PROPERTIES }}" + ./run init action gradle + ./gradlew bundlePlayRelease + - uses: actions/upload-artifact@v3 + with: + name: AAB + path: app/build/outputs/bundle/playRelease/app-play-release.aab diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c1fe39 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +*.iml +.gradle +.idea +.vscode +.DS_Store +build/ +/captures +.externalNativeBuild +.cxx +local.properties +/app/libs/ +/app/src/main/assets/sing-box +/service_account_credentials.json +jniLibs/ +/library/libcore_build/ +.idea/deploymentTargetDropDown.xml +/nkmr + +# submodules +/external diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..4136f4f --- /dev/null +++ b/AUTHORS @@ -0,0 +1,8 @@ +SagerNet was originally created in late 2021, by +nekohasekai . + +Here is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS -- +people who have submitted patches, fixed bugs, added translations, and +generally made SagerNet that much better: + +https://github.com/SagerNet/SagerNet/graphs/contributors diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..16f27f1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ +Copyright (C) 2021 by nekohasekai + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..577a519 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# NekoBox for Android + +[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21) +[![Releases](https://img.shields.io/github/v/release/MatsuriDayo/NekoBoxForAndroid)](https://github.com/MatsuriDayo/NekoBoxForAndroid/releases) +[![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-orange.svg)](https://www.gnu.org/licenses/gpl-3.0) + +sing-box / universal proxy toolchain for Android. + +## 下载 / Downloads + +### GitHub Releases + +[![GitHub All Releases](https://img.shields.io/github/downloads/Matsuridayo/NekoBoxForAndroid/total?label=downloads-total&logo=github&style=flat-square)](https://github.com/Matsuridayo/NekoBoxForAndroid/releases) + +[下载](https://github.com/Matsuridayo/NekoBoxForAndroid/releases) + +## 更改记录 & 发布频道 / Changelog & Telegram channel + +https://t.me/Matsuridayo + +## 项目主页 & 文档 / Homepage & Documents + +https://matsuridayo.github.io + +## 代理 / Proxy + +* SOCKS (4/4a/5) +* HTTP(S) +* SSH +* Shadowsocks +* VMess +* VLESS +* WireGuard +* Trojan +* Trojan-Go ( trojan-go-plugin ) +* NaïveProxy ( naive-plugin ) +* Hysteria ( hysteria-plugin ) + +请到项目主页下载插件。 + +Please go to the project homepage to download plugins. + +### 订阅 / Subscription + +* Raw: some widely used formats (like shadowsocks, clash and v2rayN) +* 原始格式:一些广泛使用的格式(如 shadowsocks、clash 和 v2rayN) +* [Open Online Config](https://github.com/Shadowsocks-NET/OpenOnlineConfig) +* [Shadowsocks SIP008](https://shadowsocks.org/guide/sip008.html) + +### 捐助 / Donate + +欢迎捐赠以支持项目开发。 + +USDT TRC20 + +`TRhnA7SXE5Sap5gSG3ijxRmdYFiD4KRhPs` + +XMR + +`49bwESYQjoRL3xmvTcjZKHEKaiGywjLYVQJMUv79bXonGiyDCs8AzE3KiGW2ytTybBCpWJUvov8SjZZEGg66a4e59GXa6k5` diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..3f2a4d9 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,2 @@ +/build +/schemas diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..daee17b --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,81 @@ +plugins { + id("com.android.application") + id("kotlin-android") + id("kotlin-kapt") + id("kotlin-parcelize") +} + +setupApp() + +android { + compileOptions { + isCoreLibraryDesugaringEnabled = true + } + kapt.arguments { + arg("room.incremental", true) + arg("room.schemaLocation", "$projectDir/schemas") + } + bundle { + language { + enableSplit = false + } + } + buildFeatures { + viewBinding = true + } + namespace = "io.nekohasekai.sagernet" +} + +dependencies { + + implementation(fileTree("libs")) + + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.3") + implementation("androidx.core:core-ktx:1.7.0") + implementation("androidx.recyclerview:recyclerview:1.2.1") + implementation("androidx.activity:activity-ktx:1.4.0") + implementation("androidx.fragment:fragment-ktx:1.4.1") + implementation("androidx.browser:browser:1.4.0") + implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation("androidx.navigation:navigation-fragment-ktx:2.4.2") + implementation("androidx.navigation:navigation-ui-ktx:2.4.2") + implementation("androidx.preference:preference-ktx:1.2.0") + implementation("androidx.appcompat:appcompat:1.4.1") + implementation("androidx.work:work-runtime-ktx:2.7.1") + implementation("androidx.work:work-multiprocess:2.7.1") + + implementation(project(":external:preferencex:preferencex")) + implementation(project(":external:preferencex:preferencex-simplemenu")) + + implementation("com.google.android.material:material:1.6.0") + implementation("com.google.code.gson:gson:2.8.9") + + implementation("com.github.jenly1314:zxing-lite:2.1.1") + implementation("com.afollestad.material-dialogs:core:3.3.0") + implementation("com.afollestad.material-dialogs:input:3.3.0") + + implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.3") + implementation("org.yaml:snakeyaml:1.30") + implementation("com.github.daniel-stoneuk:material-about-library:3.2.0-rc01") + implementation("com.jakewharton:process-phoenix:2.1.2") + implementation("com.esotericsoftware:kryo:5.2.1") + implementation("com.google.guava:guava:31.0.1-android") + implementation("org.ini4j:ini4j:0.5.4") + + implementation("com.simplecityapps:recyclerview-fastscroll:2.0.1") { + exclude(group = "androidx.recyclerview") + exclude(group = "androidx.appcompat") + } + implementation("org.smali:dexlib2:2.5.2") { + exclude(group = "com.google.guava", module = "guava") + } + + implementation("androidx.room:room-runtime:2.4.2") + kapt("androidx.room:room-compiler:2.4.2") + implementation("androidx.room:room-ktx:2.4.2") + implementation("com.github.MatrixDev.Roomigrant:RoomigrantLib:0.3.4") + kapt("com.github.MatrixDev.Roomigrant:RoomigrantCompiler:0.3.4") + + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5") +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..1d4a8bc --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,47 @@ +-repackageclasses '' +-allowaccessmodification + +-keep class io.nekohasekai.sagernet.** { *;} +-keep class moe.matsuri.nb4a.** { *;} + +# Clean Kotlin +-assumenosideeffects class kotlin.jvm.internal.Intrinsics { + static void checkParameterIsNotNull(java.lang.Object, java.lang.String); + static void checkExpressionValueIsNotNull(java.lang.Object, java.lang.String); + static void checkNotNullExpressionValue(java.lang.Object, java.lang.String); + static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String, java.lang.String); + static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String); + static void checkFieldIsNotNull(java.lang.Object, java.lang.String, java.lang.String); + static void checkFieldIsNotNull(java.lang.Object, java.lang.String); + static void checkNotNull(java.lang.Object); + static void checkNotNull(java.lang.Object, java.lang.String); + static void checkNotNullParameter(java.lang.Object, java.lang.String); + static void throwUninitializedPropertyAccessException(java.lang.String); +} + +# ini4j +-keep public class org.ini4j.spi.** { (); } + +# SnakeYaml +-keep class org.yaml.snakeyaml.** { *; } + +-dontobfuscate +-keepattributes SourceFile + +-dontwarn java.beans.BeanInfo +-dontwarn java.beans.FeatureDescriptor +-dontwarn java.beans.IntrospectionException +-dontwarn java.beans.Introspector +-dontwarn java.beans.PropertyDescriptor +-dontwarn java.beans.Transient +-dontwarn java.beans.VetoableChangeListener +-dontwarn java.beans.VetoableChangeSupport +-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl +-dontwarn org.bouncycastle.jce.provider.BouncyCastleProvider +-dontwarn org.bouncycastle.jsse.BCSSLParameters +-dontwarn org.bouncycastle.jsse.BCSSLSocket +-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider +-dontwarn org.openjsse.javax.net.ssl.SSLParameters +-dontwarn org.openjsse.javax.net.ssl.SSLSocket +-dontwarn org.openjsse.net.ssl.OpenJSSE +-dontwarn java.beans.PropertyVetoException diff --git a/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/1.json b/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/1.json new file mode 100644 index 0000000..5e9db91 --- /dev/null +++ b/app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/1.json @@ -0,0 +1,330 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "f66fd943df1d9e86d281a2e32c9fdd47", + "entities": [ + { + "tableName": "proxy_groups", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userOrder` INTEGER NOT NULL, `ungrouped` INTEGER NOT NULL, `name` TEXT, `type` INTEGER NOT NULL, `subscription` BLOB, `order` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userOrder", + "columnName": "userOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ungrouped", + "columnName": "ungrouped", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "subscription", + "columnName": "subscription", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "proxy_entities", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groupId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `status` INTEGER NOT NULL, `ping` INTEGER NOT NULL, `uuid` TEXT NOT NULL, `error` TEXT, `socksBean` BLOB, `httpBean` BLOB, `ssBean` BLOB, `vmessBean` BLOB, `trojanBean` BLOB, `trojanGoBean` BLOB, `naiveBean` BLOB, `hysteriaBean` BLOB, `tuicBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `chainBean` BLOB, `nekoBean` BLOB, `configBean` BLOB)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "groupId", + "columnName": "groupId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userOrder", + "columnName": "userOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tx", + "columnName": "tx", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "rx", + "columnName": "rx", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ping", + "columnName": "ping", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "socksBean", + "columnName": "socksBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "httpBean", + "columnName": "httpBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "ssBean", + "columnName": "ssBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "vmessBean", + "columnName": "vmessBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "trojanBean", + "columnName": "trojanBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "trojanGoBean", + "columnName": "trojanGoBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "naiveBean", + "columnName": "naiveBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "hysteriaBean", + "columnName": "hysteriaBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "tuicBean", + "columnName": "tuicBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "sshBean", + "columnName": "sshBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "wgBean", + "columnName": "wgBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "chainBean", + "columnName": "chainBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "nekoBean", + "columnName": "nekoBean", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "configBean", + "columnName": "configBean", + "affinity": "BLOB", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "groupId", + "unique": false, + "columnNames": [ + "groupId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `groupId` ON `${TABLE_NAME}` (`groupId`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "rules", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `userOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `domains` TEXT NOT NULL, `ip` TEXT NOT NULL, `port` TEXT NOT NULL, `sourcePort` TEXT NOT NULL, `network` TEXT NOT NULL, `source` TEXT NOT NULL, `protocol` TEXT NOT NULL, `outbound` INTEGER NOT NULL, `packages` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "userOrder", + "columnName": "userOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "enabled", + "columnName": "enabled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "domains", + "columnName": "domains", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ip", + "columnName": "ip", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "port", + "columnName": "port", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "sourcePort", + "columnName": "sourcePort", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "network", + "columnName": "network", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "protocol", + "columnName": "protocol", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "outbound", + "columnName": "outbound", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "packages", + "columnName": "packages", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f66fd943df1d9e86d281a2e32c9fdd47')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/io.nekohasekai.sagernet.database.preference.PublicDatabase/1.json b/app/schemas/io.nekohasekai.sagernet.database.preference.PublicDatabase/1.json new file mode 100644 index 0000000..4986920 --- /dev/null +++ b/app/schemas/io.nekohasekai.sagernet.database.preference.PublicDatabase/1.json @@ -0,0 +1,46 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "f1aab1fb633378621635c344dbc8ac7b", + "entities": [ + { + "tableName": "KeyValuePair", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `valueType` INTEGER NOT NULL, `value` BLOB NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "valueType", + "columnName": "valueType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "key" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f1aab1fb633378621635c344dbc8ac7b')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/moe.matsuri.nb4a.TempDatabase/1.json b/app/schemas/moe.matsuri.nb4a.TempDatabase/1.json new file mode 100644 index 0000000..4986920 --- /dev/null +++ b/app/schemas/moe.matsuri.nb4a.TempDatabase/1.json @@ -0,0 +1,46 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "f1aab1fb633378621635c344dbc8ac7b", + "entities": [ + { + "tableName": "KeyValuePair", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `valueType` INTEGER NOT NULL, `value` BLOB NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "valueType", + "columnName": "valueType", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "key" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f1aab1fb633378621635c344dbc8ac7b')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d6373d9 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/aidl/io/nekohasekai/sagernet/aidl/ISagerNetService.aidl b/app/src/main/aidl/io/nekohasekai/sagernet/aidl/ISagerNetService.aidl new file mode 100644 index 0000000..bb7ce08 --- /dev/null +++ b/app/src/main/aidl/io/nekohasekai/sagernet/aidl/ISagerNetService.aidl @@ -0,0 +1,13 @@ +package io.nekohasekai.sagernet.aidl; + +import io.nekohasekai.sagernet.aidl.ISagerNetServiceCallback; + +interface ISagerNetService { + int getState(); + String getProfileName(); + + void registerCallback(in ISagerNetServiceCallback cb); + oneway void unregisterCallback(in ISagerNetServiceCallback cb); + + int urlTest(); +} diff --git a/app/src/main/aidl/io/nekohasekai/sagernet/aidl/ISagerNetServiceCallback.aidl b/app/src/main/aidl/io/nekohasekai/sagernet/aidl/ISagerNetServiceCallback.aidl new file mode 100644 index 0000000..8eb485d --- /dev/null +++ b/app/src/main/aidl/io/nekohasekai/sagernet/aidl/ISagerNetServiceCallback.aidl @@ -0,0 +1,14 @@ +package io.nekohasekai.sagernet.aidl; + +import io.nekohasekai.sagernet.aidl.SpeedDisplayData; +import io.nekohasekai.sagernet.aidl.TrafficData; + +oneway interface ISagerNetServiceCallback { + void stateChanged(int state, String profileName, String msg); + void missingPlugin(String profileName, String pluginName); + void routeAlert(int type, String routeName); + void updateWakeLockStatus(boolean acquired); + void cbSpeedUpdate(in SpeedDisplayData stats); + void cbTrafficUpdate(in TrafficData stats); + void cbLogUpdate(String str); +} diff --git a/app/src/main/aidl/io/nekohasekai/sagernet/aidl/SpeedDisplayData.aidl b/app/src/main/aidl/io/nekohasekai/sagernet/aidl/SpeedDisplayData.aidl new file mode 100644 index 0000000..bc5f0fb --- /dev/null +++ b/app/src/main/aidl/io/nekohasekai/sagernet/aidl/SpeedDisplayData.aidl @@ -0,0 +1,3 @@ +package io.nekohasekai.sagernet.aidl; + +parcelable SpeedDisplayData; diff --git a/app/src/main/aidl/io/nekohasekai/sagernet/aidl/TrafficData.aidl b/app/src/main/aidl/io/nekohasekai/sagernet/aidl/TrafficData.aidl new file mode 100644 index 0000000..99d43b4 --- /dev/null +++ b/app/src/main/aidl/io/nekohasekai/sagernet/aidl/TrafficData.aidl @@ -0,0 +1,3 @@ +package io.nekohasekai.sagernet.aidl; + +parcelable TrafficData; diff --git a/app/src/main/assets/LICENSE b/app/src/main/assets/LICENSE new file mode 100644 index 0000000..c585bf6 --- /dev/null +++ b/app/src/main/assets/LICENSE @@ -0,0 +1,19 @@ +Copyright (C) 2021 by nekohasekai + + +This program is free software: you can +redistribute it and/or modify it under +the terms of the GNU General Public License +as published by the Free Software Foundation, +either version 3 of the License, +or (at your option) any later version. + +This program is distributed in the hope +that it will be useful, but WITHOUT ANY WARRANTY; +without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +See the GNU General Public License for more details. + +You should have received a copy of the +GNU General Public License along with this program. +If not, see . \ No newline at end of file diff --git a/app/src/main/assets/analysis.txt b/app/src/main/assets/analysis.txt new file mode 100644 index 0000000..a4f7cc7 --- /dev/null +++ b/app/src/main/assets/analysis.txt @@ -0,0 +1,5 @@ +domain:appcenter.ms +domain:app-measurement.com +domain:firebase.io +domain:crashlytics.com +domain:google-analytics.com \ No newline at end of file diff --git a/app/src/main/assets/yacd.version.txt b/app/src/main/assets/yacd.version.txt new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/app/src/main/assets/yacd.version.txt @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/app/src/main/assets/yacd.zip b/app/src/main/assets/yacd.zip new file mode 100644 index 0000000000000000000000000000000000000000..46d2b59b6faf8e8c419f6025d688e8a7668ccf65 GIT binary patch literal 742351 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 vnDC7cn2VE>-xOlT%W2BV#|{B% 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 z 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 z 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 Z1u^>db>2Wiiv!?~jD(_ixu|i#{|AwFXubdd literal 0 HcmV?d00001 diff --git a/app/src/main/java/com/github/shadowsocks/plugin/Utils.kt b/app/src/main/java/com/github/shadowsocks/plugin/Utils.kt new file mode 100644 index 0000000..66ec88d --- /dev/null +++ b/app/src/main/java/com/github/shadowsocks/plugin/Utils.kt @@ -0,0 +1,9 @@ +@file:JvmName("Utils") + +package com.github.shadowsocks.plugin + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +class Empty : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/github/shadowsocks/plugin/fragment/AlertDialogFragment.kt b/app/src/main/java/com/github/shadowsocks/plugin/fragment/AlertDialogFragment.kt new file mode 100644 index 0000000..870b57a --- /dev/null +++ b/app/src/main/java/com/github/shadowsocks/plugin/fragment/AlertDialogFragment.kt @@ -0,0 +1,60 @@ +package com.github.shadowsocks.plugin.fragment + +import android.app.Activity +import android.content.DialogInterface +import android.os.Bundle +import android.os.Parcelable +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatDialogFragment +import androidx.fragment.app.Fragment +import androidx.fragment.app.setFragmentResult +import androidx.fragment.app.setFragmentResultListener +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +/** + * Based on: https://android.googlesource.com/platform/ +packages/apps/ExactCalculator/+/8c43f06/src/com/android/calculator2/AlertDialogFragment.java + */ +abstract class AlertDialogFragment : + AppCompatDialogFragment(), DialogInterface.OnClickListener { + companion object { + private const val KEY_RESULT = "result" + private const val KEY_ARG = "arg" + private const val KEY_RET = "ret" + private const val KEY_WHICH = "which" + + fun setResultListener(fragment: Fragment, requestKey: String, + listener: (Int, Ret?) -> Unit) { + fragment.setFragmentResultListener(requestKey) { _, bundle -> + listener(bundle.getInt(KEY_WHICH, Activity.RESULT_CANCELED), bundle.getParcelable(KEY_RET)) + } + } + inline fun , Ret : Parcelable?> setResultListener( + fragment: Fragment, noinline listener: (Int, Ret?) -> Unit) = + setResultListener(fragment, T::class.java.name, listener) + } + protected abstract fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) + + private val resultKey get() = requireArguments().getString(KEY_RESULT) + protected val arg by lazy { requireArguments().getParcelable(KEY_ARG)!! } + protected open fun ret(which: Int): Ret? = null + + private fun args() = arguments ?: Bundle().also { arguments = it } + fun arg(arg: Arg) = args().putParcelable(KEY_ARG, arg) + fun key(resultKey: String = javaClass.name) = args().putString(KEY_RESULT, resultKey) + + override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog = + MaterialAlertDialogBuilder(requireContext()).also { it.prepare(this) }.create() + + override fun onClick(dialog: DialogInterface?, which: Int) { + setFragmentResult(resultKey ?: return, Bundle().apply { + putInt(KEY_WHICH, which) + putParcelable(KEY_RET, ret(which) ?: return@apply) + }) + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + onClick(null, Activity.RESULT_CANCELED) + } +} diff --git a/app/src/main/java/com/wireguard/crypto/Curve25519.java b/app/src/main/java/com/wireguard/crypto/Curve25519.java new file mode 100644 index 0000000..55f2809 --- /dev/null +++ b/app/src/main/java/com/wireguard/crypto/Curve25519.java @@ -0,0 +1,497 @@ +/* + * Copyright © 2016 Southern Storm Software, Pty Ltd. + * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.crypto; + +import androidx.annotation.Nullable; + +import java.util.Arrays; + +/** + * Implementation of Curve25519 ECDH. + *

+ * This implementation was imported to WireGuard from noise-java: + * https://github.com/rweather/noise-java + *

+ * This implementation is based on that from arduinolibs: + * https://github.com/rweather/arduinolibs + *

+ * Differences in this version are due to using 26-bit limbs for the + * representation instead of the 8/16/32-bit limbs in the original. + *

+ * References: http://cr.yp.to/ecdh.html, RFC 7748 + */ +@SuppressWarnings({"MagicNumber", "NonConstantFieldWithUpperCaseName", "SuspiciousNameCombination"}) +public final class Curve25519 { + // Numbers modulo 2^255 - 19 are broken up into ten 26-bit words. + private static final int NUM_LIMBS_255BIT = 10; + private static final int NUM_LIMBS_510BIT = 20; + + private final int[] A; + private final int[] AA; + private final int[] B; + private final int[] BB; + private final int[] C; + private final int[] CB; + private final int[] D; + private final int[] DA; + private final int[] E; + private final long[] t1; + private final int[] t2; + private final int[] x_1; + private final int[] x_2; + private final int[] x_3; + private final int[] z_2; + private final int[] z_3; + + /** + * Constructs the temporary state holder for Curve25519 evaluation. + */ + private Curve25519() { + // Allocate memory for all of the temporary variables we will need. + x_1 = new int[NUM_LIMBS_255BIT]; + x_2 = new int[NUM_LIMBS_255BIT]; + x_3 = new int[NUM_LIMBS_255BIT]; + z_2 = new int[NUM_LIMBS_255BIT]; + z_3 = new int[NUM_LIMBS_255BIT]; + A = new int[NUM_LIMBS_255BIT]; + B = new int[NUM_LIMBS_255BIT]; + C = new int[NUM_LIMBS_255BIT]; + D = new int[NUM_LIMBS_255BIT]; + E = new int[NUM_LIMBS_255BIT]; + AA = new int[NUM_LIMBS_255BIT]; + BB = new int[NUM_LIMBS_255BIT]; + DA = new int[NUM_LIMBS_255BIT]; + CB = new int[NUM_LIMBS_255BIT]; + t1 = new long[NUM_LIMBS_510BIT]; + t2 = new int[NUM_LIMBS_510BIT]; + } + + /** + * Conditional swap of two values. + * + * @param select Set to 1 to swap, 0 to leave as-is. + * @param x The first value. + * @param y The second value. + */ + private static void cswap(int select, final int[] x, final int[] y) { + select = -select; + for (int index = 0; index < NUM_LIMBS_255BIT; ++index) { + final int dummy = select & (x[index] ^ y[index]); + x[index] ^= dummy; + y[index] ^= dummy; + } + } + + /** + * Evaluates the Curve25519 curve. + * + * @param result Buffer to place the result of the evaluation into. + * @param offset Offset into the result buffer. + * @param privateKey The private key to use in the evaluation. + * @param publicKey The public key to use in the evaluation, or null + * if the base point of the curve should be used. + */ + public static void eval(final byte[] result, final int offset, + final byte[] privateKey, @Nullable final byte[] publicKey) { + final Curve25519 state = new Curve25519(); + try { + // Unpack the public key value. If null, use 9 as the base point. + Arrays.fill(state.x_1, 0); + if (publicKey != null) { + // Convert the input value from little-endian into 26-bit limbs. + for (int index = 0; index < 32; ++index) { + final int bit = (index * 8) % 26; + final int word = (index * 8) / 26; + final int value = publicKey[index] & 0xFF; + if (bit <= (26 - 8)) { + state.x_1[word] |= value << bit; + } else { + state.x_1[word] |= value << bit; + state.x_1[word] &= 0x03FFFFFF; + state.x_1[word + 1] |= value >> (26 - bit); + } + } + + // Just in case, we reduce the number modulo 2^255 - 19 to + // make sure that it is in range of the field before we start. + // This eliminates values between 2^255 - 19 and 2^256 - 1. + state.reduceQuick(state.x_1); + state.reduceQuick(state.x_1); + } else { + state.x_1[0] = 9; + } + + // Initialize the other temporary variables. + Arrays.fill(state.x_2, 0); // x_2 = 1 + state.x_2[0] = 1; + Arrays.fill(state.z_2, 0); // z_2 = 0 + System.arraycopy(state.x_1, 0, state.x_3, 0, state.x_1.length); // x_3 = x_1 + Arrays.fill(state.z_3, 0); // z_3 = 1 + state.z_3[0] = 1; + + // Evaluate the curve for every bit of the private key. + state.evalCurve(privateKey); + + // Compute x_2 * (z_2 ^ (p - 2)) where p = 2^255 - 19. + state.recip(state.z_3, state.z_2); + state.mul(state.x_2, state.x_2, state.z_3); + + // Convert x_2 into little-endian in the result buffer. + for (int index = 0; index < 32; ++index) { + final int bit = (index * 8) % 26; + final int word = (index * 8) / 26; + if (bit <= (26 - 8)) + result[offset + index] = (byte) (state.x_2[word] >> bit); + else + result[offset + index] = (byte) ((state.x_2[word] >> bit) | (state.x_2[word + 1] << (26 - bit))); + } + } finally { + // Clean up all temporary state before we exit. + state.destroy(); + } + } + + /** + * Subtracts two numbers modulo 2^255 - 19. + * + * @param result The result. + * @param x The first number to subtract. + * @param y The second number to subtract. + */ + private static void sub(final int[] result, final int[] x, final int[] y) { + int index; + int borrow; + + // Subtract y from x to generate the intermediate result. + borrow = 0; + for (index = 0; index < NUM_LIMBS_255BIT; ++index) { + borrow = x[index] - y[index] - ((borrow >> 26) & 0x01); + result[index] = borrow & 0x03FFFFFF; + } + + // If we had a borrow, then the result has gone negative and we + // have to add 2^255 - 19 to the result to make it positive again. + // The top bits of "borrow" will be all 1's if there is a borrow + // or it will be all 0's if there was no borrow. Easiest is to + // conditionally subtract 19 and then mask off the high bits. + borrow = result[0] - ((-((borrow >> 26) & 0x01)) & 19); + result[0] = borrow & 0x03FFFFFF; + for (index = 1; index < NUM_LIMBS_255BIT; ++index) { + borrow = result[index] - ((borrow >> 26) & 0x01); + result[index] = borrow & 0x03FFFFFF; + } + result[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; + } + + /** + * Adds two numbers modulo 2^255 - 19. + * + * @param result The result. + * @param x The first number to add. + * @param y The second number to add. + */ + private void add(final int[] result, final int[] x, final int[] y) { + int carry = x[0] + y[0]; + result[0] = carry & 0x03FFFFFF; + for (int index = 1; index < NUM_LIMBS_255BIT; ++index) { + carry = (carry >> 26) + x[index] + y[index]; + result[index] = carry & 0x03FFFFFF; + } + reduceQuick(result); + } + + /** + * Destroy all sensitive data in this object. + */ + private void destroy() { + // Destroy all temporary variables. + Arrays.fill(x_1, 0); + Arrays.fill(x_2, 0); + Arrays.fill(x_3, 0); + Arrays.fill(z_2, 0); + Arrays.fill(z_3, 0); + Arrays.fill(A, 0); + Arrays.fill(B, 0); + Arrays.fill(C, 0); + Arrays.fill(D, 0); + Arrays.fill(E, 0); + Arrays.fill(AA, 0); + Arrays.fill(BB, 0); + Arrays.fill(DA, 0); + Arrays.fill(CB, 0); + Arrays.fill(t1, 0L); + Arrays.fill(t2, 0); + } + + /** + * Evaluates the curve for every bit in a secret key. + * + * @param s The 32-byte secret key. + */ + private void evalCurve(final byte[] s) { + int sposn = 31; + int sbit = 6; + int svalue = s[sposn] | 0x40; + int swap = 0; + + // Iterate over all 255 bits of "s" from the highest to the lowest. + // We ignore the high bit of the 256-bit representation of "s". + while (true) { + // Conditional swaps on entry to this bit but only if we + // didn't swap on the previous bit. + final int select = (svalue >> sbit) & 0x01; + swap ^= select; + cswap(swap, x_2, x_3); + cswap(swap, z_2, z_3); + swap = select; + + // Evaluate the curve. + add(A, x_2, z_2); // A = x_2 + z_2 + square(AA, A); // AA = A^2 + sub(B, x_2, z_2); // B = x_2 - z_2 + square(BB, B); // BB = B^2 + sub(E, AA, BB); // E = AA - BB + add(C, x_3, z_3); // C = x_3 + z_3 + sub(D, x_3, z_3); // D = x_3 - z_3 + mul(DA, D, A); // DA = D * A + mul(CB, C, B); // CB = C * B + add(x_3, DA, CB); // x_3 = (DA + CB)^2 + square(x_3, x_3); + sub(z_3, DA, CB); // z_3 = x_1 * (DA - CB)^2 + square(z_3, z_3); + mul(z_3, z_3, x_1); + mul(x_2, AA, BB); // x_2 = AA * BB + mulA24(z_2, E); // z_2 = E * (AA + a24 * E) + add(z_2, z_2, AA); + mul(z_2, z_2, E); + + // Move onto the next lower bit of "s". + if (sbit > 0) { + --sbit; + } else if (sposn == 0) { + break; + } else if (sposn == 1) { + --sposn; + svalue = s[sposn] & 0xF8; + sbit = 7; + } else { + --sposn; + svalue = s[sposn]; + sbit = 7; + } + } + + // Final conditional swaps. + cswap(swap, x_2, x_3); + cswap(swap, z_2, z_3); + } + + /** + * Multiplies two numbers modulo 2^255 - 19. + * + * @param result The result. + * @param x The first number to multiply. + * @param y The second number to multiply. + */ + private void mul(final int[] result, final int[] x, final int[] y) { + // Multiply the two numbers to create the intermediate result. + long v = x[0]; + for (int i = 0; i < NUM_LIMBS_255BIT; ++i) { + t1[i] = v * y[i]; + } + for (int i = 1; i < NUM_LIMBS_255BIT; ++i) { + v = x[i]; + for (int j = 0; j < (NUM_LIMBS_255BIT - 1); ++j) { + t1[i + j] += v * y[j]; + } + t1[i + NUM_LIMBS_255BIT - 1] = v * y[NUM_LIMBS_255BIT - 1]; + } + + // Propagate carries and convert back into 26-bit words. + v = t1[0]; + t2[0] = ((int) v) & 0x03FFFFFF; + for (int i = 1; i < NUM_LIMBS_510BIT; ++i) { + v = (v >> 26) + t1[i]; + t2[i] = ((int) v) & 0x03FFFFFF; + } + + // Reduce the result modulo 2^255 - 19. + reduce(result, t2, NUM_LIMBS_255BIT); + } + + /** + * Multiplies a number by the a24 constant, modulo 2^255 - 19. + * + * @param result The result. + * @param x The number to multiply by a24. + */ + private void mulA24(final int[] result, final int[] x) { + final long a24 = 121665; + long carry = 0; + for (int index = 0; index < NUM_LIMBS_255BIT; ++index) { + carry += a24 * x[index]; + t2[index] = ((int) carry) & 0x03FFFFFF; + carry >>= 26; + } + t2[NUM_LIMBS_255BIT] = ((int) carry) & 0x03FFFFFF; + reduce(result, t2, 1); + } + + /** + * Raise x to the power of (2^250 - 1). + * + * @param result The result. Must not overlap with x. + * @param x The argument. + */ + private void pow250(final int[] result, final int[] x) { + // The big-endian hexadecimal expansion of (2^250 - 1) is: + // 03FFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF + // + // The naive implementation needs to do 2 multiplications per 1 bit and + // 1 multiplication per 0 bit. We can improve upon this by creating a + // pattern 0000000001 ... 0000000001. If we square and multiply the + // pattern by itself we can turn the pattern into the partial results + // 0000000011 ... 0000000011, 0000000111 ... 0000000111, etc. + // This averages out to about 1.1 multiplications per 1 bit instead of 2. + + // Build a pattern of 250 bits in length of repeated copies of 0000000001. + square(A, x); + for (int j = 0; j < 9; ++j) + square(A, A); + mul(result, A, x); + for (int i = 0; i < 23; ++i) { + for (int j = 0; j < 10; ++j) + square(A, A); + mul(result, result, A); + } + + // Multiply bit-shifted versions of the 0000000001 pattern into + // the result to "fill in" the gaps in the pattern. + square(A, result); + mul(result, result, A); + for (int j = 0; j < 8; ++j) { + square(A, A); + mul(result, result, A); + } + } + + /** + * Computes the reciprocal of a number modulo 2^255 - 19. + * + * @param result The result. Must not overlap with x. + * @param x The argument. + */ + private void recip(final int[] result, final int[] x) { + // The reciprocal is the same as x ^ (p - 2) where p = 2^255 - 19. + // The big-endian hexadecimal expansion of (p - 2) is: + // 7FFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFEB + // Start with the 250 upper bits of the expansion of (p - 2). + pow250(result, x); + + // Deal with the 5 lowest bits of (p - 2), 01011, from highest to lowest. + square(result, result); + square(result, result); + mul(result, result, x); + square(result, result); + square(result, result); + mul(result, result, x); + square(result, result); + mul(result, result, x); + } + + /** + * Reduce a number modulo 2^255 - 19. + * + * @param result The result. + * @param x The value to be reduced. This array will be + * modified during the reduction. + * @param size The number of limbs in the high order half of x. + */ + private void reduce(final int[] result, final int[] x, final int size) { + // Calculate (x mod 2^255) + ((x / 2^255) * 19) which will + // either produce the answer we want or it will produce a + // value of the form "answer + j * (2^255 - 19)". There are + // 5 left-over bits in the top-most limb of the bottom half. + int carry = 0; + int limb = x[NUM_LIMBS_255BIT - 1] >> 21; + x[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; + for (int index = 0; index < size; ++index) { + limb += x[NUM_LIMBS_255BIT + index] << 5; + carry += (limb & 0x03FFFFFF) * 19 + x[index]; + x[index] = carry & 0x03FFFFFF; + limb >>= 26; + carry >>= 26; + } + if (size < NUM_LIMBS_255BIT) { + // The high order half of the number is short; e.g. for mulA24(). + // Propagate the carry through the rest of the low order part. + for (int index = size; index < NUM_LIMBS_255BIT; ++index) { + carry += x[index]; + x[index] = carry & 0x03FFFFFF; + carry >>= 26; + } + } + + // The "j" value may still be too large due to the final carry-out. + // We must repeat the reduction. If we already have the answer, + // then this won't do any harm but we must still do the calculation + // to preserve the overall timing. The "j" value will be between + // 0 and 19, which means that the carry we care about is in the + // top 5 bits of the highest limb of the bottom half. + carry = (x[NUM_LIMBS_255BIT - 1] >> 21) * 19; + x[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; + for (int index = 0; index < NUM_LIMBS_255BIT; ++index) { + carry += x[index]; + result[index] = carry & 0x03FFFFFF; + carry >>= 26; + } + + // At this point "x" will either be the answer or it will be the + // answer plus (2^255 - 19). Perform a trial subtraction to + // complete the reduction process. + reduceQuick(result); + } + + /** + * Reduces a number modulo 2^255 - 19 where it is known that the + * number can be reduced with only 1 trial subtraction. + * + * @param x The number to reduce, and the result. + */ + private void reduceQuick(final int[] x) { + // Perform a trial subtraction of (2^255 - 19) from "x" which is + // equivalent to adding 19 and subtracting 2^255. We add 19 here; + // the subtraction of 2^255 occurs in the next step. + int carry = 19; + for (int index = 0; index < NUM_LIMBS_255BIT; ++index) { + carry += x[index]; + t2[index] = carry & 0x03FFFFFF; + carry >>= 26; + } + + // If there was a borrow, then the original "x" is the correct answer. + // If there was no borrow, then "t2" is the correct answer. Select the + // correct answer but do it in a way that instruction timing will not + // reveal which value was selected. Borrow will occur if bit 21 of + // "t2" is zero. Turn the bit into a selection mask. + final int mask = -((t2[NUM_LIMBS_255BIT - 1] >> 21) & 0x01); + final int nmask = ~mask; + t2[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF; + for (int index = 0; index < NUM_LIMBS_255BIT; ++index) + x[index] = (x[index] & nmask) | (t2[index] & mask); + } + + /** + * Squares a number modulo 2^255 - 19. + * + * @param result The result. + * @param x The number to square. + */ + private void square(final int[] result, final int[] x) { + mul(result, x, x); + } +} diff --git a/app/src/main/java/com/wireguard/crypto/Ed25519.java b/app/src/main/java/com/wireguard/crypto/Ed25519.java new file mode 100644 index 0000000..a60babf --- /dev/null +++ b/app/src/main/java/com/wireguard/crypto/Ed25519.java @@ -0,0 +1,2508 @@ +/* + * Copyright © 2020 WireGuard LLC. All Rights Reserved. + * Copyright 2017 Google Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.crypto; + +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.util.Arrays; + +/** + * Implementation of Ed25519 signature verification. + * + *

This implementation is based on the ed25519/ref10 implementation in NaCl.

+ * + *

It implements this twisted Edwards curve: + * + *

+ * -x^2 + y^2 = 1 + (-121665 / 121666 mod 2^255-19)*x^2*y^2
+ * 
+ * + * @see
Bernstein D.J., Birkner P., Joye M., Lange + * T., Peters C. (2008) Twisted Edwards Curves + * @see Hisil H., Wong K.KH., Carter G., Dawson E. + * (2008) Twisted Edwards Curves Revisited + */ +public final class Ed25519 { + + // d = -121665 / 121666 mod 2^255-19 + private static final long[] D; + // 2d + private static final long[] D2; + // 2^((p-1)/4) mod p where p = 2^255-19 + private static final long[] SQRTM1; + + /** + * Base point for the Edwards twisted curve = (x, 4/5) and its exponentiations. B_TABLE[i][j] = + * (j+1)*256^i*B for i in [0, 32) and j in [0, 8). Base point B = B_TABLE[0][0] + */ + private static final CachedXYT[][] B_TABLE; + private static final CachedXYT[] B2; + + private static final BigInteger P_BI = + BigInteger.valueOf(2).pow(255).subtract(BigInteger.valueOf(19)); + private static final BigInteger D_BI = + BigInteger.valueOf(-121665).multiply(BigInteger.valueOf(121666).modInverse(P_BI)).mod(P_BI); + private static final BigInteger D2_BI = BigInteger.valueOf(2).multiply(D_BI).mod(P_BI); + private static final BigInteger SQRTM1_BI = + BigInteger.valueOf(2).modPow(P_BI.subtract(BigInteger.ONE).divide(BigInteger.valueOf(4)), P_BI); + + private Ed25519() { + } + + private static class Point { + private BigInteger x; + private BigInteger y; + } + + private static BigInteger recoverX(BigInteger y) { + // x^2 = (y^2 - 1) / (d * y^2 + 1) mod 2^255-19 + BigInteger xx = + y.pow(2) + .subtract(BigInteger.ONE) + .multiply(D_BI.multiply(y.pow(2)).add(BigInteger.ONE).modInverse(P_BI)); + BigInteger x = xx.modPow(P_BI.add(BigInteger.valueOf(3)).divide(BigInteger.valueOf(8)), P_BI); + if (!x.pow(2).subtract(xx).mod(P_BI).equals(BigInteger.ZERO)) { + x = x.multiply(SQRTM1_BI).mod(P_BI); + } + if (x.testBit(0)) { + x = P_BI.subtract(x); + } + return x; + } + + private static Point edwards(Point a, Point b) { + Point o = new Point(); + BigInteger xxyy = D_BI.multiply(a.x.multiply(b.x).multiply(a.y).multiply(b.y)).mod(P_BI); + o.x = + (a.x.multiply(b.y).add(b.x.multiply(a.y))) + .multiply(BigInteger.ONE.add(xxyy).modInverse(P_BI)) + .mod(P_BI); + o.y = + (a.y.multiply(b.y).add(a.x.multiply(b.x))) + .multiply(BigInteger.ONE.subtract(xxyy).modInverse(P_BI)) + .mod(P_BI); + return o; + } + + private static byte[] toLittleEndian(BigInteger n) { + byte[] b = new byte[32]; + byte[] nBytes = n.toByteArray(); + System.arraycopy(nBytes, 0, b, 32 - nBytes.length, nBytes.length); + for (int i = 0; i < b.length / 2; i++) { + byte t = b[i]; + b[i] = b[b.length - i - 1]; + b[b.length - i - 1] = t; + } + return b; + } + + private static CachedXYT getCachedXYT(Point p) { + return new CachedXYT( + Field25519.expand(toLittleEndian(p.y.add(p.x).mod(P_BI))), + Field25519.expand(toLittleEndian(p.y.subtract(p.x).mod(P_BI))), + Field25519.expand(toLittleEndian(D2_BI.multiply(p.x).multiply(p.y).mod(P_BI)))); + } + + static { + Point b = new Point(); + b.y = BigInteger.valueOf(4).multiply(BigInteger.valueOf(5).modInverse(P_BI)).mod(P_BI); + b.x = recoverX(b.y); + + D = Field25519.expand(toLittleEndian(D_BI)); + D2 = Field25519.expand(toLittleEndian(D2_BI)); + SQRTM1 = Field25519.expand(toLittleEndian(SQRTM1_BI)); + + Point bi = b; + B_TABLE = new CachedXYT[32][8]; + for (int i = 0; i < 32; i++) { + Point bij = bi; + for (int j = 0; j < 8; j++) { + B_TABLE[i][j] = getCachedXYT(bij); + bij = edwards(bij, bi); + } + for (int j = 0; j < 8; j++) { + bi = edwards(bi, bi); + } + } + bi = b; + Point b2 = edwards(b, b); + B2 = new CachedXYT[8]; + for (int i = 0; i < 8; i++) { + B2[i] = getCachedXYT(bi); + bi = edwards(bi, b2); + } + } + + private static final int PUBLIC_KEY_LEN = Field25519.FIELD_LEN; + private static final int SIGNATURE_LEN = Field25519.FIELD_LEN * 2; + + /** + * Defines field 25519 function based on curve25519-donna C + * implementation (mostly identical). + * + *

Field elements are written as an array of signed, 64-bit limbs (an array of longs), least + * significant first. The value of the field element is: + * + *

+     * x[0] + 2^26·x[1] + 2^51·x[2] + 2^77·x[3] + 2^102·x[4] + 2^128·x[5] + 2^153·x[6] + 2^179·x[7] +
+     * 2^204·x[8] + 2^230·x[9],
+     * 
+ * + *

i.e. the limbs are 26, 25, 26, 25, ... bits wide. + */ + private static final class Field25519 { + /** + * During Field25519 computation, the mixed radix representation may be in different forms: + *

    + *
  • Reduced-size form: the array has size at most 10. + *
  • Non-reduced-size form: the array is not reduced modulo 2^255 - 19 and has size at most + * 19. + *
+ *

+ * TODO(quannguyen): + *

    + *
  • Clarify ill-defined terminologies. + *
  • The reduction procedure is different from DJB's paper + * (http://cr.yp.to/ecdh/curve25519-20060209.pdf). The coefficients after reducing degree and + * reducing coefficients aren't guaranteed to be in range {-2^25, ..., 2^25}. We should check to + * see what's going on. + *
  • Consider using method mult() everywhere and making product() private. + *
+ */ + + static final int FIELD_LEN = 32; + static final int LIMB_CNT = 10; + private static final long TWO_TO_25 = 1 << 25; + private static final long TWO_TO_26 = TWO_TO_25 << 1; + + private static final int[] EXPAND_START = {0, 3, 6, 9, 12, 16, 19, 22, 25, 28}; + private static final int[] EXPAND_SHIFT = {0, 2, 3, 5, 6, 0, 1, 3, 4, 6}; + private static final int[] MASK = {0x3ffffff, 0x1ffffff}; + private static final int[] SHIFT = {26, 25}; + + /** + * Sums two numbers: output = in1 + in2 + *

+ * On entry: in1, in2 are in reduced-size form. + */ + static void sum(long[] output, long[] in1, long[] in2) { + for (int i = 0; i < LIMB_CNT; i++) { + output[i] = in1[i] + in2[i]; + } + } + + /** + * Sums two numbers: output += in + *

+ * On entry: in is in reduced-size form. + */ + static void sum(long[] output, long[] in) { + sum(output, output, in); + } + + /** + * Find the difference of two numbers: output = in1 - in2 + * (note the order of the arguments!). + *

+ * On entry: in1, in2 are in reduced-size form. + */ + static void sub(long[] output, long[] in1, long[] in2) { + for (int i = 0; i < LIMB_CNT; i++) { + output[i] = in1[i] - in2[i]; + } + } + + /** + * Find the difference of two numbers: output = in - output + * (note the order of the arguments!). + *

+ * On entry: in, output are in reduced-size form. + */ + static void sub(long[] output, long[] in) { + sub(output, in, output); + } + + /** + * Multiply a number by a scalar: output = in * scalar + */ + static void scalarProduct(long[] output, long[] in, long scalar) { + for (int i = 0; i < LIMB_CNT; i++) { + output[i] = in[i] * scalar; + } + } + + /** + * Multiply two numbers: out = in2 * in + *

+ * output must be distinct to both inputs. The inputs are reduced coefficient form, + * the output is not. + *

+ * out[x] <= 14 * the largest product of the input limbs. + */ + static void product(long[] out, long[] in2, long[] in) { + out[0] = in2[0] * in[0]; + out[1] = in2[0] * in[1] + + in2[1] * in[0]; + out[2] = 2 * in2[1] * in[1] + + in2[0] * in[2] + + in2[2] * in[0]; + out[3] = in2[1] * in[2] + + in2[2] * in[1] + + in2[0] * in[3] + + in2[3] * in[0]; + out[4] = in2[2] * in[2] + + 2 * (in2[1] * in[3] + in2[3] * in[1]) + + in2[0] * in[4] + + in2[4] * in[0]; + out[5] = in2[2] * in[3] + + in2[3] * in[2] + + in2[1] * in[4] + + in2[4] * in[1] + + in2[0] * in[5] + + in2[5] * in[0]; + out[6] = 2 * (in2[3] * in[3] + in2[1] * in[5] + in2[5] * in[1]) + + in2[2] * in[4] + + in2[4] * in[2] + + in2[0] * in[6] + + in2[6] * in[0]; + out[7] = in2[3] * in[4] + + in2[4] * in[3] + + in2[2] * in[5] + + in2[5] * in[2] + + in2[1] * in[6] + + in2[6] * in[1] + + in2[0] * in[7] + + in2[7] * in[0]; + out[8] = in2[4] * in[4] + + 2 * (in2[3] * in[5] + in2[5] * in[3] + in2[1] * in[7] + in2[7] * in[1]) + + in2[2] * in[6] + + in2[6] * in[2] + + in2[0] * in[8] + + in2[8] * in[0]; + out[9] = in2[4] * in[5] + + in2[5] * in[4] + + in2[3] * in[6] + + in2[6] * in[3] + + in2[2] * in[7] + + in2[7] * in[2] + + in2[1] * in[8] + + in2[8] * in[1] + + in2[0] * in[9] + + in2[9] * in[0]; + out[10] = + 2 * (in2[5] * in[5] + in2[3] * in[7] + in2[7] * in[3] + in2[1] * in[9] + in2[9] * in[1]) + + in2[4] * in[6] + + in2[6] * in[4] + + in2[2] * in[8] + + in2[8] * in[2]; + out[11] = in2[5] * in[6] + + in2[6] * in[5] + + in2[4] * in[7] + + in2[7] * in[4] + + in2[3] * in[8] + + in2[8] * in[3] + + in2[2] * in[9] + + in2[9] * in[2]; + out[12] = in2[6] * in[6] + + 2 * (in2[5] * in[7] + in2[7] * in[5] + in2[3] * in[9] + in2[9] * in[3]) + + in2[4] * in[8] + + in2[8] * in[4]; + out[13] = in2[6] * in[7] + + in2[7] * in[6] + + in2[5] * in[8] + + in2[8] * in[5] + + in2[4] * in[9] + + in2[9] * in[4]; + out[14] = 2 * (in2[7] * in[7] + in2[5] * in[9] + in2[9] * in[5]) + + in2[6] * in[8] + + in2[8] * in[6]; + out[15] = in2[7] * in[8] + + in2[8] * in[7] + + in2[6] * in[9] + + in2[9] * in[6]; + out[16] = in2[8] * in[8] + + 2 * (in2[7] * in[9] + in2[9] * in[7]); + out[17] = in2[8] * in[9] + + in2[9] * in[8]; + out[18] = 2 * in2[9] * in[9]; + } + + /** + * Reduce a field element by calling reduceSizeByModularReduction and reduceCoefficients. + * + * @param input An input array of any length. If the array has 19 elements, it will be used as + * temporary buffer and its contents changed. + * @param output An output array of size LIMB_CNT. After the call |output[i]| < 2^26 will hold. + */ + static void reduce(long[] input, long[] output) { + long[] tmp; + if (input.length == 19) { + tmp = input; + } else { + tmp = new long[19]; + System.arraycopy(input, 0, tmp, 0, input.length); + } + reduceSizeByModularReduction(tmp); + reduceCoefficients(tmp); + System.arraycopy(tmp, 0, output, 0, LIMB_CNT); + } + + /** + * Reduce a long form to a reduced-size form by taking the input mod 2^255 - 19. + *

+ * On entry: |output[i]| < 14*2^54 + * On exit: |output[0..8]| < 280*2^54 + */ + static void reduceSizeByModularReduction(long[] output) { + // The coefficients x[10], x[11],..., x[18] are eliminated by reduction modulo 2^255 - 19. + // For example, the coefficient x[18] is multiplied by 19 and added to the coefficient x[8]. + // + // Each of these shifts and adds ends up multiplying the value by 19. + // + // For output[0..8], the absolute entry value is < 14*2^54 and we add, at most, 19*14*2^54 thus, + // on exit, |output[0..8]| < 280*2^54. + output[8] += output[18] << 4; + output[8] += output[18] << 1; + output[8] += output[18]; + output[7] += output[17] << 4; + output[7] += output[17] << 1; + output[7] += output[17]; + output[6] += output[16] << 4; + output[6] += output[16] << 1; + output[6] += output[16]; + output[5] += output[15] << 4; + output[5] += output[15] << 1; + output[5] += output[15]; + output[4] += output[14] << 4; + output[4] += output[14] << 1; + output[4] += output[14]; + output[3] += output[13] << 4; + output[3] += output[13] << 1; + output[3] += output[13]; + output[2] += output[12] << 4; + output[2] += output[12] << 1; + output[2] += output[12]; + output[1] += output[11] << 4; + output[1] += output[11] << 1; + output[1] += output[11]; + output[0] += output[10] << 4; + output[0] += output[10] << 1; + output[0] += output[10]; + } + + /** + * Reduce all coefficients of the short form input so that |x| < 2^26. + *

+ * On entry: |output[i]| < 280*2^54 + */ + static void reduceCoefficients(long[] output) { + output[10] = 0; + + for (int i = 0; i < LIMB_CNT; i += 2) { + long over = output[i] / TWO_TO_26; + // The entry condition (that |output[i]| < 280*2^54) means that over is, at most, 280*2^28 in + // the first iteration of this loop. This is added to the next limb and we can approximate the + // resulting bound of that limb by 281*2^54. + output[i] -= over << 26; + output[i + 1] += over; + + // For the first iteration, |output[i+1]| < 281*2^54, thus |over| < 281*2^29. When this is + // added to the next limb, the resulting bound can be approximated as 281*2^54. + // + // For subsequent iterations of the loop, 281*2^54 remains a conservative bound and no + // overflow occurs. + over = output[i + 1] / TWO_TO_25; + output[i + 1] -= over << 25; + output[i + 2] += over; + } + // Now |output[10]| < 281*2^29 and all other coefficients are reduced. + output[0] += output[10] << 4; + output[0] += output[10] << 1; + output[0] += output[10]; + + output[10] = 0; + // Now output[1..9] are reduced, and |output[0]| < 2^26 + 19*281*2^29 so |over| will be no more + // than 2^16. + long over = output[0] / TWO_TO_26; + output[0] -= over << 26; + output[1] += over; + // Now output[0,2..9] are reduced, and |output[1]| < 2^25 + 2^16 < 2^26. The bound on + // |output[1]| is sufficient to meet our needs. + } + + /** + * A helpful wrapper around {@ref Field25519#product}: output = in * in2. + *

+ * On entry: |in[i]| < 2^27 and |in2[i]| < 2^27. + *

+ * The output is reduced degree (indeed, one need only provide storage for 10 limbs) and + * |output[i]| < 2^26. + */ + static void mult(long[] output, long[] in, long[] in2) { + long[] t = new long[19]; + product(t, in, in2); + // |t[i]| < 2^26 + reduce(t, output); + } + + /** + * Square a number: out = in**2 + *

+ * output must be distinct from the input. The inputs are reduced coefficient form, the output is + * not. + *

+ * out[x] <= 14 * the largest product of the input limbs. + */ + private static void squareInner(long[] out, long[] in) { + out[0] = in[0] * in[0]; + out[1] = 2 * in[0] * in[1]; + out[2] = 2 * (in[1] * in[1] + in[0] * in[2]); + out[3] = 2 * (in[1] * in[2] + in[0] * in[3]); + out[4] = in[2] * in[2] + + 4 * in[1] * in[3] + + 2 * in[0] * in[4]; + out[5] = 2 * (in[2] * in[3] + in[1] * in[4] + in[0] * in[5]); + out[6] = 2 * (in[3] * in[3] + in[2] * in[4] + in[0] * in[6] + 2 * in[1] * in[5]); + out[7] = 2 * (in[3] * in[4] + in[2] * in[5] + in[1] * in[6] + in[0] * in[7]); + out[8] = in[4] * in[4] + + 2 * (in[2] * in[6] + in[0] * in[8] + 2 * (in[1] * in[7] + in[3] * in[5])); + out[9] = 2 * (in[4] * in[5] + in[3] * in[6] + in[2] * in[7] + in[1] * in[8] + in[0] * in[9]); + out[10] = 2 * (in[5] * in[5] + + in[4] * in[6] + + in[2] * in[8] + + 2 * (in[3] * in[7] + in[1] * in[9])); + out[11] = 2 * (in[5] * in[6] + in[4] * in[7] + in[3] * in[8] + in[2] * in[9]); + out[12] = in[6] * in[6] + + 2 * (in[4] * in[8] + 2 * (in[5] * in[7] + in[3] * in[9])); + out[13] = 2 * (in[6] * in[7] + in[5] * in[8] + in[4] * in[9]); + out[14] = 2 * (in[7] * in[7] + in[6] * in[8] + 2 * in[5] * in[9]); + out[15] = 2 * (in[7] * in[8] + in[6] * in[9]); + out[16] = in[8] * in[8] + 4 * in[7] * in[9]; + out[17] = 2 * in[8] * in[9]; + out[18] = 2 * in[9] * in[9]; + } + + /** + * Returns in^2. + *

+ * On entry: The |in| argument is in reduced coefficients form and |in[i]| < 2^27. + *

+ * On exit: The |output| argument is in reduced coefficients form (indeed, one need only provide + * storage for 10 limbs) and |out[i]| < 2^26. + */ + static void square(long[] output, long[] in) { + long[] t = new long[19]; + squareInner(t, in); + // |t[i]| < 14*2^54 because the largest product of two limbs will be < 2^(27+27) and SquareInner + // adds together, at most, 14 of those products. + reduce(t, output); + } + + /** + * Takes a little-endian, 32-byte number and expands it into mixed radix form. + */ + static long[] expand(byte[] input) { + long[] output = new long[LIMB_CNT]; + for (int i = 0; i < LIMB_CNT; i++) { + output[i] = ((((long) (input[EXPAND_START[i]] & 0xff)) + | ((long) (input[EXPAND_START[i] + 1] & 0xff)) << 8 + | ((long) (input[EXPAND_START[i] + 2] & 0xff)) << 16 + | ((long) (input[EXPAND_START[i] + 3] & 0xff)) << 24) >> EXPAND_SHIFT[i]) & MASK[i & 1]; + } + return output; + } + + /** + * Takes a fully reduced mixed radix form number and contract it into a little-endian, 32-byte + * array. + *

+ * On entry: |input_limbs[i]| < 2^26 + */ + @SuppressWarnings("NarrowingCompoundAssignment") + static byte[] contract(long[] inputLimbs) { + long[] input = Arrays.copyOf(inputLimbs, LIMB_CNT); + for (int j = 0; j < 2; j++) { + for (int i = 0; i < 9; i++) { + // This calculation is a time-invariant way to make input[i] non-negative by borrowing + // from the next-larger limb. + int carry = -(int) ((input[i] & (input[i] >> 31)) >> SHIFT[i & 1]); + input[i] = input[i] + (carry << SHIFT[i & 1]); + input[i + 1] -= carry; + } + + // There's no greater limb for input[9] to borrow from, but we can multiply by 19 and borrow + // from input[0], which is valid mod 2^255-19. + { + int carry = -(int) ((input[9] & (input[9] >> 31)) >> 25); + input[9] += (carry << 25); + input[0] -= (carry * 19); + } + + // After the first iteration, input[1..9] are non-negative and fit within 25 or 26 bits, + // depending on position. However, input[0] may be negative. + } + + // The first borrow-propagation pass above ended with every limb except (possibly) input[0] + // non-negative. + // + // If input[0] was negative after the first pass, then it was because of a carry from input[9]. + // On entry, input[9] < 2^26 so the carry was, at most, one, since (2**26-1) >> 25 = 1. Thus + // input[0] >= -19. + // + // In the second pass, each limb is decreased by at most one. Thus the second borrow-propagation + // pass could only have wrapped around to decrease input[0] again if the first pass left + // input[0] negative *and* input[1] through input[9] were all zero. In that case, input[1] is + // now 2^25 - 1, and this last borrow-propagation step will leave input[1] non-negative. + { + int carry = -(int) ((input[0] & (input[0] >> 31)) >> 26); + input[0] += (carry << 26); + input[1] -= carry; + } + + // All input[i] are now non-negative. However, there might be values between 2^25 and 2^26 in a + // limb which is, nominally, 25 bits wide. + for (int j = 0; j < 2; j++) { + for (int i = 0; i < 9; i++) { + int carry = (int) (input[i] >> SHIFT[i & 1]); + input[i] &= MASK[i & 1]; + input[i + 1] += carry; + } + } + + { + int carry = (int) (input[9] >> 25); + input[9] &= 0x1ffffff; + input[0] += 19 * carry; + } + + // If the first carry-chain pass, just above, ended up with a carry from input[9], and that + // caused input[0] to be out-of-bounds, then input[0] was < 2^26 + 2*19, because the carry was, + // at most, two. + // + // If the second pass carried from input[9] again then input[0] is < 2*19 and the input[9] -> + // input[0] carry didn't push input[0] out of bounds. + + // It still remains the case that input might be between 2^255-19 and 2^255. In this case, + // input[1..9] must take their maximum value and input[0] must be >= (2^255-19) & 0x3ffffff, + // which is 0x3ffffed. + int mask = gte((int) input[0], 0x3ffffed); + for (int i = 1; i < LIMB_CNT; i++) { + mask &= eq((int) input[i], MASK[i & 1]); + } + + // mask is either 0xffffffff (if input >= 2^255-19) and zero otherwise. Thus this conditionally + // subtracts 2^255-19. + input[0] -= mask & 0x3ffffed; + input[1] -= mask & 0x1ffffff; + for (int i = 2; i < LIMB_CNT; i += 2) { + input[i] -= mask & 0x3ffffff; + input[i + 1] -= mask & 0x1ffffff; + } + + for (int i = 0; i < LIMB_CNT; i++) { + input[i] <<= EXPAND_SHIFT[i]; + } + byte[] output = new byte[FIELD_LEN]; + for (int i = 0; i < LIMB_CNT; i++) { + output[EXPAND_START[i]] |= input[i] & 0xff; + output[EXPAND_START[i] + 1] |= (input[i] >> 8) & 0xff; + output[EXPAND_START[i] + 2] |= (input[i] >> 16) & 0xff; + output[EXPAND_START[i] + 3] |= (input[i] >> 24) & 0xff; + } + return output; + } + + /** + * Computes inverse of z = z(2^255 - 21) + *

+ * Shamelessly copied from agl's code which was shamelessly copied from djb's code. Only the + * comment format and the variable namings are different from those. + */ + static void inverse(long[] out, long[] z) { + long[] z2 = new long[Field25519.LIMB_CNT]; + long[] z9 = new long[Field25519.LIMB_CNT]; + long[] z11 = new long[Field25519.LIMB_CNT]; + long[] z2To5Minus1 = new long[Field25519.LIMB_CNT]; + long[] z2To10Minus1 = new long[Field25519.LIMB_CNT]; + long[] z2To20Minus1 = new long[Field25519.LIMB_CNT]; + long[] z2To50Minus1 = new long[Field25519.LIMB_CNT]; + long[] z2To100Minus1 = new long[Field25519.LIMB_CNT]; + long[] t0 = new long[Field25519.LIMB_CNT]; + long[] t1 = new long[Field25519.LIMB_CNT]; + + square(z2, z); // 2 + square(t1, z2); // 4 + square(t0, t1); // 8 + mult(z9, t0, z); // 9 + mult(z11, z9, z2); // 11 + square(t0, z11); // 22 + mult(z2To5Minus1, t0, z9); // 2^5 - 2^0 = 31 + + square(t0, z2To5Minus1); // 2^6 - 2^1 + square(t1, t0); // 2^7 - 2^2 + square(t0, t1); // 2^8 - 2^3 + square(t1, t0); // 2^9 - 2^4 + square(t0, t1); // 2^10 - 2^5 + mult(z2To10Minus1, t0, z2To5Minus1); // 2^10 - 2^0 + + square(t0, z2To10Minus1); // 2^11 - 2^1 + square(t1, t0); // 2^12 - 2^2 + for (int i = 2; i < 10; i += 2) { // 2^20 - 2^10 + square(t0, t1); + square(t1, t0); + } + mult(z2To20Minus1, t1, z2To10Minus1); // 2^20 - 2^0 + + square(t0, z2To20Minus1); // 2^21 - 2^1 + square(t1, t0); // 2^22 - 2^2 + for (int i = 2; i < 20; i += 2) { // 2^40 - 2^20 + square(t0, t1); + square(t1, t0); + } + mult(t0, t1, z2To20Minus1); // 2^40 - 2^0 + + square(t1, t0); // 2^41 - 2^1 + square(t0, t1); // 2^42 - 2^2 + for (int i = 2; i < 10; i += 2) { // 2^50 - 2^10 + square(t1, t0); + square(t0, t1); + } + mult(z2To50Minus1, t0, z2To10Minus1); // 2^50 - 2^0 + + square(t0, z2To50Minus1); // 2^51 - 2^1 + square(t1, t0); // 2^52 - 2^2 + for (int i = 2; i < 50; i += 2) { // 2^100 - 2^50 + square(t0, t1); + square(t1, t0); + } + mult(z2To100Minus1, t1, z2To50Minus1); // 2^100 - 2^0 + + square(t1, z2To100Minus1); // 2^101 - 2^1 + square(t0, t1); // 2^102 - 2^2 + for (int i = 2; i < 100; i += 2) { // 2^200 - 2^100 + square(t1, t0); + square(t0, t1); + } + mult(t1, t0, z2To100Minus1); // 2^200 - 2^0 + + square(t0, t1); // 2^201 - 2^1 + square(t1, t0); // 2^202 - 2^2 + for (int i = 2; i < 50; i += 2) { // 2^250 - 2^50 + square(t0, t1); + square(t1, t0); + } + mult(t0, t1, z2To50Minus1); // 2^250 - 2^0 + + square(t1, t0); // 2^251 - 2^1 + square(t0, t1); // 2^252 - 2^2 + square(t1, t0); // 2^253 - 2^3 + square(t0, t1); // 2^254 - 2^4 + square(t1, t0); // 2^255 - 2^5 + mult(out, t1, z11); // 2^255 - 21 + } + + + /** + * Returns 0xffffffff iff a == b and zero otherwise. + */ + private static int eq(int a, int b) { + a = ~(a ^ b); + a &= a << 16; + a &= a << 8; + a &= a << 4; + a &= a << 2; + a &= a << 1; + return a >> 31; + } + + /** + * returns 0xffffffff if a >= b and zero otherwise, where a and b are both non-negative. + */ + private static int gte(int a, int b) { + a -= b; + // a >= 0 iff a >= b. + return ~(a >> 31); + } + } + + // (x = 0, y = 1) point + private static final CachedXYT CACHED_NEUTRAL = new CachedXYT( + new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + new long[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + private static final PartialXYZT NEUTRAL = new PartialXYZT( + new XYZ(new long[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0}), + new long[]{1, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + + /** + * Projective point representation (X:Y:Z) satisfying x = X/Z, y = Y/Z + *

+ * Note that this is referred as ge_p2 in ref10 impl. + * Also note that x = X, y = Y and z = Z below following Java coding style. + *

+ * See + * Koyama K., Tsuruoka Y. (1993) Speeding up Elliptic Cryptosystems by Using a Signed Binary + * Window Method. + *

+ * https://hyperelliptic.org/EFD/g1p/auto-twisted-projective.html + */ + private static class XYZ { + + final long[] x; + final long[] y; + final long[] z; + + XYZ() { + this(new long[Field25519.LIMB_CNT], new long[Field25519.LIMB_CNT], new long[Field25519.LIMB_CNT]); + } + + XYZ(long[] x, long[] y, long[] z) { + this.x = x; + this.y = y; + this.z = z; + } + + XYZ(XYZ xyz) { + x = Arrays.copyOf(xyz.x, Field25519.LIMB_CNT); + y = Arrays.copyOf(xyz.y, Field25519.LIMB_CNT); + z = Arrays.copyOf(xyz.z, Field25519.LIMB_CNT); + } + + XYZ(PartialXYZT partialXYZT) { + this(); + fromPartialXYZT(this, partialXYZT); + } + + /** + * ge_p1p1_to_p2.c + */ + static XYZ fromPartialXYZT(XYZ out, PartialXYZT in) { + Field25519.mult(out.x, in.xyz.x, in.t); + Field25519.mult(out.y, in.xyz.y, in.xyz.z); + Field25519.mult(out.z, in.xyz.z, in.t); + return out; + } + + /** + * Encodes this point to bytes. + */ + byte[] toBytes() { + long[] recip = new long[Field25519.LIMB_CNT]; + long[] x = new long[Field25519.LIMB_CNT]; + long[] y = new long[Field25519.LIMB_CNT]; + Field25519.inverse(recip, z); + Field25519.mult(x, this.x, recip); + Field25519.mult(y, this.y, recip); + byte[] s = Field25519.contract(y); + s[31] = (byte) (s[31] ^ (getLsb(x) << 7)); + return s; + } + + + /** + * Best effort fix-timing array comparison. + * + * @return true if two arrays are equal. + */ + private static boolean bytesEqual(final byte[] x, final byte[] y) { + if (x == null || y == null) { + return false; + } + if (x.length != y.length) { + return false; + } + int res = 0; + for (int i = 0; i < x.length; i++) { + res |= x[i] ^ y[i]; + } + return res == 0; + } + + /** + * Checks that the point is on curve + */ + boolean isOnCurve() { + long[] x2 = new long[Field25519.LIMB_CNT]; + Field25519.square(x2, x); + long[] y2 = new long[Field25519.LIMB_CNT]; + Field25519.square(y2, y); + long[] z2 = new long[Field25519.LIMB_CNT]; + Field25519.square(z2, z); + long[] z4 = new long[Field25519.LIMB_CNT]; + Field25519.square(z4, z2); + long[] lhs = new long[Field25519.LIMB_CNT]; + // lhs = y^2 - x^2 + Field25519.sub(lhs, y2, x2); + // lhs = z^2 * (y2 - x2) + Field25519.mult(lhs, lhs, z2); + long[] rhs = new long[Field25519.LIMB_CNT]; + // rhs = x^2 * y^2 + Field25519.mult(rhs, x2, y2); + // rhs = D * x^2 * y^2 + Field25519.mult(rhs, rhs, D); + // rhs = z^4 + D * x^2 * y^2 + Field25519.sum(rhs, z4); + // Field25519.mult reduces its output, but Field25519.sum does not, so we have to manually + // reduce it here. + Field25519.reduce(rhs, rhs); + // z^2 (y^2 - x^2) == z^4 + D * x^2 * y^2 + return bytesEqual(Field25519.contract(lhs), Field25519.contract(rhs)); + } + } + + /** + * Represents extended projective point representation (X:Y:Z:T) satisfying x = X/Z, y = Y/Z, + * XY = ZT + *

+ * Note that this is referred as ge_p3 in ref10 impl. + * Also note that t = T below following Java coding style. + *

+ * See + * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited. + *

+ * https://hyperelliptic.org/EFD/g1p/auto-twisted-extended.html + */ + private static class XYZT { + + final XYZ xyz; + final long[] t; + + XYZT() { + this(new XYZ(), new long[Field25519.LIMB_CNT]); + } + + XYZT(XYZ xyz, long[] t) { + this.xyz = xyz; + this.t = t; + } + + XYZT(PartialXYZT partialXYZT) { + this(); + fromPartialXYZT(this, partialXYZT); + } + + /** + * ge_p1p1_to_p2.c + */ + private static XYZT fromPartialXYZT(XYZT out, PartialXYZT in) { + Field25519.mult(out.xyz.x, in.xyz.x, in.t); + Field25519.mult(out.xyz.y, in.xyz.y, in.xyz.z); + Field25519.mult(out.xyz.z, in.xyz.z, in.t); + Field25519.mult(out.t, in.xyz.x, in.xyz.y); + return out; + } + + /** + * Decodes {@code s} into an extented projective point. + * See Section 5.1.3 Decoding in https://tools.ietf.org/html/rfc8032#section-5.1.3 + */ + private static XYZT fromBytesNegateVarTime(byte[] s) throws GeneralSecurityException { + long[] x = new long[Field25519.LIMB_CNT]; + long[] y = Field25519.expand(s); + long[] z = new long[Field25519.LIMB_CNT]; + z[0] = 1; + long[] t = new long[Field25519.LIMB_CNT]; + long[] u = new long[Field25519.LIMB_CNT]; + long[] v = new long[Field25519.LIMB_CNT]; + long[] vxx = new long[Field25519.LIMB_CNT]; + long[] check = new long[Field25519.LIMB_CNT]; + Field25519.square(u, y); + Field25519.mult(v, u, D); + Field25519.sub(u, u, z); // u = y^2 - 1 + Field25519.sum(v, v, z); // v = dy^2 + 1 + + long[] v3 = new long[Field25519.LIMB_CNT]; + Field25519.square(v3, v); + Field25519.mult(v3, v3, v); // v3 = v^3 + Field25519.square(x, v3); + Field25519.mult(x, x, v); + Field25519.mult(x, x, u); // x = uv^7 + + pow2252m3(x, x); // x = (uv^7)^((q-5)/8) + Field25519.mult(x, x, v3); + Field25519.mult(x, x, u); // x = uv^3(uv^7)^((q-5)/8) + + Field25519.square(vxx, x); + Field25519.mult(vxx, vxx, v); + Field25519.sub(check, vxx, u); // vx^2-u + if (isNonZeroVarTime(check)) { + Field25519.sum(check, vxx, u); // vx^2+u + if (isNonZeroVarTime(check)) { + throw new GeneralSecurityException("Cannot convert given bytes to extended projective " + + "coordinates. No square root exists for modulo 2^255-19"); + } + Field25519.mult(x, x, SQRTM1); + } + + if (!isNonZeroVarTime(x) && (s[31] & 0xff) >> 7 != 0) { + throw new GeneralSecurityException("Cannot convert given bytes to extended projective " + + "coordinates. Computed x is zero and encoded x's least significant bit is not zero"); + } + if (getLsb(x) == ((s[31] & 0xff) >> 7)) { + neg(x, x); + } + + Field25519.mult(t, x, y); + return new XYZT(new XYZ(x, y, z), t); + } + } + + /** + * Partial projective point representation ((X:Z),(Y:T)) satisfying x=X/Z, y=Y/T + *

+ * Note that this is referred as complete form in the original ref10 impl (ge_p1p1). + * Also note that t = T below following Java coding style. + *

+ * Although this has the same types as XYZT, it is redefined to have its own type so that it is + * readable and 1:1 corresponds to ref10 impl. + *

+ * Can be converted to XYZT as follows: + * X1 = X * T = x * Z * T = x * Z1 + * Y1 = Y * Z = y * T * Z = y * Z1 + * Z1 = Z * T = Z * T + * T1 = X * Y = x * Z * y * T = x * y * Z1 = X1Y1 / Z1 + */ + private static class PartialXYZT { + + final XYZ xyz; + final long[] t; + + PartialXYZT() { + this(new XYZ(), new long[Field25519.LIMB_CNT]); + } + + PartialXYZT(XYZ xyz, long[] t) { + this.xyz = xyz; + this.t = t; + } + + PartialXYZT(PartialXYZT other) { + xyz = new XYZ(other.xyz); + t = Arrays.copyOf(other.t, Field25519.LIMB_CNT); + } + } + + /** + * Corresponds to the caching mentioned in the last paragraph of Section 3.1 of + * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited. + * with Z = 1. + */ + private static class CachedXYT { + + final long[] yPlusX; + final long[] yMinusX; + final long[] t2d; + + /** + * Creates a cached XYZT with Z = 1 + * + * @param yPlusX y + x + * @param yMinusX y - x + * @param t2d 2d * xy + */ + CachedXYT(long[] yPlusX, long[] yMinusX, long[] t2d) { + this.yPlusX = yPlusX; + this.yMinusX = yMinusX; + this.t2d = t2d; + } + + CachedXYT(CachedXYT other) { + yPlusX = Arrays.copyOf(other.yPlusX, Field25519.LIMB_CNT); + yMinusX = Arrays.copyOf(other.yMinusX, Field25519.LIMB_CNT); + t2d = Arrays.copyOf(other.t2d, Field25519.LIMB_CNT); + } + + // z is one implicitly, so this just copies {@code in} to {@code output}. + void multByZ(long[] output, long[] in) { + System.arraycopy(in, 0, output, 0, Field25519.LIMB_CNT); + } + + /** + * If icopy is 1, copies {@code other} into this point. Time invariant wrt to icopy value. + */ + void copyConditional(CachedXYT other, int icopy) { + copyConditional(yPlusX, other.yPlusX, icopy); + copyConditional(yMinusX, other.yMinusX, icopy); + copyConditional(t2d, other.t2d, icopy); + } + + /** + * Conditionally copies a reduced-form limb arrays {@code b} into {@code a} if {@code icopy} is 1, + * but leave {@code a} unchanged if 'iswap' is 0. Runs in data-invariant time to avoid + * side-channel attacks. + * + *

NOTE that this function requires that {@code icopy} be 1 or 0; other values give wrong + * results. Also, the two limb arrays must be in reduced-coefficient, reduced-degree form: the + * values in a[10..19] or b[10..19] aren't swapped, and all all values in a[0..9],b[0..9] must + * have magnitude less than Integer.MAX_VALUE. + */ + static void copyConditional(long[] a, long[] b, int icopy) { + int copy = -icopy; + for (int i = 0; i < Field25519.LIMB_CNT; i++) { + int x = copy & (((int) a[i]) ^ ((int) b[i])); + a[i] = ((int) a[i]) ^ x; + } + } + } + + private static class CachedXYZT extends CachedXYT { + + private final long[] z; + + CachedXYZT() { + this(new long[Field25519.LIMB_CNT], new long[Field25519.LIMB_CNT], new long[Field25519.LIMB_CNT], new long[Field25519.LIMB_CNT]); + } + + /** + * ge_p3_to_cached.c + */ + CachedXYZT(XYZT xyzt) { + this(); + Field25519.sum(yPlusX, xyzt.xyz.y, xyzt.xyz.x); + Field25519.sub(yMinusX, xyzt.xyz.y, xyzt.xyz.x); + System.arraycopy(xyzt.xyz.z, 0, z, 0, Field25519.LIMB_CNT); + Field25519.mult(t2d, xyzt.t, D2); + } + + /** + * Creates a cached XYZT + * + * @param yPlusX Y + X + * @param yMinusX Y - X + * @param z Z + * @param t2d 2d * (XY/Z) + */ + CachedXYZT(long[] yPlusX, long[] yMinusX, long[] z, long[] t2d) { + super(yPlusX, yMinusX, t2d); + this.z = z; + } + + @Override + public void multByZ(long[] output, long[] in) { + Field25519.mult(output, in, z); + } + } + + /** + * Addition defined in Section 3.1 of + * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited. + *

+ * Please note that this is a partial of the operation listed there leaving out the final + * conversion from PartialXYZT to XYZT. + * + * @param extended extended projective point input + * @param cached cached projective point input + */ + private static void add(PartialXYZT partialXYZT, XYZT extended, CachedXYT cached) { + long[] t = new long[Field25519.LIMB_CNT]; + + // Y1 + X1 + Field25519.sum(partialXYZT.xyz.x, extended.xyz.y, extended.xyz.x); + + // Y1 - X1 + Field25519.sub(partialXYZT.xyz.y, extended.xyz.y, extended.xyz.x); + + // A = (Y1 - X1) * (Y2 - X2) + Field25519.mult(partialXYZT.xyz.y, partialXYZT.xyz.y, cached.yMinusX); + + // B = (Y1 + X1) * (Y2 + X2) + Field25519.mult(partialXYZT.xyz.z, partialXYZT.xyz.x, cached.yPlusX); + + // C = T1 * 2d * T2 = 2d * T1 * T2 (2d is written as k in the paper) + Field25519.mult(partialXYZT.t, extended.t, cached.t2d); + + // Z1 * Z2 + cached.multByZ(partialXYZT.xyz.x, extended.xyz.z); + + // D = 2 * Z1 * Z2 + Field25519.sum(t, partialXYZT.xyz.x, partialXYZT.xyz.x); + + // X3 = B - A + Field25519.sub(partialXYZT.xyz.x, partialXYZT.xyz.z, partialXYZT.xyz.y); + + // Y3 = B + A + Field25519.sum(partialXYZT.xyz.y, partialXYZT.xyz.z, partialXYZT.xyz.y); + + // Z3 = D + C + Field25519.sum(partialXYZT.xyz.z, t, partialXYZT.t); + + // T3 = D - C + Field25519.sub(partialXYZT.t, t, partialXYZT.t); + } + + /** + * Based on the addition defined in Section 3.1 of + * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited. + *

+ * Please note that this is a partial of the operation listed there leaving out the final + * conversion from PartialXYZT to XYZT. + * + * @param extended extended projective point input + * @param cached cached projective point input + */ + private static void sub(PartialXYZT partialXYZT, XYZT extended, CachedXYT cached) { + long[] t = new long[Field25519.LIMB_CNT]; + + // Y1 + X1 + Field25519.sum(partialXYZT.xyz.x, extended.xyz.y, extended.xyz.x); + + // Y1 - X1 + Field25519.sub(partialXYZT.xyz.y, extended.xyz.y, extended.xyz.x); + + // A = (Y1 - X1) * (Y2 + X2) + Field25519.mult(partialXYZT.xyz.y, partialXYZT.xyz.y, cached.yPlusX); + + // B = (Y1 + X1) * (Y2 - X2) + Field25519.mult(partialXYZT.xyz.z, partialXYZT.xyz.x, cached.yMinusX); + + // C = T1 * 2d * T2 = 2d * T1 * T2 (2d is written as k in the paper) + Field25519.mult(partialXYZT.t, extended.t, cached.t2d); + + // Z1 * Z2 + cached.multByZ(partialXYZT.xyz.x, extended.xyz.z); + + // D = 2 * Z1 * Z2 + Field25519.sum(t, partialXYZT.xyz.x, partialXYZT.xyz.x); + + // X3 = B - A + Field25519.sub(partialXYZT.xyz.x, partialXYZT.xyz.z, partialXYZT.xyz.y); + + // Y3 = B + A + Field25519.sum(partialXYZT.xyz.y, partialXYZT.xyz.z, partialXYZT.xyz.y); + + // Z3 = D - C + Field25519.sub(partialXYZT.xyz.z, t, partialXYZT.t); + + // T3 = D + C + Field25519.sum(partialXYZT.t, t, partialXYZT.t); + } + + /** + * Doubles {@code p} and puts the result into this PartialXYZT. + *

+ * This is based on the addition defined in formula 7 in Section 3.3 of + * Hisil H., Wong K.KH., Carter G., Dawson E. (2008) Twisted Edwards Curves Revisited. + *

+ * Please note that this is a partial of the operation listed there leaving out the final + * conversion from PartialXYZT to XYZT and also this fixes a typo in calculation of Y3 and T3 in + * the paper, H should be replaced with A+B. + */ + private static void doubleXYZ(PartialXYZT partialXYZT, XYZ p) { + long[] t0 = new long[Field25519.LIMB_CNT]; + + // XX = X1^2 + Field25519.square(partialXYZT.xyz.x, p.x); + + // YY = Y1^2 + Field25519.square(partialXYZT.xyz.z, p.y); + + // B' = Z1^2 + Field25519.square(partialXYZT.t, p.z); + + // B = 2 * B' + Field25519.sum(partialXYZT.t, partialXYZT.t, partialXYZT.t); + + // A = X1 + Y1 + Field25519.sum(partialXYZT.xyz.y, p.x, p.y); + + // AA = A^2 + Field25519.square(t0, partialXYZT.xyz.y); + + // Y3 = YY + XX + Field25519.sum(partialXYZT.xyz.y, partialXYZT.xyz.z, partialXYZT.xyz.x); + + // Z3 = YY - XX + Field25519.sub(partialXYZT.xyz.z, partialXYZT.xyz.z, partialXYZT.xyz.x); + + // X3 = AA - Y3 + Field25519.sub(partialXYZT.xyz.x, t0, partialXYZT.xyz.y); + + // T3 = B - Z3 + Field25519.sub(partialXYZT.t, partialXYZT.t, partialXYZT.xyz.z); + } + + /** + * Doubles {@code p} and puts the result into this PartialXYZT. + */ + private static void doubleXYZT(PartialXYZT partialXYZT, XYZT p) { + doubleXYZ(partialXYZT, p.xyz); + } + + /** + * Compares two byte values in constant time. + */ + private static int eq(int a, int b) { + int r = ~(a ^ b) & 0xff; + r &= r << 4; + r &= r << 2; + r &= r << 1; + return (r >> 7) & 1; + } + + /** + * This is a constant time operation where point b*B*256^pos is stored in {@code t}. + * When b is 0, t remains the same (i.e., neutral point). + *

+ * Although B_TABLE[32][8] (B_TABLE[i][j] = (j+1)*B*256^i) has j values in [0, 7], the select + * method negates the corresponding point if b is negative (which is straight forward in elliptic + * curves by just negating y coordinate). Therefore we can get multiples of B with the half of + * memory requirements. + * + * @param t neutral element (i.e., point 0), also serves as output. + * @param pos in B[pos][j] = (j+1)*B*256^pos + * @param b value in [-8, 8] range. + */ + private static void select(CachedXYT t, int pos, byte b) { + int bnegative = (b & 0xff) >> 7; + int babs = b - (((-bnegative) & b) << 1); + + t.copyConditional(B_TABLE[pos][0], eq(babs, 1)); + t.copyConditional(B_TABLE[pos][1], eq(babs, 2)); + t.copyConditional(B_TABLE[pos][2], eq(babs, 3)); + t.copyConditional(B_TABLE[pos][3], eq(babs, 4)); + t.copyConditional(B_TABLE[pos][4], eq(babs, 5)); + t.copyConditional(B_TABLE[pos][5], eq(babs, 6)); + t.copyConditional(B_TABLE[pos][6], eq(babs, 7)); + t.copyConditional(B_TABLE[pos][7], eq(babs, 8)); + + long[] yPlusX = Arrays.copyOf(t.yMinusX, Field25519.LIMB_CNT); + long[] yMinusX = Arrays.copyOf(t.yPlusX, Field25519.LIMB_CNT); + long[] t2d = Arrays.copyOf(t.t2d, Field25519.LIMB_CNT); + neg(t2d, t2d); + CachedXYT minust = new CachedXYT(yPlusX, yMinusX, t2d); + t.copyConditional(minust, bnegative); + } + + /** + * Computes {@code a}*B + * where a = a[0]+256*a[1]+...+256^31 a[31] and + * B is the Ed25519 base point (x,4/5) with x positive. + *

+ * Preconditions: + * a[31] <= 127 + * + * @throws IllegalStateException iff there is arithmetic error. + */ + @SuppressWarnings("NarrowingCompoundAssignment") + private static XYZ scalarMultWithBase(byte[] a) { + byte[] e = new byte[2 * Field25519.FIELD_LEN]; + for (int i = 0; i < Field25519.FIELD_LEN; i++) { + e[2 * i + 0] = (byte) (((a[i] & 0xff) >> 0) & 0xf); + e[2 * i + 1] = (byte) (((a[i] & 0xff) >> 4) & 0xf); + } + // each e[i] is between 0 and 15 + // e[63] is between 0 and 7 + + // Rewrite e in a way that each e[i] is in [-8, 8]. + // This can be done since a[63] is in [0, 7], the carry-over onto the most significant byte + // a[63] can be at most 1. + int carry = 0; + for (int i = 0; i < e.length - 1; i++) { + e[i] += carry; + carry = e[i] + 8; + carry >>= 4; + e[i] -= carry << 4; + } + e[e.length - 1] += carry; + + PartialXYZT ret = new PartialXYZT(NEUTRAL); + XYZT xyzt = new XYZT(); + // Although B_TABLE's i can be at most 31 (stores only 32 4bit multiples of B) and we have 64 + // 4bit values in e array, the below for loop adds cached values by iterating e by two in odd + // indices. After the result, we can double the result point 4 times to shift the multiplication + // scalar by 4 bits. + for (int i = 1; i < e.length; i += 2) { + CachedXYT t = new CachedXYT(CACHED_NEUTRAL); + select(t, i / 2, e[i]); + add(ret, XYZT.fromPartialXYZT(xyzt, ret), t); + } + + // Doubles the result 4 times to shift the multiplication scalar 4 bits to get the actual result + // for the odd indices in e. + XYZ xyz = new XYZ(); + doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret)); + doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret)); + doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret)); + doubleXYZ(ret, XYZ.fromPartialXYZT(xyz, ret)); + + // Add multiples of B for even indices of e. + for (int i = 0; i < e.length; i += 2) { + CachedXYT t = new CachedXYT(CACHED_NEUTRAL); + select(t, i / 2, e[i]); + add(ret, XYZT.fromPartialXYZT(xyzt, ret), t); + } + + // This check is to protect against flaws, i.e. if there is a computation error through a + // faulty CPU or if the implementation contains a bug. + XYZ result = new XYZ(ret); + if (!result.isOnCurve()) { + throw new IllegalStateException("arithmetic error in scalar multiplication"); + } + return result; + } + + @SuppressWarnings("NarrowingCompoundAssignment") + private static byte[] slide(byte[] a) { + byte[] r = new byte[256]; + // Writes each bit in a[0..31] into r[0..255]: + // a = a[0]+256*a[1]+...+256^31*a[31] is equal to + // r = r[0]+2*r[1]+...+2^255*r[255] + for (int i = 0; i < 256; i++) { + r[i] = (byte) (1 & ((a[i >> 3] & 0xff) >> (i & 7))); + } + + // Transforms r[i] as odd values in [-15, 15] + for (int i = 0; i < 256; i++) { + if (r[i] != 0) { + for (int b = 1; b <= 6 && i + b < 256; b++) { + if (r[i + b] != 0) { + if (r[i] + (r[i + b] << b) <= 15) { + r[i] += r[i + b] << b; + r[i + b] = 0; + } else if (r[i] - (r[i + b] << b) >= -15) { + r[i] -= r[i + b] << b; + for (int k = i + b; k < 256; k++) { + if (r[k] == 0) { + r[k] = 1; + break; + } + r[k] = 0; + } + } else { + break; + } + } + } + } + } + return r; + } + + /** + * Computes {@code a}*{@code pointA}+{@code b}*B + * where a = a[0]+256*a[1]+...+256^31*a[31]. + * and b = b[0]+256*b[1]+...+256^31*b[31]. + * B is the Ed25519 base point (x,4/5) with x positive. + *

+ * Note that execution time varies based on the input since this will only be used in verification + * of signatures. + */ + private static XYZ doubleScalarMultVarTime(byte[] a, XYZT pointA, byte[] b) { + // pointA, 3*pointA, 5*pointA, 7*pointA, 9*pointA, 11*pointA, 13*pointA, 15*pointA + CachedXYZT[] pointAArray = new CachedXYZT[8]; + pointAArray[0] = new CachedXYZT(pointA); + PartialXYZT t = new PartialXYZT(); + doubleXYZT(t, pointA); + XYZT doubleA = new XYZT(t); + for (int i = 1; i < pointAArray.length; i++) { + add(t, doubleA, pointAArray[i - 1]); + pointAArray[i] = new CachedXYZT(new XYZT(t)); + } + + byte[] aSlide = slide(a); + byte[] bSlide = slide(b); + t = new PartialXYZT(NEUTRAL); + XYZT u = new XYZT(); + int i = 255; + for (; i >= 0; i--) { + if (aSlide[i] != 0 || bSlide[i] != 0) { + break; + } + } + for (; i >= 0; i--) { + doubleXYZ(t, new XYZ(t)); + if (aSlide[i] > 0) { + add(t, XYZT.fromPartialXYZT(u, t), pointAArray[aSlide[i] / 2]); + } else if (aSlide[i] < 0) { + sub(t, XYZT.fromPartialXYZT(u, t), pointAArray[-aSlide[i] / 2]); + } + if (bSlide[i] > 0) { + add(t, XYZT.fromPartialXYZT(u, t), B2[bSlide[i] / 2]); + } else if (bSlide[i] < 0) { + sub(t, XYZT.fromPartialXYZT(u, t), B2[-bSlide[i] / 2]); + } + } + + return new XYZ(t); + } + + /** + * Returns true if {@code in} is nonzero. + *

+ * Note that execution time might depend on the input {@code in}. + */ + private static boolean isNonZeroVarTime(long[] in) { + long[] inCopy = new long[in.length + 1]; + System.arraycopy(in, 0, inCopy, 0, in.length); + Field25519.reduceCoefficients(inCopy); + byte[] bytes = Field25519.contract(inCopy); + for (byte b : bytes) { + if (b != 0) { + return true; + } + } + return false; + } + + /** + * Returns the least significant bit of {@code in}. + */ + private static int getLsb(long[] in) { + return Field25519.contract(in)[0] & 1; + } + + /** + * Negates all values in {@code in} and store it in {@code out}. + */ + private static void neg(long[] out, long[] in) { + for (int i = 0; i < in.length; i++) { + out[i] = -in[i]; + } + } + + /** + * Computes {@code in}^(2^252-3) mod 2^255-19 and puts the result in {@code out}. + */ + private static void pow2252m3(long[] out, long[] in) { + long[] t0 = new long[Field25519.LIMB_CNT]; + long[] t1 = new long[Field25519.LIMB_CNT]; + long[] t2 = new long[Field25519.LIMB_CNT]; + + // z2 = z1^2^1 + Field25519.square(t0, in); + + // z8 = z2^2^2 + Field25519.square(t1, t0); + for (int i = 1; i < 2; i++) { + Field25519.square(t1, t1); + } + + // z9 = z1*z8 + Field25519.mult(t1, in, t1); + + // z11 = z2*z9 + Field25519.mult(t0, t0, t1); + + // z22 = z11^2^1 + Field25519.square(t0, t0); + + // z_5_0 = z9*z22 + Field25519.mult(t0, t1, t0); + + // z_10_5 = z_5_0^2^5 + Field25519.square(t1, t0); + for (int i = 1; i < 5; i++) { + Field25519.square(t1, t1); + } + + // z_10_0 = z_10_5*z_5_0 + Field25519.mult(t0, t1, t0); + + // z_20_10 = z_10_0^2^10 + Field25519.square(t1, t0); + for (int i = 1; i < 10; i++) { + Field25519.square(t1, t1); + } + + // z_20_0 = z_20_10*z_10_0 + Field25519.mult(t1, t1, t0); + + // z_40_20 = z_20_0^2^20 + Field25519.square(t2, t1); + for (int i = 1; i < 20; i++) { + Field25519.square(t2, t2); + } + + // z_40_0 = z_40_20*z_20_0 + Field25519.mult(t1, t2, t1); + + // z_50_10 = z_40_0^2^10 + Field25519.square(t1, t1); + for (int i = 1; i < 10; i++) { + Field25519.square(t1, t1); + } + + // z_50_0 = z_50_10*z_10_0 + Field25519.mult(t0, t1, t0); + + // z_100_50 = z_50_0^2^50 + Field25519.square(t1, t0); + for (int i = 1; i < 50; i++) { + Field25519.square(t1, t1); + } + + // z_100_0 = z_100_50*z_50_0 + Field25519.mult(t1, t1, t0); + + // z_200_100 = z_100_0^2^100 + Field25519.square(t2, t1); + for (int i = 1; i < 100; i++) { + Field25519.square(t2, t2); + } + + // z_200_0 = z_200_100*z_100_0 + Field25519.mult(t1, t2, t1); + + // z_250_50 = z_200_0^2^50 + Field25519.square(t1, t1); + for (int i = 1; i < 50; i++) { + Field25519.square(t1, t1); + } + + // z_250_0 = z_250_50*z_50_0 + Field25519.mult(t0, t1, t0); + + // z_252_2 = z_250_0^2^2 + Field25519.square(t0, t0); + for (int i = 1; i < 2; i++) { + Field25519.square(t0, t0); + } + + // z_252_3 = z_252_2*z1 + Field25519.mult(out, t0, in); + } + + /** + * Returns 3 bytes of {@code in} starting from {@code idx} in Little-Endian format. + */ + private static long load3(byte[] in, int idx) { + long result; + result = (long) in[idx] & 0xff; + result |= (long) (in[idx + 1] & 0xff) << 8; + result |= (long) (in[idx + 2] & 0xff) << 16; + return result; + } + + /** + * Returns 4 bytes of {@code in} starting from {@code idx} in Little-Endian format. + */ + private static long load4(byte[] in, int idx) { + long result = load3(in, idx); + result |= (long) (in[idx + 3] & 0xff) << 24; + return result; + } + + /** + * Input: + * s[0]+256*s[1]+...+256^63*s[63] = s + *

+ * Output: + * s[0]+256*s[1]+...+256^31*s[31] = s mod l + * where l = 2^252 + 27742317777372353535851937790883648493. + * Overwrites s in place. + */ + private static void reduce(byte[] s) { + // Observation: + // 2^252 mod l is equivalent to -27742317777372353535851937790883648493 mod l + // Let m = -27742317777372353535851937790883648493 + // Thus a*2^252+b mod l is equivalent to a*m+b mod l + // + // First s is divided into chunks of 21 bits as follows: + // s0+2^21*s1+2^42*s3+...+2^462*s23 = s[0]+256*s[1]+...+256^63*s[63] + long s0 = 2097151 & load3(s, 0); + long s1 = 2097151 & (load4(s, 2) >> 5); + long s2 = 2097151 & (load3(s, 5) >> 2); + long s3 = 2097151 & (load4(s, 7) >> 7); + long s4 = 2097151 & (load4(s, 10) >> 4); + long s5 = 2097151 & (load3(s, 13) >> 1); + long s6 = 2097151 & (load4(s, 15) >> 6); + long s7 = 2097151 & (load3(s, 18) >> 3); + long s8 = 2097151 & load3(s, 21); + long s9 = 2097151 & (load4(s, 23) >> 5); + long s10 = 2097151 & (load3(s, 26) >> 2); + long s11 = 2097151 & (load4(s, 28) >> 7); + long s12 = 2097151 & (load4(s, 31) >> 4); + long s13 = 2097151 & (load3(s, 34) >> 1); + long s14 = 2097151 & (load4(s, 36) >> 6); + long s15 = 2097151 & (load3(s, 39) >> 3); + long s16 = 2097151 & load3(s, 42); + long s17 = 2097151 & (load4(s, 44) >> 5); + long s18 = 2097151 & (load3(s, 47) >> 2); + long s19 = 2097151 & (load4(s, 49) >> 7); + long s20 = 2097151 & (load4(s, 52) >> 4); + long s21 = 2097151 & (load3(s, 55) >> 1); + long s22 = 2097151 & (load4(s, 57) >> 6); + long s23 = (load4(s, 60) >> 3); + long carry0; + long carry1; + long carry2; + long carry3; + long carry4; + long carry5; + long carry6; + long carry7; + long carry8; + long carry9; + long carry10; + long carry11; + long carry12; + long carry13; + long carry14; + long carry15; + long carry16; + + // s23*2^462 = s23*2^210*2^252 is equivalent to s23*2^210*m in mod l + // As m is a 125 bit number, the result needs to scattered to 6 limbs (125/21 ceil is 6) + // starting from s11 (s11*2^210) + // m = [666643, 470296, 654183, -997805, 136657, -683901] in 21-bit limbs + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + // s23 = 0; + + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + // s22 = 0; + + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + // s21 = 0; + + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + // s20 = 0; + + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + // s19 = 0; + + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + // s18 = 0; + + // Reduce the bit length of limbs from s6 to s15 to 21-bits. + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= carry16 << 21; + + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= carry15 << 21; + + // Resume reduction where we left off. + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + // s17 = 0; + + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + // s16 = 0; + + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + // s15 = 0; + + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + // s14 = 0; + + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + // s13 = 0; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + // Reduce the range of limbs from s0 to s11 to 21-bits. + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + // Carry chain reduction to propagate excess bits from s0 to s5 to the most significant limbs. + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry11 = s11 >> 21; + s12 += carry11; + s11 -= carry11 << 21; + + // Do one last reduction as s12 might be 1. + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + // s12 = 0; + + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + + // Serialize the result into the s. + s[0] = (byte) s0; + s[1] = (byte) (s0 >> 8); + s[2] = (byte) ((s0 >> 16) | (s1 << 5)); + s[3] = (byte) (s1 >> 3); + s[4] = (byte) (s1 >> 11); + s[5] = (byte) ((s1 >> 19) | (s2 << 2)); + s[6] = (byte) (s2 >> 6); + s[7] = (byte) ((s2 >> 14) | (s3 << 7)); + s[8] = (byte) (s3 >> 1); + s[9] = (byte) (s3 >> 9); + s[10] = (byte) ((s3 >> 17) | (s4 << 4)); + s[11] = (byte) (s4 >> 4); + s[12] = (byte) (s4 >> 12); + s[13] = (byte) ((s4 >> 20) | (s5 << 1)); + s[14] = (byte) (s5 >> 7); + s[15] = (byte) ((s5 >> 15) | (s6 << 6)); + s[16] = (byte) (s6 >> 2); + s[17] = (byte) (s6 >> 10); + s[18] = (byte) ((s6 >> 18) | (s7 << 3)); + s[19] = (byte) (s7 >> 5); + s[20] = (byte) (s7 >> 13); + s[21] = (byte) s8; + s[22] = (byte) (s8 >> 8); + s[23] = (byte) ((s8 >> 16) | (s9 << 5)); + s[24] = (byte) (s9 >> 3); + s[25] = (byte) (s9 >> 11); + s[26] = (byte) ((s9 >> 19) | (s10 << 2)); + s[27] = (byte) (s10 >> 6); + s[28] = (byte) ((s10 >> 14) | (s11 << 7)); + s[29] = (byte) (s11 >> 1); + s[30] = (byte) (s11 >> 9); + s[31] = (byte) (s11 >> 17); + } + + /** + * Input: + * a[0]+256*a[1]+...+256^31*a[31] = a + * b[0]+256*b[1]+...+256^31*b[31] = b + * c[0]+256*c[1]+...+256^31*c[31] = c + *

+ * Output: + * s[0]+256*s[1]+...+256^31*s[31] = (ab+c) mod l + * where l = 2^252 + 27742317777372353535851937790883648493. + */ + private static void mulAdd(byte[] s, byte[] a, byte[] b, byte[] c) { + // This is very similar to Ed25519.reduce, the difference in here is that it computes ab+c + // See Ed25519.reduce for related comments. + long a0 = 2097151 & load3(a, 0); + long a1 = 2097151 & (load4(a, 2) >> 5); + long a2 = 2097151 & (load3(a, 5) >> 2); + long a3 = 2097151 & (load4(a, 7) >> 7); + long a4 = 2097151 & (load4(a, 10) >> 4); + long a5 = 2097151 & (load3(a, 13) >> 1); + long a6 = 2097151 & (load4(a, 15) >> 6); + long a7 = 2097151 & (load3(a, 18) >> 3); + long a8 = 2097151 & load3(a, 21); + long a9 = 2097151 & (load4(a, 23) >> 5); + long a10 = 2097151 & (load3(a, 26) >> 2); + long a11 = (load4(a, 28) >> 7); + long b0 = 2097151 & load3(b, 0); + long b1 = 2097151 & (load4(b, 2) >> 5); + long b2 = 2097151 & (load3(b, 5) >> 2); + long b3 = 2097151 & (load4(b, 7) >> 7); + long b4 = 2097151 & (load4(b, 10) >> 4); + long b5 = 2097151 & (load3(b, 13) >> 1); + long b6 = 2097151 & (load4(b, 15) >> 6); + long b7 = 2097151 & (load3(b, 18) >> 3); + long b8 = 2097151 & load3(b, 21); + long b9 = 2097151 & (load4(b, 23) >> 5); + long b10 = 2097151 & (load3(b, 26) >> 2); + long b11 = (load4(b, 28) >> 7); + long c0 = 2097151 & load3(c, 0); + long c1 = 2097151 & (load4(c, 2) >> 5); + long c2 = 2097151 & (load3(c, 5) >> 2); + long c3 = 2097151 & (load4(c, 7) >> 7); + long c4 = 2097151 & (load4(c, 10) >> 4); + long c5 = 2097151 & (load3(c, 13) >> 1); + long c6 = 2097151 & (load4(c, 15) >> 6); + long c7 = 2097151 & (load3(c, 18) >> 3); + long c8 = 2097151 & load3(c, 21); + long c9 = 2097151 & (load4(c, 23) >> 5); + long c10 = 2097151 & (load3(c, 26) >> 2); + long c11 = (load4(c, 28) >> 7); + long s0; + long s1; + long s2; + long s3; + long s4; + long s5; + long s6; + long s7; + long s8; + long s9; + long s10; + long s11; + long s12; + long s13; + long s14; + long s15; + long s16; + long s17; + long s18; + long s19; + long s20; + long s21; + long s22; + long s23; + long carry0; + long carry1; + long carry2; + long carry3; + long carry4; + long carry5; + long carry6; + long carry7; + long carry8; + long carry9; + long carry10; + long carry11; + long carry12; + long carry13; + long carry14; + long carry15; + long carry16; + long carry17; + long carry18; + long carry19; + long carry20; + long carry21; + long carry22; + + s0 = c0 + a0 * b0; + s1 = c1 + a0 * b1 + a1 * b0; + s2 = c2 + a0 * b2 + a1 * b1 + a2 * b0; + s3 = c3 + a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0; + s4 = c4 + a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0; + s5 = c5 + a0 * b5 + a1 * b4 + a2 * b3 + a3 * b2 + a4 * b1 + a5 * b0; + s6 = c6 + a0 * b6 + a1 * b5 + a2 * b4 + a3 * b3 + a4 * b2 + a5 * b1 + a6 * b0; + s7 = c7 + a0 * b7 + a1 * b6 + a2 * b5 + a3 * b4 + a4 * b3 + a5 * b2 + a6 * b1 + a7 * b0; + s8 = c8 + a0 * b8 + a1 * b7 + a2 * b6 + a3 * b5 + a4 * b4 + a5 * b3 + a6 * b2 + a7 * b1 + + a8 * b0; + s9 = c9 + a0 * b9 + a1 * b8 + a2 * b7 + a3 * b6 + a4 * b5 + a5 * b4 + a6 * b3 + a7 * b2 + + a8 * b1 + a9 * b0; + s10 = c10 + a0 * b10 + a1 * b9 + a2 * b8 + a3 * b7 + a4 * b6 + a5 * b5 + a6 * b4 + a7 * b3 + + a8 * b2 + a9 * b1 + a10 * b0; + s11 = c11 + a0 * b11 + a1 * b10 + a2 * b9 + a3 * b8 + a4 * b7 + a5 * b6 + a6 * b5 + a7 * b4 + + a8 * b3 + a9 * b2 + a10 * b1 + a11 * b0; + s12 = a1 * b11 + a2 * b10 + a3 * b9 + a4 * b8 + a5 * b7 + a6 * b6 + a7 * b5 + a8 * b4 + a9 * b3 + + a10 * b2 + a11 * b1; + s13 = a2 * b11 + a3 * b10 + a4 * b9 + a5 * b8 + a6 * b7 + a7 * b6 + a8 * b5 + a9 * b4 + a10 * b3 + + a11 * b2; + s14 = a3 * b11 + a4 * b10 + a5 * b9 + a6 * b8 + a7 * b7 + a8 * b6 + a9 * b5 + a10 * b4 + + a11 * b3; + s15 = a4 * b11 + a5 * b10 + a6 * b9 + a7 * b8 + a8 * b7 + a9 * b6 + a10 * b5 + a11 * b4; + s16 = a5 * b11 + a6 * b10 + a7 * b9 + a8 * b8 + a9 * b7 + a10 * b6 + a11 * b5; + s17 = a6 * b11 + a7 * b10 + a8 * b9 + a9 * b8 + a10 * b7 + a11 * b6; + s18 = a7 * b11 + a8 * b10 + a9 * b9 + a10 * b8 + a11 * b7; + s19 = a8 * b11 + a9 * b10 + a10 * b9 + a11 * b8; + s20 = a9 * b11 + a10 * b10 + a11 * b9; + s21 = a10 * b11 + a11 * b10; + s22 = a11 * b11; + s23 = 0; + + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= carry16 << 21; + carry18 = (s18 + (1 << 20)) >> 21; + s19 += carry18; + s18 -= carry18 << 21; + carry20 = (s20 + (1 << 20)) >> 21; + s21 += carry20; + s20 -= carry20 << 21; + carry22 = (s22 + (1 << 20)) >> 21; + s23 += carry22; + s22 -= carry22 << 21; + + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= carry15 << 21; + carry17 = (s17 + (1 << 20)) >> 21; + s18 += carry17; + s17 -= carry17 << 21; + carry19 = (s19 + (1 << 20)) >> 21; + s20 += carry19; + s19 -= carry19 << 21; + carry21 = (s21 + (1 << 20)) >> 21; + s22 += carry21; + s21 -= carry21 << 21; + + s11 += s23 * 666643; + s12 += s23 * 470296; + s13 += s23 * 654183; + s14 -= s23 * 997805; + s15 += s23 * 136657; + s16 -= s23 * 683901; + // s23 = 0; + + s10 += s22 * 666643; + s11 += s22 * 470296; + s12 += s22 * 654183; + s13 -= s22 * 997805; + s14 += s22 * 136657; + s15 -= s22 * 683901; + // s22 = 0; + + s9 += s21 * 666643; + s10 += s21 * 470296; + s11 += s21 * 654183; + s12 -= s21 * 997805; + s13 += s21 * 136657; + s14 -= s21 * 683901; + // s21 = 0; + + s8 += s20 * 666643; + s9 += s20 * 470296; + s10 += s20 * 654183; + s11 -= s20 * 997805; + s12 += s20 * 136657; + s13 -= s20 * 683901; + // s20 = 0; + + s7 += s19 * 666643; + s8 += s19 * 470296; + s9 += s19 * 654183; + s10 -= s19 * 997805; + s11 += s19 * 136657; + s12 -= s19 * 683901; + // s19 = 0; + + s6 += s18 * 666643; + s7 += s18 * 470296; + s8 += s18 * 654183; + s9 -= s18 * 997805; + s10 += s18 * 136657; + s11 -= s18 * 683901; + // s18 = 0; + + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry12 = (s12 + (1 << 20)) >> 21; + s13 += carry12; + s12 -= carry12 << 21; + carry14 = (s14 + (1 << 20)) >> 21; + s15 += carry14; + s14 -= carry14 << 21; + carry16 = (s16 + (1 << 20)) >> 21; + s17 += carry16; + s16 -= carry16 << 21; + + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + carry13 = (s13 + (1 << 20)) >> 21; + s14 += carry13; + s13 -= carry13 << 21; + carry15 = (s15 + (1 << 20)) >> 21; + s16 += carry15; + s15 -= carry15 << 21; + + s5 += s17 * 666643; + s6 += s17 * 470296; + s7 += s17 * 654183; + s8 -= s17 * 997805; + s9 += s17 * 136657; + s10 -= s17 * 683901; + // s17 = 0; + + s4 += s16 * 666643; + s5 += s16 * 470296; + s6 += s16 * 654183; + s7 -= s16 * 997805; + s8 += s16 * 136657; + s9 -= s16 * 683901; + // s16 = 0; + + s3 += s15 * 666643; + s4 += s15 * 470296; + s5 += s15 * 654183; + s6 -= s15 * 997805; + s7 += s15 * 136657; + s8 -= s15 * 683901; + // s15 = 0; + + s2 += s14 * 666643; + s3 += s14 * 470296; + s4 += s14 * 654183; + s5 -= s14 * 997805; + s6 += s14 * 136657; + s7 -= s14 * 683901; + // s14 = 0; + + s1 += s13 * 666643; + s2 += s13 * 470296; + s3 += s13 * 654183; + s4 -= s13 * 997805; + s5 += s13 * 136657; + s6 -= s13 * 683901; + // s13 = 0; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = (s0 + (1 << 20)) >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry2 = (s2 + (1 << 20)) >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry4 = (s4 + (1 << 20)) >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry6 = (s6 + (1 << 20)) >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry8 = (s8 + (1 << 20)) >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry10 = (s10 + (1 << 20)) >> 21; + s11 += carry10; + s10 -= carry10 << 21; + + carry1 = (s1 + (1 << 20)) >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry3 = (s3 + (1 << 20)) >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry5 = (s5 + (1 << 20)) >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry7 = (s7 + (1 << 20)) >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry9 = (s9 + (1 << 20)) >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry11 = (s11 + (1 << 20)) >> 21; + s12 += carry11; + s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + s12 = 0; + + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + carry11 = s11 >> 21; + s12 += carry11; + s11 -= carry11 << 21; + + s0 += s12 * 666643; + s1 += s12 * 470296; + s2 += s12 * 654183; + s3 -= s12 * 997805; + s4 += s12 * 136657; + s5 -= s12 * 683901; + // s12 = 0; + + carry0 = s0 >> 21; + s1 += carry0; + s0 -= carry0 << 21; + carry1 = s1 >> 21; + s2 += carry1; + s1 -= carry1 << 21; + carry2 = s2 >> 21; + s3 += carry2; + s2 -= carry2 << 21; + carry3 = s3 >> 21; + s4 += carry3; + s3 -= carry3 << 21; + carry4 = s4 >> 21; + s5 += carry4; + s4 -= carry4 << 21; + carry5 = s5 >> 21; + s6 += carry5; + s5 -= carry5 << 21; + carry6 = s6 >> 21; + s7 += carry6; + s6 -= carry6 << 21; + carry7 = s7 >> 21; + s8 += carry7; + s7 -= carry7 << 21; + carry8 = s8 >> 21; + s9 += carry8; + s8 -= carry8 << 21; + carry9 = s9 >> 21; + s10 += carry9; + s9 -= carry9 << 21; + carry10 = s10 >> 21; + s11 += carry10; + s10 -= carry10 << 21; + + s[0] = (byte) s0; + s[1] = (byte) (s0 >> 8); + s[2] = (byte) ((s0 >> 16) | (s1 << 5)); + s[3] = (byte) (s1 >> 3); + s[4] = (byte) (s1 >> 11); + s[5] = (byte) ((s1 >> 19) | (s2 << 2)); + s[6] = (byte) (s2 >> 6); + s[7] = (byte) ((s2 >> 14) | (s3 << 7)); + s[8] = (byte) (s3 >> 1); + s[9] = (byte) (s3 >> 9); + s[10] = (byte) ((s3 >> 17) | (s4 << 4)); + s[11] = (byte) (s4 >> 4); + s[12] = (byte) (s4 >> 12); + s[13] = (byte) ((s4 >> 20) | (s5 << 1)); + s[14] = (byte) (s5 >> 7); + s[15] = (byte) ((s5 >> 15) | (s6 << 6)); + s[16] = (byte) (s6 >> 2); + s[17] = (byte) (s6 >> 10); + s[18] = (byte) ((s6 >> 18) | (s7 << 3)); + s[19] = (byte) (s7 >> 5); + s[20] = (byte) (s7 >> 13); + s[21] = (byte) s8; + s[22] = (byte) (s8 >> 8); + s[23] = (byte) ((s8 >> 16) | (s9 << 5)); + s[24] = (byte) (s9 >> 3); + s[25] = (byte) (s9 >> 11); + s[26] = (byte) ((s9 >> 19) | (s10 << 2)); + s[27] = (byte) (s10 >> 6); + s[28] = (byte) ((s10 >> 14) | (s11 << 7)); + s[29] = (byte) (s11 >> 1); + s[30] = (byte) (s11 >> 9); + s[31] = (byte) (s11 >> 17); + } + + // The order of the generator as unsigned bytes in little endian order. + // (2^252 + 0x14def9dea2f79cd65812631a5cf5d3ed, cf. RFC 7748) + private static final byte[] GROUP_ORDER = { + (byte) 0xed, (byte) 0xd3, (byte) 0xf5, (byte) 0x5c, + (byte) 0x1a, (byte) 0x63, (byte) 0x12, (byte) 0x58, + (byte) 0xd6, (byte) 0x9c, (byte) 0xf7, (byte) 0xa2, + (byte) 0xde, (byte) 0xf9, (byte) 0xde, (byte) 0x14, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x10}; + + // Checks whether s represents an integer smaller than the order of the group. + // This is needed to ensure that EdDSA signatures are non-malleable, as failing to check + // the range of S allows to modify signatures (cf. RFC 8032, Section 5.2.7 and Section 8.4.) + // @param s an integer in little-endian order. + private static boolean isSmallerThanGroupOrder(byte[] s) { + for (int j = Field25519.FIELD_LEN - 1; j >= 0; j--) { + // compare unsigned bytes + int a = s[j] & 0xff; + int b = GROUP_ORDER[j] & 0xff; + if (a != b) { + return a < b; + } + } + return false; + } + + /** + * Returns true if the EdDSA {@code signature} with {@code message}, can be verified with + * {@code publicKey}. + */ + public static boolean verify(final byte[] message, final byte[] signature, + final byte[] publicKey) { + try { + if (signature.length != SIGNATURE_LEN) { + return false; + } + if (publicKey.length != PUBLIC_KEY_LEN) { + return false; + } + byte[] s = Arrays.copyOfRange(signature, Field25519.FIELD_LEN, SIGNATURE_LEN); + if (!isSmallerThanGroupOrder(s)) { + return false; + } + MessageDigest digest = MessageDigest.getInstance("SHA-512"); + digest.update(signature, 0, Field25519.FIELD_LEN); + digest.update(publicKey); + digest.update(message); + byte[] h = digest.digest(); + reduce(h); + + XYZT negPublicKey = XYZT.fromBytesNegateVarTime(publicKey); + XYZ xyz = doubleScalarMultVarTime(h, negPublicKey, s); + byte[] expectedR = xyz.toBytes(); + for (int i = 0; i < Field25519.FIELD_LEN; i++) { + if (expectedR[i] != signature[i]) { + return false; + } + } + return true; + } catch (final GeneralSecurityException ignored) { + return false; + } + } +} diff --git a/app/src/main/java/com/wireguard/crypto/Key.java b/app/src/main/java/com/wireguard/crypto/Key.java new file mode 100644 index 0000000..9e25e60 --- /dev/null +++ b/app/src/main/java/com/wireguard/crypto/Key.java @@ -0,0 +1,288 @@ +/* + * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.crypto; + +import com.wireguard.crypto.KeyFormatException.Type; + +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Arrays; + +/** + * Represents a WireGuard public or private key. This class uses specialized constant-time base64 + * and hexadecimal codec implementations that resist side-channel attacks. + *

+ * Instances of this class are immutable. + */ +@SuppressWarnings("MagicNumber") +public final class Key { + private final byte[] key; + + /** + * Constructs an object encapsulating the supplied key. + * + * @param key an array of bytes containing a binary key. Callers of this constructor are + * responsible for ensuring that the array is of the correct length. + */ + private Key(final byte[] key) { + // Defensively copy to ensure immutability. + this.key = Arrays.copyOf(key, key.length); + } + + /** + * Decodes a single 4-character base64 chunk to an integer in constant time. + * + * @param src an array of at least 4 characters in base64 format + * @param srcOffset the offset of the beginning of the chunk in {@code src} + * @return the decoded 3-byte integer, or some arbitrary integer value if the input was not + * valid base64 + */ + private static int decodeBase64(final char[] src, final int srcOffset) { + int val = 0; + for (int i = 0; i < 4; ++i) { + final char c = src[i + srcOffset]; + val |= (-1 + + ((((('A' - 1) - c) & (c - ('Z' + 1))) >>> 8) & (c - 64)) + + ((((('a' - 1) - c) & (c - ('z' + 1))) >>> 8) & (c - 70)) + + ((((('0' - 1) - c) & (c - ('9' + 1))) >>> 8) & (c + 5)) + + ((((('+' - 1) - c) & (c - ('+' + 1))) >>> 8) & 63) + + ((((('/' - 1) - c) & (c - ('/' + 1))) >>> 8) & 64) + ) << (18 - 6 * i); + } + return val; + } + + /** + * Encodes a single 4-character base64 chunk from 3 consecutive bytes in constant time. + * + * @param src an array of at least 3 bytes + * @param srcOffset the offset of the beginning of the chunk in {@code src} + * @param dest an array of at least 4 characters + * @param destOffset the offset of the beginning of the chunk in {@code dest} + */ + private static void encodeBase64(final byte[] src, final int srcOffset, + final char[] dest, final int destOffset) { + final byte[] input = { + (byte) ((src[srcOffset] >>> 2) & 63), + (byte) ((src[srcOffset] << 4 | ((src[1 + srcOffset] & 0xff) >>> 4)) & 63), + (byte) ((src[1 + srcOffset] << 2 | ((src[2 + srcOffset] & 0xff) >>> 6)) & 63), + (byte) ((src[2 + srcOffset]) & 63), + }; + for (int i = 0; i < 4; ++i) { + dest[i + destOffset] = (char) (input[i] + 'A' + + (((25 - input[i]) >>> 8) & 6) + - (((51 - input[i]) >>> 8) & 75) + - (((61 - input[i]) >>> 8) & 15) + + (((62 - input[i]) >>> 8) & 3)); + } + } + + /** + * Decodes a WireGuard public or private key from its base64 string representation. This + * function throws a {@link KeyFormatException} if the source string is not well-formed. + * + * @param str the base64 string representation of a WireGuard key + * @return the decoded key encapsulated in an immutable container + */ + public static Key fromBase64(final String str) throws KeyFormatException { + final char[] input = str.toCharArray(); + if (input.length != Format.BASE64.length || input[Format.BASE64.length - 1] != '=') + throw new KeyFormatException(Format.BASE64, Type.LENGTH); + final byte[] key = new byte[Format.BINARY.length]; + int i; + int ret = 0; + for (i = 0; i < key.length / 3; ++i) { + final int val = decodeBase64(input, i * 4); + ret |= val >>> 31; + key[i * 3] = (byte) ((val >>> 16) & 0xff); + key[i * 3 + 1] = (byte) ((val >>> 8) & 0xff); + key[i * 3 + 2] = (byte) (val & 0xff); + } + final char[] endSegment = { + input[i * 4], + input[i * 4 + 1], + input[i * 4 + 2], + 'A', + }; + final int val = decodeBase64(endSegment, 0); + ret |= (val >>> 31) | (val & 0xff); + key[i * 3] = (byte) ((val >>> 16) & 0xff); + key[i * 3 + 1] = (byte) ((val >>> 8) & 0xff); + + if (ret != 0) + throw new KeyFormatException(Format.BASE64, Type.CONTENTS); + return new Key(key); + } + + /** + * Wraps a WireGuard public or private key in an immutable container. This function throws a + * {@link KeyFormatException} if the source data is not the correct length. + * + * @param bytes an array of bytes containing a WireGuard key in binary format + * @return the key encapsulated in an immutable container + */ + public static Key fromBytes(final byte[] bytes) throws KeyFormatException { + if (bytes.length != Format.BINARY.length) + throw new KeyFormatException(Format.BINARY, Type.LENGTH); + return new Key(bytes); + } + + /** + * Decodes a WireGuard public or private key from its hexadecimal string representation. This + * function throws a {@link KeyFormatException} if the source string is not well-formed. + * + * @param str the hexadecimal string representation of a WireGuard key + * @return the decoded key encapsulated in an immutable container + */ + public static Key fromHex(final String str) throws KeyFormatException { + final char[] input = str.toCharArray(); + if (input.length != Format.HEX.length) + throw new KeyFormatException(Format.HEX, Type.LENGTH); + final byte[] key = new byte[Format.BINARY.length]; + int ret = 0; + for (int i = 0; i < key.length; ++i) { + int c; + int cNum; + int cNum0; + int cAlpha; + int cAlpha0; + int cVal; + final int cAcc; + + c = input[i * 2]; + cNum = c ^ 48; + cNum0 = ((cNum - 10) >>> 8) & 0xff; + cAlpha = (c & ~32) - 55; + cAlpha0 = (((cAlpha - 10) ^ (cAlpha - 16)) >>> 8) & 0xff; + ret |= ((cNum0 | cAlpha0) - 1) >>> 8; + cVal = (cNum0 & cNum) | (cAlpha0 & cAlpha); + cAcc = cVal * 16; + + c = input[i * 2 + 1]; + cNum = c ^ 48; + cNum0 = ((cNum - 10) >>> 8) & 0xff; + cAlpha = (c & ~32) - 55; + cAlpha0 = (((cAlpha - 10) ^ (cAlpha - 16)) >>> 8) & 0xff; + ret |= ((cNum0 | cAlpha0) - 1) >>> 8; + cVal = (cNum0 & cNum) | (cAlpha0 & cAlpha); + key[i] = (byte) (cAcc | cVal); + } + if (ret != 0) + throw new KeyFormatException(Format.HEX, Type.CONTENTS); + return new Key(key); + } + + /** + * Generates a private key using the system's {@link SecureRandom} number generator. + * + * @return a well-formed random private key + */ + static Key generatePrivateKey() { + final SecureRandom secureRandom = new SecureRandom(); + final byte[] privateKey = new byte[Format.BINARY.getLength()]; + secureRandom.nextBytes(privateKey); + privateKey[0] &= 248; + privateKey[31] &= 127; + privateKey[31] |= 64; + return new Key(privateKey); + } + + /** + * Generates a public key from an existing private key. + * + * @param privateKey a private key + * @return a well-formed public key that corresponds to the supplied private key + */ + static Key generatePublicKey(final Key privateKey) { + final byte[] publicKey = new byte[Format.BINARY.getLength()]; + Curve25519.eval(publicKey, 0, privateKey.getBytes(), null); + return new Key(publicKey); + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) + return true; + if (obj == null || obj.getClass() != getClass()) + return false; + final Key other = (Key) obj; + return MessageDigest.isEqual(key, other.key); + } + + /** + * Returns the key as an array of bytes. + * + * @return an array of bytes containing the raw binary key + */ + public byte[] getBytes() { + // Defensively copy to ensure immutability. + return Arrays.copyOf(key, key.length); + } + + @Override + public int hashCode() { + int ret = 0; + for (int i = 0; i < key.length / 4; ++i) + ret ^= (key[i * 4 + 0] >> 0) + (key[i * 4 + 1] >> 8) + (key[i * 4 + 2] >> 16) + (key[i * 4 + 3] >> 24); + return ret; + } + + /** + * Encodes the key to base64. + * + * @return a string containing the encoded key + */ + public String toBase64() { + final char[] output = new char[Format.BASE64.length]; + int i; + for (i = 0; i < key.length / 3; ++i) + encodeBase64(key, i * 3, output, i * 4); + final byte[] endSegment = { + key[i * 3], + key[i * 3 + 1], + 0, + }; + encodeBase64(endSegment, 0, output, i * 4); + output[Format.BASE64.length - 1] = '='; + return new String(output); + } + + /** + * Encodes the key to hexadecimal ASCII characters. + * + * @return a string containing the encoded key + */ + public String toHex() { + final char[] output = new char[Format.HEX.length]; + for (int i = 0; i < key.length; ++i) { + output[i * 2] = (char) (87 + (key[i] >> 4 & 0xf) + + ((((key[i] >> 4 & 0xf) - 10) >> 8) & ~38)); + output[i * 2 + 1] = (char) (87 + (key[i] & 0xf) + + ((((key[i] & 0xf) - 10) >> 8) & ~38)); + } + return new String(output); + } + + /** + * The supported formats for encoding a WireGuard key. + */ + public enum Format { + BASE64(44), + BINARY(32), + HEX(64); + + private final int length; + + Format(final int length) { + this.length = length; + } + + public int getLength() { + return length; + } + } + +} diff --git a/app/src/main/java/com/wireguard/crypto/KeyFormatException.java b/app/src/main/java/com/wireguard/crypto/KeyFormatException.java new file mode 100644 index 0000000..5818b4d --- /dev/null +++ b/app/src/main/java/com/wireguard/crypto/KeyFormatException.java @@ -0,0 +1,34 @@ +/* + * Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.crypto; + +/** + * An exception thrown when attempting to parse an invalid key (too short, too long, or byte + * data inappropriate for the format). The format being parsed can be accessed with the + * {@link #getFormat} method. + */ +public final class KeyFormatException extends Exception { + private final Key.Format format; + private final Type type; + + KeyFormatException(final Key.Format format, final Type type) { + this.format = format; + this.type = type; + } + + public Key.Format getFormat() { + return format; + } + + public Type getType() { + return type; + } + + public enum Type { + CONTENTS, + LENGTH + } +} diff --git a/app/src/main/java/com/wireguard/crypto/KeyPair.java b/app/src/main/java/com/wireguard/crypto/KeyPair.java new file mode 100644 index 0000000..f8238e9 --- /dev/null +++ b/app/src/main/java/com/wireguard/crypto/KeyPair.java @@ -0,0 +1,51 @@ +/* + * Copyright © 2017-2019 WireGuard LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.wireguard.crypto; + +/** + * Represents a Curve25519 key pair as used by WireGuard. + *

+ * Instances of this class are immutable. + */ +public class KeyPair { + private final Key privateKey; + private final Key publicKey; + + /** + * Creates a key pair using a newly-generated private key. + */ + public KeyPair() { + this(Key.generatePrivateKey()); + } + + /** + * Creates a key pair using an existing private key. + * + * @param privateKey a private key, used to derive the public key + */ + public KeyPair(final Key privateKey) { + this.privateKey = privateKey; + publicKey = Key.generatePublicKey(privateKey); + } + + /** + * Returns the private key from the key pair. + * + * @return the private key + */ + public Key getPrivateKey() { + return privateKey; + } + + /** + * Returns the public key from the key pair. + * + * @return the public key + */ + public Key getPublicKey() { + return publicKey; + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/Constants.kt b/app/src/main/java/io/nekohasekai/sagernet/Constants.kt new file mode 100644 index 0000000..bc2e4b2 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/Constants.kt @@ -0,0 +1,182 @@ +package io.nekohasekai.sagernet + +const val CONNECTION_TEST_URL = "http://cp.cloudflare.com/" + +object Key { + + const val DB_PUBLIC = "configuration.db" + const val DB_PROFILE = "sager_net.db" + + const val APP_EXPERT = "isExpert" + const val APP_THEME = "appTheme" + const val NIGHT_THEME = "nightTheme" + const val SERVICE_MODE = "serviceMode" + const val MODE_VPN = "vpn" + const val MODE_PROXY = "proxy" + + const val REMOTE_DNS = "remoteDns" + const val DIRECT_DNS = "directDns" + const val DIRECT_DNS_USE_SYSTEM = "directDnsUseSystem" + const val ENABLE_DNS_ROUTING = "enableDnsRouting" + const val ENABLE_FAKEDNS = "enableFakeDns" + const val DNS_NETWORK = "dnsNetwork" + + const val IPV6_MODE = "ipv6Mode" + + const val PROXY_APPS = "proxyApps" + const val BYPASS_MODE = "bypassMode" + const val INDIVIDUAL = "individual" + const val METERED_NETWORK = "meteredNetwork" + + const val DOMAIN_STRATEGY = "domainStrategy" + const val TRAFFIC_SNIFFING = "trafficSniffing" + const val RESOLVE_DESTINATION = "resolveDestination" + + const val BYPASS_LAN = "bypassLan" + const val BYPASS_LAN_IN_CORE_ONLY = "bypassLanInCoreOnly" + + const val MIXED_PORT = "mixedPort" + const val ALLOW_ACCESS = "allowAccess" + const val SPEED_INTERVAL = "speedInterval" + const val SHOW_DIRECT_SPEED = "showDirectSpeed" + const val LOCAL_DNS_PORT = "portLocalDns" + + const val APPEND_HTTP_PROXY = "appendHttpProxy" + + const val REQUIRE_TRANSPROXY = "requireTransproxy" + const val TRANSPROXY_MODE = "transproxyMode" + const val TRANSPROXY_PORT = "transproxyPort" + const val CONNECTION_TEST_URL = "connectionTestURL" + + const val TCP_KEEP_ALIVE_INTERVAL = "tcpKeepAliveInterval" + const val RULES_PROVIDER = "rulesProvider" + const val ENABLE_LOG = "enableLog" + const val LOG_BUF_SIZE = "logBufSize" + const val MTU = "mtu" + const val ALWAYS_SHOW_ADDRESS = "alwaysShowAddress" + + // Protocol Settings + const val MUX_PROTOCOLS = "mux" + const val MUX_CONCURRENCY = "muxConcurrency" + + const val ACQUIRE_WAKE_LOCK = "acquireWakeLock" + const val SHOW_BOTTOM_BAR = "showBottomBar" + + const val TUN_IMPLEMENTATION = "tunImplementation" + const val PROFILE_TRAFFIC_STATISTICS = "profileTrafficStatistics" + + const val PROFILE_DIRTY = "profileDirty" + const val PROFILE_ID = "profileId" + const val PROFILE_NAME = "profileName" + const val PROFILE_GROUP = "profileGroup" + const val PROFILE_CURRENT = "profileCurrent" + + const val SERVER_ADDRESS = "serverAddress" + const val SERVER_PORT = "serverPort" + const val SERVER_USERNAME = "serverUsername" + const val SERVER_PASSWORD = "serverPassword" + const val SERVER_METHOD = "serverMethod" + const val SERVER_PLUGIN = "serverPlugin" + const val SERVER_PLUGIN_CONFIGURE = "serverPluginConfigure" + const val SERVER_PASSWORD1 = "serverPassword1" + + const val SERVER_PROTOCOL = "serverProtocol" + const val SERVER_OBFS = "serverObfs" + + const val SERVER_SECURITY = "serverSecurity" + const val SERVER_NETWORK = "serverNetwork" + const val SERVER_HOST = "serverHost" + const val SERVER_PATH = "serverPath" + const val SERVER_SNI = "serverSNI" + const val SERVER_ENCRYPTION = "serverEncryption" + const val SERVER_ALPN = "serverALPN" + const val SERVER_CERTIFICATES = "serverCertificates" + const val SERVER_CONFIG = "serverConfig" + + const val SERVER_SECURITY_CATEGORY = "serverSecurityCategory" + const val SERVER_WS_CATEGORY = "serverWsCategory" + const val SERVER_SS_CATEGORY = "serverSsCategory" + const val SERVER_HEADERS = "serverHeaders" + const val SERVER_ALLOW_INSECURE = "serverAllowInsecure" + + const val SERVER_AUTH_TYPE = "serverAuthType" + const val SERVER_UPLOAD_SPEED = "serverUploadSpeed" + const val SERVER_DOWNLOAD_SPEED = "serverDownloadSpeed" + const val SERVER_STREAM_RECEIVE_WINDOW = "serverStreamReceiveWindow" + const val SERVER_CONNECTION_RECEIVE_WINDOW = "serverConnectionReceiveWindow" + const val SERVER_MTU = "serverMTU" + const val SERVER_DISABLE_MTU_DISCOVERY = "serverDisableMtuDiscovery" + const val SERVER_HOP_INTERVAL = "hopInterval" + + const val SERVER_PRIVATE_KEY = "serverPrivateKey" + const val SERVER_LOCAL_ADDRESS = "serverLocalAddress" + const val SERVER_INSECURE_CONCURRENCY = "serverInsecureConcurrency" + + const val SERVER_UDP_RELAY_MODE = "serverUDPRelayMode" + const val SERVER_CONGESTION_CONTROLLER = "serverCongestionController" + const val SERVER_DISABLE_SNI = "serverDisableSNI" + const val SERVER_REDUCE_RTT = "serverReduceRTT" + const val SERVER_FAST_CONNECT = "serverFastConnect" + + const val ROUTE_NAME = "routeName" + const val ROUTE_DOMAIN = "routeDomain" + const val ROUTE_IP = "routeIP" + const val ROUTE_PORT = "routePort" + const val ROUTE_SOURCE_PORT = "routeSourcePort" + const val ROUTE_NETWORK = "routeNetwork" + const val ROUTE_SOURCE = "routeSource" + const val ROUTE_PROTOCOL = "routeProtocol" + const val ROUTE_OUTBOUND = "routeOutbound" + const val ROUTE_OUTBOUND_RULE = "routeOutboundRule" + const val ROUTE_PACKAGES = "routePackages" + + const val GROUP_NAME = "groupName" + const val GROUP_TYPE = "groupType" + const val GROUP_ORDER = "groupOrder" + + const val GROUP_SUBSCRIPTION = "groupSubscription" + const val SUBSCRIPTION_LINK = "subscriptionLink" + const val SUBSCRIPTION_FORCE_RESOLVE = "subscriptionForceResolve" + const val SUBSCRIPTION_DEDUPLICATION = "subscriptionDeduplication" + const val SUBSCRIPTION_UPDATE = "subscriptionUpdate" + const val SUBSCRIPTION_UPDATE_WHEN_CONNECTED_ONLY = "subscriptionUpdateWhenConnectedOnly" + const val SUBSCRIPTION_USER_AGENT = "subscriptionUserAgent" + const val SUBSCRIPTION_AUTO_UPDATE = "subscriptionAutoUpdate" + const val SUBSCRIPTION_AUTO_UPDATE_DELAY = "subscriptionAutoUpdateDelay" + + // + + const val NEKO_PLUGIN_MANAGED = "nekoPlugins" + const val APP_TLS_VERSION = "appTLSVersion" + const val ENABLE_CLASH_API = "enableClashAPI" +} + +object TunImplementation { + const val GVISOR = 0 + const val SYSTEM = 1 +} + +object IPv6Mode { + const val DISABLE = 0 + const val ENABLE = 1 + const val PREFER = 2 + const val ONLY = 3 +} + +object GroupType { + const val BASIC = 0 + const val SUBSCRIPTION = 1 +} + +object GroupOrder { + const val ORIGIN = 0 + const val BY_NAME = 1 + const val BY_DELAY = 2 +} + +object Action { + const val SERVICE = "io.nekohasekai.sagernet.SERVICE" + const val CLOSE = "io.nekohasekai.sagernet.CLOSE" + const val RELOAD = "io.nekohasekai.sagernet.RELOAD" + const val SWITCH_WAKE_LOCK = "io.nekohasekai.sagernet.SWITCH_WAKELOCK" +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/QuickToggleShortcut.kt b/app/src/main/java/io/nekohasekai/sagernet/QuickToggleShortcut.kt new file mode 100644 index 0000000..2cd9d7d --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/QuickToggleShortcut.kt @@ -0,0 +1,88 @@ +/******************************************************************************* + * * + * Copyright (C) 2017 by Max Lv * + * Copyright (C) 2017 by Mygod Studio * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + * * + *******************************************************************************/ + +package io.nekohasekai.sagernet + +import android.app.Activity +import android.content.Intent +import android.content.pm.ShortcutManager +import android.os.Build +import android.os.Bundle +import androidx.core.content.getSystemService +import androidx.core.content.pm.ShortcutInfoCompat +import androidx.core.content.pm.ShortcutManagerCompat +import androidx.core.graphics.drawable.IconCompat +import io.nekohasekai.sagernet.aidl.ISagerNetService +import io.nekohasekai.sagernet.bg.BaseService +import io.nekohasekai.sagernet.bg.SagerConnection +import io.nekohasekai.sagernet.database.DataStore + +@Suppress("DEPRECATION") +class QuickToggleShortcut : Activity(), SagerConnection.Callback { + private val connection = SagerConnection() + private var profileId = -1L + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (intent.action == Intent.ACTION_CREATE_SHORTCUT) { + setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, + ShortcutInfoCompat.Builder(this, "toggle") + .setIntent(Intent(this, + QuickToggleShortcut::class.java).setAction(Intent.ACTION_MAIN)) + .setIcon(IconCompat.createWithResource(this, + R.drawable.ic_qu_shadowsocks_launcher)) + .setShortLabel(getString(R.string.quick_toggle)) + .build())) + finish() + } else { + profileId = intent.getLongExtra("profile", -1L) + connection.connect(this, this) + if (Build.VERSION.SDK_INT >= 25) { + getSystemService()!!.reportShortcutUsed(if (profileId >= 0) "shortcut-profile-$profileId" else "toggle") + } + } + } + + override fun onServiceConnected(service: ISagerNetService) { + val state = BaseService.State.values()[service.state] + when { + state.canStop -> { + if (profileId == DataStore.selectedProxy || profileId == -1L) { + SagerNet.stopService() + } else { + DataStore.selectedProxy = profileId + SagerNet.reloadService() + } + } + state == BaseService.State.Stopped -> { + if (profileId >= 0L) DataStore.selectedProxy = profileId + SagerNet.startService() + } + } + finish() + } + + override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) {} + + override fun onDestroy() { + connection.disconnect(this) + super.onDestroy() + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt b/app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt new file mode 100644 index 0000000..62a1d12 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt @@ -0,0 +1,283 @@ +package io.nekohasekai.sagernet + +import android.annotation.SuppressLint +import android.app.* +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.res.Configuration +import android.net.ConnectivityManager +import android.net.Network +import android.os.Build +import android.os.PowerManager +import android.os.StrictMode +import android.os.UserManager +import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat +import androidx.core.content.getSystemService +import go.Seq +import io.nekohasekai.sagernet.bg.SagerConnection +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher +import io.nekohasekai.sagernet.ui.MainActivity +import io.nekohasekai.sagernet.utils.* +import kotlinx.coroutines.DEBUG_PROPERTY_NAME +import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON +import libcore.BoxPlatformInterface +import libcore.Libcore +import libcore.NB4AInterface +import moe.matsuri.nb4a.utils.JavaUtil +import moe.matsuri.nb4a.utils.cleanWebview +import java.net.InetSocketAddress +import androidx.work.Configuration as WorkConfiguration + +class SagerNet : Application(), + BoxPlatformInterface, + WorkConfiguration.Provider, NB4AInterface { + + override fun attachBaseContext(base: Context) { + super.attachBaseContext(base) + + application = this + } + + val externalAssets by lazy { getExternalFilesDir(null) ?: filesDir } + val process = JavaUtil.getProcessName() + val isMainProcess = process == BuildConfig.APPLICATION_ID + val isBgProcess = process.endsWith(":bg") + + override fun onCreate() { + super.onCreate() + + System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON) + Thread.setDefaultUncaughtExceptionHandler(CrashHandler) + + if (isMainProcess || isBgProcess) { + // fix multi process issue in Android 9+ + JavaUtil.handleWebviewDir(this) + + runOnDefaultDispatcher { + PackageCache.register() + cleanWebview() + } + } + + Seq.setContext(this) + updateNotificationChannels() + + // nb4a: init core + externalAssets.mkdirs() + Libcore.initCore( + process, + cacheDir.absolutePath + "/", + filesDir.absolutePath + "/", + externalAssets.absolutePath + "/", + DataStore.logBufSize, + DataStore.enableLog, + this + ) + + // libbox: platform interface + Libcore.setBoxPlatformInterface(this) + + if (isMainProcess) { + Theme.apply(this) + Theme.applyNightTheme() + runOnDefaultDispatcher { + DefaultNetworkListener.start(this) { + underlyingNetwork = it + } + } + } + + if (BuildConfig.DEBUG) StrictMode.setVmPolicy( + StrictMode.VmPolicy.Builder() + .detectLeakedSqlLiteObjects() + .detectLeakedClosableObjects() + .detectLeakedRegistrationObjects() + .penaltyLog() + .build() + ) + } + + fun getPackageInfo(packageName: String) = packageManager.getPackageInfo( + packageName, if (Build.VERSION.SDK_INT >= 28) PackageManager.GET_SIGNING_CERTIFICATES + else @Suppress("DEPRECATION") PackageManager.GET_SIGNATURES + )!! + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + updateNotificationChannels() + } + + override fun getWorkManagerConfiguration(): WorkConfiguration { + return WorkConfiguration.Builder() + .setDefaultProcessName("${BuildConfig.APPLICATION_ID}:bg") + .build() + } + + override fun onTrimMemory(level: Int) { + super.onTrimMemory(level) + + Libcore.forceGc() + } + + @SuppressLint("InlinedApi") + companion object { + + lateinit var application: SagerNet + + val isTv by lazy { + uiMode.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION + } + + // /data/user_de available when not unlocked + val deviceStorage by lazy { + if (Build.VERSION.SDK_INT < 24) application else DeviceStorageApp(application) + } + + val configureIntent: (Context) -> PendingIntent by lazy { + { + PendingIntent.getActivity( + it, + 0, + Intent( + application, MainActivity::class.java + ).setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0 + ) + } + } + val activity by lazy { application.getSystemService()!! } + val clipboard by lazy { application.getSystemService()!! } + val connectivity by lazy { application.getSystemService()!! } + val notification by lazy { application.getSystemService()!! } + val user by lazy { application.getSystemService()!! } + val uiMode by lazy { application.getSystemService()!! } + val power by lazy { application.getSystemService()!! } + + val packageInfo: PackageInfo by lazy { application.getPackageInfo(application.packageName) } + + fun getClipboardText(): String { + return clipboard.primaryClip?.takeIf { it.itemCount > 0 } + ?.getItemAt(0)?.text?.toString() ?: "" + } + + fun trySetPrimaryClip(clip: String) = try { + clipboard.setPrimaryClip(ClipData.newPlainText(null, clip)) + true + } catch (e: RuntimeException) { + Logs.w(e) + false + } + + fun updateNotificationChannels() { + if (Build.VERSION.SDK_INT >= 26) @RequiresApi(26) { + notification.createNotificationChannels( + listOf( + NotificationChannel( + "service-vpn", + application.getText(R.string.service_vpn), + if (Build.VERSION.SDK_INT >= 28) NotificationManager.IMPORTANCE_MIN + else NotificationManager.IMPORTANCE_LOW + ), // #1355 + NotificationChannel( + "service-proxy", + application.getText(R.string.service_proxy), + NotificationManager.IMPORTANCE_LOW + ), NotificationChannel( + "service-subscription", + application.getText(R.string.service_subscription), + NotificationManager.IMPORTANCE_DEFAULT + ) + ) + ) + } + } + + fun startService() = ContextCompat.startForegroundService( + application, Intent(application, SagerConnection.serviceClass) + ) + + fun reloadService() = + application.sendBroadcast(Intent(Action.RELOAD).setPackage(application.packageName)) + + fun stopService() = + application.sendBroadcast(Intent(Action.CLOSE).setPackage(application.packageName)) + + var underlyingNetwork: Network? = null + + } + + + // libbox interface + + override fun autoDetectInterfaceControl(fd: Int) { + DataStore.vpnService?.protect(fd) + } + + override fun openTun(singTunOptionsJson: String, tunPlatformOptionsJson: String): Long { + if (DataStore.vpnService == null) { + throw Exception("no VpnService") + } + return DataStore.vpnService!!.startVpn(singTunOptionsJson, tunPlatformOptionsJson).toLong() + } + + override fun useProcFS(): Boolean { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.Q + } + + @RequiresApi(Build.VERSION_CODES.Q) + override fun findConnectionOwner( + ipProto: Int, srcIp: String, srcPort: Int, destIp: String, destPort: Int + ): Int { + return connectivity.getConnectionOwnerUid( + ipProto, InetSocketAddress(srcIp, srcPort), InetSocketAddress(destIp, destPort) + ) + } + + override fun packageNameByUid(uid: Int): String { + PackageCache.awaitLoadSync() + + if (uid <= 1000L) { + return "android" + } + + val packageNames = PackageCache.uidMap[uid] + if (!packageNames.isNullOrEmpty()) for (packageName in packageNames) { + return packageName + } + + error("unknown uid $uid") + } + + override fun uidByPackageName(packageName: String): Int { + PackageCache.awaitLoadSync() + return PackageCache[packageName] ?: 0 + } + + // nb4a interface + + override fun write(p: ByteArray): Long { + // NB4AGuiLogWriter + if (isBgProcess) { + runOnDefaultDispatcher { + DataStore.baseService?.data?.binder?.broadcast { + it.cbLogUpdate(String(p)) + } + } + } else { + DataStore.postLogListener?.let { it(String(p)) } + } + return p.size.toLong() + } + + override fun useOfficialAssets(): Boolean { + return DataStore.rulesProvider == 0 + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/aidl/SpeedDisplayData.kt b/app/src/main/java/io/nekohasekai/sagernet/aidl/SpeedDisplayData.kt new file mode 100644 index 0000000..5cafbf3 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/aidl/SpeedDisplayData.kt @@ -0,0 +1,18 @@ +package io.nekohasekai.sagernet.aidl + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class SpeedDisplayData( + // Bytes per second + var txRateProxy: Long = 0L, + var rxRateProxy: Long = 0L, + var txRateDirect: Long = 0L, + var rxRateDirect: Long = 0L, + + // Bytes for the current session + // Outbound "bypass" usage is not counted + var txTotal: Long = 0L, + var rxTotal: Long = 0L, +) : Parcelable diff --git a/app/src/main/java/io/nekohasekai/sagernet/aidl/TrafficData.kt b/app/src/main/java/io/nekohasekai/sagernet/aidl/TrafficData.kt new file mode 100644 index 0000000..02c7943 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/aidl/TrafficData.kt @@ -0,0 +1,11 @@ +package io.nekohasekai.sagernet.aidl + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class TrafficData( + var id: Long = 0L, + var tx: Long = 0L, + var rx: Long = 0L, +) : Parcelable diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/AbstractInstance.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/AbstractInstance.kt new file mode 100644 index 0000000..196e7e9 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/AbstractInstance.kt @@ -0,0 +1,9 @@ +package io.nekohasekai.sagernet.bg + +import java.io.Closeable + +interface AbstractInstance : Closeable { + + fun launch() + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt new file mode 100644 index 0000000..cb63e25 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt @@ -0,0 +1,345 @@ +package io.nekohasekai.sagernet.bg + +import android.app.Service +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.* +import android.widget.Toast +import io.nekohasekai.sagernet.Action +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.aidl.ISagerNetService +import io.nekohasekai.sagernet.aidl.ISagerNetServiceCallback +import io.nekohasekai.sagernet.bg.proto.ProxyInstance +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.SagerDatabase +import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.plugin.PluginManager +import io.nekohasekai.sagernet.utils.DefaultNetworkListener +import kotlinx.coroutines.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import libcore.Libcore +import moe.matsuri.nb4a.Protocols +import java.net.UnknownHostException + +class BaseService { + + enum class State( + val canStop: Boolean = false, + val started: Boolean = false, + val connected: Boolean = false, + ) { + /** + * Idle state is only used by UI and will never be returned by BaseService. + */ + Idle, Connecting(true, true, false), Connected(true, true, true), Stopping, Stopped, + } + + interface ExpectedException + + class Data internal constructor(private val service: Interface) { + var state = State.Stopped + var proxy: ProxyInstance? = null + var notification: ServiceNotification? = null + + val receiver = broadcastReceiver { _, intent -> + when (intent.action) { + Intent.ACTION_SHUTDOWN -> service.persistStats() + Action.RELOAD -> service.forceLoad() + Action.SWITCH_WAKE_LOCK -> runOnDefaultDispatcher { service.switchWakeLock() } + else -> service.stopRunner() + } + } + var closeReceiverRegistered = false + + val binder = Binder(this) + var connectingJob: Job? = null + + fun changeState(s: State, msg: String? = null) { + if (state == s && msg == null) return + binder.stateChanged(s, msg) + state = s + } + } + + class Binder(private var data: Data? = null) : ISagerNetService.Stub(), CoroutineScope, + AutoCloseable { + private val callbacks = object : RemoteCallbackList() { + override fun onCallbackDied(callback: ISagerNetServiceCallback?, cookie: Any?) { + super.onCallbackDied(callback, cookie) + } + } + + override val coroutineContext = Dispatchers.Main.immediate + Job() + + override fun getState(): Int = (data?.state ?: State.Idle).ordinal + override fun getProfileName(): String = data?.proxy?.profile?.displayName() ?: "Idle" + + override fun registerCallback(cb: ISagerNetServiceCallback) { + callbacks.register(cb) + cb.updateWakeLockStatus(data?.proxy?.service?.wakeLock != null) + } + + val boardcastMutex = Mutex() + + suspend fun broadcast(work: (ISagerNetServiceCallback) -> Unit) { + boardcastMutex.withLock { + val count = callbacks.beginBroadcast() + try { + repeat(count) { + try { + work(callbacks.getBroadcastItem(it)) + } catch (_: RemoteException) { + } catch (_: Exception) { + } + } + } finally { + callbacks.finishBroadcast() + } + } + } + + override fun unregisterCallback(cb: ISagerNetServiceCallback) { + callbacks.unregister(cb) + } + + override fun urlTest(): Int { + if (data?.proxy?.box == null) { + error("core not started") + } + try { + return Libcore.urlTest( + data!!.proxy!!.box, DataStore.connectionTestURL, 3000 + ) + } catch (e: Exception) { + error(Protocols.genFriendlyMsg(e.readableMessage)) + } + } + + fun stateChanged(s: State, msg: String?) = launch { + val profileName = profileName + broadcast { it.stateChanged(s.ordinal, profileName, msg) } + } + + fun missingPlugin(pluginName: String) = launch { + val profileName = profileName + broadcast { it.missingPlugin(profileName, pluginName) } + } + + override fun close() { + callbacks.kill() + cancel() + data = null + } + } + + interface Interface { + val data: Data + val tag: String + fun createNotification(profileName: String): ServiceNotification + + fun onBind(intent: Intent): IBinder? = + if (intent.action == Action.SERVICE) data.binder else null + + fun forceLoad() { + if (DataStore.selectedProxy == 0L) { + stopRunner(false, (this as Context).getString(R.string.profile_empty)) + } + val s = data.state + when { + s == State.Stopped -> startRunner() + s.canStop -> stopRunner(true) + else -> Logs.w("Illegal state $s when invoking use") + } + } + + suspend fun startProcesses() { + data.proxy!!.launch() + } + + fun startRunner() { + this as Context + if (Build.VERSION.SDK_INT >= 26) startForegroundService(Intent(this, javaClass)) + else startService(Intent(this, javaClass)) + } + + fun killProcesses() { + data.proxy?.close() + wakeLock?.apply { + release() + wakeLock = null + } + runOnDefaultDispatcher { + DefaultNetworkListener.stop(this) + } + } + + fun stopRunner(restart: Boolean = false, msg: String? = null) { + DataStore.baseService = null + DataStore.vpnService = null + + if (data.state == State.Stopping) return + data.notification?.destroy() + data.notification = null + this as Service + + data.changeState(State.Stopping) + + runOnMainDispatcher { + data.connectingJob?.cancelAndJoin() // ensure stop connecting first + // we use a coroutineScope here to allow clean-up in parallel + coroutineScope { + killProcesses() + val data = data + if (data.closeReceiverRegistered) { + unregisterReceiver(data.receiver) + data.closeReceiverRegistered = false + } + data.proxy = null + } + + // change the state + data.changeState(State.Stopped, msg) + // stop the service if nothing has bound to it + if (restart) startRunner() else { + stopSelf() + } + } + } + + open fun persistStats() { + // TODO NEW save app stats? + } + + // networks + var upstreamInterfaceName: String? + + suspend fun preInit() { + DefaultNetworkListener.start(this) { + SagerNet.connectivity.getLinkProperties(it)?.also { link -> + val oldName = upstreamInterfaceName + if (oldName != link.interfaceName) { + upstreamInterfaceName = link.interfaceName + } + if (oldName != null && upstreamInterfaceName != null && oldName != upstreamInterfaceName) { + Logs.d("Network changed: $oldName -> $upstreamInterfaceName") + Libcore.resetAllConnections(true) + } + } + } + } + + var wakeLock: PowerManager.WakeLock? + fun acquireWakeLock() + suspend fun switchWakeLock() { + wakeLock?.apply { + release() + wakeLock = null + data.binder.broadcast { + it.updateWakeLockStatus(false) + } + } ?: apply { + acquireWakeLock() + data.binder.broadcast { + it.updateWakeLockStatus(true) + } + } + } + + suspend fun lateInit() { + wakeLock?.apply { + release() + wakeLock = null + } + + if (DataStore.acquireWakeLock) { + acquireWakeLock() + data.binder.broadcast { + it.updateWakeLockStatus(true) + } + } else { + data.binder.broadcast { + it.updateWakeLockStatus(false) + } + } + } + + fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + DataStore.baseService = this + + val data = data + if (data.state != State.Stopped) return Service.START_NOT_STICKY + val profile = SagerDatabase.proxyDao.getById(DataStore.selectedProxy) + this as Context + if (profile == null) { // gracefully shutdown: https://stackoverflow.com/q/47337857/2245107 + data.notification = createNotification("") + stopRunner(false, getString(R.string.profile_empty)) + return Service.START_NOT_STICKY + } + + val proxy = ProxyInstance(profile, this) + data.proxy = proxy + if (!data.closeReceiverRegistered) { + registerReceiver(data.receiver, IntentFilter().apply { + addAction(Action.RELOAD) + addAction(Intent.ACTION_SHUTDOWN) + addAction(Action.CLOSE) + addAction(Action.SWITCH_WAKE_LOCK) + }, "$packageName.SERVICE", null) + data.closeReceiverRegistered = true + } + + data.changeState(State.Connecting) + runOnMainDispatcher { + try { + data.notification = createNotification(profile.displayName()) + + Executable.killAll() // clean up old processes + preInit() + proxy.init() + DataStore.currentProfile = profile.id + + proxy.processes = GuardedProcessPool { + Logs.w(it) + stopRunner(false, it.readableMessage) + } + + startProcesses() + data.changeState(State.Connected) + + for ((type, routeName) in proxy.config.alerts) { + data.binder.broadcast { + it.routeAlert(type, routeName) + } + } + + lateInit() + } catch (_: CancellationException) { // if the job was cancelled, it is canceller's responsibility to call stopRunner + } catch (_: UnknownHostException) { + stopRunner(false, getString(R.string.invalid_server)) + } catch (e: PluginManager.PluginNotFoundException) { + Toast.makeText(this@Interface, e.readableMessage, Toast.LENGTH_SHORT).show() + Logs.d(e.readableMessage) + data.binder.missingPlugin(e.plugin) + stopRunner(false, null) + } catch (exc: Throwable) { + if (exc.javaClass.name.endsWith("proxyerror")) { + // error from golang + Logs.w(exc.readableMessage) + } else { + Logs.w(exc) + } + stopRunner( + false, "${getString(R.string.service_failed)}: ${exc.readableMessage}" + ) + } finally { + data.connectingJob = null + } + } + return Service.START_NOT_STICKY + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/Executable.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/Executable.kt new file mode 100644 index 0000000..8e69f0b --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/Executable.kt @@ -0,0 +1,41 @@ +package io.nekohasekai.sagernet.bg + +import android.system.ErrnoException +import android.system.Os +import android.system.OsConstants +import android.text.TextUtils +import io.nekohasekai.sagernet.ktx.Logs +import java.io.File +import java.io.IOException + +object Executable { + private val EXECUTABLES = setOf( + "libtrojan.so", + "libtrojan-go.so", + "libnaive.so", + "libhysteria.so", + "libwg.so" + ) + + fun killAll(alsoKillBg: Boolean = false) { + for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) } + ?: return) { + val exe = File(try { + File(process, "cmdline").inputStream().bufferedReader().use { + it.readText() + } + } catch (_: IOException) { + continue + }.split(Character.MIN_VALUE, limit = 2).first()) + if (EXECUTABLES.contains(exe.name) || (alsoKillBg && exe.name.endsWith(":bg"))) try { + Os.kill(process.name.toInt(), OsConstants.SIGKILL) + Logs.w("SIGKILL ${exe.name} (${process.name}) succeed") + } catch (e: ErrnoException) { + if (e.errno != OsConstants.ESRCH) { + Logs.w("SIGKILL ${exe.absolutePath} (${process.name}) failed") + Logs.w(e) + } + } + } + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/GuardedProcessPool.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/GuardedProcessPool.kt new file mode 100644 index 0000000..db70000 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/GuardedProcessPool.kt @@ -0,0 +1,121 @@ +package io.nekohasekai.sagernet.bg + +import android.os.Build +import android.os.SystemClock +import android.system.ErrnoException +import android.system.Os +import android.system.OsConstants +import androidx.annotation.MainThread +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.utils.Commandline +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import libcore.Libcore +import java.io.File +import java.io.IOException +import java.io.InputStream +import kotlin.concurrent.thread + +class GuardedProcessPool(private val onFatal: suspend (IOException) -> Unit) : CoroutineScope { + companion object { + private val pid by lazy { + Class.forName("java.lang.ProcessManager\$ProcessImpl").getDeclaredField("pid") + .apply { isAccessible = true } + } + } + + private inner class Guard( + private val cmd: List, + private val env: Map = mapOf() + ) { + private lateinit var process: Process + + private fun streamLogger(input: InputStream, logger: (String) -> Unit) = try { + input.bufferedReader().forEachLine(logger) + } catch (_: IOException) { + } // ignore + + fun start() { + process = ProcessBuilder(cmd).directory(SagerNet.application.noBackupFilesDir).apply { + environment().putAll(env) + }.start() + } + + @DelicateCoroutinesApi + suspend fun looper(onRestartCallback: (suspend () -> Unit)?) { + var running = true + val cmdName = File(cmd.first()).nameWithoutExtension + val exitChannel = Channel() + try { + while (true) { + thread(name = "stderr-$cmdName") { + streamLogger(process.errorStream) { Libcore.nekoLogPrintln("[$cmdName] $it") } + } + thread(name = "stdout-$cmdName") { + streamLogger(process.inputStream) { Libcore.nekoLogPrintln("[$cmdName] $it") } + // this thread also acts as a daemon thread for waitFor + runBlocking { exitChannel.send(process.waitFor()) } + } + val startTime = SystemClock.elapsedRealtime() + val exitCode = exitChannel.receive() + running = false + when { + SystemClock.elapsedRealtime() - startTime < 1000 -> throw IOException( + "$cmdName exits too fast (exit code: $exitCode)" + ) + exitCode == 128 + OsConstants.SIGKILL -> Logs.w("$cmdName was killed") + else -> Logs.w(IOException("$cmdName unexpectedly exits with code $exitCode")) + } + Logs.i("restart process: ${Commandline.toString(cmd)} (last exit code: $exitCode)") + start() + running = true + onRestartCallback?.invoke() + } + } catch (e: IOException) { + Logs.w("error occurred. stop guard: ${Commandline.toString(cmd)}") + GlobalScope.launch(Dispatchers.Main) { onFatal(e) } + } finally { + if (running) withContext(NonCancellable) { // clean-up cannot be cancelled + if (Build.VERSION.SDK_INT < 24) { + try { + Os.kill(pid.get(process) as Int, OsConstants.SIGTERM) + } catch (e: ErrnoException) { + if (e.errno != OsConstants.ESRCH) Logs.w(e) + } catch (e: ReflectiveOperationException) { + Logs.w(e) + } + if (withTimeoutOrNull(500) { exitChannel.receive() } != null) return@withContext + } + process.destroy() // kill the process + if (Build.VERSION.SDK_INT >= 26) { + if (withTimeoutOrNull(1000) { exitChannel.receive() } != null) return@withContext + process.destroyForcibly() // Force to kill the process if it's still alive + } + exitChannel.receive() + } // otherwise process already exited, nothing to be done + } + } + } + + override val coroutineContext = Dispatchers.Main.immediate + Job() + + @MainThread + fun start( + cmd: List, + env: MutableMap = mutableMapOf(), + onRestartCallback: (suspend () -> Unit)? = null + ) { + Logs.i("start process: ${Commandline.toString(cmd)}") + Guard(cmd, env).apply { + start() // if start fails, IOException will be thrown directly + launch { looper(onRestartCallback) } + } + } + + @MainThread + fun close(scope: CoroutineScope) { + cancel() + coroutineContext[Job]!!.also { job -> scope.launch { job.cancelAndJoin() } } + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/ProxyService.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/ProxyService.kt new file mode 100644 index 0000000..448ac26 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/ProxyService.kt @@ -0,0 +1,27 @@ +package io.nekohasekai.sagernet.bg + +import android.annotation.SuppressLint +import android.app.Service +import android.content.Intent +import android.os.PowerManager +import io.nekohasekai.sagernet.SagerNet + +class ProxyService : Service(), BaseService.Interface { + override val data = BaseService.Data(this) + override val tag: String get() = "SagerNetProxyService" + override fun createNotification(profileName: String): ServiceNotification = + ServiceNotification(this, profileName, "service-proxy", true) + + override var wakeLock: PowerManager.WakeLock? = null + override var upstreamInterfaceName: String? = null + + @SuppressLint("WakelockTimeout") + override fun acquireWakeLock() { + wakeLock = SagerNet.power.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "sagernet:proxy") + .apply { acquire() } + } + + override fun onBind(intent: Intent) = super.onBind(intent) + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = + super.onStartCommand(intent, flags, startId) +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/SagerConnection.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/SagerConnection.kt new file mode 100644 index 0000000..bfc0d24 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/SagerConnection.kt @@ -0,0 +1,167 @@ +package io.nekohasekai.sagernet.bg + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.IBinder +import android.os.RemoteException +import io.nekohasekai.sagernet.Action +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.aidl.* +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.ktx.runOnMainDispatcher + +class SagerConnection(private var listenForDeath: Boolean = false) : ServiceConnection, + IBinder.DeathRecipient { + + companion object { + val serviceClass + get() = when (DataStore.serviceMode) { + Key.MODE_PROXY -> ProxyService::class + Key.MODE_VPN -> VpnService::class // Key.MODE_TRANS -> TransproxyService::class + else -> throw UnknownError() + }.java + } + + interface Callback { + // smaller ISagerNetServiceCallback + + fun cbSpeedUpdate(stats: SpeedDisplayData) {} + fun cbTrafficUpdate(data: TrafficData) {} + + fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) + + fun missingPlugin(profileName: String, pluginName: String) {} + fun routeAlert(type: Int, routeName: String) {} + + fun onServiceConnected(service: ISagerNetService) + + /** + * Different from Android framework, this method will be called even when you call `detachService`. + */ + fun onServiceDisconnected() {} + fun onBinderDied() {} + } + + private var connectionActive = false + private var callbackRegistered = false + private var callback: Callback? = null + private val serviceCallback = object : ISagerNetServiceCallback.Stub() { + + override fun stateChanged(state: Int, profileName: String?, msg: String?) { + val s = BaseService.State.values()[state] + DataStore.serviceState = s + val callback = callback ?: return + runOnMainDispatcher { + callback.stateChanged(s, profileName, msg) + } + } + + override fun cbSpeedUpdate(stats: SpeedDisplayData) { + val callback = callback ?: return + runOnMainDispatcher { + callback.cbSpeedUpdate(stats) + } + } + + override fun cbTrafficUpdate(stats: TrafficData) { + val callback = callback ?: return + runOnMainDispatcher { + callback.cbTrafficUpdate(stats) + } + } + + override fun missingPlugin(profileName: String, pluginName: String) { + val callback = callback ?: return + runOnMainDispatcher { + callback.missingPlugin(profileName, pluginName) + } + } + + override fun routeAlert(type: Int, routeName: String) { + val callback = callback ?: return + runOnMainDispatcher { + callback.routeAlert(type, routeName) + } + } + + override fun updateWakeLockStatus(acquired: Boolean) { + } + + override fun cbLogUpdate(str: String?) { + DataStore.postLogListener?.let { + if (str != null) { + it(str) + } + } + } + + } + + private var binder: IBinder? = null + + var service: ISagerNetService? = null + + override fun onServiceConnected(name: ComponentName?, binder: IBinder) { + this.binder = binder + val service = ISagerNetService.Stub.asInterface(binder)!! + this.service = service + try { + if (listenForDeath) binder.linkToDeath(this, 0) + check(!callbackRegistered) + service.registerCallback(serviceCallback) + callbackRegistered = true + } catch (e: RemoteException) { + e.printStackTrace() + } + callback!!.onServiceConnected(service) + } + + override fun onServiceDisconnected(name: ComponentName?) { + unregisterCallback() + callback?.onServiceDisconnected() + service = null + binder = null + } + + override fun binderDied() { + service = null + callbackRegistered = false + callback?.also { runOnMainDispatcher { it.onBinderDied() } } + } + + private fun unregisterCallback() { + val service = service + if (service != null && callbackRegistered) try { + service.unregisterCallback(serviceCallback) + } catch (_: RemoteException) { + } + callbackRegistered = false + } + + fun connect(context: Context, callback: Callback) { + if (connectionActive) return + connectionActive = true + check(this.callback == null) + this.callback = callback + val intent = Intent(context, serviceClass).setAction(Action.SERVICE) + context.bindService(intent, this, Context.BIND_AUTO_CREATE) + } + + fun disconnect(context: Context) { + unregisterCallback() + if (connectionActive) try { + context.unbindService(this) + } catch (_: IllegalArgumentException) { + } // ignore + connectionActive = false + if (listenForDeath) try { + binder?.unlinkToDeath(this, 0) + } catch (_: NoSuchElementException) { + } + binder = null + service = null + callback = null + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/ServiceNotification.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/ServiceNotification.kt new file mode 100644 index 0000000..eddb573 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/ServiceNotification.kt @@ -0,0 +1,202 @@ +package io.nekohasekai.sagernet.bg + +import android.app.PendingIntent +import android.app.Service +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Build +import android.text.format.Formatter +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import io.nekohasekai.sagernet.Action +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.aidl.ISagerNetServiceCallback +import io.nekohasekai.sagernet.aidl.SpeedDisplayData +import io.nekohasekai.sagernet.aidl.TrafficData +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.ktx.app +import io.nekohasekai.sagernet.ktx.getColorAttr +import io.nekohasekai.sagernet.ui.SwitchActivity +import io.nekohasekai.sagernet.utils.Theme + +/** + * User can customize visibility of notification since Android 8. + * The default visibility: + * + * Android 8.x: always visible due to system limitations + * VPN: always invisible because of VPN notification/icon + * Other: always visible + * + * See also: https://github.com/aosp-mirror/platform_frameworks_base/commit/070d142993403cc2c42eca808ff3fafcee220ac4 + */ +class ServiceNotification( + private val service: BaseService.Interface, profileName: String, + channel: String, visible: Boolean = false, +) : BroadcastReceiver() { + companion object { + const val notificationId = 1 + val flags = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0 + } + + val trafficStatistics = DataStore.profileTrafficStatistics + val showDirectSpeed = DataStore.showDirectSpeed + + private val callback: ISagerNetServiceCallback by lazy { + object : ISagerNetServiceCallback.Stub() { + override fun cbSpeedUpdate(stats: SpeedDisplayData) { + if (!trafficStatistics) return + builder.apply { + if (showDirectSpeed) { + val speedDetail = (service as Context).getString( + R.string.speed_detail, service.getString( + R.string.speed, Formatter.formatFileSize(service, stats.txRateProxy) + ), service.getString( + R.string.speed, Formatter.formatFileSize(service, stats.rxRateProxy) + ), service.getString( + R.string.speed, + Formatter.formatFileSize(service, stats.txRateDirect) + ), service.getString( + R.string.speed, + Formatter.formatFileSize(service, stats.rxRateDirect) + ) + ) + setStyle(NotificationCompat.BigTextStyle().bigText(speedDetail)) + setContentText(speedDetail) + } else { + val speedSimple = (service as Context).getString( + R.string.traffic, service.getString( + R.string.speed, Formatter.formatFileSize(service, stats.txRateProxy) + ), service.getString( + R.string.speed, Formatter.formatFileSize(service, stats.rxRateProxy) + ) + ) + setContentText(speedSimple) + } + setSubText( + service.getString( + R.string.traffic, + Formatter.formatFileSize(service, stats.txTotal), + Formatter.formatFileSize(service, stats.rxTotal) + ) + ) + } + update() + } + + override fun cbTrafficUpdate(stats: TrafficData?) { + } + + override fun stateChanged(state: Int, profileName: String?, msg: String?) { + } + + override fun missingPlugin(profileName: String?, pluginName: String?) { + } + + override fun routeAlert(type: Int, routeName: String?) { + } + + override fun updateWakeLockStatus(acquired: Boolean) { + updateActions(acquired) + builder.priority = + if (acquired) NotificationCompat.PRIORITY_HIGH else NotificationCompat.PRIORITY_LOW + update() + } + + override fun cbLogUpdate(str: String?) { + } + } + } + private var callbackRegistered = false + + private val builder = NotificationCompat.Builder(service as Context, channel) + .setWhen(0) + .setTicker(service.getString(R.string.forward_success)) + .setContentTitle(profileName) + .setOnlyAlertOnce(true) + .setContentIntent(SagerNet.configureIntent(service)) + .setSmallIcon(R.drawable.ic_service_active) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .setPriority(if (visible) NotificationCompat.PRIORITY_LOW else NotificationCompat.PRIORITY_MIN) + + init { + service as Context + updateActions(false) + + Theme.apply(app) + Theme.apply(service) + builder.color = service.getColorAttr(R.attr.colorPrimary) + + updateCallback(SagerNet.power.isInteractive) + service.registerReceiver(this, IntentFilter().apply { + addAction(Intent.ACTION_SCREEN_ON) + addAction(Intent.ACTION_SCREEN_OFF) + }) + show() + } + + fun updateActions(wakeLockAcquired: Boolean) { + service as Context + + builder.clearActions() + val closeAction = NotificationCompat.Action.Builder( + 0, service.getText(R.string.stop), PendingIntent.getBroadcast( + service, 0, Intent(Action.CLOSE).setPackage(service.packageName), flags + ) + ).apply { + setShowsUserInterface(false) + }.build() + builder.addAction(closeAction) + + val switchAction = NotificationCompat.Action.Builder( + 0, service.getString(R.string.action_switch), PendingIntent.getActivity( + service, 0, Intent(service, SwitchActivity::class.java), flags + ) + ).apply { + setShowsUserInterface(false) + }.build() + builder.addAction(switchAction) + + val wakeLockAction = NotificationCompat.Action.Builder( + 0, + service.getText(if (!wakeLockAcquired) R.string.acquire_wake_lock else R.string.release_wake_lock), + PendingIntent.getBroadcast( + service, + 0, + Intent(Action.SWITCH_WAKE_LOCK).setPackage(service.packageName), + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0 + ) + ).apply { + setShowsUserInterface(false) + }.build() + builder.addAction(wakeLockAction) + } + + override fun onReceive(context: Context, intent: Intent) { + if (service.data.state == BaseService.State.Connected) updateCallback(intent.action == Intent.ACTION_SCREEN_ON) + } + + private fun updateCallback(screenOn: Boolean) { + if (!trafficStatistics) return + if (screenOn) { + service.data.binder.registerCallback(callback) + callbackRegistered = true + } else if (callbackRegistered) { // unregister callback to save battery + service.data.binder.unregisterCallback(callback) + callbackRegistered = false + } + } + + private fun show() = (service as Service).startForeground(notificationId, builder.build()) + private fun update() = + NotificationManagerCompat.from(service as Service).notify(notificationId, builder.build()) + + fun destroy() { + (service as Service).stopForeground(true) + service.unregisterReceiver(this) + updateCallback(false) + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/SubscriptionUpdater.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/SubscriptionUpdater.kt new file mode 100644 index 0000000..5a367ee --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/SubscriptionUpdater.kt @@ -0,0 +1,97 @@ +package io.nekohasekai.sagernet.bg + +import android.content.Context +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.work.CoroutineWorker +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.PeriodicWorkRequest +import androidx.work.WorkerParameters +import androidx.work.multiprocess.RemoteWorkManager +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.SagerDatabase +import io.nekohasekai.sagernet.group.GroupUpdater +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.ktx.app +import java.util.concurrent.TimeUnit + +object SubscriptionUpdater { + + private const val WORK_NAME = "SubscriptionUpdater" + + suspend fun reconfigureUpdater() { + RemoteWorkManager.getInstance(app).cancelUniqueWork(WORK_NAME) + + val subscriptions = SagerDatabase.groupDao.subscriptions() + .filter { it.subscription!!.autoUpdate } + if (subscriptions.isEmpty()) return + + // PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS + var minDelay = + subscriptions.minByOrNull { it.subscription!!.autoUpdateDelay }!!.subscription!!.autoUpdateDelay.toLong() + val now = System.currentTimeMillis() / 1000L + var minInitDelay = + subscriptions.minOf { now - it.subscription!!.lastUpdated - (minDelay * 60) } + if (minDelay < 15) minDelay = 15 + if (minInitDelay > 60) minInitDelay = 60 + + // main process + RemoteWorkManager.getInstance(app).enqueueUniquePeriodicWork( + WORK_NAME, + ExistingPeriodicWorkPolicy.REPLACE, + PeriodicWorkRequest.Builder(UpdateTask::class.java, minDelay, TimeUnit.MINUTES) + .apply { + if (minInitDelay > 0) setInitialDelay(minInitDelay, TimeUnit.SECONDS) + } + .build() + ) + } + + class UpdateTask( + appContext: Context, params: WorkerParameters + ) : CoroutineWorker(appContext, params) { + + val nm = NotificationManagerCompat.from(applicationContext) + + val notification = NotificationCompat.Builder(applicationContext, "service-subscription") + .setWhen(0) + .setTicker(applicationContext.getString(R.string.forward_success)) + .setContentTitle(applicationContext.getString(R.string.subscription_update)) + .setSmallIcon(R.drawable.ic_service_active) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + + override suspend fun doWork(): Result { + var subscriptions = + SagerDatabase.groupDao.subscriptions().filter { it.subscription!!.autoUpdate } + if (!DataStore.serviceState.connected) { + Logs.d("work: not connected") + subscriptions = subscriptions.filter { !it.subscription!!.updateWhenConnectedOnly } + } + + if (subscriptions.isNotEmpty()) for (profile in subscriptions) { + val subscription = profile.subscription!! + + if (((System.currentTimeMillis() / 1000).toInt() - subscription.lastUpdated) < subscription.autoUpdateDelay * 60) { + Logs.d("work: not updating " + profile.displayName()) + continue + } + Logs.d("work: updating " + profile.displayName()) + + notification.setContentText( + applicationContext.getString( + R.string.subscription_update_message, profile.displayName() + ) + ) + nm.notify(2, notification.build()) + + GroupUpdater.executeUpdate(profile, false) + } + + nm.cancel(2) + + return Result.success() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/TileService.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/TileService.kt new file mode 100644 index 0000000..5c56d9e --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/TileService.kt @@ -0,0 +1,104 @@ +/******************************************************************************* + * * + * Copyright (C) 2017 by Max Lv * + * Copyright (C) 2017 by Mygod Studio * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation, either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + * * + *******************************************************************************/ + +package io.nekohasekai.sagernet.bg + +import android.graphics.drawable.Icon +import android.service.quicksettings.Tile +import androidx.annotation.RequiresApi +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.aidl.ISagerNetService +import android.service.quicksettings.TileService as BaseTileService + +@RequiresApi(24) +class TileService : BaseTileService(), SagerConnection.Callback { + private val iconIdle by lazy { Icon.createWithResource(this, R.drawable.ic_service_idle) } + private val iconBusy by lazy { Icon.createWithResource(this, R.drawable.ic_service_busy) } + private val iconConnected by lazy { + Icon.createWithResource(this, R.drawable.ic_service_active) + } + private var tapPending = false + + private val connection = SagerConnection() + override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) = + updateTile(state) { profileName } + + override fun onServiceConnected(service: ISagerNetService) { + updateTile(BaseService.State.values()[service.state]) { service.profileName } + if (tapPending) { + tapPending = false + onClick() + } + } + + override fun onStartListening() { + super.onStartListening() + connection.connect(this, this) + } + + override fun onStopListening() { + connection.disconnect(this) + super.onStopListening() + } + + override fun onClick() { + toggle() + } + + private fun updateTile(serviceState: BaseService.State, profileName: () -> String?) { + qsTile?.apply { + label = null + when (serviceState) { + BaseService.State.Idle -> error("serviceState") + BaseService.State.Connecting -> { + icon = iconBusy + state = Tile.STATE_ACTIVE + } + BaseService.State.Connected -> { + icon = iconConnected + label = profileName() + state = Tile.STATE_ACTIVE + } + BaseService.State.Stopping -> { + icon = iconBusy + state = Tile.STATE_UNAVAILABLE + } + BaseService.State.Stopped -> { + icon = iconIdle + state = Tile.STATE_INACTIVE + } + } + label = label ?: getString(R.string.app_name) + updateTile() + } + } + + private fun toggle() { + val service = connection.service + if (service == null) tapPending = + true else BaseService.State.values()[service.state].let { state -> + when { + state.canStop -> SagerNet.stopService() + state == BaseService.State.Stopped -> SagerNet.startService() + } + } + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt new file mode 100644 index 0000000..2d4c0d3 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt @@ -0,0 +1,269 @@ +package io.nekohasekai.sagernet.bg + +import android.Manifest +import android.annotation.SuppressLint +import android.app.Service +import android.content.Intent +import android.content.pm.PackageManager +import android.net.ProxyInfo +import android.os.Build +import android.os.ParcelFileDescriptor +import android.os.PowerManager +import io.nekohasekai.sagernet.* +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.fmt.LOCALHOST +import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean +import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.ui.VpnRequestActivity +import io.nekohasekai.sagernet.utils.Subnet +import libcore.* +import moe.matsuri.nb4a.net.LocalResolverImpl +import moe.matsuri.nb4a.proxy.neko.needBypassRootUid +import android.net.VpnService as BaseVpnService + +class VpnService : BaseVpnService(), + BaseService.Interface { + + companion object { + + const val PRIVATE_VLAN4_CLIENT = "172.19.0.1" + const val PRIVATE_VLAN4_ROUTER = "172.19.0.2" + const val FAKEDNS_VLAN4_CLIENT = "198.18.0.0" + const val PRIVATE_VLAN6_CLIENT = "fdfe:dcba:9876::1" + const val PRIVATE_VLAN6_ROUTER = "fdfe:dcba:9876::2" + + } + + var conn: ParcelFileDescriptor? = null + + private var metered = false + + override var upstreamInterfaceName: String? = null + + override suspend fun startProcesses() { + DataStore.vpnService = this + super.startProcesses() // launch proxy instance + } + + override var wakeLock: PowerManager.WakeLock? = null + + @SuppressLint("WakelockTimeout") + override fun acquireWakeLock() { + wakeLock = SagerNet.power.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "sagernet:vpn") + .apply { acquire() } + } + + @Suppress("EXPERIMENTAL_API_USAGE") + override fun killProcesses() { + conn?.close() + conn = null + super.killProcesses() + } + + override fun onBind(intent: Intent) = when (intent.action) { + SERVICE_INTERFACE -> super.onBind(intent) + else -> super.onBind(intent) + } + + override val data = BaseService.Data(this) + override val tag = "SagerNetVpnService" + override fun createNotification(profileName: String) = + ServiceNotification(this, profileName, "service-vpn") + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (DataStore.serviceMode == Key.MODE_VPN) { + if (prepare(this) != null) { + startActivity( + Intent( + this, VpnRequestActivity::class.java + ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) + } else return super.onStartCommand(intent, flags, startId) + } + stopRunner() + return Service.START_NOT_STICKY + } + + inner class NullConnectionException : NullPointerException(), + BaseService.ExpectedException { + override fun getLocalizedMessage() = getString(R.string.reboot_required) + } + + fun startVpn(tunOptionsJson: String, tunPlatformOptionsJson: String): Int { +// Logs.d(tunOptionsJson) +// Logs.d(tunPlatformOptionsJson) +// val tunOptions = JSONObject(tunOptionsJson) + + // address & route & MTU ...... use NB4A GUI config + val profile = data.proxy!!.profile + val builder = Builder().setConfigureIntent(SagerNet.configureIntent(this)) + .setSession(profile.displayName()) + .setMtu(DataStore.mtu) + val ipv6Mode = DataStore.ipv6Mode + + // address + builder.addAddress(PRIVATE_VLAN4_CLIENT, 30) + if (ipv6Mode != IPv6Mode.DISABLE) { + builder.addAddress(PRIVATE_VLAN6_CLIENT, 126) + } + builder.addDnsServer(PRIVATE_VLAN4_ROUTER) + + // route + if (DataStore.bypassLan && !DataStore.bypassLanInCoreOnly) { + resources.getStringArray(R.array.bypass_private_route).forEach { + val subnet = Subnet.fromString(it)!! + builder.addRoute(subnet.address.hostAddress!!, subnet.prefixSize) + } + builder.addRoute(PRIVATE_VLAN4_ROUTER, 32) + builder.addRoute(FAKEDNS_VLAN4_CLIENT, 15) + // https://issuetracker.google.com/issues/149636790 + if (ipv6Mode != IPv6Mode.DISABLE) { + builder.addRoute("2000::", 3) + } + } else { + builder.addRoute("0.0.0.0", 0) + if (ipv6Mode != IPv6Mode.DISABLE) { + builder.addRoute("::", 0) + } + } + + // ? + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + // TODO listen for change? + if (SagerNet.underlyingNetwork != null) { + builder.setUnderlyingNetworks(arrayOf(SagerNet.underlyingNetwork)) + } + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) builder.setMetered(metered) + + // app route + val packageName = packageName + var proxyApps = DataStore.proxyApps + var bypass = DataStore.bypass + var workaroundSYSTEM = false /* DataStore.tunImplementation == TunImplementation.SYSTEM */ + var needBypassRootUid = workaroundSYSTEM || data.proxy!!.config.trafficMap.values.any { + it.nekoBean?.needBypassRootUid() == true || it.hysteriaBean?.protocol == HysteriaBean.PROTOCOL_FAKETCP + } + + if (proxyApps || needBypassRootUid) { + val individual = mutableSetOf() + val allApps by lazy { + packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS).filter { + when (it.packageName) { + packageName -> false + "android" -> true + else -> it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true + } + }.map { + it.packageName + } + } + if (proxyApps) { + individual.addAll(DataStore.individual.split('\n').filter { it.isNotBlank() }) + if (bypass && needBypassRootUid) { + val individualNew = allApps.toMutableList() + individualNew.removeAll(individual) + individual.clear() + individual.addAll(individualNew) + bypass = false + } + } else { + individual.addAll(allApps) + bypass = false + } + + val added = mutableListOf() + + individual.apply { + // Allow Matsuri itself using VPN. + remove(packageName) + if (!bypass) add(packageName) + }.forEach { + try { + if (bypass) { + builder.addDisallowedApplication(it) + } else { + builder.addAllowedApplication(it) + } + added.add(it) + } catch (ex: PackageManager.NameNotFoundException) { + Logs.w(ex) + } + } + + if (bypass) { + Logs.d("Add bypass: ${added.joinToString(", ")}") + } else { + Logs.d("Add allow: ${added.joinToString(", ")}") + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && DataStore.appendHttpProxy) { + builder.setHttpProxy(ProxyInfo.buildDirectProxy(LOCALHOST, DataStore.mixedPort)) + } + + metered = DataStore.meteredNetwork + if (Build.VERSION.SDK_INT >= 29) builder.setMetered(metered) + conn = builder.establish() ?: throw NullConnectionException() + + // post setup + Libcore.setLocalResolver(LocalResolverImpl) + + return conn!!.fd + } + +// +// val appStats = mutableListOf() +// +// override fun updateStats(stats: AppStats) { +// appStats.add(stats) +// } +// +// fun persistAppStats() { +// if (!DataStore.appTrafficStatistics) return +// val tun = getTun() ?: return +// appStats.clear() +// tun.readAppTraffics(this) +// val toUpdate = mutableListOf() +// val all = SagerDatabase.statsDao.all().associateBy { it.packageName } +// for (stats in appStats) { +// if (stats.nekoConnectionsJSON.isNotBlank()) continue +// val packageName = if (stats.uid >= 10000) { +// PackageCache.uidMap[stats.uid]?.iterator()?.next() ?: "android" +// } else { +// "android" +// } +// if (!all.containsKey(packageName)) { +// SagerDatabase.statsDao.create( +// StatsEntity( +// packageName = packageName, +// tcpConnections = stats.tcpConnTotal, +// udpConnections = stats.udpConnTotal, +// uplink = stats.uplinkTotal, +// downlink = stats.downlinkTotal +// ) +// ) +// } else { +// val entity = all[packageName]!! +// entity.tcpConnections += stats.tcpConnTotal +// entity.udpConnections += stats.udpConnTotal +// entity.uplink += stats.uplinkTotal +// entity.downlink += stats.downlinkTotal +// toUpdate.add(entity) +// } +// if (toUpdate.isNotEmpty()) { +// SagerDatabase.statsDao.update(toUpdate) +// } +// +// } +// } + + override fun onRevoke() = stopRunner() + + override fun onDestroy() { + Libcore.setLocalResolver(null) + DataStore.vpnService = null + super.onDestroy() + data.binder.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt new file mode 100644 index 0000000..0826578 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt @@ -0,0 +1,277 @@ +package io.nekohasekai.sagernet.bg.proto + +import android.os.Build +import android.os.SystemClock +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.bg.AbstractInstance +import io.nekohasekai.sagernet.bg.GuardedProcessPool +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.ProxyEntity +import io.nekohasekai.sagernet.fmt.ConfigBuildResult +import io.nekohasekai.sagernet.fmt.buildConfig +import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean +import io.nekohasekai.sagernet.fmt.hysteria.buildHysteriaConfig +import io.nekohasekai.sagernet.fmt.naive.NaiveBean +import io.nekohasekai.sagernet.fmt.naive.buildNaiveConfig +import io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean +import io.nekohasekai.sagernet.fmt.trojan_go.buildTrojanGoConfig +import io.nekohasekai.sagernet.fmt.tuic.TuicBean +import io.nekohasekai.sagernet.fmt.tuic.buildTuicConfig +import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.plugin.PluginManager +import kotlinx.coroutines.* +import libcore.BoxInstance +import libcore.Libcore +import moe.matsuri.nb4a.proxy.neko.NekoBean +import moe.matsuri.nb4a.proxy.neko.NekoJSInterface +import moe.matsuri.nb4a.plugin.NekoPluginManager +import moe.matsuri.nb4a.proxy.neko.updateAllConfig +import org.json.JSONObject +import java.io.File + +abstract class BoxInstance( + val profile: ProxyEntity +) : AbstractInstance { + + lateinit var config: ConfigBuildResult + lateinit var box: BoxInstance + + val pluginPath = hashMapOf() + val pluginConfigs = hashMapOf>() + val externalInstances = hashMapOf() + open lateinit var processes: GuardedProcessPool + private var cacheFiles = ArrayList() + fun isInitialized(): Boolean { + return ::config.isInitialized && ::box.isInitialized + } + + protected fun initPlugin(name: String): PluginManager.InitResult { + return pluginPath.getOrPut(name) { PluginManager.init(name)!! } + } + + protected open fun buildConfig() { + config = buildConfig(profile) + } + + protected open suspend fun loadConfig() { + NekoJSInterface.Default.destroyAllJsi() + box = Libcore.newSingBoxInstance(config.config) + } + + open suspend fun init() { + buildConfig() + for ((chain) in config.externalIndex) { + chain.entries.forEachIndexed { index, (port, profile) -> + when (val bean = profile.requireBean()) { + is TrojanGoBean -> { + initPlugin("trojan-go-plugin") + pluginConfigs[port] = profile.type to bean.buildTrojanGoConfig(port) + } + is NaiveBean -> { + initPlugin("naive-plugin") + pluginConfigs[port] = profile.type to bean.buildNaiveConfig(port) + } + is HysteriaBean -> { + initPlugin("hysteria-plugin") + pluginConfigs[port] = profile.type to bean.buildHysteriaConfig(port) { + File( + app.cacheDir, "hysteria_" + SystemClock.elapsedRealtime() + ".ca" + ).apply { + parentFile?.mkdirs() + cacheFiles.add(this) + } + } + } + is TuicBean -> { + initPlugin("tuic-plugin") + pluginConfigs[port] = profile.type to bean.buildTuicConfig(port) { + File( + app.noBackupFilesDir, + "tuic_" + SystemClock.elapsedRealtime() + ".ca" + ).apply { + parentFile?.mkdirs() + cacheFiles.add(this) + } + } + } + is NekoBean -> { + // check if plugin binary can be loaded + initPlugin(bean.plgId) + + // build config and check if succeed + bean.updateAllConfig(port) + if (bean.allConfig == null) { + throw NekoPluginManager.PluginInternalException(bean.protocolId) + } + } + } + } + } + loadConfig() + } + + override fun launch() { + // TODO move, this is not box + val context = + if (Build.VERSION.SDK_INT < 24 || SagerNet.user.isUserUnlocked) SagerNet.application else SagerNet.deviceStorage + val cache = File(context.cacheDir, "tmpcfg") + cache.mkdirs() + + for ((chain) in config.externalIndex) { + chain.entries.forEachIndexed { index, (port, profile) -> + val bean = profile.requireBean() + val needChain = index != chain.size - 1 + val (profileType, config) = pluginConfigs[port] ?: (0 to "") + + when { + externalInstances.containsKey(port) -> { + externalInstances[port]!!.launch() + } + bean is TrojanGoBean -> { + val configFile = File( + cache, "trojan_go_" + SystemClock.elapsedRealtime() + ".json" + ) + configFile.parentFile?.mkdirs() + configFile.writeText(config) + cacheFiles.add(configFile) + + val commands = mutableListOf( + initPlugin("trojan-go-plugin").path, "-config", configFile.absolutePath + ) + + processes.start(commands) + } + bean is NaiveBean -> { + val configFile = File( + cache, "naive_" + SystemClock.elapsedRealtime() + ".json" + ) + + configFile.parentFile?.mkdirs() + configFile.writeText(config) + cacheFiles.add(configFile) + + val envMap = mutableMapOf() + + if (bean.certificates.isNotBlank()) { + val certFile = File( + cache, "naive_" + SystemClock.elapsedRealtime() + ".crt" + ) + + certFile.parentFile?.mkdirs() + certFile.writeText(bean.certificates) + cacheFiles.add(certFile) + + envMap["SSL_CERT_FILE"] = certFile.absolutePath + } + + val commands = mutableListOf( + initPlugin("naive-plugin").path, configFile.absolutePath + ) + + processes.start(commands, envMap) + } + bean is HysteriaBean -> { + val configFile = File( + cache, "hysteria_" + SystemClock.elapsedRealtime() + ".json" + ) + + configFile.parentFile?.mkdirs() + configFile.writeText(config) + cacheFiles.add(configFile) + + val commands = mutableListOf( + initPlugin("hysteria-plugin").path, + "--no-check", + "--config", + configFile.absolutePath, + "--log-level", + if (DataStore.enableLog) "trace" else "warn", + "client" + ) + + if (bean.protocol == HysteriaBean.PROTOCOL_FAKETCP) { + commands.addAll(0, listOf("su", "-c")) + } + + processes.start(commands) + } + bean is NekoBean -> { + // config built from JS + val nekoRunConfigs = bean.allConfig.optJSONArray("nekoRunConfigs") + val configs = mutableMapOf() + + nekoRunConfigs?.forEach { _, any -> + any as JSONObject + + val name = any.getString("name") + val configFile = File(cache, name) + configFile.parentFile?.mkdirs() + val content = any.getString("content") + configFile.writeText(content) + + cacheFiles.add(configFile) + configs[name] = configFile.absolutePath + + Logs.d(name + "\n\n" + content) + } + + val nekoCommands = bean.allConfig.getJSONArray("nekoCommands") + val commands = mutableListOf() + + nekoCommands.forEach { _, any -> + if (any is String) { + if (configs.containsKey(any)) { + commands.add(configs[any]!!) + } else if (any == "%exe%") { + commands.add(initPlugin(bean.plgId).path) + } else { + commands.add(any) + } + } + } + + processes.start(commands) + } + bean is TuicBean -> { + val configFile = File( + context.noBackupFilesDir, + "tuic_" + SystemClock.elapsedRealtime() + ".json" + ) + + configFile.parentFile?.mkdirs() + configFile.writeText(config) + cacheFiles.add(configFile) + + val commands = mutableListOf( + initPlugin("tuic-plugin").path, + "-c", + configFile.absolutePath, + ) + + processes.start(commands) + } + } + } + } + + box.start() + } + + @Suppress("EXPERIMENTAL_API_USAGE") + override fun close() { + for (instance in externalInstances.values) { + runCatching { + instance.close() + } + } + + cacheFiles.removeAll { it.delete(); true } + + if (::processes.isInitialized) processes.close(GlobalScope + Dispatchers.IO) + + if (::box.isInitialized) { + box.close() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/ProxyInstance.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/ProxyInstance.kt new file mode 100644 index 0000000..9c930fd --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/ProxyInstance.kt @@ -0,0 +1,44 @@ +package io.nekohasekai.sagernet.bg.proto + +import io.nekohasekai.sagernet.bg.BaseService +import io.nekohasekai.sagernet.database.ProxyEntity +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.ktx.runOnIoDispatcher +import kotlinx.coroutines.runBlocking + +class ProxyInstance(profile: ProxyEntity, val service: BaseService.Interface) : + BoxInstance(profile) { + + // for TrafficLooper + private var looper: TrafficLooper? = null + + override fun buildConfig() { + super.buildConfig() + Logs.d(config.config) + } + + override suspend fun init() { + super.init() + pluginConfigs.forEach { (_, plugin) -> + val (_, content) = plugin + Logs.d(content) + } + } + + override fun launch() { + box.setAsMain() + super.launch() + runOnIoDispatcher { + looper = TrafficLooper(service.data, this) + looper?.start() + } + } + + override fun close() { + super.close() + runBlocking { + looper?.stop() + looper = null + } + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TestInstance.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TestInstance.kt new file mode 100644 index 0000000..e4b85df --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TestInstance.kt @@ -0,0 +1,48 @@ +package io.nekohasekai.sagernet.bg.proto + +import io.nekohasekai.sagernet.BuildConfig +import io.nekohasekai.sagernet.bg.GuardedProcessPool +import io.nekohasekai.sagernet.database.ProxyEntity +import io.nekohasekai.sagernet.fmt.buildConfig +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher +import io.nekohasekai.sagernet.ktx.tryResume +import io.nekohasekai.sagernet.ktx.tryResumeWithException +import libcore.Libcore +import kotlin.coroutines.suspendCoroutine + +class TestInstance(profile: ProxyEntity, val link: String, val timeout: Int) : + BoxInstance(profile) { + + suspend fun doTest(): Int { + return suspendCoroutine { c -> + processes = GuardedProcessPool { + Logs.w(it) + c.tryResumeWithException(it) + } + runOnDefaultDispatcher { + use { + try { + init() + launch() + c.tryResume(Libcore.urlTest(box, link, timeout)) + } catch (e: Exception) { + c.tryResumeWithException(e) + } + } + } + } + } + + override fun buildConfig() { + config = buildConfig(profile, true) + } + + override suspend fun loadConfig() { + // don't call destroyAllJsi here + if (BuildConfig.DEBUG) Logs.d(config.config) + box = Libcore.newSingBoxInstance(config.config) + box.forTest = true + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficLooper.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficLooper.kt new file mode 100644 index 0000000..e9623da --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficLooper.kt @@ -0,0 +1,130 @@ +package io.nekohasekai.sagernet.bg.proto + +import io.nekohasekai.sagernet.aidl.SpeedDisplayData +import io.nekohasekai.sagernet.aidl.TrafficData +import io.nekohasekai.sagernet.bg.BaseService +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.ProfileManager +import io.nekohasekai.sagernet.ktx.Logs +import kotlinx.coroutines.* +import kotlin.time.DurationUnit +import kotlin.time.toDuration + +class TrafficLooper + ( + val data: BaseService.Data, private val sc: CoroutineScope +) { + + private var job: Job? = null + private val items = mutableMapOf() + + suspend fun stop() { + job?.cancel() + // finally + val traffic = mutableMapOf() + data.proxy?.config?.trafficMap?.forEach { (tag, ent) -> + val item = items[tag] ?: return@forEach + ent.rx = item.rx + ent.tx = item.tx + ProfileManager.updateProfile(ent) // update DB + traffic[ent.id] = TrafficData( + id = ent.id, + rx = ent.rx, + tx = ent.tx, + ) + } + data.binder.broadcast { b -> + for (t in traffic) { + b.cbTrafficUpdate(t.value) + } + } + Logs.d("finally traffic post done") + } + + fun start() { + job = sc.launch { loop() } + } + + private suspend fun loop() { + val delayMs = DataStore.speedInterval + val showDirectSpeed = DataStore.showDirectSpeed + if (delayMs == 0) return + + var trafficUpdater: TrafficUpdater? = null + var proxy: ProxyInstance? + var itemMain: TrafficUpdater.TrafficLooperData? = null + var itemMainBase: TrafficUpdater.TrafficLooperData? = null + var itemBypass: TrafficUpdater.TrafficLooperData? = null + + while (sc.isActive) { + delay(delayMs.toDuration(DurationUnit.MILLISECONDS)) + proxy = data.proxy ?: continue + + if (trafficUpdater == null) { + if (!proxy.isInitialized()) continue + items.clear() + itemBypass = TrafficUpdater.TrafficLooperData(tag = "bypass") + items["bypass"] = itemBypass +// proxy.config.trafficMap.forEach { (tag, ent) -> + proxy.config.outboundTags.forEach { tag -> + // TODO g-xx query traffic return 0? + val ent = proxy.config.trafficMap[tag] ?: return@forEach + val item = TrafficUpdater.TrafficLooperData( + tag = tag, + rx = ent.rx, + tx = ent.tx, + ) + if (tag == proxy.config.outboundTagMain) { + itemMain = item + itemMainBase = TrafficUpdater.TrafficLooperData( + tag = tag, + rx = ent.rx, + tx = ent.tx, + ) + } + items[tag] = item + Logs.d("traffic count $tag to ${ent.id}") + } + trafficUpdater = TrafficUpdater( + box = proxy.box, items = items + ) + proxy.box.setV2rayStats(items.keys.joinToString("\n")) + } + + trafficUpdater.updateAll() + if (!sc.isActive) return + + // speed + val speed = SpeedDisplayData( + itemMain!!.txRate, + itemMain!!.rxRate, + if (showDirectSpeed) itemBypass!!.txRate else 0L, + if (showDirectSpeed) itemBypass!!.rxRate else 0L, + itemMain!!.tx - itemMainBase!!.tx, + itemMain!!.rx - itemMainBase!!.rx + ) + + // traffic + val traffic = mutableMapOf() + proxy.config.trafficMap.forEach { (tag, ent) -> + val item = items[tag] ?: return@forEach + ent.rx = item.rx + ent.tx = item.tx +// ProfileManager.updateProfile(ent) // update DB + traffic[ent.id] = TrafficData( + id = ent.id, + rx = ent.rx, + tx = ent.tx, + ) // display + } + + // broadcast + data.binder.broadcast { b -> + b.cbSpeedUpdate(speed) + for (t in traffic) { + b.cbTrafficUpdate(t.value) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficUpdater.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficUpdater.kt new file mode 100644 index 0000000..b91810f --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficUpdater.kt @@ -0,0 +1,69 @@ +package io.nekohasekai.sagernet.bg.proto + +import io.nekohasekai.sagernet.ktx.onIoDispatcher + +class TrafficUpdater( + private val box: libcore.BoxInstance, + val items: Map, // contain "bypass" +) { + + class TrafficLooperData( + var tag: String, + var tx: Long = 0, + var rx: Long = 0, + var txRate: Long = 0, + var rxRate: Long = 0, + var lastUpdate: Long = 0, + ) + + private suspend fun updateOne(item: TrafficLooperData): TrafficLooperData { + // last update + val now = System.currentTimeMillis() + val interval = now - item.lastUpdate + item.lastUpdate = now + if (interval <= 0) return item.apply { + rxRate = 0 + txRate = 0 + } + + // query + var tx = 0L + var rx = 0L + onIoDispatcher { + tx = box.queryStats(item.tag, "uplink") + rx = box.queryStats(item.tag, "downlink") + } + + // add diff + item.rx += rx + item.tx += tx + item.rxRate = rx * 1000 / interval + item.txRate = tx * 1000 / interval + + // return diff + return TrafficLooperData( + tag = item.tag, + rx = rx, + tx = tx, + rxRate = item.rxRate, + txRate = item.txRate, + ) + } + + suspend fun updateAll() { + val updated = mutableMapOf() // diffs + items.forEach { (tag, item) -> + var diff = updated[tag] + // query a tag only once + if (diff == null) { + diff = updateOne(item) + updated[tag] = diff + } else { + item.rx += diff.rx + item.tx += diff.tx + item.rxRate += diff.rxRate + item.txRate += diff.txRate + } + } + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/UrlTest.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/UrlTest.kt new file mode 100644 index 0000000..3cec7cf --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/UrlTest.kt @@ -0,0 +1,15 @@ +package io.nekohasekai.sagernet.bg.proto + +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.ProxyEntity + +class UrlTest { + + val link = DataStore.connectionTestURL + val timeout = 3000 + + suspend fun doTest(profile: ProxyEntity): Int { + return TestInstance(profile, link, timeout).doTest() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt b/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt new file mode 100644 index 0000000..1ea66f6 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt @@ -0,0 +1,250 @@ +package io.nekohasekai.sagernet.database + +import android.os.Binder +import androidx.preference.PreferenceDataStore +import io.nekohasekai.sagernet.* +import io.nekohasekai.sagernet.bg.BaseService +import io.nekohasekai.sagernet.bg.VpnService +import io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener +import io.nekohasekai.sagernet.database.preference.PublicDatabase +import io.nekohasekai.sagernet.database.preference.RoomPreferenceDataStore +import io.nekohasekai.sagernet.ktx.* +import moe.matsuri.nb4a.TempDatabase + +object DataStore : OnPreferenceDataStoreChangeListener { + + // share service state in main process + @Volatile + var serviceState = BaseService.State.Idle + + val configurationStore = RoomPreferenceDataStore(PublicDatabase.kvPairDao) + val profileCacheStore = RoomPreferenceDataStore(TempDatabase.profileCacheDao) + + // last used, but may not be running + var currentProfile by configurationStore.long(Key.PROFILE_CURRENT) + + var selectedProxy by configurationStore.long(Key.PROFILE_ID) + var selectedGroup by configurationStore.long(Key.PROFILE_GROUP) { currentGroupId() } // "ungrouped" group id = 1 + + // only in bg process + var vpnService: VpnService? = null + var baseService: BaseService.Interface? = null + + // only in GUI process + var postLogListener: ((String) -> Unit)? = null + + fun currentGroupId(): Long { + val currentSelected = configurationStore.getLong(Key.PROFILE_GROUP, -1) + if (currentSelected > 0L) return currentSelected + val groups = SagerDatabase.groupDao.allGroups() + if (groups.isNotEmpty()) { + val groupId = groups[0].id + selectedGroup = groupId + return groupId + } + val groupId = SagerDatabase.groupDao.createGroup(ProxyGroup(ungrouped = true)) + selectedGroup = groupId + return groupId + } + + fun currentGroup(): ProxyGroup { + var group: ProxyGroup? = null + val currentSelected = configurationStore.getLong(Key.PROFILE_GROUP, -1) + if (currentSelected > 0L) { + group = SagerDatabase.groupDao.getById(currentSelected) + } + if (group != null) return group + val groups = SagerDatabase.groupDao.allGroups() + if (groups.isEmpty()) { + group = ProxyGroup(ungrouped = true).apply { + id = SagerDatabase.groupDao.createGroup(this) + } + } else { + group = groups[0] + } + selectedGroup = group.id + return group + } + + fun selectedGroupForImport(): Long { + val current = currentGroup() + if (current.type == GroupType.BASIC) return current.id + val groups = SagerDatabase.groupDao.allGroups() + return groups.find { it.type == GroupType.BASIC }!!.id + } + + var nekoPlugins by configurationStore.string(Key.NEKO_PLUGIN_MANAGED) + var appTLSVersion by configurationStore.string(Key.APP_TLS_VERSION) + var enableClashAPI by configurationStore.boolean(Key.ENABLE_CLASH_API) + var showBottomBar by configurationStore.boolean(Key.SHOW_BOTTOM_BAR) + + // + + var isExpert by configurationStore.boolean(Key.APP_EXPERT) + var appTheme by configurationStore.int(Key.APP_THEME) + var nightTheme by configurationStore.stringToInt(Key.NIGHT_THEME) + var serviceMode by configurationStore.string(Key.SERVICE_MODE) { Key.MODE_VPN } + +// var domainStrategy by configurationStore.string(Key.DOMAIN_STRATEGY) { "AsIs" } + var trafficSniffing by configurationStore.boolean(Key.TRAFFIC_SNIFFING) { true } + var resolveDestination by configurationStore.boolean(Key.RESOLVE_DESTINATION) + +// var tcpKeepAliveInterval by configurationStore.stringToInt(Key.TCP_KEEP_ALIVE_INTERVAL) { 15 } + var mtu by configurationStore.stringToInt(Key.MTU) { 9000 } + + var bypassLan by configurationStore.boolean(Key.BYPASS_LAN) + var bypassLanInCoreOnly by configurationStore.boolean(Key.BYPASS_LAN_IN_CORE_ONLY) + + var allowAccess by configurationStore.boolean(Key.ALLOW_ACCESS) + var speedInterval by configurationStore.stringToInt(Key.SPEED_INTERVAL) + + var remoteDns by configurationStore.string(Key.REMOTE_DNS) { "https://8.8.8.8/dns-query" } + var directDns by configurationStore.string(Key.DIRECT_DNS) { "https://223.5.5.5/dns-query" } + var directDnsUseSystem by configurationStore.boolean(Key.DIRECT_DNS_USE_SYSTEM) + var enableDnsRouting by configurationStore.boolean(Key.ENABLE_DNS_ROUTING) { true } + var enableFakeDns by configurationStore.boolean(Key.ENABLE_FAKEDNS) + var dnsNetwork by configurationStore.stringSet(Key.DNS_NETWORK) + + var rulesProvider by configurationStore.stringToInt(Key.RULES_PROVIDER) + var enableLog by configurationStore.boolean(Key.ENABLE_LOG) + var logBufSize by configurationStore.int(Key.LOG_BUF_SIZE) { 0 } + var acquireWakeLock by configurationStore.boolean(Key.ACQUIRE_WAKE_LOCK) + + // hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat + private val userIndex by lazy { Binder.getCallingUserHandle().hashCode() } + var mixedPort: Int + get() = getLocalPort(Key.MIXED_PORT, 2080) + set(value) = saveLocalPort(Key.MIXED_PORT, value) + var localDNSPort: Int + get() = getLocalPort(Key.LOCAL_DNS_PORT, 6450) + set(value) { + saveLocalPort(Key.LOCAL_DNS_PORT, value) + } + var transproxyPort: Int + get() = getLocalPort(Key.TRANSPROXY_PORT, 9200) + set(value) = saveLocalPort(Key.TRANSPROXY_PORT, value) + + fun initGlobal() { + if (configurationStore.getString(Key.MIXED_PORT) == null) { + mixedPort = mixedPort + } + if (configurationStore.getString(Key.LOCAL_DNS_PORT) == null) { + localDNSPort = localDNSPort + } + if (configurationStore.getString(Key.TRANSPROXY_PORT) == null) { + transproxyPort = transproxyPort + } + } + + + private fun getLocalPort(key: String, default: Int): Int { + return parsePort(configurationStore.getString(key), default + userIndex) + } + + private fun saveLocalPort(key: String, value: Int) { + configurationStore.putString(key, "$value") + } + + var ipv6Mode by configurationStore.stringToInt(Key.IPV6_MODE) { IPv6Mode.DISABLE } + + var meteredNetwork by configurationStore.boolean(Key.METERED_NETWORK) + var proxyApps by configurationStore.boolean(Key.PROXY_APPS) + var bypass by configurationStore.boolean(Key.BYPASS_MODE) { true } + var individual by configurationStore.string(Key.INDIVIDUAL) + var showDirectSpeed by configurationStore.boolean(Key.SHOW_DIRECT_SPEED) { true } + + var appendHttpProxy by configurationStore.boolean(Key.APPEND_HTTP_PROXY) + var requireTransproxy by configurationStore.boolean(Key.REQUIRE_TRANSPROXY) + var transproxyMode by configurationStore.stringToInt(Key.TRANSPROXY_MODE) + var connectionTestURL by configurationStore.string(Key.CONNECTION_TEST_URL) { CONNECTION_TEST_URL } + var alwaysShowAddress by configurationStore.boolean(Key.ALWAYS_SHOW_ADDRESS) + + var tunImplementation by configurationStore.stringToInt(Key.TUN_IMPLEMENTATION) { TunImplementation.SYSTEM } + var profileTrafficStatistics by configurationStore.boolean(Key.PROFILE_TRAFFIC_STATISTICS) { true } + + var yacdURL by configurationStore.string("yacdURL") { "http://127.0.0.1:9090/ui" } + + // protocol + + var muxProtocols by configurationStore.stringSet(Key.MUX_PROTOCOLS) + var muxConcurrency by configurationStore.stringToInt(Key.MUX_CONCURRENCY) { 8 } + + // old cache, DO NOT ADD + + var dirty by profileCacheStore.boolean(Key.PROFILE_DIRTY) + var editingId by profileCacheStore.long(Key.PROFILE_ID) + var editingGroup by profileCacheStore.long(Key.PROFILE_GROUP) + var profileName by profileCacheStore.string(Key.PROFILE_NAME) + var serverAddress by profileCacheStore.string(Key.SERVER_ADDRESS) + var serverPort by profileCacheStore.stringToInt(Key.SERVER_PORT) + var serverUsername by profileCacheStore.string(Key.SERVER_USERNAME) + var serverPassword by profileCacheStore.string(Key.SERVER_PASSWORD) + var serverPassword1 by profileCacheStore.string(Key.SERVER_PASSWORD1) + var serverMethod by profileCacheStore.string(Key.SERVER_METHOD) + + var sharedStorage by profileCacheStore.string("sharedStorage") + + var serverProtocol by profileCacheStore.string(Key.SERVER_PROTOCOL) + var serverObfs by profileCacheStore.string(Key.SERVER_OBFS) + + var serverNetwork by profileCacheStore.string(Key.SERVER_NETWORK) + var serverHost by profileCacheStore.string(Key.SERVER_HOST) + var serverPath by profileCacheStore.string(Key.SERVER_PATH) + var serverSNI by profileCacheStore.string(Key.SERVER_SNI) + var serverEncryption by profileCacheStore.string(Key.SERVER_ENCRYPTION) + var serverALPN by profileCacheStore.string(Key.SERVER_ALPN) + var serverCertificates by profileCacheStore.string(Key.SERVER_CERTIFICATES) + var serverHeaders by profileCacheStore.string(Key.SERVER_HEADERS) + var serverAllowInsecure by profileCacheStore.boolean(Key.SERVER_ALLOW_INSECURE) + + var serverAuthType by profileCacheStore.stringToInt(Key.SERVER_AUTH_TYPE) + var serverUploadSpeed by profileCacheStore.stringToInt(Key.SERVER_UPLOAD_SPEED) + var serverDownloadSpeed by profileCacheStore.stringToInt(Key.SERVER_DOWNLOAD_SPEED) + var serverStreamReceiveWindow by profileCacheStore.stringToIntIfExists(Key.SERVER_STREAM_RECEIVE_WINDOW) + var serverConnectionReceiveWindow by profileCacheStore.stringToIntIfExists(Key.SERVER_CONNECTION_RECEIVE_WINDOW) + var serverMTU by profileCacheStore.stringToInt(Key.SERVER_MTU) { 1420 } + var serverDisableMtuDiscovery by profileCacheStore.boolean(Key.SERVER_DISABLE_MTU_DISCOVERY) + var serverHopInterval by profileCacheStore.stringToInt(Key.SERVER_HOP_INTERVAL) { 10 } + + var serverProtocolVersion by profileCacheStore.stringToInt(Key.SERVER_PROTOCOL) + var serverPrivateKey by profileCacheStore.string(Key.SERVER_PRIVATE_KEY) + var serverInsecureConcurrency by profileCacheStore.stringToInt(Key.SERVER_INSECURE_CONCURRENCY) + + var serverUDPRelayMode by profileCacheStore.string(Key.SERVER_UDP_RELAY_MODE) + var serverCongestionController by profileCacheStore.string(Key.SERVER_CONGESTION_CONTROLLER) + var serverDisableSNI by profileCacheStore.boolean(Key.SERVER_DISABLE_SNI) + var serverReduceRTT by profileCacheStore.boolean(Key.SERVER_REDUCE_RTT) + var serverFastConnect by profileCacheStore.boolean(Key.SERVER_FAST_CONNECT) + + var routeName by profileCacheStore.string(Key.ROUTE_NAME) + var routeDomain by profileCacheStore.string(Key.ROUTE_DOMAIN) + var routeIP by profileCacheStore.string(Key.ROUTE_IP) + var routePort by profileCacheStore.string(Key.ROUTE_PORT) + var routeSourcePort by profileCacheStore.string(Key.ROUTE_SOURCE_PORT) + var routeNetwork by profileCacheStore.string(Key.ROUTE_NETWORK) + var routeSource by profileCacheStore.string(Key.ROUTE_SOURCE) + var routeProtocol by profileCacheStore.string(Key.ROUTE_PROTOCOL) + var routeOutbound by profileCacheStore.stringToInt(Key.ROUTE_OUTBOUND) + var routeOutboundRule by profileCacheStore.long(Key.ROUTE_OUTBOUND_RULE) + var routePackages by profileCacheStore.string(Key.ROUTE_PACKAGES) + + + var serverConfig by profileCacheStore.string(Key.SERVER_CONFIG) + + var groupName by profileCacheStore.string(Key.GROUP_NAME) + var groupType by profileCacheStore.stringToInt(Key.GROUP_TYPE) + var groupOrder by profileCacheStore.stringToInt(Key.GROUP_ORDER) + + var subscriptionLink by profileCacheStore.string(Key.SUBSCRIPTION_LINK) + var subscriptionForceResolve by profileCacheStore.boolean(Key.SUBSCRIPTION_FORCE_RESOLVE) + var subscriptionDeduplication by profileCacheStore.boolean(Key.SUBSCRIPTION_DEDUPLICATION) + var subscriptionUpdateWhenConnectedOnly by profileCacheStore.boolean(Key.SUBSCRIPTION_UPDATE_WHEN_CONNECTED_ONLY) + var subscriptionUserAgent by profileCacheStore.string(Key.SUBSCRIPTION_USER_AGENT) + var subscriptionAutoUpdate by profileCacheStore.boolean(Key.SUBSCRIPTION_AUTO_UPDATE) + var subscriptionAutoUpdateDelay by profileCacheStore.stringToInt(Key.SUBSCRIPTION_AUTO_UPDATE_DELAY) { 360 } + + var rulesFirstCreate by profileCacheStore.boolean("rulesFirstCreate") + + override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) { + } +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/GroupManager.kt b/app/src/main/java/io/nekohasekai/sagernet/database/GroupManager.kt new file mode 100644 index 0000000..b2688f4 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/database/GroupManager.kt @@ -0,0 +1,114 @@ +package io.nekohasekai.sagernet.database + +import io.nekohasekai.sagernet.GroupType +import io.nekohasekai.sagernet.bg.SubscriptionUpdater +import io.nekohasekai.sagernet.ktx.applyDefaultValues + +object GroupManager { + + interface Listener { + suspend fun groupAdd(group: ProxyGroup) + suspend fun groupUpdated(group: ProxyGroup) + + suspend fun groupRemoved(groupId: Long) + suspend fun groupUpdated(groupId: Long) + } + + interface Interface { + suspend fun confirm(message: String): Boolean + suspend fun alert(message: String) + suspend fun onUpdateSuccess( + group: ProxyGroup, + changed: Int, + added: List, + updated: Map, + deleted: List, + duplicate: List, + byUser: Boolean + ) + + suspend fun onUpdateFailure(group: ProxyGroup, message: String) + } + + private val listeners = ArrayList() + var userInterface: Interface? = null + + suspend fun iterator(what: suspend Listener.() -> Unit) { + synchronized(listeners) { + listeners.toList() + }.forEach { listener -> + what(listener) + } + } + + fun addListener(listener: Listener) { + synchronized(listeners) { + listeners.add(listener) + } + } + + fun removeListener(listener: Listener) { + synchronized(listeners) { + listeners.remove(listener) + } + } + + suspend fun clearGroup(groupId: Long) { + DataStore.selectedProxy = 0L + SagerDatabase.proxyDao.deleteAll(groupId) + iterator { groupUpdated(groupId) } + } + + fun rearrange(groupId: Long) { + val entities = SagerDatabase.proxyDao.getByGroup(groupId) + for (index in entities.indices) { + entities[index].userOrder = (index + 1).toLong() + } + SagerDatabase.proxyDao.updateProxy(entities) + } + + suspend fun postUpdate(group: ProxyGroup) { + iterator { groupUpdated(group) } + } + + suspend fun postUpdate(groupId: Long) { + postUpdate(SagerDatabase.groupDao.getById(groupId) ?: return) + } + + suspend fun postReload(groupId: Long) { + iterator { groupUpdated(groupId) } + } + + suspend fun createGroup(group: ProxyGroup): ProxyGroup { + group.userOrder = SagerDatabase.groupDao.nextOrder() ?: 1 + group.id = SagerDatabase.groupDao.createGroup(group.applyDefaultValues()) + iterator { groupAdd(group) } + if (group.type == GroupType.SUBSCRIPTION) { + SubscriptionUpdater.reconfigureUpdater() + } + return group + } + + suspend fun updateGroup(group: ProxyGroup) { + SagerDatabase.groupDao.updateGroup(group) + iterator { groupUpdated(group) } + if (group.type == GroupType.SUBSCRIPTION) { + SubscriptionUpdater.reconfigureUpdater() + } + } + + suspend fun deleteGroup(groupId: Long) { + SagerDatabase.groupDao.deleteById(groupId) + SagerDatabase.proxyDao.deleteByGroup(groupId) + iterator { groupRemoved(groupId) } + SubscriptionUpdater.reconfigureUpdater() + } + + suspend fun deleteGroup(group: List) { + SagerDatabase.groupDao.deleteGroup(group) + SagerDatabase.proxyDao.deleteByGroup(group.map { it.id }.toLongArray()) + for (proxyGroup in group) iterator { groupRemoved(proxyGroup.id) } + SubscriptionUpdater.reconfigureUpdater() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/ParcelizeBridge.java b/app/src/main/java/io/nekohasekai/sagernet/database/ParcelizeBridge.java new file mode 100644 index 0000000..9bdb6d8 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/database/ParcelizeBridge.java @@ -0,0 +1,13 @@ +package io.nekohasekai.sagernet.database; + +import android.os.Parcel; + +/** + * see: https://youtrack.jetbrains.com/issue/KT-19853 + */ +public class ParcelizeBridge { + + public static RuleEntity createRule(Parcel parcel) { + return (RuleEntity) RuleEntity.CREATOR.createFromParcel(parcel); + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/ProfileManager.kt b/app/src/main/java/io/nekohasekai/sagernet/database/ProfileManager.kt new file mode 100644 index 0000000..31816c2 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/database/ProfileManager.kt @@ -0,0 +1,258 @@ +package io.nekohasekai.sagernet.database + +import android.database.sqlite.SQLiteCantOpenDatabaseException +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.aidl.SpeedDisplayData +import io.nekohasekai.sagernet.aidl.TrafficData +import io.nekohasekai.sagernet.fmt.AbstractBean +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.ktx.app +import io.nekohasekai.sagernet.ktx.applyDefaultValues +import java.io.IOException +import java.sql.SQLException +import java.util.* + + +object ProfileManager { + + interface Listener { + suspend fun onAdd(profile: ProxyEntity) + suspend fun onUpdated(data: TrafficData) + suspend fun onUpdated(profile: ProxyEntity) + suspend fun onRemoved(groupId: Long, profileId: Long) + } + + interface RuleListener { + suspend fun onAdd(rule: RuleEntity) + suspend fun onUpdated(rule: RuleEntity) + suspend fun onRemoved(ruleId: Long) + suspend fun onCleared() + } + + private val listeners = ArrayList() + private val ruleListeners = ArrayList() + + suspend fun iterator(what: suspend Listener.() -> Unit) { + synchronized(listeners) { + listeners.toList() + }.forEach { listener -> + what(listener) + } + } + + suspend fun ruleIterator(what: suspend RuleListener.() -> Unit) { + val ruleListeners = synchronized(ruleListeners) { + ruleListeners.toList() + } + for (listener in ruleListeners) { + what(listener) + } + } + + fun addListener(listener: Listener) { + synchronized(listeners) { + listeners.add(listener) + } + } + + fun removeListener(listener: Listener) { + synchronized(listeners) { + listeners.remove(listener) + } + } + + fun addListener(listener: RuleListener) { + synchronized(ruleListeners) { + ruleListeners.add(listener) + } + } + + fun removeListener(listener: RuleListener) { + synchronized(ruleListeners) { + ruleListeners.remove(listener) + } + } + + suspend fun createProfile(groupId: Long, bean: AbstractBean): ProxyEntity { + bean.applyDefaultValues() + + val profile = ProxyEntity(groupId = groupId).apply { + id = 0 + putBean(bean) + userOrder = SagerDatabase.proxyDao.nextOrder(groupId) ?: 1 + } + profile.id = SagerDatabase.proxyDao.addProxy(profile) + iterator { onAdd(profile) } + return profile + } + + suspend fun updateProfile(profile: ProxyEntity) { + SagerDatabase.proxyDao.updateProxy(profile) + iterator { onUpdated(profile) } + } + + suspend fun updateProfile(profiles: List) { + SagerDatabase.proxyDao.updateProxy(profiles) + profiles.forEach { + iterator { onUpdated(it) } + } + } + + suspend fun deleteProfile2(groupId: Long, profileId: Long) { + if (SagerDatabase.proxyDao.deleteById(profileId) == 0) return + if (DataStore.selectedProxy == profileId) { + DataStore.selectedProxy = 0L + } + } + + suspend fun deleteProfile(groupId: Long, profileId: Long) { + if (SagerDatabase.proxyDao.deleteById(profileId) == 0) return + if (DataStore.selectedProxy == profileId) { + DataStore.selectedProxy = 0L + } + iterator { onRemoved(groupId, profileId) } + if (SagerDatabase.proxyDao.countByGroup(groupId) > 1) { + GroupManager.rearrange(groupId) + } + } + + fun getProfile(profileId: Long): ProxyEntity? { + if (profileId == 0L) return null + return try { + SagerDatabase.proxyDao.getById(profileId) + } catch (ex: SQLiteCantOpenDatabaseException) { + throw IOException(ex) + } catch (ex: SQLException) { + Logs.w(ex) + null + } + } + + fun getProfiles(profileIds: List): List { + if (profileIds.isEmpty()) return listOf() + return try { + SagerDatabase.proxyDao.getEntities(profileIds) + } catch (ex: SQLiteCantOpenDatabaseException) { + throw IOException(ex) + } catch (ex: SQLException) { + Logs.w(ex) + listOf() + } + } + + // postUpdate: post to listeners, don't change the DB + + suspend fun postUpdate(profileId: Long) { + postUpdate(getProfile(profileId) ?: return) + } + + suspend fun postUpdate(profile: ProxyEntity) { + iterator { onUpdated(profile) } + } + + suspend fun postUpdate(data: TrafficData) { + iterator { onUpdated(data) } + } + + suspend fun createRule(rule: RuleEntity, post: Boolean = true): RuleEntity { + rule.userOrder = SagerDatabase.rulesDao.nextOrder() ?: 1 + rule.id = SagerDatabase.rulesDao.createRule(rule) + if (post) { + ruleIterator { onAdd(rule) } + } + return rule + } + + suspend fun updateRule(rule: RuleEntity) { + SagerDatabase.rulesDao.updateRule(rule) + ruleIterator { onUpdated(rule) } + } + + suspend fun deleteRule(ruleId: Long) { + SagerDatabase.rulesDao.deleteById(ruleId) + ruleIterator { onRemoved(ruleId) } + } + + suspend fun deleteRules(rules: List) { + SagerDatabase.rulesDao.deleteRules(rules) + ruleIterator { + rules.forEach { + onRemoved(it.id) + } + } + } + + suspend fun getRules(): List { + var rules = SagerDatabase.rulesDao.allRules() + if (rules.isEmpty() && !DataStore.rulesFirstCreate) { + DataStore.rulesFirstCreate = true + createRule( + RuleEntity( + name = app.getString(R.string.route_opt_block_quic), + port = "443", + network = "udp", + outbound = -2 + ) + ) + createRule( + RuleEntity( + name = app.getString(R.string.route_opt_block_ads), + domains = "geosite:category-ads-all", + outbound = -2 + ) + ) + createRule( + RuleEntity( + name = app.getString(R.string.route_opt_block_analysis), + domains = app.assets.open("analysis.txt").use { + it.bufferedReader() + .readLines() + .filter { it.isNotBlank() } + .joinToString("\n") + }, + outbound = -2, + ) + ) + var country = Locale.getDefault().country.lowercase() + var displayCountry = Locale.getDefault().displayCountry + if (country in arrayOf( + "ir" + ) + ) { + createRule( + RuleEntity( + name = app.getString(R.string.route_bypass_domain, displayCountry), + domains = "domain:$country", + outbound = -1 + ), false + ) + } else { + country = Locale.CHINA.country.lowercase() + displayCountry = Locale.CHINA.displayCountry + createRule( + RuleEntity( + name = app.getString(R.string.route_play_store, displayCountry), + domains = "domain:googleapis.cn", + ), false + ) + createRule( + RuleEntity( + name = app.getString(R.string.route_bypass_domain, displayCountry), + domains = "geosite:$country", + outbound = -1 + ), false + ) + } + createRule( + RuleEntity( + name = app.getString(R.string.route_bypass_ip, displayCountry), + ip = "geoip:$country", + outbound = -1 + ), false + ) + rules = SagerDatabase.rulesDao.allRules() + } + return rules + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt new file mode 100644 index 0000000..467b5f6 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt @@ -0,0 +1,489 @@ +package io.nekohasekai.sagernet.database + +import android.content.Context +import android.content.Intent +import androidx.room.* +import com.esotericsoftware.kryo.io.ByteBufferInput +import com.esotericsoftware.kryo.io.ByteBufferOutput +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.fmt.* +import io.nekohasekai.sagernet.fmt.http.HttpBean +import io.nekohasekai.sagernet.fmt.http.toUri +import io.nekohasekai.sagernet.fmt.hysteria.* +import io.nekohasekai.sagernet.fmt.internal.ChainBean +import io.nekohasekai.sagernet.fmt.naive.NaiveBean +import io.nekohasekai.sagernet.fmt.naive.buildNaiveConfig +import io.nekohasekai.sagernet.fmt.naive.toUri +import io.nekohasekai.sagernet.fmt.shadowsocks.* +import io.nekohasekai.sagernet.fmt.socks.SOCKSBean +import io.nekohasekai.sagernet.fmt.socks.toUri +import io.nekohasekai.sagernet.fmt.ssh.SSHBean +import io.nekohasekai.sagernet.fmt.trojan.TrojanBean +import io.nekohasekai.sagernet.fmt.trojan.toUri +import io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean +import io.nekohasekai.sagernet.fmt.trojan_go.buildTrojanGoConfig +import io.nekohasekai.sagernet.fmt.trojan_go.toUri +import io.nekohasekai.sagernet.fmt.tuic.TuicBean +import io.nekohasekai.sagernet.fmt.tuic.buildTuicConfig +import io.nekohasekai.sagernet.fmt.v2ray.* +import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean +import io.nekohasekai.sagernet.ktx.app +import io.nekohasekai.sagernet.ktx.applyDefaultValues +import io.nekohasekai.sagernet.ui.profile.* +import moe.matsuri.nb4a.Protocols +import moe.matsuri.nb4a.proxy.neko.* +import moe.matsuri.nb4a.proxy.config.ConfigBean +import moe.matsuri.nb4a.proxy.config.ConfigSettingActivity +import moe.matsuri.nb4a.proxy.neko.NekoBean +import moe.matsuri.nb4a.proxy.neko.NekoSettingActivity +import moe.matsuri.nb4a.proxy.neko.haveStandardLink +import moe.matsuri.nb4a.proxy.neko.shareLink + +@Entity( + tableName = "proxy_entities", indices = [Index("groupId", name = "groupId")] +) +data class ProxyEntity( + @PrimaryKey(autoGenerate = true) var id: Long = 0L, + var groupId: Long = 0L, + var type: Int = 0, + var userOrder: Long = 0L, + var tx: Long = 0L, + var rx: Long = 0L, + var status: Int = 0, + var ping: Int = 0, + var uuid: String = "", + var error: String? = null, + var socksBean: SOCKSBean? = null, + var httpBean: HttpBean? = null, + var ssBean: ShadowsocksBean? = null, + var vmessBean: VMessBean? = null, + var trojanBean: TrojanBean? = null, + var trojanGoBean: TrojanGoBean? = null, + var naiveBean: NaiveBean? = null, + var hysteriaBean: HysteriaBean? = null, + var tuicBean: TuicBean? = null, + var sshBean: SSHBean? = null, + var wgBean: WireGuardBean? = null, + var chainBean: ChainBean? = null, + var nekoBean: NekoBean? = null, + var configBean: ConfigBean? = null, +) : Serializable() { + + companion object { + const val TYPE_SOCKS = 0 + const val TYPE_HTTP = 1 + const val TYPE_SS = 2 + const val TYPE_VMESS = 4 + + const val TYPE_TROJAN = 6 + const val TYPE_TROJAN_GO = 7 + const val TYPE_NAIVE = 9 + const val TYPE_HYSTERIA = 15 + + const val TYPE_SSH = 17 + const val TYPE_WG = 18 + + const val TYPE_TUIC = 20 + + const val TYPE_CONFIG = 998 + const val TYPE_NEKO = 999 + + const val TYPE_CHAIN = 8 + + val chainName by lazy { app.getString(R.string.proxy_chain) } + + private val placeHolderBean = SOCKSBean().applyDefaultValues() + + @JvmField + val CREATOR = object : Serializable.CREATOR() { + + override fun newInstance(): ProxyEntity { + return ProxyEntity() + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } + + @Ignore + @Transient + var info: String = "" + + @Ignore + @Transient + var dirty: Boolean = false + + override fun initializeDefaultValues() { + } + + override fun serializeToBuffer(output: ByteBufferOutput) { + output.writeInt(0) + + output.writeLong(id) + output.writeLong(groupId) + output.writeInt(type) + output.writeLong(userOrder) + output.writeLong(tx) + output.writeLong(rx) + output.writeInt(status) + output.writeInt(ping) + output.writeString(uuid) + output.writeString(error) + + val data = KryoConverters.serialize(requireBean()) + output.writeVarInt(data.size, true) + output.writeBytes(data) + + output.writeBoolean(dirty) + } + + override fun deserializeFromBuffer(input: ByteBufferInput) { + val version = input.readInt() + + id = input.readLong() + groupId = input.readLong() + type = input.readInt() + userOrder = input.readLong() + tx = input.readLong() + rx = input.readLong() + status = input.readInt() + ping = input.readInt() + uuid = input.readString() + error = input.readString() + putByteArray(input.readBytes(input.readVarInt(true))) + + dirty = input.readBoolean() + } + + + fun putByteArray(byteArray: ByteArray) { + when (type) { + TYPE_SOCKS -> socksBean = KryoConverters.socksDeserialize(byteArray) + TYPE_HTTP -> httpBean = KryoConverters.httpDeserialize(byteArray) + TYPE_SS -> ssBean = KryoConverters.shadowsocksDeserialize(byteArray) + TYPE_VMESS -> vmessBean = KryoConverters.vmessDeserialize(byteArray) + TYPE_TROJAN -> trojanBean = KryoConverters.trojanDeserialize(byteArray) + TYPE_TROJAN_GO -> trojanGoBean = KryoConverters.trojanGoDeserialize(byteArray) + TYPE_NAIVE -> naiveBean = KryoConverters.naiveDeserialize(byteArray) + TYPE_HYSTERIA -> hysteriaBean = KryoConverters.hysteriaDeserialize(byteArray) + TYPE_SSH -> sshBean = KryoConverters.sshDeserialize(byteArray) + TYPE_WG -> wgBean = KryoConverters.wireguardDeserialize(byteArray) + TYPE_TUIC -> tuicBean = KryoConverters.tuicDeserialize(byteArray) + TYPE_CHAIN -> chainBean = KryoConverters.chainDeserialize(byteArray) + TYPE_NEKO -> nekoBean = KryoConverters.nekoDeserialize(byteArray) + TYPE_CONFIG -> configBean = KryoConverters.configDeserialize(byteArray) + } + } + + fun displayType() = when (type) { + TYPE_SOCKS -> socksBean!!.protocolName() + TYPE_HTTP -> if (httpBean!!.isTLS()) "HTTPS" else "HTTP" + TYPE_SS -> "Shadowsocks" + TYPE_VMESS -> if (vmessBean!!.isVLESS) "VLESS" else "VMess" + TYPE_TROJAN -> "Trojan" + TYPE_TROJAN_GO -> "Trojan-Go" + TYPE_NAIVE -> "Naïve" + TYPE_HYSTERIA -> "Hysteria" + TYPE_SSH -> "SSH" + TYPE_WG -> "WireGuard" + TYPE_TUIC -> "TUIC" + TYPE_CHAIN -> chainName + TYPE_NEKO -> nekoBean!!.displayType() + TYPE_CONFIG -> configBean!!.displayType() + else -> "Undefined type $type" + } + + fun displayName() = requireBean().displayName() + fun displayAddress() = requireBean().displayAddress() + + fun requireBean(): AbstractBean { + return when (type) { + TYPE_SOCKS -> socksBean + TYPE_HTTP -> httpBean + TYPE_SS -> ssBean + TYPE_VMESS -> vmessBean + TYPE_TROJAN -> trojanBean + TYPE_TROJAN_GO -> trojanGoBean + TYPE_NAIVE -> naiveBean + TYPE_HYSTERIA -> hysteriaBean + TYPE_SSH -> sshBean + TYPE_WG -> wgBean + TYPE_TUIC -> tuicBean + TYPE_CHAIN -> chainBean + TYPE_NEKO -> nekoBean + TYPE_CONFIG -> configBean + else -> error("Undefined type $type") + } ?: error("Null ${displayType()} profile") + } + + fun haveLink(): Boolean { + return when (type) { + TYPE_CHAIN -> false + else -> true + } + } + + fun haveStandardLink(): Boolean { + return when (requireBean()) { + is TuicBean -> false + is SSHBean -> false + is WireGuardBean -> false + is NekoBean -> nekoBean!!.haveStandardLink() + is ConfigBean -> false + else -> true + } + } + + fun toLink(compact: Boolean = false): String? = with(requireBean()) { + when (this) { + is SOCKSBean -> toUri() + is HttpBean -> toUri() + is ShadowsocksBean -> toUri() + is VMessBean -> if (compact) toV2rayN() else toUri() + is TrojanBean -> toUri() + is TrojanGoBean -> toUri() + is NaiveBean -> toUri() + is HysteriaBean -> toUri() + is SSHBean -> toUniversalLink() + is WireGuardBean -> toUniversalLink() + is TuicBean -> toUniversalLink() + is ConfigBean -> toUniversalLink() + is NekoBean -> shareLink() + else -> null + } + } + + fun exportConfig(): Pair { + var name = "${requireBean().displayName()}.json" + + return with(requireBean()) { + StringBuilder().apply { + val config = buildConfig(this@ProxyEntity) + append(config.config) + + if (!config.externalIndex.all { it.chain.isEmpty() }) { + name = "profiles.txt" + } + + for ((chain) in config.externalIndex) { + chain.entries.forEachIndexed { index, (port, profile) -> + when (val bean = profile.requireBean()) { + is TrojanGoBean -> { + append("\n\n") + append(bean.buildTrojanGoConfig(port)) + } + is NaiveBean -> { + append("\n\n") + append(bean.buildNaiveConfig(port)) + } + is HysteriaBean -> { + append("\n\n") + append(bean.buildHysteriaConfig(port, null)) + } + is TuicBean -> { + append("\n\n") + append(bean.buildTuicConfig(port, null)) + } + } + } + } + }.toString() + } to name + } + + fun needExternal(): Boolean { + return when (type) { + TYPE_TROJAN_GO -> true + TYPE_NAIVE -> true + TYPE_HYSTERIA -> !hysteriaBean!!.canUseSingBox() + TYPE_TUIC -> true + TYPE_NEKO -> true + else -> false + } + } + + fun isV2RayNetworkTcp(): Boolean { + val bean = requireBean() as StandardV2RayBean + return when (bean.type) { + "tcp", "ws", "http" -> true + else -> false + } + } + + fun needCoreMux(): Boolean { + return when (type) { + TYPE_VMESS -> isV2RayNetworkTcp() && Protocols.shouldEnableMux("vmess") + TYPE_TROJAN -> isV2RayNetworkTcp() && Protocols.shouldEnableMux("trojan") + TYPE_SS -> !ssBean!!.sUoT && Protocols.shouldEnableMux("shadowsocks") + else -> false + } + } + + fun putBean(bean: AbstractBean): ProxyEntity { + socksBean = null + httpBean = null + ssBean = null + vmessBean = null + trojanBean = null + trojanGoBean = null + naiveBean = null + hysteriaBean = null + sshBean = null + wgBean = null + tuicBean = null + chainBean = null + configBean = null + nekoBean = null + + when (bean) { + is SOCKSBean -> { + type = TYPE_SOCKS + socksBean = bean + } + is HttpBean -> { + type = TYPE_HTTP + httpBean = bean + } + is ShadowsocksBean -> { + type = TYPE_SS + ssBean = bean + } + is VMessBean -> { + type = TYPE_VMESS + vmessBean = bean + } + is TrojanBean -> { + type = TYPE_TROJAN + trojanBean = bean + } + is TrojanGoBean -> { + type = TYPE_TROJAN_GO + trojanGoBean = bean + } + is NaiveBean -> { + type = TYPE_NAIVE + naiveBean = bean + } + is HysteriaBean -> { + type = TYPE_HYSTERIA + hysteriaBean = bean + } + is SSHBean -> { + type = TYPE_SSH + sshBean = bean + } + is WireGuardBean -> { + type = TYPE_WG + wgBean = bean + } + is TuicBean -> { + type = TYPE_TUIC + tuicBean = bean + } + is ChainBean -> { + type = TYPE_CHAIN + chainBean = bean + } + is NekoBean -> { + type = TYPE_NEKO + nekoBean = bean + } + is ConfigBean -> { + type = TYPE_CONFIG + configBean = bean + } + else -> error("Undefined type $type") + } + return this + } + + fun settingIntent(ctx: Context, isSubscription: Boolean): Intent { + return Intent( + ctx, when (type) { + TYPE_SOCKS -> SocksSettingsActivity::class.java + TYPE_HTTP -> HttpSettingsActivity::class.java + TYPE_SS -> ShadowsocksSettingsActivity::class.java + TYPE_VMESS -> VMessSettingsActivity::class.java + TYPE_TROJAN -> TrojanSettingsActivity::class.java + TYPE_TROJAN_GO -> TrojanGoSettingsActivity::class.java + TYPE_NAIVE -> NaiveSettingsActivity::class.java + TYPE_HYSTERIA -> HysteriaSettingsActivity::class.java + TYPE_SSH -> SSHSettingsActivity::class.java + TYPE_WG -> WireGuardSettingsActivity::class.java + TYPE_TUIC -> TuicSettingsActivity::class.java + TYPE_CHAIN -> ChainSettingsActivity::class.java + TYPE_NEKO -> NekoSettingActivity::class.java + TYPE_CONFIG -> ConfigSettingActivity::class.java + else -> throw IllegalArgumentException() + } + ).apply { + putExtra(ProfileSettingsActivity.EXTRA_PROFILE_ID, id) + putExtra(ProfileSettingsActivity.EXTRA_IS_SUBSCRIPTION, isSubscription) + } + } + + @androidx.room.Dao + interface Dao { + + @Query("select * from proxy_entities") + fun getAll(): List + + @Query("SELECT id FROM proxy_entities WHERE groupId = :groupId ORDER BY userOrder") + fun getIdsByGroup(groupId: Long): List + + @Query("SELECT * FROM proxy_entities WHERE groupId = :groupId ORDER BY userOrder") + fun getByGroup(groupId: Long): List + + @Query("SELECT * FROM proxy_entities WHERE id in (:proxyIds)") + fun getEntities(proxyIds: List): List + + @Query("SELECT COUNT(*) FROM proxy_entities WHERE groupId = :groupId") + fun countByGroup(groupId: Long): Long + + @Query("SELECT MAX(userOrder) + 1 FROM proxy_entities WHERE groupId = :groupId") + fun nextOrder(groupId: Long): Long? + + @Query("SELECT * FROM proxy_entities WHERE id = :proxyId") + fun getById(proxyId: Long): ProxyEntity? + + @Query("DELETE FROM proxy_entities WHERE id IN (:proxyId)") + fun deleteById(proxyId: Long): Int + + @Query("DELETE FROM proxy_entities WHERE groupId = :groupId") + fun deleteByGroup(groupId: Long) + + @Query("DELETE FROM proxy_entities WHERE groupId in (:groupId)") + fun deleteByGroup(groupId: LongArray) + + @Delete + fun deleteProxy(proxy: ProxyEntity): Int + + @Delete + fun deleteProxy(proxies: List): Int + + @Update + fun updateProxy(proxy: ProxyEntity): Int + + @Update + fun updateProxy(proxies: List): Int + + @Insert + fun addProxy(proxy: ProxyEntity): Long + + @Insert + fun insert(proxies: List) + + @Query("DELETE FROM proxy_entities WHERE groupId = :groupId") + fun deleteAll(groupId: Long): Int + + @Query("DELETE FROM proxy_entities") + fun reset() + + } + + override fun describeContents(): Int { + return 0 + } +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/ProxyGroup.kt b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyGroup.kt new file mode 100644 index 0000000..5d2f94f --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/database/ProxyGroup.kt @@ -0,0 +1,140 @@ +package io.nekohasekai.sagernet.database + +import androidx.room.* +import com.esotericsoftware.kryo.io.ByteBufferInput +import com.esotericsoftware.kryo.io.ByteBufferOutput +import io.nekohasekai.sagernet.GroupOrder +import io.nekohasekai.sagernet.GroupType +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.fmt.Serializable +import io.nekohasekai.sagernet.ktx.app +import io.nekohasekai.sagernet.ktx.applyDefaultValues + +@Entity(tableName = "proxy_groups") +data class ProxyGroup( + @PrimaryKey(autoGenerate = true) var id: Long = 0L, + var userOrder: Long = 0L, + var ungrouped: Boolean = false, + var name: String? = null, + var type: Int = GroupType.BASIC, + var subscription: SubscriptionBean? = null, + var order: Int = GroupOrder.ORIGIN, +) : Serializable() { + + @Transient + var export = false + + override fun initializeDefaultValues() { + subscription?.applyDefaultValues() + } + + override fun serializeToBuffer(output: ByteBufferOutput) { + if (export) { + + output.writeInt(0) + output.writeString(name) + output.writeInt(type) + val subscription = subscription!! + subscription.serializeForShare(output) + + } else { + output.writeInt(0) + output.writeLong(id) + output.writeLong(userOrder) + output.writeBoolean(ungrouped) + output.writeString(name) + output.writeInt(type) + + if (type == GroupType.SUBSCRIPTION) { + subscription?.serializeToBuffer(output) + } + output.writeInt(order) + } + } + + override fun deserializeFromBuffer(input: ByteBufferInput) { + if (export) { + val version = input.readInt() + + name = input.readString() + type = input.readInt() + val subscription = SubscriptionBean() + this.subscription = subscription + + subscription.deserializeFromShare(input) + } else { + val version = input.readInt() + + id = input.readLong() + userOrder = input.readLong() + ungrouped = input.readBoolean() + name = input.readString() + type = input.readInt() + + if (type == GroupType.SUBSCRIPTION) { + val subscription = SubscriptionBean() + this.subscription = subscription + + subscription.deserializeFromBuffer(input) + } + order = input.readInt() + } + } + + fun displayName(): String { + return name.takeIf { !it.isNullOrBlank() } ?: app.getString(R.string.group_default) + } + + @androidx.room.Dao + interface Dao { + + @Query("SELECT * FROM proxy_groups ORDER BY userOrder") + fun allGroups(): List + + @Query("SELECT * FROM proxy_groups WHERE type = ${GroupType.SUBSCRIPTION}") + suspend fun subscriptions(): List + + @Query("SELECT MAX(userOrder) + 1 FROM proxy_groups") + fun nextOrder(): Long? + + @Query("SELECT * FROM proxy_groups WHERE id = :groupId") + fun getById(groupId: Long): ProxyGroup? + + @Query("DELETE FROM proxy_groups WHERE id = :groupId") + fun deleteById(groupId: Long): Int + + @Delete + fun deleteGroup(group: ProxyGroup) + + @Delete + fun deleteGroup(groupList: List) + + @Insert + fun createGroup(group: ProxyGroup): Long + + @Update + fun updateGroup(group: ProxyGroup) + + @Query("DELETE FROM proxy_groups") + fun reset() + + @Insert + fun insert(groupList: List) + + } + + companion object { + @JvmField + val CREATOR = object : Serializable.CREATOR() { + + override fun newInstance(): ProxyGroup { + return ProxyGroup() + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/RuleEntity.kt b/app/src/main/java/io/nekohasekai/sagernet/database/RuleEntity.kt new file mode 100644 index 0000000..20cbbc0 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/database/RuleEntity.kt @@ -0,0 +1,106 @@ +package io.nekohasekai.sagernet.database + +import android.os.Parcelable +import androidx.room.* +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.ktx.app +import kotlinx.parcelize.Parcelize + +@Entity(tableName = "rules") +@Parcelize +data class RuleEntity( + @PrimaryKey(autoGenerate = true) var id: Long = 0L, + var name: String = "", + var userOrder: Long = 0L, + var enabled: Boolean = false, + var domains: String = "", + var ip: String = "", + var port: String = "", + var sourcePort: String = "", + var network: String = "", + var source: String = "", + var protocol: String = "", + var outbound: Long = 0, + var packages: List = listOf(), +) : Parcelable { + + fun displayName(): String { + return name.takeIf { it.isNotBlank() } ?: "Rule $id" + } + + fun mkSummary(): String { + var summary = "" + if (domains.isNotBlank()) summary += "$domains\n" + if (ip.isNotBlank()) summary += "$ip\n" + if (source.isNotBlank()) summary += "source: $source\n" + if (sourcePort.isNotBlank()) summary += "sourcePort: $sourcePort\n" + if (port.isNotBlank()) summary += "port: $port\n" + if (network.isNotBlank()) summary += "network: $network\n" + if (protocol.isNotBlank()) summary += "protocol: $protocol\n" + if (packages.isNotEmpty()) summary += app.getString( + R.string.apps_message, packages.size + ) + "\n" + val lines = summary.trim().split("\n") + return if (lines.size > 3) { + lines.subList(0, 3).joinToString("\n", postfix = "\n...") + } else { + summary.trim() + } + } + + fun displayOutbound(): String { + return when (outbound) { + 0L -> app.getString(R.string.route_proxy) + -1L -> app.getString(R.string.route_bypass) + -2L -> app.getString(R.string.route_block) + else -> ProfileManager.getProfile(outbound)?.displayName() + ?: app.getString(R.string.route_proxy) + } + } + + @androidx.room.Dao + interface Dao { + + @Query("SELECT * from rules WHERE (packages != '') AND enabled = 1") + fun checkVpnNeeded(): List + + @Query("SELECT * FROM rules ORDER BY userOrder") + fun allRules(): List + + @Query("SELECT * FROM rules WHERE enabled = :enabled ORDER BY userOrder") + fun enabledRules(enabled: Boolean = true): List + + @Query("SELECT MAX(userOrder) + 1 FROM rules") + fun nextOrder(): Long? + + @Query("SELECT * FROM rules WHERE id = :ruleId") + fun getById(ruleId: Long): RuleEntity? + + @Query("DELETE FROM rules WHERE id = :ruleId") + fun deleteById(ruleId: Long): Int + + @Delete + fun deleteRule(rule: RuleEntity) + + @Delete + fun deleteRules(rules: List) + + @Insert + fun createRule(rule: RuleEntity): Long + + @Update + fun updateRule(rule: RuleEntity) + + @Update + fun updateRules(rules: List) + + @Query("DELETE FROM rules") + fun reset() + + @Insert + fun insert(rules: List) + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt b/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt new file mode 100644 index 0000000..a88d9fa --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt @@ -0,0 +1,48 @@ +package io.nekohasekai.sagernet.database + +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import dev.matrix.roomigrant.GenerateRoomMigrations +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.fmt.KryoConverters +import io.nekohasekai.sagernet.fmt.gson.GsonConverters +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +@Database( + entities = [ProxyGroup::class, ProxyEntity::class, RuleEntity::class], + version = 1 +) +@TypeConverters(value = [KryoConverters::class, GsonConverters::class]) +@GenerateRoomMigrations +abstract class SagerDatabase : RoomDatabase() { + + companion object { + @OptIn(DelicateCoroutinesApi::class) + @Suppress("EXPERIMENTAL_API_USAGE") + private val instance by lazy { + SagerNet.application.getDatabasePath(Key.DB_PROFILE).parentFile?.mkdirs() + Room.databaseBuilder(SagerNet.application, SagerDatabase::class.java, Key.DB_PROFILE) + .addMigrations(*SagerDatabase_Migrations.build()) + .allowMainThreadQueries() + .enableMultiInstanceInvalidation() + .fallbackToDestructiveMigration() + .setQueryExecutor { GlobalScope.launch { it.run() } } + .build() + } + + val groupDao get() = instance.groupDao() + val proxyDao get() = instance.proxyDao() + val rulesDao get() = instance.rulesDao() + + } + + abstract fun groupDao(): ProxyGroup.Dao + abstract fun proxyDao(): ProxyEntity.Dao + abstract fun rulesDao(): RuleEntity.Dao + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/SubscriptionBean.java b/app/src/main/java/io/nekohasekai/sagernet/database/SubscriptionBean.java new file mode 100644 index 0000000..1e7e07a --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/database/SubscriptionBean.java @@ -0,0 +1,138 @@ +package io.nekohasekai.sagernet.database; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import java.util.ArrayList; +import java.util.List; + +import io.nekohasekai.sagernet.fmt.Serializable; + +public class SubscriptionBean extends Serializable { + + public Integer type; + public String link; + public String token; + public Boolean forceResolve; + public Boolean deduplication; + public Boolean updateWhenConnectedOnly; + public String customUserAgent; + public Boolean autoUpdate; + public Integer autoUpdateDelay; + public Integer lastUpdated; + + // SIP008 + + public Long bytesUsed; + public Long bytesRemaining; + + // Open Online Config + + public String username; + public Integer expiryDate; + public List protocols; + + + // https://github.com/crossutility/Quantumult/blob/master/extra-subscription-feature.md + + public String subscriptionUserinfo; + + public SubscriptionBean() { + } + + @Override + public void serializeToBuffer(ByteBufferOutput output) { + output.writeInt(1); + + output.writeInt(type); + + output.writeString(link); + + output.writeBoolean(forceResolve); + output.writeBoolean(deduplication); + output.writeBoolean(updateWhenConnectedOnly); + output.writeString(customUserAgent); + output.writeBoolean(autoUpdate); + output.writeInt(autoUpdateDelay); + output.writeInt(lastUpdated); + + output.writeString(subscriptionUserinfo); + } + + public void serializeForShare(ByteBufferOutput output) { + output.writeInt(0); + + output.writeInt(type); + + output.writeString(link); + + output.writeBoolean(forceResolve); + output.writeBoolean(deduplication); + output.writeBoolean(updateWhenConnectedOnly); + output.writeString(customUserAgent); + } + + @Override + public void deserializeFromBuffer(ByteBufferInput input) { + int version = input.readInt(); + + type = input.readInt(); + link = input.readString(); + forceResolve = input.readBoolean(); + deduplication = input.readBoolean(); + updateWhenConnectedOnly = input.readBoolean(); + customUserAgent = input.readString(); + autoUpdate = input.readBoolean(); + autoUpdateDelay = input.readInt(); + lastUpdated = input.readInt(); + subscriptionUserinfo = input.readString(); + } + + public void deserializeFromShare(ByteBufferInput input) { + int version = input.readInt(); + + type = input.readInt(); + link = input.readString(); + forceResolve = input.readBoolean(); + deduplication = input.readBoolean(); + updateWhenConnectedOnly = input.readBoolean(); + customUserAgent = input.readString(); + } + + @Override + public void initializeDefaultValues() { + if (type == null) type = 0; + if (link == null) link = ""; + if (token == null) token = ""; + if (forceResolve == null) forceResolve = false; + if (deduplication == null) deduplication = false; + if (updateWhenConnectedOnly == null) updateWhenConnectedOnly = false; + if (customUserAgent == null) customUserAgent = ""; + if (autoUpdate == null) autoUpdate = false; + if (autoUpdateDelay == null) autoUpdateDelay = 1440; + if (lastUpdated == null) lastUpdated = 0; + + if (bytesUsed == null) bytesUsed = 0L; + if (bytesRemaining == null) bytesRemaining = 0L; + + if (username == null) username = ""; + if (expiryDate == null) expiryDate = 0; + if (protocols == null) protocols = new ArrayList<>(); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public SubscriptionBean newInstance() { + return new SubscriptionBean(); + } + + @Override + public SubscriptionBean[] newArray(int size) { + return new SubscriptionBean[size]; + } + }; + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/preference/EditTextPreferenceModifiers.kt b/app/src/main/java/io/nekohasekai/sagernet/database/preference/EditTextPreferenceModifiers.kt new file mode 100644 index 0000000..501b18f --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/database/preference/EditTextPreferenceModifiers.kt @@ -0,0 +1,43 @@ +package io.nekohasekai.sagernet.database.preference + +import android.graphics.Typeface +import android.text.InputFilter +import android.view.inputmethod.EditorInfo +import android.widget.EditText +import androidx.preference.EditTextPreference + +object EditTextPreferenceModifiers { + object Monospace : EditTextPreference.OnBindEditTextListener { + override fun onBindEditText(editText: EditText) { + editText.typeface = Typeface.MONOSPACE + } + } + + object Hosts : EditTextPreference.OnBindEditTextListener { + + override fun onBindEditText(editText: EditText) { + editText.setHorizontallyScrolling(true) + editText.setSelection(editText.text.length) + } + } + + object Port : EditTextPreference.OnBindEditTextListener { + private val portLengthFilter = arrayOf(InputFilter.LengthFilter(5)) + + override fun onBindEditText(editText: EditText) { + editText.inputType = EditorInfo.TYPE_CLASS_NUMBER + editText.filters = portLengthFilter + editText.setSingleLine() + editText.setSelection(editText.text.length) + } + } + + object Number : EditTextPreference.OnBindEditTextListener { + + override fun onBindEditText(editText: EditText) { + editText.inputType = EditorInfo.TYPE_CLASS_NUMBER + editText.setSingleLine() + editText.setSelection(editText.text.length) + } + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/preference/KeyValuePair.kt b/app/src/main/java/io/nekohasekai/sagernet/database/preference/KeyValuePair.kt new file mode 100644 index 0000000..0d13ee9 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/database/preference/KeyValuePair.kt @@ -0,0 +1,170 @@ +package io.nekohasekai.sagernet.database.preference + +import android.os.Parcel +import android.os.Parcelable +import androidx.room.* +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer + +@Entity +class KeyValuePair() : Parcelable { + companion object { + const val TYPE_UNINITIALIZED = 0 + const val TYPE_BOOLEAN = 1 + const val TYPE_FLOAT = 2 + + @Deprecated("Use TYPE_LONG.") + const val TYPE_INT = 3 + const val TYPE_LONG = 4 + const val TYPE_STRING = 5 + const val TYPE_STRING_SET = 6 + + @JvmField + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): KeyValuePair { + return KeyValuePair(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } + + @androidx.room.Dao + interface Dao { + + @Query("SELECT * FROM `KeyValuePair`") + fun all(): List + + @Query("SELECT * FROM `KeyValuePair` WHERE `key` = :key") + operator fun get(key: String): KeyValuePair? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun put(value: KeyValuePair): Long + + @Query("DELETE FROM `KeyValuePair` WHERE `key` = :key") + fun delete(key: String): Int + + @Query("DELETE FROM `KeyValuePair`") + fun reset(): Int + + @Insert + fun insert(list: List) + } + + @PrimaryKey + var key: String = "" + var valueType: Int = TYPE_UNINITIALIZED + var value: ByteArray = ByteArray(0) + + val boolean: Boolean? + get() = if (valueType == TYPE_BOOLEAN) ByteBuffer.wrap(value).get() != 0.toByte() else null + val float: Float? + get() = if (valueType == TYPE_FLOAT) ByteBuffer.wrap(value).float else null + + @Suppress("DEPRECATION") + @Deprecated("Use long.", ReplaceWith("long")) + val int: Int? + get() = if (valueType == TYPE_INT) ByteBuffer.wrap(value).int else null + val long: Long? + get() = when (valueType) { + @Suppress("DEPRECATION") TYPE_INT, + -> ByteBuffer.wrap(value).int.toLong() + TYPE_LONG -> ByteBuffer.wrap(value).long + else -> null + } + val string: String? + get() = if (valueType == TYPE_STRING) String(value) else null + val stringSet: Set? + get() = if (valueType == TYPE_STRING_SET) { + val buffer = ByteBuffer.wrap(value) + val result = HashSet() + while (buffer.hasRemaining()) { + val chArr = ByteArray(buffer.int) + buffer.get(chArr) + result.add(String(chArr)) + } + result + } else null + + @Ignore + constructor(key: String) : this() { + this.key = key + } + + // putting null requires using DataStore + fun put(value: Boolean): KeyValuePair { + valueType = TYPE_BOOLEAN + this.value = ByteBuffer.allocate(1).put((if (value) 1 else 0).toByte()).array() + return this + } + + fun put(value: Float): KeyValuePair { + valueType = TYPE_FLOAT + this.value = ByteBuffer.allocate(4).putFloat(value).array() + return this + } + + @Suppress("DEPRECATION") + @Deprecated("Use long.") + fun put(value: Int): KeyValuePair { + valueType = TYPE_INT + this.value = ByteBuffer.allocate(4).putInt(value).array() + return this + } + + fun put(value: Long): KeyValuePair { + valueType = TYPE_LONG + this.value = ByteBuffer.allocate(8).putLong(value).array() + return this + } + + fun put(value: String): KeyValuePair { + valueType = TYPE_STRING + this.value = value.toByteArray() + return this + } + + fun put(value: Set): KeyValuePair { + valueType = TYPE_STRING_SET + val stream = ByteArrayOutputStream() + val intBuffer = ByteBuffer.allocate(4) + for (v in value) { + intBuffer.rewind() + stream.write(intBuffer.putInt(v.length).array()) + stream.write(v.toByteArray()) + } + this.value = stream.toByteArray() + return this + } + + @Suppress("IMPLICIT_CAST_TO_ANY") + override fun toString(): String { + return when (valueType) { + TYPE_BOOLEAN -> boolean + TYPE_FLOAT -> float + TYPE_LONG -> long + TYPE_STRING -> string + TYPE_STRING_SET -> stringSet + else -> null + }?.toString() ?: "null" + } + + constructor(parcel: Parcel) : this() { + key = parcel.readString()!! + valueType = parcel.readInt() + value = parcel.createByteArray()!! + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeString(key) + parcel.writeInt(valueType) + parcel.writeByteArray(value) + } + + override fun describeContents(): Int { + return 0 + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/preference/OnPreferenceDataStoreChangeListener.kt b/app/src/main/java/io/nekohasekai/sagernet/database/preference/OnPreferenceDataStoreChangeListener.kt new file mode 100644 index 0000000..9cca6d3 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/database/preference/OnPreferenceDataStoreChangeListener.kt @@ -0,0 +1,7 @@ +package io.nekohasekai.sagernet.database.preference + +import androidx.preference.PreferenceDataStore + +interface OnPreferenceDataStoreChangeListener { + fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/preference/PublicDatabase.kt b/app/src/main/java/io/nekohasekai/sagernet/database/preference/PublicDatabase.kt new file mode 100644 index 0000000..5c8a7bd --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/database/preference/PublicDatabase.kt @@ -0,0 +1,31 @@ +package io.nekohasekai.sagernet.database.preference + +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import dev.matrix.roomigrant.GenerateRoomMigrations +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.SagerNet +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +@Database(entities = [KeyValuePair::class], version = 1) +@GenerateRoomMigrations +abstract class PublicDatabase : RoomDatabase() { + companion object { + private val instance by lazy { + SagerNet.application.getDatabasePath(Key.DB_PROFILE).parentFile?.mkdirs() + Room.databaseBuilder(SagerNet.application, PublicDatabase::class.java, Key.DB_PUBLIC) + .allowMainThreadQueries() + .enableMultiInstanceInvalidation() + .fallbackToDestructiveMigration() + .setQueryExecutor { GlobalScope.launch { it.run() } } + .build() + } + + val kvPairDao get() = instance.keyValuePairDao() + } + + abstract fun keyValuePairDao(): KeyValuePair.Dao + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/database/preference/RoomPreferenceDataStore.kt b/app/src/main/java/io/nekohasekai/sagernet/database/preference/RoomPreferenceDataStore.kt new file mode 100644 index 0000000..4d90479 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/database/preference/RoomPreferenceDataStore.kt @@ -0,0 +1,90 @@ +package io.nekohasekai.sagernet.database.preference + +import androidx.preference.PreferenceDataStore + +@Suppress("MemberVisibilityCanBePrivate", "unused") +open class RoomPreferenceDataStore(private val kvPairDao: KeyValuePair.Dao) : + PreferenceDataStore() { + + fun getBoolean(key: String) = kvPairDao[key]?.boolean + fun getFloat(key: String) = kvPairDao[key]?.float + fun getInt(key: String) = kvPairDao[key]?.long?.toInt() + fun getLong(key: String) = kvPairDao[key]?.long + fun getString(key: String) = kvPairDao[key]?.string + fun getStringSet(key: String) = kvPairDao[key]?.stringSet + fun reset() = kvPairDao.reset() + + override fun getBoolean(key: String, defValue: Boolean) = getBoolean(key) ?: defValue + override fun getFloat(key: String, defValue: Float) = getFloat(key) ?: defValue + override fun getInt(key: String, defValue: Int) = getInt(key) ?: defValue + override fun getLong(key: String, defValue: Long) = getLong(key) ?: defValue + override fun getString(key: String, defValue: String?) = getString(key) ?: defValue + override fun getStringSet(key: String, defValue: MutableSet?) = + getStringSet(key) ?: defValue + + fun putBoolean(key: String, value: Boolean?) = + if (value == null) remove(key) else putBoolean(key, value) + + fun putFloat(key: String, value: Float?) = + if (value == null) remove(key) else putFloat(key, value) + + fun putInt(key: String, value: Int?) = + if (value == null) remove(key) else putLong(key, value.toLong()) + + fun putLong(key: String, value: Long?) = if (value == null) remove(key) else putLong(key, value) + override fun putBoolean(key: String, value: Boolean) { + kvPairDao.put(KeyValuePair(key).put(value)) + fireChangeListener(key) + } + + override fun putFloat(key: String, value: Float) { + kvPairDao.put(KeyValuePair(key).put(value)) + fireChangeListener(key) + } + + override fun putInt(key: String, value: Int) { + kvPairDao.put(KeyValuePair(key).put(value.toLong())) + fireChangeListener(key) + } + + override fun putLong(key: String, value: Long) { + kvPairDao.put(KeyValuePair(key).put(value)) + fireChangeListener(key) + } + + override fun putString(key: String, value: String?) = if (value == null) remove(key) else { + kvPairDao.put(KeyValuePair(key).put(value)) + fireChangeListener(key) + } + + override fun putStringSet(key: String, values: MutableSet?) = + if (values == null) remove(key) else { + kvPairDao.put(KeyValuePair(key).put(values)) + fireChangeListener(key) + } + + fun remove(key: String) { + kvPairDao.delete(key) + fireChangeListener(key) + } + + private val listeners = HashSet() + private fun fireChangeListener(key: String) { + val listeners = synchronized(listeners) { + listeners.toList() + } + listeners.forEach { it.onPreferenceDataStoreChanged(this, key) } + } + + fun registerChangeListener(listener: OnPreferenceDataStoreChangeListener) { + synchronized(listeners) { + listeners.add(listener) + } + } + + fun unregisterChangeListener(listener: OnPreferenceDataStoreChangeListener) { + synchronized(listeners) { + listeners.remove(listener) + } + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/AbstractBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/AbstractBean.java new file mode 100644 index 0000000..53dda06 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/AbstractBean.java @@ -0,0 +1,151 @@ +package io.nekohasekai.sagernet.fmt; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; + +import io.nekohasekai.sagernet.ktx.NetsKt; +import moe.matsuri.nb4a.utils.JavaUtil; + +public abstract class AbstractBean extends Serializable { + + public String serverAddress; + public Integer serverPort; + + public String name; + + // + + public String customOutboundJson; + public String customConfigJson; + + // + public transient String finalAddress; + public transient int finalPort; + + public String displayName() { + if (JavaUtil.isNotBlank(name)) { + return name; + } else { + return displayAddress(); + } + } + + public String displayAddress() { + return NetsKt.wrapIPV6Host(serverAddress) + ":" + serverPort; + } + + public String network() { + return "tcp,udp"; + } + + public boolean canICMPing() { + return true; + } + + public boolean canTCPing() { + return true; + } + + public boolean canMapping() { + return true; + } + + @Override + public void initializeDefaultValues() { + if (JavaUtil.isNullOrBlank(serverAddress)) { + serverAddress = "127.0.0.1"; + } else if (serverAddress.startsWith("[") && serverAddress.endsWith("]")) { + serverAddress = NetsKt.unwrapIPV6Host(serverAddress); + } + if (serverPort == null) { + serverPort = 1080; + } + if (name == null) name = ""; + + finalAddress = serverAddress; + finalPort = serverPort; + + if (customOutboundJson == null) customOutboundJson = ""; + if (customConfigJson == null) customConfigJson = ""; + } + + + private transient boolean serializeWithoutName; + + @Override + public void serializeToBuffer(@NonNull ByteBufferOutput output) { + serialize(output); + + output.writeInt(1); + if (!serializeWithoutName) { + output.writeString(name); + } + output.writeString(customOutboundJson); + output.writeString(customConfigJson); + } + + @Override + public void deserializeFromBuffer(@NonNull ByteBufferInput input) { + deserialize(input); + + int extraVersion = input.readInt(); + + name = input.readString(); + customOutboundJson = input.readString(); + customConfigJson = input.readString(); + } + + public void serialize(ByteBufferOutput output) { + output.writeString(serverAddress); + output.writeInt(serverPort); + } + + public void deserialize(ByteBufferInput input) { + serverAddress = input.readString(); + serverPort = input.readInt(); + } + + @NotNull + @Override + public abstract AbstractBean clone(); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + try { + serializeWithoutName = true; + ((AbstractBean) o).serializeWithoutName = true; + return Arrays.equals(KryoConverters.serialize(this), KryoConverters.serialize((AbstractBean) o)); + } finally { + serializeWithoutName = false; + ((AbstractBean) o).serializeWithoutName = false; + } + } + + @Override + public int hashCode() { + try { + serializeWithoutName = true; + return Arrays.hashCode(KryoConverters.serialize(this)); + } finally { + serializeWithoutName = false; + } + } + + @NotNull + @Override + public String toString() { + return getClass().getSimpleName() + " " + JavaUtil.gson.toJson(this); + } + + public void applyFeatureSettings(AbstractBean other) { + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt new file mode 100644 index 0000000..0dbf390 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt @@ -0,0 +1,710 @@ +package io.nekohasekai.sagernet.fmt + +import io.nekohasekai.sagernet.IPv6Mode +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.bg.VpnService +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.ProxyEntity +import io.nekohasekai.sagernet.database.ProxyEntity.Companion.TYPE_CONFIG +import io.nekohasekai.sagernet.database.SagerDatabase +import io.nekohasekai.sagernet.fmt.ConfigBuildResult.IndexEntity +import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean +import io.nekohasekai.sagernet.fmt.hysteria.buildSingBoxOutboundHysteriaBean +import io.nekohasekai.sagernet.fmt.hysteria.isMultiPort +import io.nekohasekai.sagernet.fmt.internal.ChainBean +import io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean +import io.nekohasekai.sagernet.fmt.shadowsocks.buildSingBoxOutboundShadowsocksBean +import io.nekohasekai.sagernet.fmt.socks.SOCKSBean +import io.nekohasekai.sagernet.fmt.socks.buildSingBoxOutboundSocksBean +import io.nekohasekai.sagernet.fmt.ssh.SSHBean +import io.nekohasekai.sagernet.fmt.ssh.buildSingBoxOutboundSSHBean +import io.nekohasekai.sagernet.fmt.tuic.TuicBean +import moe.matsuri.nb4a.SingBoxOptions.* +import io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean +import io.nekohasekai.sagernet.fmt.v2ray.buildSingBoxOutboundStandardV2RayBean +import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean +import io.nekohasekai.sagernet.fmt.wireguard.buildSingBoxOutboundWireguardBean +import io.nekohasekai.sagernet.ktx.isIpAddress +import io.nekohasekai.sagernet.ktx.mkPort +import io.nekohasekai.sagernet.utils.PackageCache +import moe.matsuri.nb4a.DNS.applyDNSNetworkSettings +import moe.matsuri.nb4a.DNS.makeSingBoxRule +import moe.matsuri.nb4a.proxy.config.ConfigBean +import moe.matsuri.nb4a.plugin.Plugins +import moe.matsuri.nb4a.utils.JavaUtil.gson +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull + +const val TAG_MIXED = "mixed-in" +const val TAG_TRANS = "trans-in" + +const val TAG_PROXY = "proxy" +const val TAG_DIRECT = "direct" +const val TAG_BYPASS = "bypass" +const val TAG_BLOCK = "block" + +const val TAG_DNS_IN = "dns-in" +const val TAG_DNS_OUT = "dns-out" + +const val LOCALHOST = "127.0.0.1" +const val LOCAL_DNS_SERVER = "underlying://0.0.0.0" + +class ConfigBuildResult( + var config: String, + var externalIndex: List, + var outboundTags: List, + var outboundTagMain: String, + var trafficMap: Map, + val alerts: List>, +) { + data class IndexEntity(var chain: LinkedHashMap) +} + +fun mergeJSON(j: String, to: MutableMap) { + if (j.isNullOrBlank()) return + val m = gson.fromJson(j, to.javaClass) + m.forEach { (k, v) -> + if (v is Map<*, *> && to[k] is Map<*, *>) { + val currentMap = (to[k] as Map<*, *>).toMutableMap() + currentMap += v + to[k] = currentMap + } else { + to[k] = v + } + } +} + +fun buildConfig( + proxy: ProxyEntity, forTest: Boolean = false +): ConfigBuildResult { + + if (proxy.type == TYPE_CONFIG) { + val bean = proxy.requireBean() as ConfigBean + if (bean.type == 0) { + return ConfigBuildResult( + bean.config, + listOf(), + listOf(TAG_PROXY), // + TAG_PROXY, // + mapOf( + TAG_PROXY to proxy + ), + listOf() + ) + } + } + + val outboundTags = ArrayList() + var outboundTagMain = TAG_BYPASS + val trafficMap = HashMap() + val globalOutbounds = ArrayList() + + fun ProxyEntity.resolveChain(): MutableList { + val bean = requireBean() + if (bean is ChainBean) { + val beans = SagerDatabase.proxyDao.getEntities(bean.proxies) + val beansMap = beans.associateBy { it.id } + val beanList = ArrayList() + for (proxyId in bean.proxies) { + val item = beansMap[proxyId] ?: continue + beanList.addAll(item.resolveChain()) + } + return beanList.asReversed() + } + return mutableListOf(this) + } + + val proxies = proxy.resolveChain() + val extraRules = if (forTest) listOf() else SagerDatabase.rulesDao.enabledRules() + val extraProxies = + if (forTest) mapOf() else SagerDatabase.proxyDao.getEntities(extraRules.mapNotNull { rule -> + rule.outbound.takeIf { it > 0 && it != proxy.id } + }.toHashSet().toList()).associate { it.id to it.resolveChain() } + + val uidListDNSRemote = mutableListOf() + val uidListDNSDirect = mutableListOf() + val domainListDNSRemote = mutableListOf() + val domainListDNSDirect = mutableListOf() + val domainListDNSBlock = mutableListOf() + val bypassDNSBeans = hashSetOf() + val isVPN = DataStore.serviceMode == Key.MODE_VPN + val bind = if (!forTest && DataStore.allowAccess) "0.0.0.0" else LOCALHOST + val remoteDns = DataStore.remoteDns.split("\n") + .mapNotNull { dns -> dns.trim().takeIf { it.isNotBlank() && !it.startsWith("#") } } + var directDNS = DataStore.directDns.split("\n") + .mapNotNull { dns -> dns.trim().takeIf { it.isNotBlank() && !it.startsWith("#") } } + val enableDnsRouting = DataStore.enableDnsRouting + val useFakeDns = DataStore.enableFakeDns && !forTest && DataStore.ipv6Mode != IPv6Mode.ONLY + val needSniff = DataStore.trafficSniffing + val externalIndexMap = ArrayList() + val requireTransproxy = if (forTest) false else DataStore.requireTransproxy + val ipv6Mode = if (forTest) IPv6Mode.ENABLE else DataStore.ipv6Mode + val resolveDestination = DataStore.resolveDestination + val alerts = mutableListOf>() + + var optionsToMerge: String = "" + + return MyOptions().apply { + if (!forTest && DataStore.enableClashAPI) experimental = ExperimentalOptions().apply { + clash_api = ClashAPIOptions().apply { + external_controller = "127.0.0.1:9090" + external_ui = "../files/yacd" + cache_file = "../cache/clash.db" + } + } + + dns = DNSOptions().apply { + // TODO nb4a hosts? +// hosts = DataStore.hosts.split("\n") +// .filter { it.isNotBlank() } +// .associate { it.substringBefore(" ") to it.substringAfter(" ") } +// .toMutableMap() + + servers = mutableListOf() + rules = mutableListOf() + + when (ipv6Mode) { + IPv6Mode.DISABLE -> { + strategy = "ipv4_only" + } + IPv6Mode.ONLY -> { + strategy = "ipv6_only" + } + } + } + + inbounds = mutableListOf() + + if (!forTest) { + if (isVPN) inbounds.add(Inbound_TunOptions().apply { + type = "tun" + tag = "tun-in" + stack = if (DataStore.tunImplementation == 1) "system" else "gvisor" + sniff = needSniff + endpoint_independent_nat = true + when (ipv6Mode) { + IPv6Mode.DISABLE -> { + inet4_address = listOf(VpnService.PRIVATE_VLAN4_CLIENT + "/28") + } + IPv6Mode.ONLY -> { + inet6_address = listOf(VpnService.PRIVATE_VLAN6_CLIENT + "/126") + } + else -> { + inet4_address = listOf(VpnService.PRIVATE_VLAN4_CLIENT + "/28") + inet6_address = listOf(VpnService.PRIVATE_VLAN6_CLIENT + "/126") + } + } + }) + inbounds.add(Inbound_MixedOptions().apply { + type = "mixed" + tag = TAG_MIXED + listen = bind + listen_port = DataStore.mixedPort + if (needSniff) { + sniff = true +// destOverride = when { +// useFakeDns && !trafficSniffing -> listOf("fakedns") +// useFakeDns -> listOf("fakedns", "http", "tls", "quic") +// else -> listOf("http", "tls", "quic") +// } +// metadataOnly = useFakeDns && !trafficSniffing +// routeOnly = true + } + }) + } + + if (requireTransproxy) { + if (DataStore.transproxyMode == 1) { + inbounds.add(Inbound_TProxyOptions().apply { + type = "tproxy" + tag = TAG_TRANS + listen = bind + listen_port = DataStore.transproxyPort + sniff = needSniff + }) + } else { + inbounds.add(Inbound_RedirectOptions().apply { + type = "redirect" + tag = TAG_TRANS + listen = bind + listen_port = DataStore.transproxyPort + sniff = needSniff + }) + } + } + + outbounds = mutableListOf() + + // init routing object + route = RouteOptions().apply { + auto_detect_interface = true + rules = mutableListOf() + } + + // returns outbound tag + fun buildChain( + chainId: Long, profileList: List + ): String { + var currentOutbound = mutableMapOf() + lateinit var pastOutbound: MutableMap + lateinit var pastInboundTag: String + var pastEntity: ProxyEntity? = null + val externalChainMap = LinkedHashMap() + externalIndexMap.add(IndexEntity(externalChainMap)) + val chainOutbounds = ArrayList>() + + // chainTagOut: v2ray outbound tag for this chain + var chainTagOut = "" + var chainTag = "c-$chainId" + var muxApplied = false + + fun genDomainStrategy(noAsIs: Boolean): String { + return when { + !resolveDestination && !noAsIs -> "" + ipv6Mode == IPv6Mode.DISABLE -> "ipv4_only" + ipv6Mode == IPv6Mode.PREFER -> "prefer_ipv6" + ipv6Mode == IPv6Mode.ONLY -> "ipv6_only" + else -> "prefer_ipv4" + } + } + + var currentDomainStrategy = genDomainStrategy(false) + + profileList.forEachIndexed { index, proxyEntity -> + val bean = proxyEntity.requireBean() + + // tagOut: v2ray outbound tag for a profile + // profile2 (in) (global) tag g-(id) + // profile1 tag (chainTag)-(id) + // profile0 (out) tag (chainTag)-(id) / single: "proxy" + var tagOut = "$chainTag-${proxyEntity.id}" + + // needGlobal: can only contain one? + var needGlobal = false + + // first profile set as global + if (index == profileList.lastIndex) { + needGlobal = true + tagOut = "g-" + proxyEntity.id + bypassDNSBeans += proxyEntity.requireBean() + } + + // last profile set as "proxy" + if (chainId == 0L && index == 0) { + tagOut = TAG_PROXY + } + + // chain rules + if (index > 0) { + // chain route/proxy rules + if (pastEntity!!.needExternal()) { + route.rules.add(Rule_DefaultOptions().apply { + inbound = listOf(pastInboundTag) + outbound = tagOut + }) + } else { + pastOutbound["detour"] = tagOut + } + } else { + // index == 0 means last profile in chain / not chain + chainTagOut = tagOut + outboundTags.add(tagOut) + if (chainId == 0L) outboundTagMain = tagOut + } + + if (needGlobal) { + if (globalOutbounds.contains(proxyEntity.id)) { + return@forEachIndexed + } + globalOutbounds.add(proxyEntity.id) + } + + // include g-xx + trafficMap[tagOut] = proxyEntity + + // Chain outbound + if (proxyEntity.needExternal()) { + val localPort = mkPort() + externalChainMap[localPort] = proxyEntity + currentOutbound = Outbound_SocksOptions().apply { + type = "socks" + server = LOCALHOST + server_port = localPort + }.asMap() + } else { + // internal outbound + + currentOutbound = when (bean) { + is ConfigBean -> + gson.fromJson(bean.config, currentOutbound.javaClass) + is StandardV2RayBean -> + buildSingBoxOutboundStandardV2RayBean(bean).asMap() + is HysteriaBean -> + buildSingBoxOutboundHysteriaBean(bean).asMap() + is SOCKSBean -> + buildSingBoxOutboundSocksBean(bean).asMap() + is ShadowsocksBean -> + buildSingBoxOutboundShadowsocksBean(bean).asMap() + is WireGuardBean -> + buildSingBoxOutboundWireguardBean(bean).asMap() + is SSHBean -> + buildSingBoxOutboundSSHBean(bean).asMap() + else -> throw IllegalStateException("can't reach") + } + + currentOutbound.apply { + // TODO nb4a keepAliveInterval? +// val keepAliveInterval = DataStore.tcpKeepAliveInterval +// val needKeepAliveInterval = keepAliveInterval !in intArrayOf(0, 15) + + if (!muxApplied && proxyEntity.needCoreMux()) { + muxApplied = true + currentOutbound["multiplex"] = MultiplexOptions().apply { + enabled = true + max_streams = DataStore.muxConcurrency + } + } + } + + // custom JSON merge + if (bean.customOutboundJson.isNotBlank()) { + mergeJSON(bean.customOutboundJson, currentOutbound) + } + if (index == 0 && bean.customConfigJson.isNotBlank()) { + optionsToMerge = bean.customConfigJson + } + + } + + pastEntity?.requireBean()?.apply { + // don't loopback + if (currentDomainStrategy != "" && !serverAddress.isIpAddress()) { + domainListDNSDirect.add("full:$serverAddress") + } + } + if (forTest) { + currentDomainStrategy = "" + } + + currentOutbound["tag"] = tagOut + currentOutbound["domain_strategy"] = currentDomainStrategy + + // External proxy need a dokodemo-door inbound to forward the traffic + // For external proxy software, their traffic must goes to v2ray-core to use protected fd. + if (bean.canMapping() && proxyEntity.needExternal()) { + // With ss protect, don't use mapping + var needExternal = true + if (index == profileList.lastIndex) { + val pluginId = when (bean) { + is HysteriaBean -> "hysteria-plugin" + is TuicBean -> "tuic-plugin" + else -> "" + } + if (Plugins.isUsingMatsuriExe(pluginId)) { + needExternal = false + } else if (bean is HysteriaBean) { + throw Exception("not supported hysteria-plugin (SagerNet)") + } + } + if (needExternal) { + val mappingPort = mkPort() + bean.finalAddress = LOCALHOST + bean.finalPort = mappingPort + + inbounds.add(Inbound_DirectOptions().apply { + type = "direct" + listen = LOCALHOST + listen_port = mappingPort + tag = "$chainTag-mapping-${proxyEntity.id}" + + override_address = bean.serverAddress + override_port = bean.serverPort + + pastInboundTag = tag + + // no chain rule and not outbound, so need to set to direct + if (index == profileList.lastIndex) { + route.rules.add(Rule_DefaultOptions().apply { + inbound = listOf(tag) + outbound = TAG_DIRECT + }) + } + }) + } + } + + outbounds.add(currentOutbound) + chainOutbounds.add(currentOutbound) + pastOutbound = currentOutbound + pastEntity = proxyEntity + } + + return chainTagOut + } + + val tagProxy = buildChain(0, proxies) + val tagMap = mutableMapOf() + extraProxies.forEach { (key, entities) -> + tagMap[key] = buildChain(key, entities) + } + + // apply user rules + for (rule in extraRules) { + val _uidList = rule.packages.map { + PackageCache[it]?.takeIf { uid -> uid >= 1000 } + }.toHashSet().filterNotNull() + + if (rule.packages.isNotEmpty()) { + if (!isVPN) { + alerts.add(0 to rule.displayName()) + continue + } + } + route.rules.add(Rule_DefaultOptions().apply { + if (rule.packages.isNotEmpty()) { + PackageCache.awaitLoadSync() + user_id = _uidList + } + + var _domainList: List? = null + if (rule.domains.isNotBlank()) { + _domainList = rule.domains.split("\n") + makeSingBoxRule(_domainList, false) + } + if (rule.ip.isNotBlank()) { + makeSingBoxRule(rule.ip.split("\n"), true) + } + if (rule.port.isNotBlank()) { + port = rule.port.split("\n").map { it.toIntOrNull() ?: 0 } + } + if (rule.sourcePort.isNotBlank()) { + source_port = rule.sourcePort.split("\n").map { it.toIntOrNull() ?: 0 } + } + if (rule.network.isNotBlank()) { + network = rule.network + } + if (rule.source.isNotBlank()) { + source_ip_cidr = rule.source.split("\n") + } + if (rule.protocol.isNotBlank()) { + protocol = rule.protocol.split("\n") + } + + // also bypass lookup + // cannot use other outbound profile to lookup... + if (rule.outbound == -1L) { + uidListDNSDirect += _uidList + if (_domainList != null) domainListDNSDirect += _domainList + } else if (rule.outbound == 0L) { + uidListDNSRemote += _uidList + if (_domainList != null) domainListDNSRemote += _domainList + } else if (rule.outbound == -2L) { + if (_domainList != null) domainListDNSBlock += _domainList + } + + outbound = when (val outId = rule.outbound) { + 0L -> tagProxy + -1L -> TAG_BYPASS + -2L -> TAG_BLOCK + else -> if (outId == proxy.id) tagProxy else tagMap[outId] + ?: throw Exception("invalid rule") + } + }) + } + + for (freedom in arrayOf(TAG_DIRECT, TAG_BYPASS)) outbounds.add(Outbound().apply { + tag = freedom + type = "direct" + }.asMap()) + + outbounds.add(Outbound().apply { + tag = TAG_BLOCK + type = "block" + }.asMap()) + + if (!forTest) { + inbounds.add(0, Inbound_DirectOptions().apply { + type = "direct" + tag = TAG_DNS_IN + listen = bind + listen_port = DataStore.localDNSPort + override_address = if (!remoteDns.first().isIpAddress()) { + "8.8.8.8" + } else { + remoteDns.first() + } + override_port = 53 + }) + + outbounds.add(Outbound().apply { + type = "dns" + tag = TAG_DNS_OUT + }.asMap()) + } + + if (DataStore.directDnsUseSystem) { + // finally able to use "localDns" now... + directDNS = listOf(LOCAL_DNS_SERVER) + } + + // routing for DNS server + for (dns in remoteDns) { + if (!dns.isIpAddress()) continue + route.rules.add(Rule_DefaultOptions().apply { + outbound = tagProxy + ip_cidr = listOf(dns) + }) + } + + for (dns in directDNS) { + if (!dns.isIpAddress()) continue + route.rules.add(Rule_DefaultOptions().apply { + outbound = TAG_DIRECT + ip_cidr = listOf(dns) + }) + } + + // Bypass Lookup for the first profile + bypassDNSBeans.forEach { + var serverAddr = it.serverAddress + if (it is HysteriaBean && it.isMultiPort()) { + serverAddr = it.serverAddress.substringBeforeLast(":") + } + + if (!serverAddr.isIpAddress()) { + domainListDNSDirect.add("full:${serverAddr}") + } + } + + remoteDns.forEach { + var address = it + if (address.contains("://")) { + address = address.substringAfter("://") + } + "https://$address".toHttpUrlOrNull()?.apply { + if (!host.isIpAddress()) { + domainListDNSDirect.add("full:$host") + } + } + } + + // remote dns obj + remoteDns.firstOrNull()?.apply { + val d = this + dns.servers.add(DNSServerOptions().apply { + address = d + tag = "dns-remote" + address_resolver = "dns-direct" + applyDNSNetworkSettings(false) + }) + } + + // add directDNS objects here + directDNS.firstOrNull()?.apply { + val d = this + dns.servers.add(DNSServerOptions().apply { + address = d + tag = "dns-direct" + detour = "direct" + address_resolver = "dns-local" + applyDNSNetworkSettings(true) + }) + } + dns.servers.add(DNSServerOptions().apply { + address = LOCAL_DNS_SERVER + tag = "dns-local" + detour = "direct" + }) + dns.servers.add(DNSServerOptions().apply { + address = "rcode://success" + tag = "dns-block" + }) + + // dns object user rules + if (enableDnsRouting) { + if (domainListDNSRemote.isNotEmpty() || uidListDNSRemote.isNotEmpty()) { + dns.rules.add( + DNSRule_DefaultOptions().apply { + makeSingBoxRule(domainListDNSRemote.toHashSet().toList()) + user_id = uidListDNSRemote.toHashSet().toList() + server = "dns-remote" + } + ) + } + if (domainListDNSDirect.isNotEmpty() || uidListDNSDirect.isNotEmpty()) { + dns.rules.add( + DNSRule_DefaultOptions().apply { + makeSingBoxRule(domainListDNSDirect.toHashSet().toList()) + user_id = uidListDNSDirect.toHashSet().toList() + server = "dns-direct" + } + ) + } + } + if (domainListDNSBlock.isNotEmpty()) { + dns.rules.add( + DNSRule_DefaultOptions().apply { + makeSingBoxRule(domainListDNSBlock.toHashSet().toList()) + server = "dns-block" + } + ) + } + + // Disable DNS for test + if (forTest) { + dns.servers.clear() + dns.rules.clear() + } + + if (!forTest) { + route.rules.add(Rule_DefaultOptions().apply { + inbound = listOf(TAG_DNS_IN) + outbound = TAG_DNS_OUT + }) + route.rules.add(Rule_DefaultOptions().apply { + port = listOf(53) + outbound = TAG_DNS_OUT + }) // TODO new mode use system dns? + if (DataStore.bypassLan && DataStore.bypassLanInCoreOnly) { + route.rules.add(Rule_DefaultOptions().apply { + outbound = TAG_BYPASS + geoip = listOf("private") + }) + } + // block mcast + route.rules.add(Rule_DefaultOptions().apply { + ip_cidr = listOf("224.0.0.0/3", "ff00::/8") + source_ip_cidr = listOf("224.0.0.0/3", "ff00::/8") + outbound = TAG_BLOCK + }) + dns.rules.add(DNSRule_DefaultOptions().apply { + domain_suffix = listOf(".arpa.", ".arpa") + server = "dns-block" + }) + } + + // fakedns obj + if (useFakeDns) { + dns.servers.add(DNSServerOptions().apply { + address = "fakedns://" + VpnService.FAKEDNS_VLAN4_CLIENT + "/15" + tag = "dns-fake" + strategy = "ipv4_only" + }) + dns.rules.add(DNSRule_DefaultOptions().apply { + inbound = listOf("tun-in") + server = "dns-fake" + }) + } + }.let { + ConfigBuildResult( + gson.toJson(it.asMap().apply { + mergeJSON(optionsToMerge, this) + }), + externalIndexMap, + outboundTags, + outboundTagMain, + trafficMap, + alerts + ) + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java new file mode 100644 index 0000000..f5fce41 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java @@ -0,0 +1,149 @@ +package io.nekohasekai.sagernet.fmt; + +import androidx.room.TypeConverter; + +import com.esotericsoftware.kryo.KryoException; +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +import io.nekohasekai.sagernet.database.SubscriptionBean; +import io.nekohasekai.sagernet.fmt.http.HttpBean; +import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean; +import io.nekohasekai.sagernet.fmt.internal.ChainBean; +import io.nekohasekai.sagernet.fmt.naive.NaiveBean; +import io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean; +import io.nekohasekai.sagernet.fmt.socks.SOCKSBean; +import io.nekohasekai.sagernet.fmt.ssh.SSHBean; +import io.nekohasekai.sagernet.fmt.trojan.TrojanBean; +import io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean; +import io.nekohasekai.sagernet.fmt.tuic.TuicBean; +import io.nekohasekai.sagernet.fmt.v2ray.VMessBean; +import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean; +import io.nekohasekai.sagernet.ktx.KryosKt; +import io.nekohasekai.sagernet.ktx.Logs; +import moe.matsuri.nb4a.proxy.neko.NekoBean; +import moe.matsuri.nb4a.proxy.config.ConfigBean; +import moe.matsuri.nb4a.utils.JavaUtil; + +public class KryoConverters { + + private static final byte[] NULL = new byte[0]; + + @TypeConverter + public static byte[] serialize(Serializable bean) { + if (bean == null) return NULL; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteBufferOutput buffer = KryosKt.byteBuffer(out); + bean.serializeToBuffer(buffer); + buffer.flush(); + buffer.close(); + return out.toByteArray(); + } + + public static T deserialize(T bean, byte[] bytes) { + if (bytes == null) return bean; + ByteArrayInputStream input = new ByteArrayInputStream(bytes); + ByteBufferInput buffer = KryosKt.byteBuffer(input); + try { + bean.deserializeFromBuffer(buffer); + } catch (KryoException e) { + Logs.INSTANCE.w(e); + } + bean.initializeDefaultValues(); + return bean; + } + + @TypeConverter + public static SOCKSBean socksDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new SOCKSBean(), bytes); + } + + @TypeConverter + public static HttpBean httpDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new HttpBean(), bytes); + } + + @TypeConverter + public static ShadowsocksBean shadowsocksDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new ShadowsocksBean(), bytes); + } + + @TypeConverter + public static ConfigBean configDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new ConfigBean(), bytes); + } + + @TypeConverter + public static VMessBean vmessDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new VMessBean(), bytes); + } + + @TypeConverter + public static TrojanBean trojanDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new TrojanBean(), bytes); + } + + @TypeConverter + public static TrojanGoBean trojanGoDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new TrojanGoBean(), bytes); + } + + @TypeConverter + public static NaiveBean naiveDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new NaiveBean(), bytes); + } + + @TypeConverter + public static HysteriaBean hysteriaDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new HysteriaBean(), bytes); + } + + @TypeConverter + public static SSHBean sshDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new SSHBean(), bytes); + } + + @TypeConverter + public static WireGuardBean wireguardDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new WireGuardBean(), bytes); + } + + @TypeConverter + public static TuicBean tuicDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new TuicBean(), bytes); + } + + @TypeConverter + public static ChainBean chainDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new ChainBean(), bytes); + } + + @TypeConverter + public static NekoBean nekoDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new NekoBean(), bytes); + } + + @TypeConverter + public static SubscriptionBean subscriptionDeserialize(byte[] bytes) { + if (JavaUtil.isEmpty(bytes)) return null; + return deserialize(new SubscriptionBean(), bytes); + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/PluginEntry.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/PluginEntry.kt new file mode 100644 index 0000000..749631d --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/PluginEntry.kt @@ -0,0 +1,58 @@ +package io.nekohasekai.sagernet.fmt + +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet + +enum class PluginEntry( + val pluginId: String, + val displayName: String, + val packageName: String, // for play and f-droid page + val downloadSource: DownloadSource = DownloadSource() +) { + TrojanGo( + "trojan-go-plugin", + SagerNet.application.getString(R.string.action_trojan_go), + "io.nekohasekai.sagernet.plugin.trojan_go" + ), + NaiveProxy( + "naive-plugin", + SagerNet.application.getString(R.string.action_naive), + "io.nekohasekai.sagernet.plugin.naive" + ), + Hysteria( + "hysteria-plugin", + SagerNet.application.getString(R.string.action_hysteria), + "moe.matsuri.exe.hysteria", DownloadSource( + playStore = false, + fdroid = false, + downloadLink = "https://github.com/MatsuriDayo/plugins/releases?q=Hysteria" + ) + ), + TUIC( + "tuic-plugin", + SagerNet.application.getString(R.string.action_tuic), + "io.nekohasekai.sagernet.plugin.tuic", + DownloadSource(fdroid = false) + ), + ; + + data class DownloadSource( + val playStore: Boolean = true, + val fdroid: Boolean = true, + val downloadLink: String = "https://sagernet.org/download/" + ) + + companion object { + + fun find(name: String): PluginEntry? { + for (pluginEntry in enumValues()) { + if (name == pluginEntry.pluginId) { + return pluginEntry + } + } + return null + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/Serializable.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/Serializable.kt new file mode 100644 index 0000000..b2c49ad --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/Serializable.kt @@ -0,0 +1,27 @@ +package io.nekohasekai.sagernet.fmt + +import android.os.Parcel +import android.os.Parcelable +import com.esotericsoftware.kryo.io.ByteBufferInput +import com.esotericsoftware.kryo.io.ByteBufferOutput + +abstract class Serializable : Parcelable { + abstract fun initializeDefaultValues() + abstract fun serializeToBuffer(output: ByteBufferOutput) + abstract fun deserializeFromBuffer(input: ByteBufferInput) + + override fun describeContents() = 0 + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeByteArray(KryoConverters.serialize(this)) + } + + abstract class CREATOR : Parcelable.Creator { + abstract fun newInstance(): T + + override fun createFromParcel(source: Parcel): T { + return KryoConverters.deserialize(newInstance(), source.createByteArray()) + } + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt new file mode 100644 index 0000000..c55c58b --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt @@ -0,0 +1,30 @@ +package io.nekohasekai.sagernet.fmt + +import io.nekohasekai.sagernet.database.ProxyEntity + +object TypeMap : HashMap() { + init { + this["socks"] = ProxyEntity.TYPE_SOCKS + this["http"] = ProxyEntity.TYPE_HTTP + this["ss"] = ProxyEntity.TYPE_SS + this["vmess"] = ProxyEntity.TYPE_VMESS + this["trojan"] = ProxyEntity.TYPE_TROJAN + this["trojan-go"] = ProxyEntity.TYPE_TROJAN_GO + this["naive"] = ProxyEntity.TYPE_NAIVE + this["hysteria"] = ProxyEntity.TYPE_HYSTERIA + this["ssh"] = ProxyEntity.TYPE_SSH + this["wg"] = ProxyEntity.TYPE_WG + this["tuic"] = ProxyEntity.TYPE_TUIC + this["neko"] = ProxyEntity.TYPE_NEKO + this["config"] = ProxyEntity.TYPE_CONFIG + } + + val reversed = HashMap() + + init { + TypeMap.forEach { (key, type) -> + reversed[type] = key + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/UniversalFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/UniversalFmt.kt new file mode 100644 index 0000000..c9840ae --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/UniversalFmt.kt @@ -0,0 +1,36 @@ +package io.nekohasekai.sagernet.fmt + +import io.nekohasekai.sagernet.database.ProxyEntity +import io.nekohasekai.sagernet.database.ProxyGroup +import moe.matsuri.nb4a.utils.Util + +fun parseUniversal(link: String): AbstractBean { + return if (link.contains("?")) { + val type = link.substringAfter("sn://").substringBefore("?") + ProxyEntity(type = TypeMap[type] ?: error("Type $type not found")).apply { + putByteArray(Util.zlibDecompress(Util.b64Decode(link.substringAfter("?")))) + }.requireBean() + } else { + val type = link.substringAfter("sn://").substringBefore(":") + ProxyEntity(type = TypeMap[type] ?: error("Type $type not found")).apply { + putByteArray(Util.b64Decode(link.substringAfter(":").substringAfter(":"))) + }.requireBean() + } +} + +fun AbstractBean.toUniversalLink(): String { + var link = "sn://" + link += TypeMap.reversed[ProxyEntity().putBean(this).type] + link += "?" + link += Util.b64EncodeUrlSafe(Util.zlibCompress(KryoConverters.serialize(this), 9)) + return link +} + + +fun ProxyGroup.toUniversalLink(): String { + var link = "sn://subscription?" + export = true + link += Util.b64EncodeUrlSafe(Util.zlibCompress(KryoConverters.serialize(this), 9)) + export = false + return link +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/gson/GsonConverters.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/gson/GsonConverters.java new file mode 100644 index 0000000..41f1fc6 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/gson/GsonConverters.java @@ -0,0 +1,35 @@ +package io.nekohasekai.sagernet.fmt.gson; + +import androidx.room.TypeConverter; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import kotlin.collections.CollectionsKt; +import kotlin.collections.SetsKt; +import moe.matsuri.nb4a.utils.JavaUtil; + +public class GsonConverters { + + @TypeConverter + public static String toJson(Object value) { + if (value instanceof Collection) { + if (((Collection) value).isEmpty()) return ""; + } + return JavaUtil.gson.toJson(value); + } + + @TypeConverter + public static List toList(String value) { + if (JavaUtil.isNullOrBlank(value)) return CollectionsKt.listOf(); + return JavaUtil.gson.fromJson(value, List.class); + } + + @TypeConverter + public static Set toSet(String value) { + if (JavaUtil.isNullOrBlank(value)) return SetsKt.setOf(); + return JavaUtil.gson.fromJson(value, Set.class); + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/http/HttpBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/http/HttpBean.java new file mode 100644 index 0000000..a92d2cf --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/http/HttpBean.java @@ -0,0 +1,59 @@ +package io.nekohasekai.sagernet.fmt.http; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; + +import io.nekohasekai.sagernet.fmt.KryoConverters; +import io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean; + +public class HttpBean extends StandardV2RayBean { + + public String username; + public String password; + + @Override + public void initializeDefaultValues() { + super.initializeDefaultValues(); + if (username == null) username = ""; + if (password == null) password = ""; + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(0); + super.serialize(output); + output.writeString(username); + output.writeString(password); + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + super.deserialize(input); + username = input.readString(); + password = input.readString(); + } + + @NotNull + @Override + public HttpBean clone() { + return KryoConverters.deserialize(new HttpBean(), KryoConverters.serialize(this)); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public HttpBean newInstance() { + return new HttpBean(); + } + + @Override + public HttpBean[] newArray(int size) { + return new HttpBean[size]; + } + }; +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/http/HttpFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/http/HttpFmt.kt new file mode 100644 index 0000000..a190067 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/http/HttpFmt.kt @@ -0,0 +1,46 @@ +package io.nekohasekai.sagernet.fmt.http + +import io.nekohasekai.sagernet.fmt.v2ray.isTLS +import io.nekohasekai.sagernet.fmt.v2ray.setTLS +import io.nekohasekai.sagernet.ktx.urlSafe +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull + +fun parseHttp(link: String): HttpBean { + val httpUrl = link.toHttpUrlOrNull() ?: error("Invalid http(s) link: $link") + + if (httpUrl.encodedPath != "/") error("Not http proxy") + + return HttpBean().apply { + serverAddress = httpUrl.host + serverPort = httpUrl.port + username = httpUrl.username + password = httpUrl.password + sni = httpUrl.queryParameter("sni") + name = httpUrl.fragment + setTLS(httpUrl.scheme == "https") + } +} + +fun HttpBean.toUri(): String { + val builder = HttpUrl.Builder().scheme(if (isTLS()) "https" else "http").host(serverAddress) + + if (serverPort in 1..65535) { + builder.port(serverPort) + } + + if (username.isNotBlank()) { + builder.username(username) + } + if (password.isNotBlank()) { + builder.password(password) + } + if (sni.isNotBlank()) { + builder.addQueryParameter("sni", sni) + } + if (name.isNotBlank()) { + builder.encodedFragment(name.urlSafe()) + } + + return builder.toString() +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaBean.java new file mode 100644 index 0000000..3a4ffb3 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaBean.java @@ -0,0 +1,155 @@ +package io.nekohasekai.sagernet.fmt.hysteria; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; + +import io.nekohasekai.sagernet.fmt.AbstractBean; +import io.nekohasekai.sagernet.fmt.KryoConverters; + +public class HysteriaBean extends AbstractBean { + + public static final int TYPE_NONE = 0; + public static final int TYPE_STRING = 1; + public static final int TYPE_BASE64 = 2; + + public Integer authPayloadType; + public String authPayload; + + public static final int PROTOCOL_UDP = 0; + public static final int PROTOCOL_FAKETCP = 1; + public static final int PROTOCOL_WECHAT_VIDEO = 2; + + public Integer protocol; + + public String obfuscation; + public String sni; + public String alpn; + public String caText; + + public Integer uploadMbps; + public Integer downloadMbps; + public Boolean allowInsecure; + public Integer streamReceiveWindow; + public Integer connectionReceiveWindow; + public Boolean disableMtuDiscovery; + public Integer hopInterval; + + @Override + public boolean canMapping() { + return protocol != PROTOCOL_FAKETCP; + } + + @Override + public void initializeDefaultValues() { + super.initializeDefaultValues(); + if (authPayloadType == null) authPayloadType = TYPE_NONE; + if (authPayload == null) authPayload = ""; + if (protocol == null) protocol = PROTOCOL_UDP; + if (obfuscation == null) obfuscation = ""; + if (sni == null) sni = ""; + if (alpn == null) alpn = ""; + if (caText == null) caText = ""; + + if (uploadMbps == null) uploadMbps = 10; + if (downloadMbps == null) downloadMbps = 50; + if (allowInsecure == null) allowInsecure = false; + + if (streamReceiveWindow == null) streamReceiveWindow = 0; + if (connectionReceiveWindow == null) connectionReceiveWindow = 0; + if (disableMtuDiscovery == null) disableMtuDiscovery = false; + if (hopInterval == null) hopInterval = 10; + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(5); + super.serialize(output); + output.writeInt(authPayloadType); + output.writeString(authPayload); + output.writeInt(protocol); + output.writeString(obfuscation); + output.writeString(sni); + output.writeString(alpn); + + output.writeInt(uploadMbps); + output.writeInt(downloadMbps); + output.writeBoolean(allowInsecure); + + output.writeString(caText); + output.writeInt(streamReceiveWindow); + output.writeInt(connectionReceiveWindow); + output.writeBoolean(disableMtuDiscovery); + output.writeInt(hopInterval); + + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + super.deserialize(input); + authPayloadType = input.readInt(); + authPayload = input.readString(); + if (version >= 3) { + protocol = input.readInt(); + } + obfuscation = input.readString(); + sni = input.readString(); + if (version >= 2) { + alpn = input.readString(); + } + uploadMbps = input.readInt(); + downloadMbps = input.readInt(); + allowInsecure = input.readBoolean(); + if (version >= 1) { + caText = input.readString(); + streamReceiveWindow = input.readInt(); + connectionReceiveWindow = input.readInt(); + if (version != 4) disableMtuDiscovery = input.readBoolean(); // note: skip 4 + } + if (version >= 5) { + hopInterval = input.readInt(); + } + } + + @Override + public void applyFeatureSettings(AbstractBean other) { + if (!(other instanceof HysteriaBean)) return; + HysteriaBean bean = ((HysteriaBean) other); + bean.uploadMbps = uploadMbps; + bean.downloadMbps = downloadMbps; + bean.allowInsecure = allowInsecure; + bean.disableMtuDiscovery = disableMtuDiscovery; + bean.hopInterval = hopInterval; + } + + @Override + public String displayAddress() { + if (HysteriaFmtKt.isMultiPort(this)) { + return serverAddress; + } + return super.displayAddress(); + } + + @NotNull + @Override + public HysteriaBean clone() { + return KryoConverters.deserialize(new HysteriaBean(), KryoConverters.serialize(this)); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public HysteriaBean newInstance() { + return new HysteriaBean(); + } + + @Override + public HysteriaBean[] newArray(int size) { + return new HysteriaBean[size]; + } + }; +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaFmt.kt new file mode 100644 index 0000000..f4de1ac --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaFmt.kt @@ -0,0 +1,237 @@ +package io.nekohasekai.sagernet.fmt.hysteria + +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.fmt.LOCALHOST +import io.nekohasekai.sagernet.ktx.* +import moe.matsuri.nb4a.SingBoxOptions +import moe.matsuri.nb4a.utils.Util +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import org.json.JSONObject +import java.io.File + + +// hysteria://host:port?auth=123456&peer=sni.domain&insecure=1|0&upmbps=100&downmbps=100&alpn=hysteria&obfs=xplus&obfsParam=123456#remarks + +fun parseHysteria(url: String): HysteriaBean { + val link = url.replace("hysteria://", "https://").toHttpUrlOrNull() ?: error( + "invalid hysteria link $url" + ) + return HysteriaBean().apply { + serverAddress = link.host + serverPort = link.port + name = link.fragment + + link.queryParameter("mport")?.also { + serverAddress = serverAddress.wrapIPV6Host() + ":" + it + } + link.queryParameter("peer")?.also { + sni = it + } + link.queryParameter("auth")?.takeIf { it.isNotBlank() }?.also { + authPayloadType = HysteriaBean.TYPE_STRING + authPayload = it + } + link.queryParameter("insecure")?.also { + allowInsecure = it == "1" + } + link.queryParameter("upmbps")?.also { + uploadMbps = it.toIntOrNull() ?: uploadMbps + } + link.queryParameter("downmbps")?.also { + downloadMbps = it.toIntOrNull() ?: downloadMbps + } + link.queryParameter("alpn")?.also { + alpn = it + } + link.queryParameter("obfsParam")?.also { + obfuscation = it + } + link.queryParameter("protocol")?.also { + when (it) { + "faketcp" -> { + protocol = HysteriaBean.PROTOCOL_FAKETCP + } + "wechat-video" -> { + protocol = HysteriaBean.PROTOCOL_WECHAT_VIDEO + } + } + } + } +} + +fun HysteriaBean.toUri(): String { + val builder = linkBuilder().host(serverAddress.substringBeforeLast(":")).port(serverPort) + if (isMultiPort()) { + builder.addQueryParameter("mport", serverAddress.substringAfterLast(":")) + } + if (allowInsecure) { + builder.addQueryParameter("insecure", "1") + } + if (sni.isNotBlank()) { + builder.addQueryParameter("peer", sni) + } + if (authPayload.isNotBlank()) { + builder.addQueryParameter("auth", authPayload) + } + builder.addQueryParameter("upmbps", "$uploadMbps") + builder.addQueryParameter("downmbps", "$downloadMbps") + if (alpn.isNotBlank()) { + builder.addQueryParameter("alpn", alpn) + } + if (obfuscation.isNotBlank()) { + builder.addQueryParameter("obfs", "xplus") + builder.addQueryParameter("obfsParam", obfuscation) + } + when (protocol) { + HysteriaBean.PROTOCOL_FAKETCP -> { + builder.addQueryParameter("protocol", "faketcp") + } + HysteriaBean.PROTOCOL_WECHAT_VIDEO -> { + builder.addQueryParameter("protocol", "wechat-video") + } + } + if (protocol == HysteriaBean.PROTOCOL_FAKETCP) { + builder.addQueryParameter("protocol", "faketcp") + } + if (name.isNotBlank()) { + builder.encodedFragment(name.urlSafe()) + } + return builder.toLink("hysteria") +} + +fun JSONObject.parseHysteria(): HysteriaBean { + return HysteriaBean().apply { + serverAddress = optString("server") + if (!isMultiPort()) { + serverAddress = optString("server").substringBeforeLast(":") + serverPort = optString("server").substringAfterLast(":").toIntOrNull() ?: 443 + } + uploadMbps = getIntNya("up_mbps") + downloadMbps = getIntNya("down_mbps") + obfuscation = getStr("obfs") + getStr("auth")?.also { + authPayloadType = HysteriaBean.TYPE_BASE64 + authPayload = it + } + getStr("auth_str")?.also { + authPayloadType = HysteriaBean.TYPE_STRING + authPayload = it + } + getStr("protocol")?.also { + when (it) { + "faketcp" -> { + protocol = HysteriaBean.PROTOCOL_FAKETCP + } + "wechat-video" -> { + protocol = HysteriaBean.PROTOCOL_WECHAT_VIDEO + } + } + } + sni = getStr("server_name") + alpn = getStr("alpn") + allowInsecure = getBool("insecure") + + streamReceiveWindow = getIntNya("recv_window_conn") + connectionReceiveWindow = getIntNya("recv_window") + disableMtuDiscovery = getBool("disable_mtu_discovery") + } +} + +fun HysteriaBean.buildHysteriaConfig(port: Int, cacheFile: (() -> File)?): String { + return JSONObject().apply { + put("server", if (isMultiPort()) serverAddress else wrapUri()) + when (protocol) { + HysteriaBean.PROTOCOL_FAKETCP -> { + put("protocol", "faketcp") + } + HysteriaBean.PROTOCOL_WECHAT_VIDEO -> { + put("protocol", "wechat-video") + } + } + put("up_mbps", uploadMbps) + put("down_mbps", downloadMbps) + put( + "socks5", JSONObject( + mapOf( + "listen" to "$LOCALHOST:$port", + ) + ) + ) + put("retry", 5) + put("obfs", obfuscation) + when (authPayloadType) { + HysteriaBean.TYPE_BASE64 -> put("auth", authPayload) + HysteriaBean.TYPE_STRING -> put("auth_str", authPayload) + } + if (sni.isBlank() && finalAddress == LOCALHOST && !serverAddress.isIpAddress()) { + sni = serverAddress + } + if (sni.isNotBlank()) { + put("server_name", sni) + } + if (alpn.isNotBlank()) put("alpn", alpn) + if (caText.isNotBlank() && cacheFile != null) { + val caFile = cacheFile() + caFile.writeText(caText) + put("ca", caFile.absolutePath) + } + + if (allowInsecure) put("insecure", true) + if (streamReceiveWindow > 0) put("recv_window_conn", streamReceiveWindow) + if (connectionReceiveWindow > 0) put("recv_window", connectionReceiveWindow) + if (disableMtuDiscovery) put("disable_mtu_discovery", true) + + // hy 1.2.0 (不兼容) + put("resolver", "udp://127.0.0.1:" + DataStore.localDNSPort) + + put("hop_interval", hopInterval) + }.toStringPretty() +} + +fun HysteriaBean.isMultiPort(): Boolean { + if (!serverAddress.contains(":")) return false + val p = serverAddress.substringAfterLast(":") + if (p.contains("-") || p.contains(",")) return true + return false +} + +fun HysteriaBean.canUseSingBox(): Boolean { + if (isMultiPort() || protocol != HysteriaBean.PROTOCOL_UDP) return false + return true +} + +fun buildSingBoxOutboundHysteriaBean(bean: HysteriaBean): SingBoxOptions.Outbound_HysteriaOptions { + // No multi-port + return SingBoxOptions.Outbound_HysteriaOptions().apply { + type = "hysteria" + server = bean.serverAddress + server_port = bean.serverPort + up_mbps = bean.uploadMbps + down_mbps = bean.downloadMbps + obfs = bean.obfuscation + disable_mtu_discovery = bean.disableMtuDiscovery + when (bean.authPayloadType) { + HysteriaBean.TYPE_BASE64 -> auth = Util.b64Decode(bean.authPayload).toList() + HysteriaBean.TYPE_STRING -> auth_str = bean.authPayload + } + if (bean.streamReceiveWindow > 0) { + recv_window_conn = bean.streamReceiveWindow.toLong() + } + if (bean.connectionReceiveWindow > 0) { + recv_window_conn = bean.connectionReceiveWindow.toLong() + } + tls = SingBoxOptions.OutboundTLSOptions().apply { + if (bean.sni.isNotBlank()) { + server_name = bean.sni + } + if (bean.alpn.isNotBlank()) { + alpn = bean.alpn.split("\n") + } + if (bean.caText.isNotBlank()) { + certificate = bean.caText + } + insecure = bean.allowInsecure + enabled = true + } + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/internal/ChainBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/internal/ChainBean.java new file mode 100644 index 0000000..1388a11 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/internal/ChainBean.java @@ -0,0 +1,80 @@ +package io.nekohasekai.sagernet.fmt.internal; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +import io.nekohasekai.sagernet.fmt.KryoConverters; +import moe.matsuri.nb4a.utils.JavaUtil; + +public class ChainBean extends InternalBean { + + public List proxies; + + @Override + public String displayName() { + if (JavaUtil.isNotBlank(name)) { + return name; + } else { + return "Chain " + Math.abs(hashCode()); + } + } + + @Override + public void initializeDefaultValues() { + super.initializeDefaultValues(); + if (name == null) name = ""; + + if (proxies == null) { + proxies = new ArrayList<>(); + } + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(1); + output.writeInt(proxies.size()); + for (Long proxy : proxies) { + output.writeLong(proxy); + } + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + if (version < 1) { + input.readString(); + input.readInt(); + } + int length = input.readInt(); + proxies = new ArrayList<>(); + for (int i = 0; i < length; i++) { + proxies.add(input.readLong()); + } + } + + @NotNull + @Override + public ChainBean clone() { + return KryoConverters.deserialize(new ChainBean(), KryoConverters.serialize(this)); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public ChainBean newInstance() { + return new ChainBean(); + } + + @Override + public ChainBean[] newArray(int size) { + return new ChainBean[size]; + } + }; +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/internal/InternalBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/internal/InternalBean.java new file mode 100644 index 0000000..2627b3a --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/internal/InternalBean.java @@ -0,0 +1,26 @@ +package io.nekohasekai.sagernet.fmt.internal; + +import io.nekohasekai.sagernet.fmt.AbstractBean; + +public abstract class InternalBean extends AbstractBean { + + @Override + public String displayAddress() { + return ""; + } + + @Override + public boolean canICMPing() { + return false; + } + + @Override + public boolean canTCPing() { + return false; + } + + @Override + public boolean canMapping() { + return false; + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/naive/NaiveBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/naive/NaiveBean.java new file mode 100644 index 0000000..841ae59 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/naive/NaiveBean.java @@ -0,0 +1,88 @@ +package io.nekohasekai.sagernet.fmt.naive; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; + +import io.nekohasekai.sagernet.fmt.AbstractBean; +import io.nekohasekai.sagernet.fmt.KryoConverters; + +public class NaiveBean extends AbstractBean { + + /** + * Available proto: https, quic. + */ + public String proto; + public String username; + public String password; + public String extraHeaders; + public String sni; + public String certificates; + public Integer insecureConcurrency; + + @Override + public void initializeDefaultValues() { + if (serverPort == null) serverPort = 443; + super.initializeDefaultValues(); + if (proto == null) proto = "https"; + if (username == null) username = ""; + if (password == null) password = ""; + if (extraHeaders == null) extraHeaders = ""; + if (certificates == null) certificates = ""; + if (sni == null) sni = ""; + if (insecureConcurrency == null) insecureConcurrency = 0; + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(2); + super.serialize(output); + output.writeString(proto); + output.writeString(username); + output.writeString(password); + // note: sequence is different from SagerNet,,, + output.writeString(extraHeaders); + output.writeString(certificates); + output.writeString(sni); + output.writeInt(insecureConcurrency); + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + super.deserialize(input); + proto = input.readString(); + username = input.readString(); + password = input.readString(); + extraHeaders = input.readString(); + if (version >= 2) { + certificates = input.readString(); + sni = input.readString(); + } + if (version >= 1) { + insecureConcurrency = input.readInt(); + } + } + + @NotNull + @Override + public NaiveBean clone() { + return KryoConverters.deserialize(new NaiveBean(), KryoConverters.serialize(this)); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public NaiveBean newInstance() { + return new NaiveBean(); + } + + @Override + public NaiveBean[] newArray(int size) { + return new NaiveBean[size]; + } + }; +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/naive/NaiveFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/naive/NaiveFmt.kt new file mode 100644 index 0000000..5eb7a62 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/naive/NaiveFmt.kt @@ -0,0 +1,90 @@ +package io.nekohasekai.sagernet.fmt.naive + +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.fmt.LOCALHOST +import io.nekohasekai.sagernet.ktx.* +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import org.json.JSONObject + +fun parseNaive(link: String): NaiveBean { + val proto = link.substringAfter("+").substringBefore(":") + val url = ("https://" + link.substringAfter("://")).toHttpUrlOrNull() + ?: error("Invalid naive link: $link") + return NaiveBean().also { + it.proto = proto + }.apply { + serverAddress = url.host + serverPort = url.port + username = url.username + password = url.password + sni = url.queryParameter("sni") + certificates = url.queryParameter("cert") + extraHeaders = url.queryParameter("extra-headers")?.unUrlSafe()?.replace("\r\n", "\n") + insecureConcurrency = url.queryParameter("insecure-concurrency")?.toIntOrNull() + name = url.fragment + initializeDefaultValues() + } +} + +fun NaiveBean.toUri(proxyOnly: Boolean = false): String { + val builder = linkBuilder().host(finalAddress).port(finalPort) + if (username.isNotBlank()) { + builder.username(username) + if (password.isNotBlank()) { + builder.password(password) + } + } + if (!proxyOnly) { + if (sni.isNotBlank()) { + builder.addQueryParameter("sni", sni) + } + if (certificates.isNotBlank()) { + builder.addQueryParameter("cert", certificates) + } + if (extraHeaders.isNotBlank()) { + builder.addQueryParameter("extra-headers", extraHeaders) + } + if (name.isNotBlank()) { + builder.encodedFragment(name.urlSafe()) + } + if (insecureConcurrency > 0) { + builder.addQueryParameter("insecure-concurrency", "$insecureConcurrency") + } + } + return builder.toLink(if (proxyOnly) proto else "naive+$proto", false) +} + +fun NaiveBean.buildNaiveConfig(port: Int): String { + return JSONObject().apply { + // process ipv6 + finalAddress = finalAddress.wrapIPV6Host() + serverAddress = serverAddress.wrapIPV6Host() + + // process sni + if (sni.isNotBlank()) { + put("host-resolver-rules", "MAP $sni $finalAddress") + finalAddress = sni + } else { + if (serverAddress.isIpAddress()) { + // for naive, using IP as SNI name hardly happens + // and host-resolver-rules cannot resolve the SNI problem + // so do nothing + } else { + put("host-resolver-rules", "MAP $serverAddress $finalAddress") + finalAddress = serverAddress + } + } + + put("listen", "socks://$LOCALHOST:$port") + put("proxy", toUri(true)) + if (extraHeaders.isNotBlank()) { + put("extra-headers", extraHeaders.split("\n").joinToString("\r\n")) + } + if (DataStore.enableLog) { + put("log", "") + } + if (insecureConcurrency > 0) { + put("insecure-concurrency", insecureConcurrency) + } + }.toStringPretty() +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksBean.java new file mode 100644 index 0000000..10c394f --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksBean.java @@ -0,0 +1,71 @@ +package io.nekohasekai.sagernet.fmt.shadowsocks; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; + +import io.nekohasekai.sagernet.fmt.AbstractBean; +import io.nekohasekai.sagernet.fmt.KryoConverters; +import moe.matsuri.nb4a.utils.JavaUtil; + +public class ShadowsocksBean extends AbstractBean { + + public String method; + public String password; + public String plugin; + + public Boolean sUoT; + + @Override + public void initializeDefaultValues() { + super.initializeDefaultValues(); + + if (JavaUtil.isNullOrBlank(method)) method = "aes-256-gcm"; + if (method == null) method = ""; + if (password == null) password = ""; + if (plugin == null) plugin = ""; + if (sUoT == null) sUoT = false; + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(2); + super.serialize(output); + output.writeString(method); + output.writeString(password); + output.writeString(plugin); + output.writeBoolean(sUoT); + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + super.deserialize(input); + method = input.readString(); + password = input.readString(); + plugin = input.readString(); + sUoT = input.readBoolean(); + } + + @NotNull + @Override + public ShadowsocksBean clone() { + return KryoConverters.deserialize(new ShadowsocksBean(), KryoConverters.serialize(this)); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public ShadowsocksBean newInstance() { + return new ShadowsocksBean(); + } + + @Override + public ShadowsocksBean[] newArray(int size) { + return new ShadowsocksBean[size]; + } + }; +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksFmt.kt new file mode 100644 index 0000000..7d75924 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksFmt.kt @@ -0,0 +1,115 @@ +package io.nekohasekai.sagernet.fmt.shadowsocks + +import moe.matsuri.nb4a.SingBoxOptions +import io.nekohasekai.sagernet.ktx.* +import moe.matsuri.nb4a.utils.Util +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import org.json.JSONObject + +fun parseShadowsocks(url: String): ShadowsocksBean { + + if (url.substringBefore("#").contains("@")) { + var link = url.replace("ss://", "https://").toHttpUrlOrNull() ?: error( + "invalid ss-android link $url" + ) + + if (link.username.isBlank()) { // fix justmysocks's shit link + link = (("https://" + url.substringAfter("ss://") + .substringBefore("#") + .decodeBase64UrlSafe()).toHttpUrlOrNull() + ?: error("invalid jms link $url") + ).newBuilder().fragment(url.substringAfter("#")).build() + } + + // ss-android style + + if (link.password.isNotBlank()) { + return ShadowsocksBean().apply { + serverAddress = link.host + serverPort = link.port + method = link.username + password = link.password + plugin = link.queryParameter("plugin") ?: "" + name = link.fragment + } + } + + val methodAndPswd = link.username.decodeBase64UrlSafe() + + return ShadowsocksBean().apply { + serverAddress = link.host + serverPort = link.port + method = methodAndPswd.substringBefore(":") + password = methodAndPswd.substringAfter(":") + plugin = link.queryParameter("plugin") ?: "" + name = link.fragment + } + } else { + // v2rayN style + var v2Url = url + + if (v2Url.contains("#")) v2Url = v2Url.substringBefore("#") + + val link = ("https://" + v2Url.substringAfter("ss://") + .decodeBase64UrlSafe()).toHttpUrlOrNull() ?: error("invalid v2rayN link $url") + + return ShadowsocksBean().apply { + serverAddress = link.host + serverPort = link.port + method = link.username + password = link.password + plugin = "" + val remarks = url.substringAfter("#").unUrlSafe() + if (remarks.isNotBlank()) name = remarks + } + } + +} + +fun ShadowsocksBean.toUri(): String { + + val builder = linkBuilder().username(Util.b64EncodeUrlSafe("$method:$password")) + .host(serverAddress) + .port(serverPort) + + if (plugin.isNotBlank()) { + builder.addQueryParameter("plugin", plugin) + } + + if (name.isNotBlank()) { + builder.encodedFragment(name.urlSafe()) + } + + return builder.toLink("ss").replace("$serverPort/", "$serverPort") + +} + +fun JSONObject.parseShadowsocks(): ShadowsocksBean { + return ShadowsocksBean().apply { + serverAddress = getStr("server") + serverPort = getIntNya("server_port") + password = getStr("password") + method = getStr("method") + name = optString("remarks", "") + + val pId = getStr("plugin") + if (!pId.isNullOrBlank()) { + plugin = pId + ";" + optString("plugin_opts", "") + } + } +} + +fun buildSingBoxOutboundShadowsocksBean(bean: ShadowsocksBean): SingBoxOptions.Outbound_ShadowsocksOptions { + return SingBoxOptions.Outbound_ShadowsocksOptions().apply { + type = "shadowsocks" + server = bean.serverAddress + server_port = bean.serverPort + password = bean.password + method = bean.method + udp_over_tcp = bean.sUoT + if (bean.plugin.isNotBlank()) { + plugin = bean.plugin.substringBefore(";") + plugin_opts = bean.plugin.substringAfter(";") + } + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSBean.java new file mode 100644 index 0000000..fb07ac1 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSBean.java @@ -0,0 +1,110 @@ +package io.nekohasekai.sagernet.fmt.socks; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; + +import io.nekohasekai.sagernet.fmt.AbstractBean; +import io.nekohasekai.sagernet.fmt.KryoConverters; + +public class SOCKSBean extends AbstractBean { + + public Integer protocol; + + public int protocolVersion() { + switch (protocol) { + case 0: + case 1: + return 4; + default: + return 5; + } + } + + public String protocolName() { + switch (protocol) { + case 0: + return "SOCKS4"; + case 1: + return "SOCKS4A"; + default: + return "SOCKS5"; + } + } + + public String protocolVersionName() { + switch (protocol) { + case 0: + return "4"; + case 1: + return "4a"; + default: + return "5"; + } + } + + public String username; + public String password; + + public static final int PROTOCOL_SOCKS4 = 0; + public static final int PROTOCOL_SOCKS4A = 1; + public static final int PROTOCOL_SOCKS5 = 2; + + @Override + public String network() { + if (protocol < PROTOCOL_SOCKS5) return "tcp"; + return super.network(); + } + + @Override + public void initializeDefaultValues() { + super.initializeDefaultValues(); + + if (protocol == null) protocol = PROTOCOL_SOCKS5; + if (username == null) username = ""; + if (password == null) password = ""; + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(1); + super.serialize(output); + output.writeInt(protocol); + output.writeString(username); + output.writeString(password); + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + super.deserialize(input); + if (version >= 1) { + protocol = input.readInt(); + } + username = input.readString(); + password = input.readString(); + } + + @NotNull + @Override + public SOCKSBean clone() { + return KryoConverters.deserialize(new SOCKSBean(), KryoConverters.serialize(this)); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public SOCKSBean newInstance() { + return new SOCKSBean(); + } + + @Override + public SOCKSBean[] newArray(int size) { + return new SOCKSBean[size]; + } + }; + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSFmt.kt new file mode 100644 index 0000000..8f8a1c7 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSFmt.kt @@ -0,0 +1,81 @@ +package io.nekohasekai.sagernet.fmt.socks + +import moe.matsuri.nb4a.SingBoxOptions +import io.nekohasekai.sagernet.ktx.* +import moe.matsuri.nb4a.utils.NGUtil +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull + +fun parseSOCKS(link: String): SOCKSBean { + if (!link.substringAfter("://").contains(":")) { + // v2rayN shit format + var url = link.substringAfter("://") + if (url.contains("#")) { + url = url.substringBeforeLast("#") + } + url = url.decodeBase64UrlSafe() + val httpUrl = "http://$url".toHttpUrlOrNull() ?: error("Invalid v2rayN link content: $url") + return SOCKSBean().apply { + serverAddress = httpUrl.host + serverPort = httpUrl.port + username = httpUrl.username.takeIf { it != "null" } ?: "" + password = httpUrl.password.takeIf { it != "null" } ?: "" + if (link.contains("#")) { + name = link.substringAfter("#").unUrlSafe() + } + } + } else { + val url = ("http://" + link.substringAfter("://")).toHttpUrlOrNull() + ?: error("Not supported: $link") + + return SOCKSBean().apply { + protocol = when { + link.startsWith("socks4://") -> SOCKSBean.PROTOCOL_SOCKS4 + link.startsWith("socks4a://") -> SOCKSBean.PROTOCOL_SOCKS4A + else -> SOCKSBean.PROTOCOL_SOCKS5 + } + serverAddress = url.host + serverPort = url.port + username = url.username + password = url.password + name = url.fragment + } + } +} + +fun SOCKSBean.toUri(): String { + + val builder = HttpUrl.Builder().scheme("http").host(serverAddress).port(serverPort) + if (!username.isNullOrBlank()) builder.username(username) + if (!password.isNullOrBlank()) builder.password(password) + if (!name.isNullOrBlank()) builder.encodedFragment(name.urlSafe()) + return builder.toLink("socks${protocolVersion()}") + +} + +fun SOCKSBean.toV2rayN(): String { + + var link = "" + if (username.isNotBlank()) { + link += username.urlSafe() + ":" + password.urlSafe() + "@" + } + link += "$serverAddress:$serverPort" + link = "socks://" + NGUtil.encode(link) + if (name.isNotBlank()) { + link += "#" + name.urlSafe() + } + + return link + +} + +fun buildSingBoxOutboundSocksBean(bean: SOCKSBean): SingBoxOptions.Outbound_SocksOptions { + return SingBoxOptions.Outbound_SocksOptions().apply { + type = "socks" + server = bean.serverAddress + server_port = bean.serverPort + username = bean.username + password = bean.password + version = bean.protocolVersionName() + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/ssh/SSHBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/ssh/SSHBean.java new file mode 100644 index 0000000..cc13e1a --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/ssh/SSHBean.java @@ -0,0 +1,98 @@ +package io.nekohasekai.sagernet.fmt.ssh; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; + +import io.nekohasekai.sagernet.fmt.AbstractBean; +import io.nekohasekai.sagernet.fmt.KryoConverters; + +public class SSHBean extends AbstractBean { + + public static final int AUTH_TYPE_NONE = 0; + public static final int AUTH_TYPE_PASSWORD = 1; + public static final int AUTH_TYPE_PRIVATE_KEY = 2; + + public String username; + public Integer authType; + public String password; + public String privateKey; + public String privateKeyPassphrase; + public String publicKey; + + @Override + public void initializeDefaultValues() { + if (serverPort == null) serverPort = 22; + + super.initializeDefaultValues(); + + if (username == null) username = "root"; + if (authType == null) authType = AUTH_TYPE_PASSWORD; + if (password == null) password = ""; + if (privateKey == null) privateKey = ""; + if (privateKeyPassphrase == null) privateKeyPassphrase = ""; + if (publicKey == null) publicKey = ""; + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(0); + super.serialize(output); + output.writeString(username); + output.writeInt(authType); + switch (authType) { + case AUTH_TYPE_NONE: + break; + case AUTH_TYPE_PASSWORD: + output.writeString(password); + break; + case AUTH_TYPE_PRIVATE_KEY: + output.writeString(privateKey); + output.writeString(privateKeyPassphrase); + break; + } + output.writeString(publicKey); + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + super.deserialize(input); + username = input.readString(); + authType = input.readInt(); + switch (authType) { + case AUTH_TYPE_NONE: + break; + case AUTH_TYPE_PASSWORD: + password = input.readString(); + break; + case AUTH_TYPE_PRIVATE_KEY: + privateKey = input.readString(); + privateKeyPassphrase = input.readString(); + break; + } + publicKey = input.readString(); + } + + @NotNull + @Override + public SSHBean clone() { + return KryoConverters.deserialize(new SSHBean(), KryoConverters.serialize(this)); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public SSHBean newInstance() { + return new SSHBean(); + } + + @Override + public SSHBean[] newArray(int size) { + return new SSHBean[size]; + } + }; +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/ssh/SSHFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/ssh/SSHFmt.kt new file mode 100644 index 0000000..4f76998 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/ssh/SSHFmt.kt @@ -0,0 +1,22 @@ +package io.nekohasekai.sagernet.fmt.ssh + +import moe.matsuri.nb4a.SingBoxOptions + +fun buildSingBoxOutboundSSHBean(bean: SSHBean): SingBoxOptions.Outbound_SSHOptions { + return SingBoxOptions.Outbound_SSHOptions().apply { + type = "ssh" + server = bean.serverAddress + server_port = bean.serverPort + user = bean.username + host_key = bean.privateKey.split("\n") + when (bean.authType) { + SSHBean.AUTH_TYPE_PRIVATE_KEY -> { + private_key = bean.privateKey + private_key_passphrase = bean.privateKeyPassphrase + } + else -> { + password = bean.password + } + } + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan/TrojanBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan/TrojanBean.java new file mode 100644 index 0000000..d283dd6 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan/TrojanBean.java @@ -0,0 +1,69 @@ +package io.nekohasekai.sagernet.fmt.trojan; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; + +import io.nekohasekai.sagernet.fmt.AbstractBean; +import io.nekohasekai.sagernet.fmt.KryoConverters; +import io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean; + +public class TrojanBean extends StandardV2RayBean { + + public String password; + + @Override + public void initializeDefaultValues() { + super.initializeDefaultValues(); + if (security == null || security.isEmpty()) security = "tls"; + if (password == null) password = ""; + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(2); + super.serialize(output); + output.writeString(password); + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + if (version >= 2) { + super.deserialize(input); // StandardV2RayBean + password = input.readString(); + } else { + // From AbstractBean + serverAddress = input.readString(); + serverPort = input.readInt(); + // From TrojanBean + password = input.readString(); + security = input.readString(); + sni = input.readString(); + alpn = input.readString(); + if (version == 1) allowInsecure = input.readBoolean(); + } + } + + @NotNull + @Override + public TrojanBean clone() { + return KryoConverters.deserialize(new TrojanBean(), KryoConverters.serialize(this)); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public TrojanBean newInstance() { + return new TrojanBean(); + } + + @Override + public TrojanBean[] newArray(int size) { + return new TrojanBean[size]; + } + }; +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan/TrojanFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan/TrojanFmt.kt new file mode 100644 index 0000000..0cb475e --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan/TrojanFmt.kt @@ -0,0 +1,23 @@ +package io.nekohasekai.sagernet.fmt.trojan + +import io.nekohasekai.sagernet.fmt.v2ray.parseDuckSoft +import io.nekohasekai.sagernet.fmt.v2ray.toUri +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull + +fun parseTrojan(server: String): TrojanBean { + + val link = server.replace("trojan://", "https://").toHttpUrlOrNull() + ?: error("invalid trojan link $server") + + return TrojanBean().apply { + parseDuckSoft(link) + link.queryParameter("allowInsecure") + ?.apply { if (this == "1" || this == "true") allowInsecure = true } + link.queryParameter("peer")?.apply { if (this.isNotBlank()) sni = this } + } + +} + +fun TrojanBean.toUri(): String { + return toUri(true).replace("vmess://", "trojan://") +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoBean.java new file mode 100644 index 0000000..f02f363 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoBean.java @@ -0,0 +1,176 @@ +package io.nekohasekai.sagernet.fmt.trojan_go; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; + +import io.nekohasekai.sagernet.fmt.AbstractBean; +import io.nekohasekai.sagernet.fmt.KryoConverters; +import moe.matsuri.nb4a.utils.JavaUtil; + +public class TrojanGoBean extends AbstractBean { + + /** + * Trojan 的密码。 + * 不可省略,不能为空字符串,不建议含有非 ASCII 可打印字符。 + * 必须使用 encodeURIComponent 编码。 + */ + public String password; + + /** + * 自定义 TLS 的 SNI。 + * 省略时默认与 trojan-host 同值。不得为空字符串。 + *

+ * 必须使用 encodeURIComponent 编码。 + */ + public String sni; + + /** + * 传输类型。 + * 省略时默认为 original,但不可为空字符串。 + * 目前可选值只有 original 和 ws,未来可能会有 h2、h2+ws 等取值。 + *

+ * 当取值为 original 时,使用原始 Trojan 传输方式,无法方便通过 CDN。 + * 当取值为 ws 时,使用 wss 作为传输层。 + */ + public String type; + + /** + * 自定义 HTTP Host 头。 + * 可以省略,省略时值同 trojan-host。 + * 可以为空字符串,但可能带来非预期情形。 + *

+ * 警告:若你的端口非标准端口(不是 80 / 443),RFC 标准规定 Host 应在主机名后附上端口号,例如 example.com:44333。至于是否遵守,请自行斟酌。 + *

+ * 必须使用 encodeURIComponent 编码。 + */ + public String host; + + /** + * 当传输类型 type 取 ws、h2、h2+ws 时,此项有效。 + * 不可省略,不可为空。 + * 必须以 / 开头。 + * 可以使用 URL 中的 & # ? 等字符,但应当是合法的 URL 路径。 + *

+ * 必须使用 encodeURIComponent 编码。 + */ + public String path; + + /** + * 用于保证 Trojan 流量密码学安全的加密层。 + * 可省略,默认为 none,即不使用加密。 + * 不可以为空字符串。 + *

+ * 必须使用 encodeURIComponent 编码。 + *

+ * 使用 Shadowsocks 算法进行流量加密时,其格式为: + *

+ * ss;method:password + *

+ * 其中 ss 是固定内容,method 是加密方法,必须为下列之一: + *

+ * aes-128-gcm + * aes-256-gcm + * chacha20-ietf-poly1305 + */ + public String encryption; + + /** + * 额外的插件选项。本字段保留。 + * 可省略,但不可以为空字符串。 + */ + // not used in NB4A + public String plugin; + + // --- + + public Boolean allowInsecure; + + @Override + public void initializeDefaultValues() { + super.initializeDefaultValues(); + + if (password == null) password = ""; + if (sni == null) sni = ""; + if (JavaUtil.isNullOrBlank(type)) type = "original"; + if (host == null) host = ""; + if (path == null) path = ""; + if (JavaUtil.isNullOrBlank(encryption)) encryption = "none"; + if (plugin == null) plugin = ""; + if (allowInsecure == null) allowInsecure = false; + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(1); + super.serialize(output); + output.writeString(password); + output.writeString(sni); + output.writeString(type); + //noinspection SwitchStatementWithTooFewBranches + switch (type) { + case "ws": { + output.writeString(host); + output.writeString(path); + break; + } + } + output.writeString(encryption); + output.writeString(plugin); + output.writeBoolean(allowInsecure); + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + super.deserialize(input); + + password = input.readString(); + sni = input.readString(); + type = input.readString(); + //noinspection SwitchStatementWithTooFewBranches + switch (type) { + case "ws": { + host = input.readString(); + path = input.readString(); + break; + } + } + encryption = input.readString(); + plugin = input.readString(); + if (version >= 1) { + allowInsecure = input.readBoolean(); + } + } + + @Override + public void applyFeatureSettings(AbstractBean other) { + if (!(other instanceof TrojanGoBean)) return; + TrojanGoBean bean = ((TrojanGoBean) other); + if (allowInsecure) { + bean.allowInsecure = true; + } + } + + @NotNull + @Override + public TrojanGoBean clone() { + return KryoConverters.deserialize(new TrojanGoBean(), KryoConverters.serialize(this)); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public TrojanGoBean newInstance() { + return new TrojanGoBean(); + } + + @Override + public TrojanGoBean[] newArray(int size) { + return new TrojanGoBean[size]; + } + }; +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoFmt.kt new file mode 100644 index 0000000..195ba84 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoFmt.kt @@ -0,0 +1,162 @@ +package io.nekohasekai.sagernet.fmt.trojan_go + +import io.nekohasekai.sagernet.IPv6Mode +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.fmt.LOCALHOST +import io.nekohasekai.sagernet.ktx.* +import moe.matsuri.nb4a.Protocols +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import org.json.JSONArray +import org.json.JSONObject + +fun parseTrojanGo(server: String): TrojanGoBean { + val link = server.replace("trojan-go://", "https://").toHttpUrlOrNull() ?: error( + "invalid trojan-link link $server" + ) + return TrojanGoBean().apply { + serverAddress = link.host + serverPort = link.port + password = link.username + link.queryParameter("sni")?.let { + sni = it + } + link.queryParameter("type")?.let { lType -> + type = lType + + when (type) { + "ws" -> { + link.queryParameter("host")?.let { + host = it + } + link.queryParameter("path")?.let { + path = it + } + } + else -> { + } + } + } + link.queryParameter("encryption")?.let { + encryption = it + } + link.queryParameter("plugin")?.let { + plugin = it + } + link.fragment.takeIf { !it.isNullOrBlank() }?.let { + name = it + } + } +} + +fun TrojanGoBean.toUri(): String { + val builder = linkBuilder().username(password).host(serverAddress).port(serverPort) + if (sni.isNotBlank()) { + builder.addQueryParameter("sni", sni) + } + if (type.isNotBlank() && type != "original") { + builder.addQueryParameter("type", type) + + when (type) { + "ws" -> { + if (host.isNotBlank()) { + builder.addQueryParameter("host", host) + } + if (path.isNotBlank()) { + builder.addQueryParameter("path", path) + } + } + } + } + if (type.isNotBlank() && type != "none") { + builder.addQueryParameter("encryption", encryption) + } + if (plugin.isNotBlank()) { + builder.addQueryParameter("plugin", plugin) + } + + if (name.isNotBlank()) { + builder.encodedFragment(name.urlSafe()) + } + + return builder.toLink("trojan-go") +} + +fun TrojanGoBean.buildTrojanGoConfig(port: Int): String { + return JSONObject().apply { + put("run_type", "client") + put("local_addr", LOCALHOST) + put("local_port", port) + put("remote_addr", finalAddress) + put("remote_port", finalPort) + put("password", JSONArray().apply { + put(password) + }) + put("log_level", if (DataStore.enableLog) 0 else 2) + if (Protocols.shouldEnableMux("trojan-go")) put("mux", JSONObject().apply { + put("enabled", true) + put("concurrency", DataStore.muxConcurrency) + }) + put("tcp", JSONObject().apply { + put("prefer_ipv4", DataStore.ipv6Mode <= IPv6Mode.ENABLE) + }) + + when (type) { + "original" -> { + } + "ws" -> put("websocket", JSONObject().apply { + put("enabled", true) + put("host", host) + put("path", path) + }) + } + + if (sni.isBlank() && finalAddress == LOCALHOST && !serverAddress.isIpAddress()) { + sni = serverAddress + } + + put("ssl", JSONObject().apply { + if (sni.isNotBlank()) put("sni", sni) + if (allowInsecure) put("verify", false) + }) + + when { + encryption == "none" -> { + } + encryption.startsWith("ss;") -> put("shadowsocks", JSONObject().apply { + put("enabled", true) + put("method", encryption.substringAfter(";").substringBefore(":")) + put("password", encryption.substringAfter(":")) + }) + } + }.toStringPretty() +} + +fun JSONObject.parseTrojanGo(): TrojanGoBean { + return TrojanGoBean().applyDefaultValues().apply { + serverAddress = optString("remote_addr", serverAddress) + serverPort = optInt("remote_port", serverPort) + when (val pass = get("password")) { + is String -> { + password = pass + } + is List<*> -> { + password = pass[0] as String + } + } + optJSONArray("ssl")?.apply { + sni = optString("sni", sni) + } + optJSONArray("websocket")?.apply { + if (optBoolean("enabled", false)) { + type = "ws" + host = optString("host", host) + path = optString("path", path) + } + } + optJSONArray("shadowsocks")?.apply { + if (optBoolean("enabled", false)) { + encryption = "ss;${optString("method", "")}:${optString("password", "")}" + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicBean.java new file mode 100644 index 0000000..a35ec85 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicBean.java @@ -0,0 +1,97 @@ +package io.nekohasekai.sagernet.fmt.tuic; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; + +import io.nekohasekai.sagernet.fmt.AbstractBean; +import io.nekohasekai.sagernet.fmt.KryoConverters; + +public class TuicBean extends AbstractBean { + + public String token; + public String caText; + public String udpRelayMode; + public String congestionController; + public String alpn; + public Boolean disableSNI; + public Boolean reduceRTT; + public Integer mtu; + public String sni; + public Boolean fastConnect; + public Boolean allowInsecure; + + @Override + public void initializeDefaultValues() { + super.initializeDefaultValues(); + if (token == null) token = ""; + if (caText == null) caText = ""; + if (udpRelayMode == null) udpRelayMode = "native"; + if (congestionController == null) congestionController = "cubic"; + if (alpn == null) alpn = ""; + if (disableSNI == null) disableSNI = false; + if (reduceRTT == null) reduceRTT = false; + if (mtu == null) mtu = 1400; + if (sni == null) sni = ""; + if (fastConnect == null) fastConnect = false; + if (allowInsecure == null) allowInsecure = false; + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(1); + super.serialize(output); + output.writeString(token); + output.writeString(caText); + output.writeString(udpRelayMode); + output.writeString(congestionController); + output.writeString(alpn); + output.writeBoolean(disableSNI); + output.writeBoolean(reduceRTT); + output.writeInt(mtu); + output.writeString(sni); + output.writeBoolean(fastConnect); + output.writeBoolean(allowInsecure); + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + super.deserialize(input); + token = input.readString(); + caText = input.readString(); + udpRelayMode = input.readString(); + congestionController = input.readString(); + alpn = input.readString(); + disableSNI = input.readBoolean(); + reduceRTT = input.readBoolean(); + mtu = input.readInt(); + sni = input.readString(); + if (version >= 1) { + fastConnect = input.readBoolean(); + allowInsecure = input.readBoolean(); + } + } + + @NotNull + @Override + public TuicBean clone() { + return KryoConverters.deserialize(new TuicBean(), KryoConverters.serialize(this)); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public TuicBean newInstance() { + return new TuicBean(); + } + + @Override + public TuicBean[] newArray(int size) { + return new TuicBean[size]; + } + }; +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicFmt.kt new file mode 100644 index 0000000..a74e1b2 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicFmt.kt @@ -0,0 +1,64 @@ +package io.nekohasekai.sagernet.fmt.tuic + +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.fmt.LOCALHOST +import io.nekohasekai.sagernet.ktx.isIpAddress +import io.nekohasekai.sagernet.ktx.toStringPretty +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import moe.matsuri.nb4a.plugin.Plugins +import org.json.JSONArray +import org.json.JSONObject +import java.io.File +import java.net.InetAddress + +fun TuicBean.buildTuicConfig(port: Int, cacheFile: (() -> File)?): String { + if (Plugins.isUsingMatsuriExe("tuic-plugin")) { + if (!serverAddress.isIpAddress()) { + runBlocking { + finalAddress = withContext(Dispatchers.IO) { + InetAddress.getAllByName(serverAddress) + }?.firstOrNull()?.hostAddress ?: "127.0.0.1" + // TODO network on main thread, tuic don't support "sni" + } + } + } + return JSONObject().apply { + put("relay", JSONObject().apply { + if (sni.isNotBlank()) { + put("server", sni) + put("ip", finalAddress) + } else if (serverAddress.isIpAddress()) { + put("server", finalAddress) + } else { + put("server", serverAddress) + put("ip", finalAddress) + } + put("port", finalPort) + put("token", token) + + if (caText.isNotBlank() && cacheFile != null) { + val caFile = cacheFile() + caFile.writeText(caText) + put("certificates", JSONArray(listOf(caFile.absolutePath))) + } + + put("udp_relay_mode", udpRelayMode) + if (alpn.isNotBlank()) { + put("alpn", JSONArray(alpn.split("\n"))) + } + put("congestion_controller", congestionController) + put("disable_sni", disableSNI) + put("reduce_rtt", reduceRTT) + put("max_udp_relay_packet_size", mtu) + if (fastConnect) put("fast_connect", true) + if (allowInsecure) put("insecure", true) + }) + put("local", JSONObject().apply { + put("ip", LOCALHOST) + put("port", port) + }) + put("log_level", if (DataStore.enableLog) "debug" else "info") + }.toStringPretty() +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java new file mode 100644 index 0000000..55ff51a --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java @@ -0,0 +1,194 @@ +package io.nekohasekai.sagernet.fmt.v2ray; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import io.nekohasekai.sagernet.fmt.AbstractBean; +import moe.matsuri.nb4a.utils.JavaUtil; + +public abstract class StandardV2RayBean extends AbstractBean { + + public String uuid; + public String encryption; // or VLESS flow + + //////// End of VMess & VLESS //////// + + // "V2Ray Transport" tcp/http/ws/quic/grpc + public String type; + + public String host; + + public String path; + + // --------------------------------------- tls? + + public String security; + + public String sni; + + public String alpn; + + public String utlsFingerprint; + + public Boolean allowInsecure; + + // --------------------------------------- reality + + + public String realityPubKey; + + public String realityShortId; + + + // --------------------------------------- // + + public Integer wsMaxEarlyData; + public String earlyDataHeaderName; + + public String certificates; + + // --------------------------------------- // + + public Integer packetEncoding; // 1:packet 2:xudp + + @Override + public void initializeDefaultValues() { + super.initializeDefaultValues(); + + if (JavaUtil.isNullOrBlank(uuid)) uuid = ""; + + if (JavaUtil.isNullOrBlank(type)) type = "tcp"; + else if ("h2".equals(type)) type = "http"; + + if (JavaUtil.isNullOrBlank(host)) host = ""; + if (JavaUtil.isNullOrBlank(path)) path = ""; + + if (JavaUtil.isNullOrBlank(security)) security = "none"; + if (JavaUtil.isNullOrBlank(sni)) sni = ""; + if (JavaUtil.isNullOrBlank(alpn)) alpn = ""; + + if (JavaUtil.isNullOrBlank(certificates)) certificates = ""; + if (JavaUtil.isNullOrBlank(earlyDataHeaderName)) earlyDataHeaderName = ""; + if (JavaUtil.isNullOrBlank(utlsFingerprint)) utlsFingerprint = ""; + + if (wsMaxEarlyData == null) wsMaxEarlyData = 0; + if (allowInsecure == null) allowInsecure = false; + if (packetEncoding == null) packetEncoding = 0; + + if (realityPubKey == null) realityPubKey = ""; + if (realityShortId == null) realityShortId = ""; + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(0); + super.serialize(output); + + output.writeString(uuid); + output.writeString(encryption); + if (this instanceof VMessBean) { + output.writeInt(((VMessBean) this).alterId); + } + + output.writeString(type); + switch (type) { + case "tcp": + case "quic": { + break; + } + case "ws": { + output.writeString(host); + output.writeString(path); + output.writeInt(wsMaxEarlyData); + output.writeString(earlyDataHeaderName); + break; + } + case "http": { + output.writeString(host); + output.writeString(path); + break; + } + case "grpc": { + output.writeString(path); + } + } + + output.writeString(security); + if ("tls".equals(security)) { + output.writeString(sni); + output.writeString(alpn); + output.writeString(certificates); + output.writeBoolean(allowInsecure); + output.writeString(utlsFingerprint); + output.writeString(realityPubKey); + output.writeString(realityShortId); + } + + output.writeInt(packetEncoding); + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + super.deserialize(input); + uuid = input.readString(); + encryption = input.readString(); + if (this instanceof VMessBean) { + ((VMessBean) this).alterId = input.readInt(); + } + + type = input.readString(); + switch (type) { + case "tcp": + case "quic": { + break; + } + case "ws": { + host = input.readString(); + path = input.readString(); + wsMaxEarlyData = input.readInt(); + earlyDataHeaderName = input.readString(); + break; + } + case "http": { + host = input.readString(); + path = input.readString(); + break; + } + case "grpc": { + path = input.readString(); + } + } + + security = input.readString(); + if ("tls".equals(security)) { + sni = input.readString(); + alpn = input.readString(); + certificates = input.readString(); + allowInsecure = input.readBoolean(); + utlsFingerprint = input.readString(); + realityPubKey = input.readString(); + realityShortId = input.readString(); + } + + packetEncoding = input.readInt(); + } + + @Override + public void applyFeatureSettings(AbstractBean other) { + if (!(other instanceof StandardV2RayBean)) return; + StandardV2RayBean bean = ((StandardV2RayBean) other); + bean.allowInsecure = allowInsecure; + bean.utlsFingerprint = utlsFingerprint; + bean.realityPubKey = realityPubKey; + bean.realityShortId = realityShortId; + } + + public boolean isVLESS() { + if (this instanceof VMessBean) { + return ((VMessBean) this).alterId == -1; + } + return false; + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt new file mode 100644 index 0000000..94a4aa2 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt @@ -0,0 +1,608 @@ +package io.nekohasekai.sagernet.fmt.v2ray + +import com.google.gson.Gson +import io.nekohasekai.sagernet.fmt.http.HttpBean +import io.nekohasekai.sagernet.fmt.trojan.TrojanBean +import moe.matsuri.nb4a.SingBoxOptions.* +import io.nekohasekai.sagernet.ktx.* +import moe.matsuri.nb4a.utils.NGUtil +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import org.json.JSONObject + +fun StandardV2RayBean.isTLS(): Boolean { + return security == "tls" +} + +fun StandardV2RayBean.setTLS(boolean: Boolean) { + security = if (boolean) "tls" else "" +} + +fun parseV2Ray(link: String): StandardV2RayBean { + // Try parse stupid formats first + + if (!link.contains("?")) { + try { + return parseV2RayN(link) + } catch (e: Exception) { + Logs.i("try v2rayN: " + e.readableMessage) + } + } + + try { + return tryResolveVmess4Kitsunebi(link) + } catch (e: Exception) { + Logs.i("try Kitsunebi: " + e.readableMessage) + } + + // "std" format + + val bean = VMessBean().apply { if (link.startsWith("vless://")) alterId = -1 } + val url = link.replace("vmess://", "https://").replace("vless://", "https://").toHttpUrl() + + if (url.password.isNotBlank()) { + // https://github.com/v2fly/v2fly-github-io/issues/26 (rarely use) + bean.serverAddress = url.host + bean.serverPort = url.port + bean.name = url.fragment + + var protocol = url.username + bean.type = protocol + bean.alterId = url.password.substringAfterLast('-').toInt() + bean.uuid = url.password.substringBeforeLast('-') + + if (protocol.endsWith("+tls")) { + bean.security = "tls" + protocol = protocol.substring(0, protocol.length - 4) + + url.queryParameter("tlsServerName")?.let { + if (it.isNotBlank()) { + bean.sni = it + } + } + } + + when (protocol) { +// "tcp" -> { +// url.queryParameter("type")?.let { type -> +// if (type == "http") { +// bean.headerType = "http" +// url.queryParameter("host")?.let { +// bean.host = it +// } +// } +// } +// } + "http" -> { + url.queryParameter("path")?.let { + bean.path = it + } + url.queryParameter("host")?.let { + bean.host = it.split("|").joinToString(",") + } + } + "ws" -> { + url.queryParameter("path")?.let { + bean.path = it + } + url.queryParameter("host")?.let { + bean.host = it + } + } + "grpc" -> { + url.queryParameter("serviceName")?.let { + bean.path = it + } + } + } + } else { + // also vless format + bean.parseDuckSoft(url) + } + + return bean +} + +// https://github.com/XTLS/Xray-core/issues/91 +// NO allowInsecure +fun StandardV2RayBean.parseDuckSoft(url: HttpUrl) { + serverAddress = url.host + serverPort = url.port + name = url.fragment + + if (this is TrojanBean) { + password = url.username + } else { + uuid = url.username + } + + if (url.pathSegments.size > 1 || url.pathSegments[0].isNotBlank()) { + path = url.pathSegments.joinToString("/") + } + + type = url.queryParameter("type") ?: "tcp" + security = url.queryParameter("security") + if (security == null) { + security = if (this is TrojanBean) { + "tls" + } else { + "none" + } + } + + when (security) { + "tls" -> { + url.queryParameter("sni")?.let { + sni = it + } + url.queryParameter("alpn")?.let { + alpn = it + } + url.queryParameter("cert")?.let { + certificates = it + } + } + } + when (type) { + "http" -> { + url.queryParameter("host")?.let { + host = it + } + url.queryParameter("path")?.let { + path = it + } + } + "ws" -> { + url.queryParameter("host")?.let { + host = it + } + url.queryParameter("path")?.let { + path = it + } + url.queryParameter("ed")?.let { ed -> + wsMaxEarlyData = ed.toInt() + + url.queryParameter("eh")?.let { + earlyDataHeaderName = it + } + } + } + "grpc" -> { + url.queryParameter("serviceName")?.let { + path = it + } + } + } + + url.queryParameter("packetEncoding")?.let { + when (it) { + "packet" -> packetEncoding = 1 + "xudp" -> packetEncoding = 2 + } + } + + url.queryParameter("flow")?.let { + if (isVLESS && it.contains("vision")) { + encryption = it + } + } +} + +// 不确定是谁的格式 +private fun tryResolveVmess4Kitsunebi(server: String): VMessBean { + // vmess://YXV0bzo1YWY1ZDBlYy02ZWEwLTNjNDMtOTNkYi1jYTMwMDg1MDNiZGJAMTgzLjIzMi41Ni4xNjE6MTIwMg + // ?remarks=*%F0%9F%87%AF%F0%9F%87%B5JP%20-355%20TG@moon365free&obfsParam=%7B%22Host%22:%22183.232.56.161%22%7D&path=/v2ray&obfs=websocket&alterId=0 + + var result = server.replace("vmess://", "") + val indexSplit = result.indexOf("?") + if (indexSplit > 0) { + result = result.substring(0, indexSplit) + } + result = NGUtil.decode(result) + + val arr1 = result.split('@') + if (arr1.count() != 2) { + throw IllegalStateException("invalid kitsunebi format") + } + val arr21 = arr1[0].split(':') + val arr22 = arr1[1].split(':') + if (arr21.count() != 2) { + throw IllegalStateException("invalid kitsunebi format") + } + + return VMessBean().apply { + serverAddress = arr22[0] + serverPort = NGUtil.parseInt(arr22[1]) + uuid = arr21[1] + encryption = arr21[0] + if (indexSplit < 0) return@apply + + val url = ("https://localhost/path?" + server.substringAfter("?")).toHttpUrl() + url.queryParameter("remarks")?.apply { name = this } + url.queryParameter("alterId")?.apply { alterId = this.toInt() } + url.queryParameter("path")?.apply { path = this } + url.queryParameter("tls")?.apply { security = "tls" } + url.queryParameter("allowInsecure") + ?.apply { if (this == "1" || this == "true") allowInsecure = true } + url.queryParameter("obfs")?.apply { + type = this.replace("websocket", "ws").replace("none", "tcp") + if (type == "ws") { + url.queryParameter("obfsParam")?.apply { + if (this.startsWith("{")) { + host = JSONObject(this).getStr("Host") + } else if (security == "tls") { + sni = this + } + } + } + } + } +} + +// SagerNet's +// Do not support some format and then throw exception +fun parseV2RayN(link: String): VMessBean { + val result = link.substringAfter("vmess://").decodeBase64UrlSafe() + if (result.contains("= vmess")) { + return parseCsvVMess(result) + } + val bean = VMessBean() + val json = JSONObject(result) + + bean.serverAddress = json.getStr("add") ?: "" + bean.serverPort = json.getIntNya("port") ?: 1080 + bean.encryption = json.getStr("scy") ?: "" + bean.uuid = json.getStr("id") ?: "" + bean.alterId = json.getIntNya("aid") ?: 0 + bean.type = json.getStr("net") ?: "" + bean.host = json.getStr("host") ?: "" + bean.path = json.getStr("path") ?: "" + + when (bean.type) { + "grpc" -> { + bean.path = bean.path + } + } + + bean.name = json.getStr("ps") ?: "" + bean.sni = json.getStr("sni") ?: bean.host + bean.security = json.getStr("tls") + + if (json.optInt("v", 2) < 2) { + when (bean.type) { + "ws" -> { + var path = "" + var host = "" + val lstParameter = bean.host.split(";") + if (lstParameter.isNotEmpty()) { + path = lstParameter[0].trim() + } + if (lstParameter.size > 1) { + path = lstParameter[0].trim() + host = lstParameter[1].trim() + } + bean.path = path + bean.host = host + } + "h2" -> { + var path = "" + var host = "" + val lstParameter = bean.host.split(";") + if (lstParameter.isNotEmpty()) { + path = lstParameter[0].trim() + } + if (lstParameter.size > 1) { + path = lstParameter[0].trim() + host = lstParameter[1].trim() + } + bean.path = path + bean.host = host + } + } + } + + return bean + +} + +private fun parseCsvVMess(csv: String): VMessBean { + + val args = csv.split(",") + + val bean = VMessBean() + + bean.serverAddress = args[1] + bean.serverPort = args[2].toInt() + bean.encryption = args[3] + bean.uuid = args[4].replace("\"", "") + + args.subList(5, args.size).forEach { + + when { + it == "over-tls=true" -> bean.security = "tls" + it.startsWith("tls-host=") -> bean.host = it.substringAfter("=") + it.startsWith("obfs=") -> bean.type = it.substringAfter("=") + it.startsWith("obfs-path=") || it.contains("Host:") -> { + runCatching { + bean.path = it.substringAfter("obfs-path=\"").substringBefore("\"obfs") + } + runCatching { + bean.host = it.substringAfter("Host:").substringBefore("[") + } + + } + + } + + } + + return bean + +} + +data class VmessQRCode( + var v: String = "", + var ps: String = "", + var add: String = "", + var port: String = "", + var id: String = "", + var aid: String = "0", + var scy: String = "", + var net: String = "", + var type: String = "", + var host: String = "", + var path: String = "", + var tls: String = "", + var sni: String = "", + var alpn: String = "" +) + +fun VMessBean.toV2rayN(): String { + if (isVLESS) return toUri(true) + return "vmess://" + VmessQRCode().apply { + v = "2" + ps = this@toV2rayN.name + add = this@toV2rayN.serverAddress + port = this@toV2rayN.serverPort.toString() + id = this@toV2rayN.uuid + aid = this@toV2rayN.alterId.toString() + net = this@toV2rayN.type + host = this@toV2rayN.host + path = this@toV2rayN.path + + when (net) { + "grpc" -> { + path = this@toV2rayN.path + } + } + + tls = if (this@toV2rayN.security == "tls") "tls" else "" + sni = this@toV2rayN.sni + scy = this@toV2rayN.encryption + }.let { + NGUtil.encode(Gson().toJson(it)) + } +} + +fun StandardV2RayBean.toUri(standard: Boolean = true): String { + if (this is VMessBean && alterId > 0) return toV2rayN() + + val builder = linkBuilder() + .username(if (this is TrojanBean) password else uuid) + .host(serverAddress) + .port(serverPort) + .addQueryParameter("type", type) + if (this !is TrojanBean) builder.addQueryParameter("encryption", encryption) + + when (type) { + "tcp" -> {} + "ws", "http" -> { + if (host.isNotBlank()) { + builder.addQueryParameter("host", host) + } + if (path.isNotBlank()) { + if (standard) { + builder.addQueryParameter("path", path) + } else { + builder.encodedPath(path.pathSafe()) + } + } + if (type == "ws") { + if (wsMaxEarlyData > 0) { + builder.addQueryParameter("ed", "$wsMaxEarlyData") + if (earlyDataHeaderName.isNotBlank()) { + builder.addQueryParameter("eh", earlyDataHeaderName) + } + } + } else if (type == "http" && !isTLS()) { + return "" // no fmt? + } + } + "grpc" -> { + if (path.isNotBlank()) { + builder.addQueryParameter("serviceName", path) + } + } + } + + if (security.isNotBlank() && security != "none") { + builder.addQueryParameter("security", security) + when (security) { + "tls" -> { + if (sni.isNotBlank()) { + builder.addQueryParameter("sni", sni) + } + if (alpn.isNotBlank()) { + builder.addQueryParameter("alpn", alpn) + } + if (certificates.isNotBlank()) { + builder.addQueryParameter("cert", certificates) + } + if (allowInsecure) builder.addQueryParameter("allowInsecure", "1") + } + } + } + + when (packetEncoding) { + 1 -> { + builder.addQueryParameter("packetEncoding", "packet") + } + 2 -> { + builder.addQueryParameter("packetEncoding", "xudp") + } + } + + if (name.isNotBlank()) { + builder.encodedFragment(name.urlSafe()) + } + + // TODO vless flow: bean.encryption != "auto" + + return builder.toLink(if (isVLESS) "vless" else "vmess") + +} + +fun buildSingBoxOutboundStreamSettings(bean: StandardV2RayBean): V2RayTransportOptions? { + when (bean.type) { + "tcp" -> { + return null + } + "ws" -> { + return V2RayTransportOptions_WebsocketOptions().apply { + type = "ws" + headers = mutableMapOf() + + if (bean.host.isNotBlank()) { + headers["Host"] = bean.host + } + + if (bean.path.contains("?ed=")) { + path = bean.path.substringBefore("?ed=") + max_early_data = bean.path.substringAfter("?ed=").toIntOrNull() ?: 2048 + early_data_header_name = "Sec-WebSocket-Protocol" + } else { + path = bean.path.takeIf { it.isNotBlank() } ?: "/" + } + + if (bean.wsMaxEarlyData > 0) { + max_early_data = bean.wsMaxEarlyData + } + + if (bean.earlyDataHeaderName.isNotBlank()) { + early_data_header_name = bean.earlyDataHeaderName + } + } + } + "http" -> { + return V2RayTransportOptions_HTTPOptions().apply { + type = "http" + if (bean.host.isNotBlank()) { + host = bean.host.split(",") + } + path = bean.path.takeIf { it.isNotBlank() } ?: "/" + } + } + "quic" -> { + return V2RayTransportOptions().apply { + type = "quic" + } + } + "grpc" -> { + return V2RayTransportOptions_GRPCOptions().apply { + type = "grpc" + service_name = bean.path + } + } + } + +// if (needKeepAliveInterval) { +// sockopt = StreamSettingsObject.SockoptObject().apply { +// tcpKeepAliveInterval = keepAliveInterval +// } +// } + + return null +} + +fun buildSingBoxOutboundTLS(bean: StandardV2RayBean): OutboundTLSOptions? { + if (bean.security != "tls") return null + return OutboundTLSOptions().apply { + enabled = true + insecure = bean.allowInsecure + if (bean.sni.isNotBlank()) server_name = bean.sni + if (bean.alpn.isNotBlank()) alpn = bean.alpn.split("\n") + if (bean.certificates.isNotBlank()) certificate = bean.certificates + if (bean.utlsFingerprint.isNotBlank()) { + utls = OutboundUTLSOptions().apply { + enabled = true + fingerprint = bean.utlsFingerprint + } + } + if (bean.realityPubKey.isNotBlank() && bean.realityShortId.isNotBlank()) { + reality = OutboundRealityOptions().apply { + enabled = true + public_key = bean.realityPubKey + short_id = bean.realityShortId + } + } + } +} + +fun buildSingBoxOutboundStandardV2RayBean(bean: StandardV2RayBean): Outbound { + when (bean) { + is HttpBean -> { + return Outbound_HTTPOptions().apply { + type = "http" + server = bean.serverAddress + server_port = bean.serverPort + username = bean.username + password = bean.password + tls = buildSingBoxOutboundTLS(bean) + } + } + is VMessBean -> { + if (bean.isVLESS) return Outbound_VLESSOptions().apply { + type = "vless" + server = bean.serverAddress + server_port = bean.serverPort + uuid = bean.uuid + if (bean.encryption.isNotBlank() && bean.encryption != "auto") { + flow = bean.encryption + } + when (bean.packetEncoding) { + 0 -> packet_encoding = "" + 1 -> packet_encoding = "packetaddr" + 2 -> packet_encoding = "xudp" + } + tls = buildSingBoxOutboundTLS(bean) + transport = buildSingBoxOutboundStreamSettings(bean) + } + return Outbound_VMessOptions().apply { + type = "vmess" + server = bean.serverAddress + server_port = bean.serverPort + uuid = bean.uuid + alter_id = bean.alterId + security = bean.encryption.takeIf { it.isNotBlank() } ?: "auto" + when (bean.packetEncoding) { + 0 -> packet_encoding = "" + 1 -> packet_encoding = "packetaddr" + 2 -> packet_encoding = "xudp" + } + tls = buildSingBoxOutboundTLS(bean) + transport = buildSingBoxOutboundStreamSettings(bean) + } + } + is TrojanBean -> { + return Outbound_TrojanOptions().apply { + type = "trojan" + server = bean.serverAddress + server_port = bean.serverPort + password = bean.password + tls = buildSingBoxOutboundTLS(bean) + transport = buildSingBoxOutboundStreamSettings(bean) + } + } + else -> throw IllegalStateException("can't reach") + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java new file mode 100644 index 0000000..84044da --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java @@ -0,0 +1,40 @@ +package io.nekohasekai.sagernet.fmt.v2ray; + +import androidx.annotation.NonNull; + +import org.jetbrains.annotations.NotNull; + +import io.nekohasekai.sagernet.fmt.KryoConverters; +import moe.matsuri.nb4a.utils.JavaUtil; + +public class VMessBean extends StandardV2RayBean { + + public Integer alterId; // alterID == -1 --> VLESS + + @Override + public void initializeDefaultValues() { + super.initializeDefaultValues(); + + alterId = alterId != null ? alterId : 0; + encryption = JavaUtil.isNotBlank(encryption) ? encryption : "auto"; + } + + @NotNull + @Override + public VMessBean clone() { + return KryoConverters.deserialize(new VMessBean(), KryoConverters.serialize(this)); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public VMessBean newInstance() { + return new VMessBean(); + } + + @Override + public VMessBean[] newArray(int size) { + return new VMessBean[size]; + } + }; +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardBean.java b/app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardBean.java new file mode 100644 index 0000000..ecd990d --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardBean.java @@ -0,0 +1,75 @@ +package io.nekohasekai.sagernet.fmt.wireguard; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; + +import io.nekohasekai.sagernet.fmt.AbstractBean; +import io.nekohasekai.sagernet.fmt.KryoConverters; + +public class WireGuardBean extends AbstractBean { + + public String localAddress; + public String privateKey; + public String peerPublicKey; + public String peerPreSharedKey; + public Integer mtu; + public String reserved; + + @Override + public void initializeDefaultValues() { + super.initializeDefaultValues(); + if (localAddress == null) localAddress = ""; + if (privateKey == null) privateKey = ""; + if (peerPublicKey == null) peerPublicKey = ""; + if (peerPreSharedKey == null) peerPreSharedKey = ""; + if (mtu == null) mtu = 1420; + if (reserved == null) reserved = ""; + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(2); + super.serialize(output); + output.writeString(localAddress); + output.writeString(privateKey); + output.writeString(peerPublicKey); + output.writeString(peerPreSharedKey); + output.writeInt(mtu); + output.writeString(reserved); + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + super.deserialize(input); + localAddress = input.readString(); + privateKey = input.readString(); + peerPublicKey = input.readString(); + peerPreSharedKey = input.readString(); + mtu = input.readInt(); + reserved = input.readString(); + } + + @NotNull + @Override + public WireGuardBean clone() { + return KryoConverters.deserialize(new WireGuardBean(), KryoConverters.serialize(this)); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public WireGuardBean newInstance() { + return new WireGuardBean(); + } + + @Override + public WireGuardBean[] newArray(int size) { + return new WireGuardBean[size]; + } + }; +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardFmt.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardFmt.kt new file mode 100644 index 0000000..57526eb --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardFmt.kt @@ -0,0 +1,17 @@ +package io.nekohasekai.sagernet.fmt.wireguard + +import moe.matsuri.nb4a.SingBoxOptions + +fun buildSingBoxOutboundWireguardBean(bean: WireGuardBean): SingBoxOptions.Outbound_WireGuardOptions_Fix { + return SingBoxOptions.Outbound_WireGuardOptions_Fix().apply { + type = "wireguard" + server = bean.serverAddress + server_port = bean.serverPort + local_address = bean.localAddress.split("\n") + private_key = bean.privateKey + peer_public_key = bean.peerPublicKey + pre_shared_key = bean.peerPreSharedKey + mtu = bean.mtu + if (bean.reserved.isNotBlank()) reserved = bean.reserved + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/group/GroupInterfaceAdapter.kt b/app/src/main/java/io/nekohasekai/sagernet/group/GroupInterfaceAdapter.kt new file mode 100644 index 0000000..7d0295e --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/group/GroupInterfaceAdapter.kt @@ -0,0 +1,102 @@ +package io.nekohasekai.sagernet.group + +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.GroupManager +import io.nekohasekai.sagernet.database.ProxyGroup +import io.nekohasekai.sagernet.ktx.onMainDispatcher +import io.nekohasekai.sagernet.ktx.runOnMainDispatcher +import io.nekohasekai.sagernet.ui.ThemedActivity +import kotlinx.coroutines.delay +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +class GroupInterfaceAdapter(val context: ThemedActivity) : GroupManager.Interface { + + override suspend fun confirm(message: String): Boolean { + return suspendCoroutine { + runOnMainDispatcher { + MaterialAlertDialogBuilder(context).setTitle(R.string.confirm) + .setMessage(message) + .setPositiveButton(R.string.yes) { _, _ -> it.resume(true) } + .setNegativeButton(R.string.no) { _, _ -> it.resume(false) } + .setOnCancelListener { _ -> it.resume(false) } + .show() + } + } + } + + override suspend fun onUpdateSuccess( + group: ProxyGroup, + changed: Int, + added: List, + updated: Map, + deleted: List, + duplicate: List, + byUser: Boolean + ) { + if (changed == 0 && duplicate.isEmpty()) { + if (byUser) context.snackbar( + context.getString( + R.string.group_no_difference, group.displayName() + ) + ).show() + } else { + context.snackbar(context.getString(R.string.group_updated, group.name, changed)).show() + + var status = "" + if (added.isNotEmpty()) { + status += context.getString( + R.string.group_added, added.joinToString("\n", postfix = "\n\n") + ) + } + if (updated.isNotEmpty()) { + status += context.getString(R.string.group_changed, + updated.map { it }.joinToString("\n", postfix = "\n\n") { + if (it.key == it.value) it.key else "${it.key} => ${it.value}" + }) + } + if (deleted.isNotEmpty()) { + status += context.getString( + R.string.group_deleted, deleted.joinToString("\n", postfix = "\n\n") + ) + } + if (duplicate.isNotEmpty()) { + status += context.getString( + R.string.group_duplicate, duplicate.joinToString("\n", postfix = "\n\n") + ) + } + + onMainDispatcher { + delay(1000L) + + MaterialAlertDialogBuilder(context).setTitle( + context.getString( + R.string.group_diff, group.displayName() + ) + ).setMessage(status.trim()).setPositiveButton(android.R.string.ok, null).show() + } + + } + + } + + override suspend fun onUpdateFailure(group: ProxyGroup, message: String) { + onMainDispatcher { + context.snackbar(message).show() + } + } + + override suspend fun alert(message: String) { + return suspendCoroutine { + runOnMainDispatcher { + MaterialAlertDialogBuilder(context).setTitle(R.string.ooc_warning) + .setMessage(message) + .setPositiveButton(android.R.string.ok) { _, _ -> it.resume(Unit) } + .setOnCancelListener { _ -> it.resume(Unit) } + .show() + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/group/GroupUpdater.kt b/app/src/main/java/io/nekohasekai/sagernet/group/GroupUpdater.kt new file mode 100644 index 0000000..426e3d7 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/group/GroupUpdater.kt @@ -0,0 +1,171 @@ +package io.nekohasekai.sagernet.group + +import io.nekohasekai.sagernet.* +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.GroupManager +import io.nekohasekai.sagernet.database.ProxyGroup +import io.nekohasekai.sagernet.database.SubscriptionBean +import io.nekohasekai.sagernet.fmt.AbstractBean +import io.nekohasekai.sagernet.fmt.http.HttpBean +import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean +import io.nekohasekai.sagernet.fmt.naive.NaiveBean +import io.nekohasekai.sagernet.fmt.trojan.TrojanBean +import io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean +import io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean +import io.nekohasekai.sagernet.fmt.v2ray.isTLS +import io.nekohasekai.sagernet.ktx.* +import kotlinx.coroutines.* +import java.net.Inet4Address +import java.net.InetAddress +import java.util.* +import java.util.concurrent.atomic.AtomicInteger + +@Suppress("EXPERIMENTAL_API_USAGE") +abstract class GroupUpdater { + + abstract suspend fun doUpdate( + proxyGroup: ProxyGroup, + subscription: SubscriptionBean, + userInterface: GroupManager.Interface?, + byUser: Boolean + ) + + data class Progress( + var max: Int + ) { + var progress by AtomicInteger() + } + + protected suspend fun forceResolve( + profiles: List, groupId: Long? + ) { + val ipv6Mode = DataStore.ipv6Mode + val lookupPool = newFixedThreadPoolContext(5, "DNS Lookup") + val lookupJobs = mutableListOf() + val progress = Progress(profiles.size) + if (groupId != null) { + GroupUpdater.progress[groupId] = progress + GroupManager.postReload(groupId) + } + val ipv6First = ipv6Mode >= IPv6Mode.PREFER + + for (profile in profiles) { + when (profile) { + // SNI rewrite unsupported + is NaiveBean -> continue + } + + if (profile.serverAddress.isIpAddress()) continue + + lookupJobs.add(GlobalScope.launch(lookupPool) { + try { + val results = if ( + SagerNet.underlyingNetwork != null && + DataStore.enableFakeDns && + DataStore.serviceState.started && + DataStore.serviceMode == Key.MODE_VPN + ) { + // FakeDNS + SagerNet.underlyingNetwork!! + .getAllByName(profile.serverAddress) + .filterNotNull() + } else { + // System DNS is enough (when VPN connected, it uses v2ray-core) + InetAddress.getAllByName(profile.serverAddress).filterNotNull() + } + if (results.isEmpty()) error("empty response") + rewriteAddress(profile, results, ipv6First) + } catch (e: Exception) { + Logs.d("Lookup ${profile.serverAddress} failed: ${e.readableMessage}", e) + } + if (groupId != null) { + progress.progress++ + GroupManager.postReload(groupId) + } + }) + } + + lookupJobs.joinAll() + lookupPool.close() + } + + protected fun rewriteAddress( + bean: AbstractBean, addresses: List, ipv6First: Boolean + ) { + val address = addresses.sortedBy { (it is Inet4Address) xor ipv6First }[0].hostAddress + + with(bean) { + when (this) { + is HttpBean -> { + if (isTLS() && sni.isBlank()) sni = bean.serverAddress + } + is StandardV2RayBean -> { + when (security) { + "tls" -> if (sni.isBlank()) sni = bean.serverAddress + } + } + is TrojanBean -> { + if (sni.isBlank()) sni = bean.serverAddress + } + is TrojanGoBean -> { + if (sni.isBlank()) sni = bean.serverAddress + } + is HysteriaBean -> { + if (sni.isBlank()) sni = bean.serverAddress + } + } + + bean.serverAddress = address + } + } + + companion object { + + val updating = Collections.synchronizedSet(mutableSetOf()) + val progress = Collections.synchronizedMap(mutableMapOf()) + + fun startUpdate(proxyGroup: ProxyGroup, byUser: Boolean) { + runOnDefaultDispatcher { + executeUpdate(proxyGroup, byUser) + } + } + + suspend fun executeUpdate(proxyGroup: ProxyGroup, byUser: Boolean): Boolean { + return coroutineScope { + if (!updating.add(proxyGroup.id)) cancel() + GroupManager.postReload(proxyGroup.id) + + val subscription = proxyGroup.subscription!! + val connected = DataStore.serviceState.connected + val userInterface = GroupManager.userInterface + + if (byUser && (subscription.link?.startsWith("http://") == true || subscription.updateWhenConnectedOnly) && !connected) { + if (userInterface == null || !userInterface.confirm(app.getString(R.string.update_subscription_warning))) { + finishUpdate(proxyGroup) + cancel() + return@coroutineScope true + } + } + + try { + RawUpdater.doUpdate(proxyGroup, subscription, userInterface, byUser) + true + } catch (e: Throwable) { + Logs.w(e) + userInterface?.onUpdateFailure(proxyGroup, e.readableMessage) + finishUpdate(proxyGroup) + false + } + } + } + + + suspend fun finishUpdate(proxyGroup: ProxyGroup) { + updating.remove(proxyGroup.id) + progress.remove(proxyGroup.id) + GroupManager.postUpdate(proxyGroup) + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt b/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt new file mode 100644 index 0000000..980104d --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt @@ -0,0 +1,495 @@ +package io.nekohasekai.sagernet.group + +import android.net.Uri +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.* +import io.nekohasekai.sagernet.fmt.AbstractBean +import io.nekohasekai.sagernet.fmt.http.HttpBean +import io.nekohasekai.sagernet.fmt.hysteria.parseHysteria +import io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean +import io.nekohasekai.sagernet.fmt.shadowsocks.parseShadowsocks +import io.nekohasekai.sagernet.fmt.socks.SOCKSBean +import io.nekohasekai.sagernet.fmt.trojan.TrojanBean +import io.nekohasekai.sagernet.fmt.trojan_go.parseTrojanGo +import io.nekohasekai.sagernet.fmt.v2ray.VMessBean +import io.nekohasekai.sagernet.fmt.v2ray.isTLS +import io.nekohasekai.sagernet.fmt.v2ray.setTLS +import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean +import io.nekohasekai.sagernet.ktx.* +import libcore.Libcore +import moe.matsuri.nb4a.Protocols +import org.ini4j.Ini +import org.json.JSONArray +import org.json.JSONObject +import org.json.JSONTokener +import org.yaml.snakeyaml.TypeDescription +import org.yaml.snakeyaml.Yaml +import org.yaml.snakeyaml.error.YAMLException +import java.io.StringReader + +@Suppress("EXPERIMENTAL_API_USAGE") +object RawUpdater : GroupUpdater() { + + override suspend fun doUpdate( + proxyGroup: ProxyGroup, + subscription: SubscriptionBean, + userInterface: GroupManager.Interface?, + byUser: Boolean + ) { + + val link = subscription.link + var proxies: List + if (link.startsWith("content://")) { + val contentText = app.contentResolver.openInputStream(Uri.parse(link)) + ?.bufferedReader() + ?.readText() + + proxies = contentText?.let { parseRaw(contentText) } + ?: error(app.getString(R.string.no_proxies_found_in_subscription)) + } else { + + val response = Libcore.newHttpClient().apply { + trySocks5(DataStore.mixedPort) + when (DataStore.appTLSVersion) { + "1.3" -> restrictedTLS() + } + }.newRequest().apply { + setURL(subscription.link) + setUserAgent(subscription.customUserAgent.takeIf { it.isNotBlank() } ?: USER_AGENT) + }.execute() + + proxies = parseRaw(response.contentString) + ?: error(app.getString(R.string.no_proxies_found)) + + subscription.subscriptionUserinfo = response.getHeader("Subscription-Userinfo") + } + + val proxiesMap = LinkedHashMap() + for (proxy in proxies) { + var index = 0 + var name = proxy.displayName() + while (proxiesMap.containsKey(name)) { + println("Exists name: $name") + index++ + name = name.replace(" (${index - 1})", "") + name = "$name ($index)" + proxy.name = name + } + proxiesMap[proxy.displayName()] = proxy + } + proxies = proxiesMap.values.toList() + + if (subscription.forceResolve) forceResolve(proxies, proxyGroup.id) + + val exists = SagerDatabase.proxyDao.getByGroup(proxyGroup.id) + val duplicate = ArrayList() + if (subscription.deduplication) { + Logs.d("Before deduplication: ${proxies.size}") + val uniqueProxies = LinkedHashSet() + val uniqueNames = HashMap() + for (_proxy in proxies) { + val proxy = Protocols.Deduplication(_proxy) + if (!uniqueProxies.add(proxy)) { + val index = uniqueProxies.indexOf(proxy) + if (uniqueNames.containsKey(proxy)) { + val name = uniqueNames[proxy]!!.replace(" ($index)", "") + if (name.isNotBlank()) { + duplicate.add("$name ($index)") + uniqueNames[proxy] = "" + } + } + duplicate.add(_proxy.displayName() + " ($index)") + } else { + uniqueNames[proxy] = _proxy.displayName() + } + } + uniqueProxies.retainAll(uniqueNames.keys) + proxies = uniqueProxies.toList().map { it.bean } + } + + Logs.d("New profiles: ${proxies.size}") + + val nameMap = proxies.associateBy { bean -> + bean.displayName() + } + + Logs.d("Unique profiles: ${nameMap.size}") + + val toDelete = ArrayList() + val toReplace = exists.mapNotNull { entity -> + val name = entity.displayName() + if (nameMap.contains(name)) name to entity else let { + toDelete.add(entity) + null + } + }.toMap() + + Logs.d("toDelete profiles: ${toDelete.size}") + Logs.d("toReplace profiles: ${toReplace.size}") + + val toUpdate = ArrayList() + val added = mutableListOf() + val updated = mutableMapOf() + val deleted = toDelete.map { it.displayName() } + + var userOrder = 1L + var changed = toDelete.size + for ((name, bean) in nameMap.entries) { + if (toReplace.contains(name)) { + val entity = toReplace[name]!! + val existsBean = entity.requireBean() + existsBean.applyFeatureSettings(bean) + when { + existsBean != bean -> { + changed++ + entity.putBean(bean) + toUpdate.add(entity) + updated[entity.displayName()] = name + + Logs.d("Updated profile: $name") + } + entity.userOrder != userOrder -> { + entity.putBean(bean) + toUpdate.add(entity) + entity.userOrder = userOrder + + Logs.d("Reordered profile: $name") + } + else -> { + Logs.d("Ignored profile: $name") + } + } + } else { + changed++ + SagerDatabase.proxyDao.addProxy(ProxyEntity( + groupId = proxyGroup.id, userOrder = userOrder + ).apply { + putBean(bean) + }) + added.add(name) + Logs.d("Inserted profile: $name") + } + userOrder++ + } + + SagerDatabase.proxyDao.updateProxy(toUpdate).also { + Logs.d("Updated profiles: $it") + } + + SagerDatabase.proxyDao.deleteProxy(toDelete).also { + Logs.d("Deleted profiles: $it") + } + + val existCount = SagerDatabase.proxyDao.countByGroup(proxyGroup.id).toInt() + + if (existCount != proxies.size) { + Logs.e("Exist profiles: $existCount, new profiles: ${proxies.size}") + } + + subscription.lastUpdated = (System.currentTimeMillis() / 1000).toInt() + SagerDatabase.groupDao.updateGroup(proxyGroup) + finishUpdate(proxyGroup) + + userInterface?.onUpdateSuccess( + proxyGroup, changed, added, updated, deleted, duplicate, byUser + ) + } + + @Suppress("UNCHECKED_CAST") + suspend fun parseRaw(text: String): List? { + + val proxies = mutableListOf() + + if (text.contains("proxies:")) { + + try { + + // clash + for (proxy in (Yaml().apply { + addTypeDescription(TypeDescription(String::class.java, "str")) + }.loadAs(text, Map::class.java)["proxies"] as? (List>) ?: error( + app.getString(R.string.no_proxies_found_in_file) + ))) { + // Note: YAML numbers parsed as "Long" + + when (proxy["type"] as String) { + "socks5" -> { + proxies.add(SOCKSBean().apply { + serverAddress = proxy["server"] as String + serverPort = proxy["port"].toString().toInt() + username = proxy["username"]?.toString() + password = proxy["password"]?.toString() + name = proxy["name"]?.toString() + }) + } + "http" -> { + proxies.add(HttpBean().apply { + serverAddress = proxy["server"] as String + serverPort = proxy["port"].toString().toInt() + username = proxy["username"]?.toString() + password = proxy["password"]?.toString() + setTLS(proxy["tls"]?.toString() == "true") + sni = proxy["sni"]?.toString() + name = proxy["name"]?.toString() + }) + } + "ss" -> { + val ssPlugin = mutableListOf() + if (proxy.contains("plugin")) { + val opts = proxy["plugin-opts"] as Map + when (proxy["plugin"]) { + "obfs" -> { + ssPlugin.apply { + add("obfs-local") + add("obfs=" + (opts["mode"]?.toString() ?: "")) + add("obfs-host=" + (opts["host"]?.toString() ?: "")) + } + } + "v2ray-plugin" -> { + ssPlugin.apply { + add("v2ray-plugin") + add("mode=" + (opts["mode"]?.toString() ?: "")) + if (opts["mode"]?.toString() == "true") add("tls") + add("host=" + (opts["host"]?.toString() ?: "")) + add("path=" + (opts["path"]?.toString() ?: "")) + if (opts["mux"]?.toString() == "true") add("mux=8") + } + } + } + } + proxies.add(ShadowsocksBean().apply { + serverAddress = proxy["server"] as String + serverPort = proxy["port"].toString().toInt() + password = proxy["password"]?.toString() + method = clashCipher(proxy["cipher"] as String) + plugin = ssPlugin.joinToString(";") + name = proxy["name"]?.toString() + }) + } + "vmess" -> { + val bean = VMessBean() + for (opt in proxy) { + when (opt.key) { + "name" -> bean.name = opt.value?.toString() + "server" -> bean.serverAddress = opt.value as String + "port" -> bean.serverPort = opt.value.toString().toInt() + "uuid" -> bean.uuid = opt.value as String + "alterId" -> bean.alterId = opt.value.toString().toInt() + "cipher" -> bean.encryption = opt.value as String + "network" -> { + bean.type = opt.value as String + // Clash "network" fix + when (bean.type) { + "h2" -> bean.type = "http" + } + } + "tls" -> bean.security = + if (opt.value?.toString() == "true") "tls" else "" + "skip-cert-verify" -> bean.allowInsecure = + opt.value?.toString() == "true" + "ws-path" -> bean.path = opt.value?.toString() + "ws-headers" -> for (wsHeader in (opt.value as Map)) { + when (wsHeader.key.lowercase()) { + "host" -> bean.host = wsHeader.value.toString() + } + } + "ws-opts", "ws-opt" -> for (wsOpt in (opt.value as Map)) { + when (wsOpt.key.lowercase()) { + "headers" -> for (wsHeader in (opt.value as Map)) { + when (wsHeader.key.lowercase()) { + "host" -> bean.host = wsHeader.value.toString() + } + } + "path" -> { + bean.path = wsOpt.value.toString() + } + "max-early-data" -> { + bean.wsMaxEarlyData = wsOpt.value.toString().toInt() + } + "early-data-header-name" -> { + bean.earlyDataHeaderName = wsOpt.value.toString() + } + } + } + "servername" -> bean.host = opt.value?.toString() + // The format of the VMessBean is wrong, so the `host` `path` has some strange transformations here. + "h2-opts", "h2-opt" -> for (h2Opt in (opt.value as Map)) { + when (h2Opt.key.lowercase()) { + "host" -> bean.host = + (h2Opt.value as List).first() + "path" -> bean.path = h2Opt.value.toString() + } + } + "http-opts", "http-opt" -> for (httpOpt in (opt.value as Map)) { + when (httpOpt.key.lowercase()) { + "path" -> bean.path = + (httpOpt.value as List).first() + "headers" -> for (hdr in (httpOpt.value as Map)) { + when (hdr.key.lowercase()) { + "host" -> bean.host = + (hdr.value as List).first() + } + } + } + } + "grpc-opts", "grpc-opt" -> for (grpcOpt in (opt.value as Map)) { + when (grpcOpt.key.lowercase()) { + "grpc-service-name" -> bean.path = + grpcOpt.value.toString() + } + } + } + } + if (bean.isTLS() && bean.sni.isNullOrBlank() && !bean.host.isNullOrBlank()) { + bean.sni = bean.host + } + proxies.add(bean) + } + "trojan" -> { + val bean = TrojanBean() + bean.security = "tls" + for (opt in proxy) { + when (opt.key) { + "name" -> bean.name = opt.value?.toString() + "server" -> bean.serverAddress = opt.value as String + "port" -> bean.serverPort = opt.value.toString().toInt() + "password" -> bean.password = opt.value?.toString() + "sni" -> bean.sni = opt.value?.toString() + "skip-cert-verify" -> bean.allowInsecure = + opt.value?.toString() == "true" + "network" -> when (opt.value) { + "ws", "grpc" -> bean.type = opt.value?.toString() + } + "ws-opts", "ws-opt" -> for (wsOpt in (opt.value as Map)) { + when (wsOpt.key.lowercase()) { + "headers" -> for (wsHeader in (opt.value as Map)) { + when (wsHeader.key.lowercase()) { + "host" -> bean.host = wsHeader.value.toString() + } + } + "path" -> { + bean.path = wsOpt.value.toString() + } + } + } + "grpc-opts", "grpc-opt" -> for (grpcOpt in (opt.value as Map)) { + when (grpcOpt.key.lowercase()) { + "grpc-service-name" -> bean.path = + grpcOpt.value.toString() + } + } + } + } + proxies.add(bean) + } + } + } + proxies.forEach { it.initializeDefaultValues() } + return proxies + } catch (e: YAMLException) { + Logs.w(e) + } + } else if (text.contains("[Interface]")) { + // wireguard + try { + proxies.addAll(parseWireGuard(text)) + return proxies + } catch (e: Exception) { + Logs.w(e) + } + } + + try { + val json = JSONTokener(text).nextValue() + return parseJSON(json) + } catch (ignored: Exception) { + } + + try { + return parseProxies(text.decodeBase64UrlSafe()).takeIf { it.isNotEmpty() } + ?: error("Not found") + } catch (e: Exception) { + Logs.w(e) + } + + try { + return parseProxies(text).takeIf { it.isNotEmpty() } ?: error("Not found") + } catch (e: SubscriptionFoundException) { + throw e + } catch (ignored: Exception) { + } + + return null + } + + fun clashCipher(cipher: String): String { + return when (cipher) { + "dummy" -> "none" + else -> cipher + } + } + + fun parseWireGuard(conf: String): List { + val ini = Ini(StringReader(conf)) + val iface = ini["Interface"] ?: error("Missing 'Interface' selection") + val bean = WireGuardBean().applyDefaultValues() + val localAddresses = iface.getAll("Address") + if (localAddresses.isNullOrEmpty()) error("Empty address in 'Interface' selection") + bean.localAddress = localAddresses.flatMap { it.split(",") }.let { address -> + address.joinToString("\n") { it.substringBefore("/") } + } + bean.privateKey = iface["PrivateKey"] + val peers = ini.getAll("Peer") + if (peers.isNullOrEmpty()) error("Missing 'Peer' selections") + val beans = mutableListOf() + for (peer in peers) { + val endpoint = peer["Endpoint"] + if (endpoint.isNullOrBlank() || !endpoint.contains(":")) { + continue + } + + val peerBean = bean.clone() + peerBean.serverAddress = endpoint.substringBeforeLast(":") + peerBean.serverPort = endpoint.substringAfterLast(":").toIntOrNull() ?: continue + peerBean.peerPublicKey = peer["PublicKey"] ?: continue + peerBean.peerPreSharedKey = peer["PresharedKey"] + beans.add(peerBean.applyDefaultValues()) + } + if (beans.isEmpty()) error("Empty available peer list") + return beans + } + + fun parseJSON(json: Any): List { + val proxies = ArrayList() + + if (json is JSONObject) { + when { + json.has("server") && (json.has("up") || json.has("up_mbps")) -> { + return listOf(json.parseHysteria()) + } + json.has("method") -> { + return listOf(json.parseShadowsocks()) + } + json.has("remote_addr") -> { + return listOf(json.parseTrojanGo()) + } + else -> json.forEach { _, it -> + if (isJsonObjectValid(it)) { + proxies.addAll(parseJSON(it)) + } + } + } + } else { + json as JSONArray + json.forEach { _, it -> + if (isJsonObjectValid(it)) { + proxies.addAll(parseJSON(it)) + } + } + } + + proxies.forEach { it.initializeDefaultValues() } + return proxies + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Asyncs.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Asyncs.kt new file mode 100644 index 0000000..af3c96c --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Asyncs.kt @@ -0,0 +1,33 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + +package io.nekohasekai.sagernet.ktx + +import kotlinx.coroutines.* + +fun block(block: suspend CoroutineScope.() -> Unit): suspend CoroutineScope.() -> Unit { + return block +} + +fun runOnDefaultDispatcher(block: suspend CoroutineScope.() -> Unit) = + GlobalScope.launch(Dispatchers.Default, block = block) + +suspend fun onDefaultDispatcher(block: suspend CoroutineScope.() -> T) = + withContext(Dispatchers.Default, block = block) + +fun runOnIoDispatcher(block: suspend CoroutineScope.() -> Unit) = + GlobalScope.launch(Dispatchers.IO, block = block) + +suspend fun onIoDispatcher(block: suspend CoroutineScope.() -> T) = + withContext(Dispatchers.IO, block = block) + +fun runOnMainDispatcher(block: suspend CoroutineScope.() -> Unit) = + GlobalScope.launch(Dispatchers.Main.immediate, block = block) + +suspend fun onMainDispatcher(block: suspend CoroutineScope.() -> T) = + withContext(Dispatchers.Main.immediate, block = block) + +fun runBlockingOnMainDispatcher(block: suspend CoroutineScope.() -> Unit) { + runBlocking { + GlobalScope.launch(Dispatchers.Main.immediate, block = block) + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Browsers.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Browsers.kt new file mode 100644 index 0000000..44eba1c --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Browsers.kt @@ -0,0 +1,29 @@ + package io.nekohasekai.sagernet.ktx + +import android.content.Context +import android.net.Uri +import androidx.browser.customtabs.CustomTabColorSchemeParams +import androidx.browser.customtabs.CustomTabsIntent +import io.nekohasekai.sagernet.R + +fun Context.launchCustomTab(link: String) { + CustomTabsIntent.Builder().apply { + setColorScheme(CustomTabsIntent.COLOR_SCHEME_SYSTEM) + setColorSchemeParams( + CustomTabsIntent.COLOR_SCHEME_LIGHT, + CustomTabColorSchemeParams.Builder().apply { + setToolbarColor(getColorAttr(R.attr.colorPrimary)) + }.build() + ) + setColorSchemeParams( + CustomTabsIntent.COLOR_SCHEME_DARK, + CustomTabColorSchemeParams.Builder().apply { + setToolbarColor(getColorAttr(R.attr.colorPrimary)) + }.build() + ) + }.build().apply { + if (intent.resolveActivity(packageManager) != null) { + launchUrl(this@launchCustomTab, Uri.parse(link)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Dialogs.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Dialogs.kt new file mode 100644 index 0000000..53a5f69 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Dialogs.kt @@ -0,0 +1,16 @@ +package io.nekohasekai.sagernet.ktx + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.Fragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import io.nekohasekai.sagernet.R + +fun Context.alert(text: String): AlertDialog { + return MaterialAlertDialogBuilder(this).setTitle(R.string.error_title) + .setMessage(text) + .setPositiveButton(android.R.string.ok, null) + .create() +} + +fun Fragment.alert(text: String) = requireContext().alert(text) \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Dimens.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Dimens.kt new file mode 100644 index 0000000..1c7a034 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Dimens.kt @@ -0,0 +1,14 @@ +package io.nekohasekai.sagernet.ktx + +import android.content.res.Resources +import kotlin.math.ceil + +private val density = Resources.getSystem().displayMetrics.density + +fun dp2pxf(dpValue: Int): Float { + return density * dpValue +} + +fun dp2px(dpValue: Int): Int { + return ceil(dp2pxf(dpValue)).toInt() +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt new file mode 100644 index 0000000..ba3f51a --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt @@ -0,0 +1,232 @@ +package io.nekohasekai.sagernet.ktx + +import com.google.gson.JsonParser +import io.nekohasekai.sagernet.fmt.AbstractBean +import io.nekohasekai.sagernet.fmt.Serializable +import io.nekohasekai.sagernet.fmt.http.parseHttp +import io.nekohasekai.sagernet.fmt.hysteria.parseHysteria +import io.nekohasekai.sagernet.fmt.naive.parseNaive +import io.nekohasekai.sagernet.fmt.parseUniversal +import io.nekohasekai.sagernet.fmt.shadowsocks.parseShadowsocks +import io.nekohasekai.sagernet.fmt.socks.parseSOCKS +import io.nekohasekai.sagernet.fmt.trojan.parseTrojan +import io.nekohasekai.sagernet.fmt.trojan_go.parseTrojanGo +import io.nekohasekai.sagernet.fmt.v2ray.parseV2Ray +import moe.matsuri.nb4a.proxy.neko.NekoJSInterface +import moe.matsuri.nb4a.plugin.NekoPluginManager +import moe.matsuri.nb4a.proxy.neko.parseShareLink +import moe.matsuri.nb4a.utils.JavaUtil.gson +import moe.matsuri.nb4a.utils.Util +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject + +// JSON & Base64 + +fun JSONObject.toStringPretty(): String { + return gson.toJson(JsonParser.parseString(this.toString())) +} + +inline fun JSONArray.filterIsInstance(): List { + val list = mutableListOf() + for (i in 0 until this.length()) { + if (this[i] is T) list.add(this[i] as T) + } + return list +} + +inline fun JSONArray.forEach(action: (Int, Any) -> Unit) { + for (i in 0 until this.length()) { + action(i, this[i]) + } +} + +inline fun JSONObject.forEach(action: (String, Any) -> Unit) { + for (k in this.keys()) { + action(k, this.get(k)) + } +} + +fun isJsonObjectValid(j: Any): Boolean { + if (j is JSONObject) return true + if (j is JSONArray) return true + try { + JSONObject(j as String) + } catch (ex: JSONException) { + try { + JSONArray(j) + } catch (ex1: JSONException) { + return false + } + } + return true +} + +// wtf hutool +fun JSONObject.getStr(name: String): String? { + val obj = this.opt(name) ?: return null + if (obj is String) { + if (obj.isBlank()) { + return null + } + return obj + } else { + return null + } +} + +fun JSONObject.getBool(name: String): Boolean? { + return try { + getBoolean(name) + } catch (ignored: Exception) { + null + } +} + + +// 重名了喵 +fun JSONObject.getIntNya(name: String): Int? { + return try { + getInt(name) + } catch (ignored: Exception) { + null + } +} + + +fun String.decodeBase64UrlSafe(): String { + return String(Util.b64Decode(this)) +} + +// Sub + +class SubscriptionFoundException(val link: String) : RuntimeException() + +suspend fun parseProxies(text: String): List { + val links = text.split('\n').flatMap { it.trim().split(' ') } + val linksByLine = text.split('\n').map { it.trim() } + + val entities = ArrayList() + val entitiesByLine = ArrayList() + + suspend fun String.parseLink(entities: ArrayList) { + if (startsWith("clash://install-config?") || startsWith("sn://subscription?")) { + throw SubscriptionFoundException(this) + } + + if (startsWith("sn://")) { + Logs.d("Try parse universal link: $this") + runCatching { + entities.add(parseUniversal(this)) + }.onFailure { + Logs.w(it) + } + } else if (startsWith("socks://") || startsWith("socks4://") || startsWith("socks4a://") || startsWith( + "socks5://" + ) + ) { + Logs.d("Try parse socks link: $this") + runCatching { + entities.add(parseSOCKS(this)) + }.onFailure { + Logs.w(it) + } + } else if (matches("(http|https)://.*".toRegex())) { + Logs.d("Try parse http link: $this") + runCatching { + entities.add(parseHttp(this)) + }.onFailure { + Logs.w(it) + } + } else if (startsWith("vmess://")) { + Logs.d("Try parse v2ray link: $this") + runCatching { + entities.add(parseV2Ray(this)) + }.onFailure { + Logs.w(it) + } + } else if (startsWith("vless://")) { + Logs.d("Try parse vless link: $this") + runCatching { + entities.add(parseV2Ray(this)) + }.onFailure { + Logs.w(it) + } + } else if (startsWith("trojan://")) { + Logs.d("Try parse trojan link: $this") + runCatching { + entities.add(parseTrojan(this)) + }.onFailure { + Logs.w(it) + } + } else if (startsWith("trojan-go://")) { + Logs.d("Try parse trojan-go link: $this") + runCatching { + entities.add(parseTrojanGo(this)) + }.onFailure { + Logs.w(it) + } + } else if (startsWith("ss://")) { + Logs.d("Try parse shadowsocks link: $this") + runCatching { + entities.add(parseShadowsocks(this)) + }.onFailure { + Logs.w(it) + } + } else if (startsWith("naive+")) { + Logs.d("Try parse naive link: $this") + runCatching { + entities.add(parseNaive(this)) + }.onFailure { + Logs.w(it) + } + } else if (startsWith("hysteria://")) { + Logs.d("Try parse hysteria link: $this") + runCatching { + entities.add(parseHysteria(this)) + }.onFailure { + Logs.w(it) + } + } else { // Neko Plugins + NekoPluginManager.getProtocols().forEach { obj -> + obj.protocolConfig.optJSONArray("links")?.forEach { _, any -> + if (any is String && startsWith(any)) { + runCatching { + entities.add( + parseShareLink( + obj.plgId, obj.protocolId, this@parseLink + ) + ) + }.onFailure { + Logs.w(it) + } + } + } + } + } + } + + for (link in links) { + link.parseLink(entities) + } + for (link in linksByLine) { + link.parseLink(entitiesByLine) + } + var isBadLink = false + if (entities.onEach { it.initializeDefaultValues() }.size == entitiesByLine.onEach { it.initializeDefaultValues() }.size) run test@{ + entities.forEachIndexed { index, bean -> + val lineBean = entitiesByLine[index] + if (bean == lineBean && bean.displayName() != lineBean.displayName()) { + isBadLink = true + return@test + } + } + } + NekoJSInterface.Default.destroyAllJsi() + return if (entities.size > entitiesByLine.size) entities else entitiesByLine +} + +fun T.applyDefaultValues(): T { + initializeDefaultValues() + return this +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Kryos.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Kryos.kt new file mode 100644 index 0000000..6440fe2 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Kryos.kt @@ -0,0 +1,61 @@ +package io.nekohasekai.sagernet.ktx + +import android.os.Parcel +import android.os.Parcelable +import com.esotericsoftware.kryo.io.ByteBufferInput +import com.esotericsoftware.kryo.io.ByteBufferOutput +import java.io.InputStream +import java.io.OutputStream + + +fun InputStream.byteBuffer() = ByteBufferInput(this) +fun OutputStream.byteBuffer() = ByteBufferOutput(this) + +fun ByteBufferInput.readStringList(): List { + return mutableListOf().apply { + repeat(readInt()) { + add(readString()) + } + } +} + +fun ByteBufferInput.readStringSet(): Set { + return linkedSetOf().apply { + repeat(readInt()) { + add(readString()) + } + } +} + + +fun ByteBufferOutput.writeStringList(list: List) { + writeInt(list.size) + for (str in list) writeString(str) +} + +fun ByteBufferOutput.writeStringList(list: Set) { + writeInt(list.size) + for (str in list) writeString(str) +} + +fun Parcelable.marshall(): ByteArray { + val parcel = Parcel.obtain() + writeToParcel(parcel, 0) + val bytes = parcel.marshall() + parcel.recycle() + return bytes +} + +fun ByteArray.unmarshall(): Parcel { + val parcel = Parcel.obtain() + parcel.unmarshall(this, 0, size) + parcel.setDataPosition(0) // This is extremely important! + return parcel +} + +fun ByteArray.unmarshall(constructor: (Parcel) -> T): T { + val parcel = unmarshall() + val result = constructor(parcel) + parcel.recycle() + return result +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Layouts.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Layouts.kt new file mode 100644 index 0000000..839bbd8 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Layouts.kt @@ -0,0 +1,75 @@ +package io.nekohasekai.sagernet.ktx + +import android.graphics.Rect +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.ui.MainActivity + +class FixedLinearLayoutManager(val recyclerView: RecyclerView) : + LinearLayoutManager(recyclerView.context, RecyclerView.VERTICAL, false) { + + override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) { + try { + super.onLayoutChildren(recycler, state) + } catch (ignored: IndexOutOfBoundsException) { + } + } + + private var listenerDisabled = false + + override fun scrollVerticallyBy( + dx: Int, recycler: RecyclerView.Recycler, + state: RecyclerView.State + ): Int { + // Matsuri style + if (!DataStore.showBottomBar) return super.scrollVerticallyBy(dx, recycler, state) + + // SagerNet Style + val scrollRange = super.scrollVerticallyBy(dx, recycler, state) + if (listenerDisabled) return scrollRange + val activity = recyclerView.context as? MainActivity + if (activity == null) { + listenerDisabled = true + return scrollRange + } + + val overscroll = dx - scrollRange + if (overscroll > 0) { + val view = + (recyclerView.findViewHolderForAdapterPosition(findLastVisibleItemPosition()) + ?: return scrollRange).itemView + val itemLocation = Rect().also { view.getGlobalVisibleRect(it) } + val fabLocation = Rect().also { activity.binding.fab.getGlobalVisibleRect(it) } + if (!itemLocation.contains(fabLocation.left, fabLocation.top) && !itemLocation.contains( + fabLocation.right, + fabLocation.bottom + ) + ) { + return scrollRange + } + activity.binding.fab.apply { + if (isShown) hide() + } + } else { + /*val screen = Rect().also { activity.window.decorView.getGlobalVisibleRect(it) } + val location = Rect().also { activity.stats.getGlobalVisibleRect(it) } + if (screen.bottom < location.bottom) { + return scrollRange + } + val height = location.bottom - location.top + val mH = activity.stats.measuredHeight + + if (mH > height) { + return scrollRange + }*/ + + activity.binding.fab.apply { + if (!isShown) show() + } + } + return scrollRange + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Logs.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Logs.kt new file mode 100644 index 0000000..454ca3a --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Logs.kt @@ -0,0 +1,64 @@ +package io.nekohasekai.sagernet.ktx + +import libcore.Libcore +import java.io.InputStream +import java.io.OutputStream + +object Logs { + + private fun mkTag(): String { + val stackTrace = Thread.currentThread().stackTrace + return stackTrace[4].className.substringAfterLast(".") + } + + // level int use logrus.go + + fun d(message: String) { + Libcore.nekoLogPrintln("[Debug] [${mkTag()}] $message") + } + + fun d(message: String, exception: Throwable) { + Libcore.nekoLogPrintln("[Debug] [${mkTag()}] $message" + "\n" + exception.stackTraceToString()) + } + + fun i(message: String) { + Libcore.nekoLogPrintln("[Info] [${mkTag()}] $message") + } + + fun i(message: String, exception: Throwable) { + Libcore.nekoLogPrintln("[Info] [${mkTag()}] $message" + "\n" + exception.stackTraceToString()) + } + + fun w(message: String) { + Libcore.nekoLogPrintln("[Warning] [${mkTag()}] $message") + } + + fun w(message: String, exception: Throwable) { + Libcore.nekoLogPrintln("[Warning] [${mkTag()}] $message" + "\n" + exception.stackTraceToString()) + } + + fun w(exception: Throwable) { + Libcore.nekoLogPrintln("[Warning] [${mkTag()}] " + exception.stackTraceToString()) + } + + fun e(message: String) { + Libcore.nekoLogPrintln("[Error] [${mkTag()}] $message") + } + + fun e(message: String, exception: Throwable) { + Libcore.nekoLogPrintln("[Error] [${mkTag()}] $message" + "\n" + exception.stackTraceToString()) + } + + fun e(exception: Throwable) { + Libcore.nekoLogPrintln("[Error] [${mkTag()}] " + exception.stackTraceToString()) + } + +} + +fun InputStream.use(out: OutputStream) { + use { input -> + out.use { output -> + input.copyTo(output) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Nets.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Nets.kt new file mode 100644 index 0000000..52f098c --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Nets.kt @@ -0,0 +1,65 @@ +@file:Suppress("SpellCheckingInspection") + +package io.nekohasekai.sagernet.ktx + +import io.nekohasekai.sagernet.fmt.AbstractBean +import moe.matsuri.nb4a.utils.NGUtil +import okhttp3.HttpUrl +import java.net.InetSocketAddress +import java.net.Socket + +fun linkBuilder() = HttpUrl.Builder().scheme("https") + +fun HttpUrl.Builder.toLink(scheme: String, appendDefaultPort: Boolean = true): String { + var url = build() + val defaultPort = HttpUrl.defaultPort(url.scheme) + var replace = false + if (appendDefaultPort && url.port == defaultPort) { + url = url.newBuilder().port(14514).build() + replace = true + } + return url.toString().replace("${url.scheme}://", "$scheme://").let { + if (replace) it.replace(":14514", ":$defaultPort") else it + } +} + +fun String.isIpAddress(): Boolean { + return NGUtil.isIpv4Address(this) || NGUtil.isIpv6Address(this) +} + +fun String.isIpAddressV6(): Boolean { + return NGUtil.isIpv6Address(this) +} + +// [2001:4860:4860::8888] -> 2001:4860:4860::8888 +fun String.unwrapIPV6Host(): String { + if (startsWith("[") && endsWith("]")) { + return substring(1, length - 1).unwrapIPV6Host() + } + return this +} + +// [2001:4860:4860::8888] or 2001:4860:4860::8888 -> [2001:4860:4860::8888] +fun String.wrapIPV6Host(): String { + val unwrapped = this.unwrapIPV6Host() + if (unwrapped.isIpAddressV6()) { + return "[$unwrapped]" + } else { + return this + } +} + +fun AbstractBean.wrapUri(): String { + return "${finalAddress.wrapIPV6Host()}:$finalPort" +} + +fun mkPort(): Int { + val socket = Socket() + socket.reuseAddress = true + socket.bind(InetSocketAddress(0)) + val port = socket.localPort + socket.close() + return port +} + +const val USER_AGENT = "NekoBox/0.5 (Prefer Clash Format)" diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Preferences.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Preferences.kt new file mode 100644 index 0000000..70c395c --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Preferences.kt @@ -0,0 +1,62 @@ +package io.nekohasekai.sagernet.ktx + +import androidx.preference.PreferenceDataStore +import kotlin.reflect.KProperty + +fun PreferenceDataStore.string( + name: String, + defaultValue: () -> String = { "" }, +) = PreferenceProxy(name, defaultValue, ::getString, ::putString) + +fun PreferenceDataStore.boolean( + name: String, + defaultValue: () -> Boolean = { false }, +) = PreferenceProxy(name, defaultValue, ::getBoolean, ::putBoolean) + +fun PreferenceDataStore.int( + name: String, + defaultValue: () -> Int = { 0 }, +) = PreferenceProxy(name, defaultValue, ::getInt, ::putInt) + +fun PreferenceDataStore.stringSet( + name: String, + defaultValue: () -> Set = { setOf() }, +) = PreferenceProxy(name, defaultValue, ::getStringSet, ::putStringSet) + +fun PreferenceDataStore.stringToInt( + name: String, + defaultValue: () -> Int = { 0 }, +) = PreferenceProxy(name, defaultValue, { key, default -> + getString(key, "$default")?.toIntOrNull() ?: default +}, { key, value -> putString(key, "$value") }) + +fun PreferenceDataStore.stringToIntIfExists( + name: String, + defaultValue: () -> Int = { 0 }, +) = PreferenceProxy(name, defaultValue, { key, default -> + getString(key, "$default")?.toIntOrNull() ?: default +}, { key, value -> putString(key, value.takeIf { it > 0 }?.toString() ?: "") }) + +fun PreferenceDataStore.long( + name: String, + defaultValue: () -> Long = { 0L }, +) = PreferenceProxy(name, defaultValue, ::getLong, ::putLong) + +fun PreferenceDataStore.stringToLong( + name: String, + defaultValue: () -> Long = { 0L }, +) = PreferenceProxy(name, defaultValue, { key, default -> + getString(key, "$default")?.toLongOrNull() ?: default +}, { key, value -> putString(key, "$value") }) + +class PreferenceProxy( + val name: String, + val defaultValue: () -> T, + val getter: (String, T) -> T?, + val setter: (String, value: T) -> Unit, +) { + + operator fun setValue(thisObj: Any?, property: KProperty<*>, value: T) = setter(name, value) + operator fun getValue(thisObj: Any?, property: KProperty<*>) = getter(name, defaultValue())!! + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt b/app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt new file mode 100644 index 0000000..23e06e9 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt @@ -0,0 +1,315 @@ +@file:SuppressLint("SoonBlockedPrivateApi") + +package io.nekohasekai.sagernet.ktx + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.annotation.SuppressLint +import android.content.* +import android.content.pm.PackageInfo +import android.content.res.Resources +import android.os.Build +import android.system.Os +import android.system.OsConstants +import android.util.TypedValue +import android.view.View +import androidx.activity.result.ActivityResultLauncher +import androidx.annotation.AttrRes +import androidx.annotation.ColorRes +import androidx.core.content.ContextCompat +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.preference.Preference +import androidx.recyclerview.widget.LinearSmoothScroller +import androidx.recyclerview.widget.RecyclerView +import com.jakewharton.processphoenix.ProcessPhoenix +import io.nekohasekai.sagernet.BuildConfig +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.bg.Executable +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.ui.MainActivity +import io.nekohasekai.sagernet.ui.ThemedActivity +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import moe.matsuri.nb4a.utils.NGUtil +import java.io.FileDescriptor +import java.net.* +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger +import java.util.concurrent.atomic.AtomicLong +import java.util.concurrent.atomic.AtomicReference +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.reflect.KMutableProperty0 +import kotlin.reflect.KProperty +import kotlin.reflect.KProperty0 + + +inline fun Iterable.forEachTry(action: (T) -> Unit) { + var result: Exception? = null + for (element in this) try { + action(element) + } catch (e: Exception) { + if (result == null) result = e else result.addSuppressed(e) + } + if (result != null) { + throw result + } +} + +val Throwable.readableMessage + get() = localizedMessage.takeIf { !it.isNullOrBlank() } ?: javaClass.simpleName + +/** + * https://android.googlesource.com/platform/prebuilts/runtime/+/94fec32/appcompat/hiddenapi-light-greylist.txt#9466 + */ + +private val socketGetFileDescriptor = Socket::class.java.getDeclaredMethod("getFileDescriptor\$") +val Socket.fileDescriptor get() = socketGetFileDescriptor.invoke(this) as FileDescriptor + +private val getInt = FileDescriptor::class.java.getDeclaredMethod("getInt$") +val FileDescriptor.int get() = getInt.invoke(this) as Int + +suspend fun HttpURLConnection.useCancellable(block: suspend HttpURLConnection.() -> T): T { + return suspendCancellableCoroutine { cont -> + cont.invokeOnCancellation { + if (Build.VERSION.SDK_INT >= 26) disconnect() else GlobalScope.launch(Dispatchers.IO) { disconnect() } + } + GlobalScope.launch(Dispatchers.IO) { + try { + cont.resume(block()) + } catch (e: Throwable) { + cont.resumeWithException(e) + } + } + } +} + +fun parsePort(str: String?, default: Int, min: Int = 1025): Int { + val value = str?.toIntOrNull() ?: default + return if (value < min || value > 65535) default else value +} + +fun broadcastReceiver(callback: (Context, Intent) -> Unit): BroadcastReceiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) = callback(context, intent) + } + +fun Context.listenForPackageChanges(onetime: Boolean = true, callback: () -> Unit) = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + callback() + if (onetime) context.unregisterReceiver(this) + } + }.apply { + registerReceiver(this, IntentFilter().apply { + addAction(Intent.ACTION_PACKAGE_ADDED) + addAction(Intent.ACTION_PACKAGE_REMOVED) + addDataScheme("package") + }) + } + +val PackageInfo.signaturesCompat + get() = if (Build.VERSION.SDK_INT >= 28) signingInfo.apkContentsSigners else @Suppress("DEPRECATION") signatures + +/** + * Based on: https://stackoverflow.com/a/26348729/2245107 + */ +fun Resources.Theme.resolveResourceId(@AttrRes resId: Int): Int { + val typedValue = TypedValue() + if (!resolveAttribute(resId, typedValue, true)) throw Resources.NotFoundException() + return typedValue.resourceId +} + +fun Preference.remove() = parent!!.removePreference(this) + +/** + * A slightly more performant variant of parseNumericAddress. + * + * Bug in Android 9.0 and lower: https://issuetracker.google.com/issues/123456213 + */ + +private val parseNumericAddress by lazy { + InetAddress::class.java.getDeclaredMethod("parseNumericAddress", String::class.java).apply { + isAccessible = true + } +} + +fun String?.parseNumericAddress(): InetAddress? = + Os.inet_pton(OsConstants.AF_INET, this) ?: Os.inet_pton(OsConstants.AF_INET6, this)?.let { + if (Build.VERSION.SDK_INT >= 29) it else parseNumericAddress.invoke( + null, this + ) as InetAddress + } + +@JvmOverloads +fun DialogFragment.showAllowingStateLoss(fragmentManager: FragmentManager, tag: String? = null) { + if (!fragmentManager.isStateSaved) show(fragmentManager, tag) +} + +fun String.pathSafe(): String { + // " " encoded as + + return URLEncoder.encode(this, "UTF-8") +} + +fun String.urlSafe(): String { + return URLEncoder.encode(this, "UTF-8").replace("+", "%20") +} + +fun String.unUrlSafe(): String { + return NGUtil.urlDecode(this) +} + +fun RecyclerView.scrollTo(index: Int, force: Boolean = false) { + if (force) post { + scrollToPosition(index) + } + postDelayed({ + try { + layoutManager?.startSmoothScroll(object : LinearSmoothScroller(context) { + init { + targetPosition = index + } + + override fun getVerticalSnapPreference(): Int { + return SNAP_TO_START + } + }) + } catch (ignored: IllegalArgumentException) { + } + }, 300L) +} + +val app get() = SagerNet.application + +val shortAnimTime by lazy { + app.resources.getInteger(android.R.integer.config_shortAnimTime).toLong() +} + +fun View.crossFadeFrom(other: View) { + clearAnimation() + other.clearAnimation() + if (visibility == View.VISIBLE && other.visibility == View.GONE) return + alpha = 0F + visibility = View.VISIBLE + animate().alpha(1F).duration = shortAnimTime + other.animate().alpha(0F).setListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + other.visibility = View.GONE + } + }).duration = shortAnimTime +} + + +fun Fragment.snackbar(textId: Int) = (requireActivity() as MainActivity).snackbar(textId) +fun Fragment.snackbar(text: CharSequence) = (requireActivity() as MainActivity).snackbar(text) + +fun ThemedActivity.startFilesForResult( + launcher: ActivityResultLauncher, input: String +) { + try { + return launcher.launch(input) + } catch (_: ActivityNotFoundException) { + } catch (_: SecurityException) { + } + snackbar(getString(R.string.file_manager_missing)).show() +} + +fun Fragment.startFilesForResult( + launcher: ActivityResultLauncher, input: String +) { + try { + return launcher.launch(input) + } catch (_: ActivityNotFoundException) { + } catch (_: SecurityException) { + } + (requireActivity() as ThemedActivity).snackbar(getString(R.string.file_manager_missing)).show() +} + +fun Fragment.needReload() { + if (DataStore.serviceState.started) { + snackbar(getString(R.string.restart)).setAction(R.string.apply) { + SagerNet.reloadService() + }.show() + } +} + +fun Fragment.needRestart() { + snackbar("Restart APP to apply changes.").setAction(R.string.apply) { + Executable.killAll(true) + ProcessPhoenix.triggerRebirth( + requireContext(), Intent(requireContext(), MainActivity::class.java) + ) + }.show() +} + +fun Context.getColour(@ColorRes colorRes: Int): Int { + return ContextCompat.getColor(this, colorRes) +} + +fun Context.getColorAttr(@AttrRes resId: Int): Int { + return ContextCompat.getColor(this, TypedValue().also { + theme.resolveAttribute(resId, it, true) + }.resourceId) +} + +var isExpert: Boolean + get() = BuildConfig.DEBUG || DataStore.isExpert + set(value) = TODO() + +val isExpertFlavor = ((BuildConfig.FLAVOR == "expert") || BuildConfig.DEBUG) +const val isOss = BuildConfig.FLAVOR == "oss" +const val isFdroid = BuildConfig.FLAVOR == "fdroid" + +fun Continuation.tryResume(value: T) { + try { + resumeWith(Result.success(value)) + } catch (ignored: IllegalStateException) { + } +} + +fun Continuation.tryResumeWithException(exception: Throwable) { + try { + resumeWith(Result.failure(exception)) + } catch (ignored: IllegalStateException) { + } +} + +operator fun KProperty0.getValue(thisRef: Any?, property: KProperty<*>): F = get() +operator fun KMutableProperty0.setValue( + thisRef: Any?, property: KProperty<*>, value: F +) = set(value) + +operator fun AtomicBoolean.getValue(thisRef: Any?, property: KProperty<*>): Boolean = get() +operator fun AtomicBoolean.setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) = + set(value) + +operator fun AtomicInteger.getValue(thisRef: Any?, property: KProperty<*>): Int = get() +operator fun AtomicInteger.setValue(thisRef: Any?, property: KProperty<*>, value: Int) = set(value) + +operator fun AtomicLong.getValue(thisRef: Any?, property: KProperty<*>): Long = get() +operator fun AtomicLong.setValue(thisRef: Any?, property: KProperty<*>, value: Long) = set(value) + +operator fun AtomicReference.getValue(thisRef: Any?, property: KProperty<*>): T = get() +operator fun AtomicReference.setValue(thisRef: Any?, property: KProperty<*>, value: T) = + set(value) + +operator fun Map.getValue(thisRef: K, property: KProperty<*>) = get(thisRef) +operator fun MutableMap.setValue(thisRef: K, property: KProperty<*>, value: V?) { + + if (value != null) { + + put(thisRef, value) + + } else { + + remove(thisRef) + + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/plugin/PluginManager.kt b/app/src/main/java/io/nekohasekai/sagernet/plugin/PluginManager.kt new file mode 100644 index 0000000..76fd704 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/plugin/PluginManager.kt @@ -0,0 +1,70 @@ +package io.nekohasekai.sagernet.plugin + +import android.content.pm.ComponentInfo +import android.content.pm.ProviderInfo +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.bg.BaseService +import io.nekohasekai.sagernet.ktx.Logs +import moe.matsuri.nb4a.plugin.Plugins +import java.io.File +import java.io.FileNotFoundException + +object PluginManager { + + class PluginNotFoundException(val plugin: String) : FileNotFoundException(plugin), + BaseService.ExpectedException { + override fun getLocalizedMessage() = + SagerNet.application.getString(R.string.plugin_unknown, plugin) + } + + data class InitResult( + val path: String, + val info: ProviderInfo, + ) + + @Throws(Throwable::class) + fun init(pluginId: String): InitResult? { + if (pluginId.isEmpty()) return null + var throwable: Throwable? = null + + try { + val result = initNative(pluginId) + if (result != null) return result + } catch (t: Throwable) { + if (throwable == null) throwable = t else Logs.w(t) + } + + throw throwable ?: PluginNotFoundException(pluginId) + } + + private fun initNative(pluginId: String): InitResult? { + val info = Plugins.getPlugin(pluginId) ?: return null + + try { + initNativeFaster(info)?.also { return InitResult(it, info) } + } catch (t: Throwable) { + Logs.w("Initializing native plugin faster mode failed", t) + } + + Logs.w("Init native returns empty result") + return null + } + + private fun initNativeFaster(provider: ProviderInfo): String? { + return provider.loadString(Plugins.METADATA_KEY_EXECUTABLE_PATH) + ?.let { relativePath -> + File(provider.applicationInfo.nativeLibraryDir).resolve(relativePath).apply { + check(canExecute()) + }.absolutePath + } + } + + fun ComponentInfo.loadString(key: String) = when (val value = metaData.get(key)) { + is String -> value + is Int -> SagerNet.application.packageManager.getResourcesForApplication(applicationInfo) + .getString(value) + null -> null + else -> error("meta-data $key has invalid type ${value.javaClass}") + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt new file mode 100644 index 0000000..5b9a9f8 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt @@ -0,0 +1,183 @@ +package io.nekohasekai.sagernet.ui + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.os.PowerManager +import android.provider.Settings +import android.text.util.Linkify +import android.view.View +import androidx.activity.result.component1 +import androidx.activity.result.component2 +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.view.ViewCompat +import androidx.recyclerview.widget.RecyclerView +import com.danielstone.materialaboutlibrary.MaterialAboutFragment +import com.danielstone.materialaboutlibrary.items.MaterialAboutActionItem +import com.danielstone.materialaboutlibrary.model.MaterialAboutCard +import com.danielstone.materialaboutlibrary.model.MaterialAboutList +import io.nekohasekai.sagernet.BuildConfig +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.databinding.LayoutAboutBinding +import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.plugin.PluginManager.loadString +import io.nekohasekai.sagernet.utils.PackageCache +import io.nekohasekai.sagernet.widget.ListHolderListener +import libcore.Libcore +import moe.matsuri.nb4a.plugin.Plugins + +class AboutFragment : ToolbarFragment(R.layout.layout_about) { + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val binding = LayoutAboutBinding.bind(view) + + ViewCompat.setOnApplyWindowInsetsListener(view, ListHolderListener) + toolbar.setTitle(R.string.menu_about) + + parentFragmentManager.beginTransaction() + .replace(R.id.about_fragment_holder, AboutContent()) + .commitAllowingStateLoss() + + runOnDefaultDispatcher { + val license = view.context.assets.open("LICENSE").bufferedReader().readText() + onMainDispatcher { + binding.license.text = license + Linkify.addLinks(binding.license, Linkify.EMAIL_ADDRESSES or Linkify.WEB_URLS) + } + } + } + + class AboutContent : MaterialAboutFragment() { + + val requestIgnoreBatteryOptimizations = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { (resultCode, _) -> + if (resultCode == Activity.RESULT_OK) { + parentFragmentManager.beginTransaction() + .replace(R.id.about_fragment_holder, AboutContent()) + .commitAllowingStateLoss() + } + } + + override fun getMaterialAboutList(activityContext: Context): MaterialAboutList { + + var versionName = BuildConfig.VERSION_NAME + if (!isOss) { + versionName += " ${BuildConfig.FLAVOR}" + } + if (BuildConfig.DEBUG) { + versionName += " DEBUG" + } + + return MaterialAboutList.Builder() + .addCard(MaterialAboutCard.Builder() + .outline(false) + .addItem(MaterialAboutActionItem.Builder() + .icon(R.drawable.ic_baseline_update_24) + .text(R.string.app_version) + .subText(versionName) + .setOnClickAction { + requireContext().launchCustomTab( + "https://github.com/MatsuriDayo/NekoBoxForAndroid/releases" + ) + } + .build()) + .addItem(MaterialAboutActionItem.Builder() + .icon(R.drawable.ic_baseline_airplanemode_active_24) + .text(getString(R.string.version_x, "sing-box")) + .subText(Libcore.versionBox()) + .setOnClickAction { } + .build()) + .apply { + for ((_, pkg) in PackageCache.installedPluginPackages) { + try { + val pluginId = pkg.providers[0].loadString(Plugins.METADATA_KEY_ID) + if (pluginId.isNullOrBlank() || pluginId.startsWith(Plugins.AUTHORITIES_PREFIX_NEKO_PLUGIN)) continue + addItem(MaterialAboutActionItem.Builder() + .icon(R.drawable.ic_baseline_nfc_24) + .text( + getString( + R.string.version_x, + pluginId + ) + " (${Plugins.displayExeProvider(pkg.packageName)})" + ) + .subText("v" + pkg.versionName) + .setOnClickAction { + startActivity(Intent().apply { + action = + Settings.ACTION_APPLICATION_DETAILS_SETTINGS + data = Uri.fromParts( + "package", pkg.packageName, null + ) + }) + } + .build()) + } catch (e: Exception) { + Logs.w(e) + } + } + } + .apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val pm = app.getSystemService(Context.POWER_SERVICE) as PowerManager + if (!pm.isIgnoringBatteryOptimizations(app.packageName)) { + addItem(MaterialAboutActionItem.Builder() + .icon(R.drawable.ic_baseline_running_with_errors_24) + .text(R.string.ignore_battery_optimizations) + .subText(R.string.ignore_battery_optimizations_sum) + .setOnClickAction { + requestIgnoreBatteryOptimizations.launch( + Intent( + Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + Uri.parse("package:${app.packageName}") + ) + ) + } + .build()) + } + } + } + .build()) + .addCard(MaterialAboutCard.Builder() + .outline(false) + .title(R.string.project) + .addItem(MaterialAboutActionItem.Builder() + .icon(R.drawable.ic_baseline_sanitizer_24) + .text(R.string.github) + .setOnClickAction { + requireContext().launchCustomTab( + "https://github.com/MatsuriDayo/NekoBoxForAndroid" + + ) + } + .build()) + .addItem(MaterialAboutActionItem.Builder() + .icon(R.drawable.ic_qu_shadowsocks_foreground) + .text(R.string.telegram) + .setOnClickAction { + requireContext().launchCustomTab( + "https://t.me/MatsuriDayo" + ) + } + .build()) + .build()) + .build() + + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + view.findViewById(R.id.mal_recyclerview).apply { + overScrollMode = RecyclerView.OVER_SCROLL_NEVER + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt new file mode 100644 index 0000000..34768ec --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt @@ -0,0 +1,381 @@ +package io.nekohasekai.sagernet.ui + +import android.content.Intent +import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.util.SparseBooleanArray +import android.view.* +import android.widget.Filter +import android.widget.Filterable +import androidx.annotation.UiThread +import androidx.core.util.contains +import androidx.core.util.set +import androidx.core.view.ViewCompat +import androidx.core.view.isGone +import androidx.core.view.isVisible +import androidx.core.widget.addTextChangedListener +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.snackbar.Snackbar +import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView +import io.nekohasekai.sagernet.BuildConfig +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.databinding.LayoutAppListBinding +import io.nekohasekai.sagernet.databinding.LayoutAppsItemBinding +import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.utils.PackageCache +import io.nekohasekai.sagernet.widget.ListHolderListener +import io.nekohasekai.sagernet.widget.ListListener +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.withContext +import moe.matsuri.nb4a.proxy.neko.NekoJSInterface +import moe.matsuri.nb4a.plugin.NekoPluginManager +import moe.matsuri.nb4a.plugin.Plugins +import moe.matsuri.nb4a.ui.Dialogs +import kotlin.coroutines.coroutineContext + +class AppListActivity : ThemedActivity() { + companion object { + private const val SWITCH = "switch" + } + + private class ProxiedApp( + private val pm: PackageManager, private val appInfo: ApplicationInfo, + val packageName: String, + ) { + val name: CharSequence = appInfo.loadLabel(pm) // cached for sorting + val icon: Drawable get() = appInfo.loadIcon(pm) + val uid get() = appInfo.uid + val sys get() = (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0 + } + + private inner class AppViewHolder(val binding: LayoutAppsItemBinding) : RecyclerView.ViewHolder( + binding.root + ), + View.OnClickListener { + private lateinit var item: ProxiedApp + + init { + binding.root.setOnClickListener(this) + } + + fun bind(app: ProxiedApp) { + item = app + binding.itemicon.setImageDrawable(app.icon) + binding.title.text = app.name + if (forNeko) { + val packageName = app.packageName + val ver = getCachedApps()[packageName]?.versionName ?: "" + binding.desc.text = "$packageName ($ver)" + // + binding.button.isVisible = true + binding.button.setImageDrawable(getDrawable(R.drawable.ic_baseline_info_24)) + binding.button.setOnClickListener { + runOnIoDispatcher { + val jsi = NekoJSInterface(packageName) + jsi.init() + val about = jsi.getAbout() + jsi.destorySuspend() + Dialogs.message( + this@AppListActivity, app.name as String, + "PackageName: ${packageName}\n" + + "Version: ${ver}\n" + + "--------\n" + about + ) + } + } + } else { + binding.desc.text = "${app.packageName} (${app.uid})" + } + handlePayload(listOf(SWITCH)) + } + + fun handlePayload(payloads: List) { + if (payloads.contains(SWITCH)) { + val selected = isProxiedApp(item) + binding.itemcheck.isChecked = selected + binding.button.isVisible = forNeko && selected + } + } + + override fun onClick(v: View?) { + if (isProxiedApp(item)) proxiedUids.delete(item.uid) else proxiedUids[item.uid] = true + DataStore.routePackages = apps.filter { isProxiedApp(it) } + .joinToString("\n") { it.packageName } + + if (forNeko) { + if (isProxiedApp(item)) { + runOnIoDispatcher { + try { + NekoPluginManager.installPlugin(item.packageName) + } catch (e: Exception) { + // failed UI + runOnUiThread { onClick(v) } + Dialogs.logExceptionAndShow(this@AppListActivity, e) { } + } + } + } else { + NekoPluginManager.removeManagedPlugin(item.packageName) + } + } + + appsAdapter.notifyItemRangeChanged(0, appsAdapter.itemCount, SWITCH) + } + } + + private inner class AppsAdapter : RecyclerView.Adapter(), + Filterable, + FastScrollRecyclerView.SectionedAdapter { + var filteredApps = apps + + suspend fun reload() { + apps = getCachedApps().map { (packageName, packageInfo) -> + coroutineContext[Job]!!.ensureActive() + ProxiedApp(packageManager, packageInfo.applicationInfo, packageName) + }.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() })) + } + + override fun onBindViewHolder(holder: AppViewHolder, position: Int) = + holder.bind(filteredApps[position]) + + override fun onBindViewHolder(holder: AppViewHolder, position: Int, payloads: List) { + if (payloads.isNotEmpty()) { + @Suppress("UNCHECKED_CAST") holder.handlePayload(payloads as List) + return + } + + onBindViewHolder(holder, position) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder = + AppViewHolder(LayoutAppsItemBinding.inflate(layoutInflater, parent, false)) + + override fun getItemCount(): Int = filteredApps.size + + private val filterImpl = object : Filter() { + override fun performFiltering(constraint: CharSequence) = FilterResults().apply { + var filteredApps = if (constraint.isEmpty()) apps else apps.filter { + it.name.contains(constraint, true) || it.packageName.contains( + constraint, true + ) || it.uid.toString().contains(constraint) + } + if (!sysApps) filteredApps = filteredApps.filter { !it.sys } + count = filteredApps.size + values = filteredApps + } + + override fun publishResults(constraint: CharSequence, results: FilterResults) { + @Suppress("UNCHECKED_CAST") + filteredApps = results.values as List + notifyDataSetChanged() + } + } + + override fun getFilter(): Filter = filterImpl + + override fun getSectionName(position: Int): String { + return filteredApps[position].name.firstOrNull()?.toString() ?: "" + } + + } + + private val loading by lazy { findViewById(R.id.loading) } + + private lateinit var binding: LayoutAppListBinding + private val proxiedUids = SparseBooleanArray() + private var loader: Job? = null + private var apps = emptyList() + private val appsAdapter = AppsAdapter() + + private fun initProxiedUids(str: String = DataStore.routePackages) { + proxiedUids.clear() + val apps = getCachedApps() + for (line in str.lineSequence()) proxiedUids[(apps[line] + ?: continue).applicationInfo.uid] = true + } + + private fun isProxiedApp(app: ProxiedApp) = proxiedUids[app.uid] + + @UiThread + private fun loadApps() { + loader?.cancel() + loader = lifecycleScope.launchWhenCreated { + loading.crossFadeFrom(binding.list) + val adapter = binding.list.adapter as AppsAdapter + withContext(Dispatchers.IO) { adapter.reload() } + adapter.filter.filter(binding.search.text?.toString() ?: "") + binding.list.crossFadeFrom(loading) + } + } + + private var forNeko = false + + fun getCachedApps(): MutableMap { + val packages = + if (forNeko) PackageCache.installedPluginPackages else PackageCache.installedPackages + return packages.toMutableMap().apply { + remove(BuildConfig.APPLICATION_ID) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + forNeko = intent?.hasExtra(Key.NEKO_PLUGIN_MANAGED) == true + + binding = LayoutAppListBinding.inflate(layoutInflater) + setContentView(binding.root) + + ListHolderListener.setup(this) + setSupportActionBar(binding.toolbar) + supportActionBar?.apply { + setTitle(R.string.select_apps) + setDisplayHomeAsUpEnabled(true) + setHomeAsUpIndicator(R.drawable.ic_navigation_close) + } + + initProxiedUids() + binding.list.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false) + binding.list.itemAnimator = DefaultItemAnimator() + binding.list.adapter = appsAdapter + + ViewCompat.setOnApplyWindowInsetsListener(binding.root, ListListener) + + binding.search.addTextChangedListener { + appsAdapter.filter.filter(it?.toString() ?: "") + } + + binding.showSystemApps.isChecked = sysApps + binding.showSystemApps.setOnCheckedChangeListener { _, isChecked -> + sysApps = isChecked + appsAdapter.filter.filter(binding.search.text?.toString() ?: "") + } + + if (forNeko) { + DataStore.routePackages = DataStore.nekoPlugins + binding.search.setText(Plugins.AUTHORITIES_PREFIX_NEKO_PLUGIN) + } + + binding.searchLayout.isGone = forNeko + binding.hintNekoPlugin.isGone = !forNeko + binding.actionLearnMore.setOnClickListener { + launchCustomTab("https://matsuridayo.github.io/m-plugin/") + } + + loadApps() + } + + private var sysApps = false + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + if (forNeko) { + menuInflater.inflate(R.menu.app_list_neko_menu, menu) + } else { + menuInflater.inflate(R.menu.app_list_menu, menu) + } + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_invert_selections -> { + runOnDefaultDispatcher { + for (app in apps) { + if (proxiedUids.contains(app.uid)) { + proxiedUids.delete(app.uid) + } else { + proxiedUids[app.uid] = true + } + } + DataStore.routePackages = apps.filter { isProxiedApp(it) } + .joinToString("\n") { it.packageName } + apps = apps.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() })) + onMainDispatcher { + appsAdapter.filter.filter(binding.search.text?.toString() ?: "") + } + } + + return true + } + R.id.action_clear_selections -> { + runOnDefaultDispatcher { + proxiedUids.clear() + DataStore.routePackages = "" + apps = apps.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() })) + onMainDispatcher { + appsAdapter.filter.filter(binding.search.text?.toString() ?: "") + } + } + } + R.id.action_export_clipboard -> { + val success = SagerNet.trySetPrimaryClip("false\n${DataStore.routePackages}") + Snackbar.make( + binding.list, + if (success) R.string.action_export_msg else R.string.action_export_err, + Snackbar.LENGTH_LONG + ).show() + return true + } + R.id.action_import_clipboard -> { + val proxiedAppString = + SagerNet.clipboard.primaryClip?.getItemAt(0)?.text?.toString() + if (!proxiedAppString.isNullOrEmpty()) { + val i = proxiedAppString.indexOf('\n') + try { + val apps = if (i < 0) "" else proxiedAppString.substring(i + 1) + DataStore.routePackages = apps + Snackbar.make( + binding.list, R.string.action_import_msg, Snackbar.LENGTH_LONG + ).show() + initProxiedUids(apps) + appsAdapter.notifyItemRangeChanged(0, appsAdapter.itemCount, SWITCH) + return true + } catch (_: IllegalArgumentException) { + } + } + Snackbar.make(binding.list, R.string.action_import_err, Snackbar.LENGTH_LONG).show() + } + R.id.uninstall_all -> { + runOnDefaultDispatcher { + proxiedUids.clear() + DataStore.routePackages = "" + apps = apps.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() })) + NekoPluginManager.plugins.forEach { + NekoPluginManager.removeManagedPlugin(it) + } + onMainDispatcher { + appsAdapter.notifyItemRangeChanged(0, appsAdapter.itemCount, SWITCH) + } + } + } + } + return super.onOptionsItemSelected(item) + } + + override fun onSupportNavigateUp(): Boolean { + if (!super.onSupportNavigateUp()) finish() + return true + } + + override fun supportNavigateUpTo(upIntent: Intent) = + super.supportNavigateUpTo(upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)) + + override fun onKeyUp(keyCode: Int, event: KeyEvent?) = if (keyCode == KeyEvent.KEYCODE_MENU) { + if (binding.toolbar.isOverflowMenuShowing) binding.toolbar.hideOverflowMenu() else binding.toolbar.showOverflowMenu() + } else super.onKeyUp(keyCode, event) + + override fun onDestroy() { + loader?.cancel() + if (forNeko) DataStore.nekoPlugins = DataStore.routePackages + super.onDestroy() + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/AppManagerActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/AppManagerActivity.kt new file mode 100644 index 0000000..75d2a6b --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/AppManagerActivity.kt @@ -0,0 +1,477 @@ +package io.nekohasekai.sagernet.ui + +import android.annotation.SuppressLint +import android.content.Intent +import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.util.SparseBooleanArray +import android.view.* +import android.widget.Filter +import android.widget.Filterable +import android.widget.TextView +import androidx.annotation.UiThread +import androidx.core.util.contains +import androidx.core.util.set +import androidx.core.view.ViewCompat +import androidx.core.widget.addTextChangedListener +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.snackbar.Snackbar +import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView +import io.nekohasekai.sagernet.BuildConfig +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.databinding.LayoutAppsBinding +import io.nekohasekai.sagernet.databinding.LayoutAppsItemBinding +import io.nekohasekai.sagernet.databinding.LayoutLoadingBinding +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.ktx.crossFadeFrom +import io.nekohasekai.sagernet.ktx.onMainDispatcher +import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher +import io.nekohasekai.sagernet.utils.PackageCache +import io.nekohasekai.sagernet.widget.ListHolderListener +import io.nekohasekai.sagernet.widget.ListListener +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.ensureActive +import kotlinx.coroutines.withContext +import okhttp3.internal.closeQuietly +import org.jf.dexlib2.dexbacked.DexBackedDexFile +import org.jf.dexlib2.iface.DexFile +import java.io.File +import java.util.zip.ZipException +import java.util.zip.ZipFile +import kotlin.coroutines.coroutineContext + +class AppManagerActivity : ThemedActivity() { + companion object { + @SuppressLint("StaticFieldLeak") + private var instance: AppManagerActivity? = null + private const val SWITCH = "switch" + + private val cachedApps + get() = PackageCache.installedPackages.toMutableMap().apply { + remove(BuildConfig.APPLICATION_ID) + } + } + + private class ProxiedApp( + private val pm: PackageManager, private val appInfo: ApplicationInfo, + val packageName: String, + ) { + val name: CharSequence = appInfo.loadLabel(pm) // cached for sorting + val icon: Drawable get() = appInfo.loadIcon(pm) + val uid get() = appInfo.uid + val sys get() = (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0 + } + + private inner class AppViewHolder(val binding: LayoutAppsItemBinding) : RecyclerView.ViewHolder( + binding.root + ), + View.OnClickListener { + private lateinit var item: ProxiedApp + + init { + binding.root.setOnClickListener(this) + } + + fun bind(app: ProxiedApp) { + item = app + binding.itemicon.setImageDrawable(app.icon) + binding.title.text = app.name + binding.desc.text = "${app.packageName} (${app.uid})" + binding.itemcheck.isChecked = isProxiedApp(app) + } + + fun handlePayload(payloads: List) { + if (payloads.contains(SWITCH)) binding.itemcheck.isChecked = isProxiedApp(item) + } + + override fun onClick(v: View?) { + if (isProxiedApp(item)) proxiedUids.delete(item.uid) else proxiedUids[item.uid] = true + DataStore.individual = apps.filter { isProxiedApp(it) } + .joinToString("\n") { it.packageName } + + appsAdapter.notifyItemRangeChanged(0, appsAdapter.itemCount, SWITCH) + } + } + + private inner class AppsAdapter : RecyclerView.Adapter(), + Filterable, + FastScrollRecyclerView.SectionedAdapter { + var filteredApps = apps + + suspend fun reload() { + apps = cachedApps.map { (packageName, packageInfo) -> + coroutineContext[Job]!!.ensureActive() + ProxiedApp(packageManager, packageInfo.applicationInfo, packageName) + }.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() })) + } + + override fun onBindViewHolder(holder: AppViewHolder, position: Int) = + holder.bind(filteredApps[position]) + + override fun onBindViewHolder(holder: AppViewHolder, position: Int, payloads: List) { + if (payloads.isNotEmpty()) { + @Suppress("UNCHECKED_CAST") holder.handlePayload(payloads as List) + return + } + + onBindViewHolder(holder, position) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder = + AppViewHolder(LayoutAppsItemBinding.inflate(layoutInflater, parent, false)) + + override fun getItemCount(): Int = filteredApps.size + + private val filterImpl = object : Filter() { + override fun performFiltering(constraint: CharSequence) = FilterResults().apply { + var filteredApps = if (constraint.isEmpty()) apps else apps.filter { + it.name.contains(constraint, true) || it.packageName.contains( + constraint, true + ) || it.uid.toString().contains(constraint) + } + if (!sysApps) filteredApps = filteredApps.filter { !it.sys } + count = filteredApps.size + values = filteredApps + } + + override fun publishResults(constraint: CharSequence, results: FilterResults) { + @Suppress("UNCHECKED_CAST") filteredApps = results.values as List + notifyDataSetChanged() + } + } + + override fun getFilter(): Filter = filterImpl + + override fun getSectionName(position: Int): String { + return filteredApps[position].name.firstOrNull()?.toString() ?: "" + } + + } + + private val loading by lazy { findViewById(R.id.loading) } + + private lateinit var binding: LayoutAppsBinding + private val proxiedUids = SparseBooleanArray() + private var loader: Job? = null + private var apps = emptyList() + private val appsAdapter = AppsAdapter() + + private fun initProxiedUids(str: String = DataStore.individual) { + proxiedUids.clear() + val apps = cachedApps + for (line in str.lineSequence()) proxiedUids[(apps[line] + ?: continue).applicationInfo.uid] = true + } + + private fun isProxiedApp(app: ProxiedApp) = proxiedUids[app.uid] + + @UiThread + private fun loadApps() { + loader?.cancel() + loader = lifecycleScope.launchWhenCreated { + loading.crossFadeFrom(binding.list) + val adapter = binding.list.adapter as AppsAdapter + withContext(Dispatchers.IO) { adapter.reload() } + adapter.filter.filter(binding.search.text?.toString() ?: "") + binding.list.crossFadeFrom(loading) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = LayoutAppsBinding.inflate(layoutInflater) + setContentView(binding.root) + + ListHolderListener.setup(this) + setSupportActionBar(binding.toolbar) + supportActionBar?.apply { + setTitle(R.string.proxied_apps) + setDisplayHomeAsUpEnabled(true) + setHomeAsUpIndicator(R.drawable.ic_navigation_close) + } + + if (!DataStore.proxyApps) { + DataStore.proxyApps = true + } + + binding.bypassGroup.check(if (DataStore.bypass) R.id.appProxyModeBypass else R.id.appProxyModeOn) + binding.bypassGroup.setOnCheckedChangeListener { _, checkedId -> + when (checkedId) { + R.id.appProxyModeDisable -> { + DataStore.proxyApps = false + finish() + } + R.id.appProxyModeOn -> DataStore.bypass = false + R.id.appProxyModeBypass -> DataStore.bypass = true + } + } + + initProxiedUids() + binding.list.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false) + binding.list.itemAnimator = DefaultItemAnimator() + binding.list.adapter = appsAdapter + + ViewCompat.setOnApplyWindowInsetsListener(binding.root, ListListener) + + binding.search.addTextChangedListener { + appsAdapter.filter.filter(it?.toString() ?: "") + } + + binding.showSystemApps.isChecked = sysApps + binding.showSystemApps.setOnCheckedChangeListener { _, isChecked -> + sysApps = isChecked + appsAdapter.filter.filter(binding.search.text?.toString() ?: "") + } + + instance = this + loadApps() + } + + private var sysApps = true + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.per_app_proxy_menu, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_scan_china_apps -> { + scanChinaApps() + return true + } + R.id.action_invert_selections -> { + runOnDefaultDispatcher { + for (app in apps) { + if (proxiedUids.contains(app.uid)) { + proxiedUids.delete(app.uid) + } else { + proxiedUids[app.uid] = true + } + } + DataStore.individual = apps.filter { isProxiedApp(it) } + .joinToString("\n") { it.packageName } + apps = apps.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() })) + onMainDispatcher { + appsAdapter.filter.filter(binding.search.text?.toString() ?: "") + } + } + + return true + } + R.id.action_clear_selections -> { + runOnDefaultDispatcher { + proxiedUids.clear() + DataStore.individual = "" + apps = apps.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() })) + onMainDispatcher { + appsAdapter.filter.filter(binding.search.text?.toString() ?: "") + } + } + } + R.id.action_export_clipboard -> { + val success = SagerNet.trySetPrimaryClip("${DataStore.bypass}\n${DataStore.individual}") + Snackbar.make( + binding.list, + if (success) R.string.action_export_msg else R.string.action_export_err, + Snackbar.LENGTH_LONG + ).show() + return true + } + R.id.action_import_clipboard -> { + val proxiedAppString = SagerNet.clipboard.primaryClip?.getItemAt(0)?.text?.toString() + if (!proxiedAppString.isNullOrEmpty()) { + val i = proxiedAppString.indexOf('\n') + try { + val (enabled, apps) = if (i < 0) { + proxiedAppString to "" + } else proxiedAppString.substring( + 0, i + ) to proxiedAppString.substring(i + 1) + binding.bypassGroup.check(if (enabled.toBoolean()) R.id.appProxyModeBypass else R.id.appProxyModeOn) + DataStore.individual = apps + Snackbar.make( + binding.list, R.string.action_import_msg, Snackbar.LENGTH_LONG + ).show() + initProxiedUids(apps) + appsAdapter.notifyItemRangeChanged(0, appsAdapter.itemCount, SWITCH) + return true + } catch (_: IllegalArgumentException) { + } + } + Snackbar.make(binding.list, R.string.action_import_err, Snackbar.LENGTH_LONG).show() + } + } + return super.onOptionsItemSelected(item) + } + + @SuppressLint("SetTextI18n") + private fun scanChinaApps() { + + val text: TextView + + val dialog = MaterialAlertDialogBuilder(this).setView( + LayoutLoadingBinding.inflate(layoutInflater).apply { + text = loadingText + }.root + ).setCancelable(false).show() + + val txt = text.text.toString() + + runOnDefaultDispatcher { + val chinaApps = ArrayList>() + val chinaRegex = ("(" + arrayOf( + "com.tencent", + "com.alibaba", + "com.umeng", + "com.qihoo", + "com.ali", + "com.alipay", + "com.amap", + "com.sina", + "com.weibo", + "com.vivo", + "com.xiaomi", + "com.huawei", + "com.taobao", + "com.secneo", + "s.h.e.l.l", + "com.stub", + "com.kiwisec", + "com.secshell", + "com.wrapper", + "cn.securitystack", + "com.mogosec", + "com.secoen", + "com.netease", + "com.mx", + "com.qq.e", + "com.baidu", + "com.bytedance", + "com.bugly", + "com.miui", + "com.oppo", + "com.coloros", + "com.iqoo", + "com.meizu", + "com.gionee", + "cn.nubia" + ).joinToString("|") { "${it.replace(".", "\\.")}\\." } + ").*").toRegex() + + val bypass = DataStore.bypass + val cachedApps = cachedApps + + apps = cachedApps.map { (packageName, packageInfo) -> + kotlin.coroutines.coroutineContext[Job]!!.ensureActive() + ProxiedApp(packageManager, packageInfo.applicationInfo, packageName) + }.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() })) + + scan@ for ((pkg, app) in cachedApps.entries) { + /*if (!sysApps && app.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0) { + continue + }*/ + + val index = appsAdapter.filteredApps.indexOfFirst { it.uid == app.applicationInfo.uid } + var changed = false + + onMainDispatcher { + text.text = (txt + " " + app.packageName + "\n\n" + chinaApps.map { it.second } + .reversed() + .joinToString("\n", postfix = "\n")).trim() + } + + try { + + val dex = File(app.applicationInfo.publicSourceDir) + val zipFile = ZipFile(dex) + var dexFile: DexFile + + for (entry in zipFile.entries()) { + if (entry.name.startsWith("classes") && entry.name.endsWith(".dex")) { + val input = zipFile.getInputStream(entry).readBytes() + dexFile = try { + DexBackedDexFile.fromInputStream(null, input.inputStream()) + } catch (e: Exception) { + Logs.w(e) + break + } + for (clazz in dexFile.classes) { + val clazzName = clazz.type.substring(1, clazz.type.length - 1) + .replace("/", ".") + .replace("$", ".") + + if (clazzName.matches(chinaRegex)) { + chinaApps.add( + app to app.applicationInfo.loadLabel(packageManager) + .toString() + ) + zipFile.closeQuietly() + + if (bypass) { + changed = !proxiedUids[app.applicationInfo.uid] + proxiedUids[app.applicationInfo.uid] = true + } else { + proxiedUids.delete(app.applicationInfo.uid) + } + + continue@scan + } + } + } + } + zipFile.closeQuietly() + + if (bypass) { + proxiedUids.delete(app.applicationInfo.uid) + } else { + changed = !proxiedUids[index] + proxiedUids[app.applicationInfo.uid] = true + } + + } catch (e: ZipException) { + Logs.w("Error in pkg ${app.packageName}:${app.versionName}", e) + continue + } + + } + + DataStore.individual = apps.filter { isProxiedApp(it) } + .joinToString("\n") { it.packageName } + + apps = apps.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() })) + + onMainDispatcher { + appsAdapter.filter.filter(binding.search.text?.toString() ?: "") + + dialog.dismiss() + } + + } + + + } + + override fun supportNavigateUpTo(upIntent: Intent) = + super.supportNavigateUpTo(upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)) + + override fun onKeyUp(keyCode: Int, event: KeyEvent?) = if (keyCode == KeyEvent.KEYCODE_MENU) { + if (binding.toolbar.isOverflowMenuShowing) binding.toolbar.hideOverflowMenu() else binding.toolbar.showOverflowMenu() + } else super.onKeyUp(keyCode, event) + + override fun onDestroy() { + instance = null + loader?.cancel() + super.onDestroy() + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt new file mode 100644 index 0000000..be36096 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt @@ -0,0 +1,342 @@ +package io.nekohasekai.sagernet.ui + +import android.os.Bundle +import android.provider.OpenableColumns +import android.text.format.DateFormat +import android.view.Menu +import android.view.MenuItem +import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.view.isInvisible +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.snackbar.Snackbar +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.databinding.LayoutAssetItemBinding +import io.nekohasekai.sagernet.databinding.LayoutAssetsBinding +import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.widget.UndoSnackbarManager +import libcore.Libcore +import org.json.JSONObject +import java.io.File +import java.io.FileWriter +import java.util.* +import java.util.concurrent.atomic.AtomicInteger + +class AssetsActivity : ThemedActivity() { + + lateinit var adapter: AssetAdapter + lateinit var layout: LayoutAssetsBinding + lateinit var undoManager: UndoSnackbarManager + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = LayoutAssetsBinding.inflate(layoutInflater) + layout = binding + setContentView(binding.root) + + setSupportActionBar(findViewById(R.id.toolbar)) + supportActionBar?.apply { + setTitle(R.string.route_assets) + setDisplayHomeAsUpEnabled(true) + setHomeAsUpIndicator(R.drawable.ic_navigation_close) + } + + binding.recyclerView.layoutManager = FixedLinearLayoutManager(binding.recyclerView) + adapter = AssetAdapter() + binding.recyclerView.adapter = adapter + + binding.refreshLayout.setOnRefreshListener { + adapter.reloadAssets() + binding.refreshLayout.isRefreshing = false + } + binding.refreshLayout.setColorSchemeColors(getColorAttr(R.attr.primaryOrTextPrimary)) + + undoManager = UndoSnackbarManager(this, adapter) + + ItemTouchHelper(object : ItemTouchHelper.SimpleCallback( + 0, ItemTouchHelper.START + ) { + + override fun getSwipeDirs( + recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder + ): Int { + val index = viewHolder.bindingAdapterPosition + if (index < 2) return 0 + return super.getSwipeDirs(recyclerView, viewHolder) + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val index = viewHolder.bindingAdapterPosition + adapter.remove(index) + undoManager.remove(index to (viewHolder as AssetHolder).file) + } + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ) = false + + }).attachToRecyclerView(binding.recyclerView) + } + + override fun snackbarInternal(text: CharSequence): Snackbar { + return Snackbar.make(layout.coordinator, text, Snackbar.LENGTH_LONG) + } + + val assetNames = arrayOf("geoip.db", "geosite.db") + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.import_asset_menu, menu) + return true + } + + val importFile = registerForActivityResult(ActivityResultContracts.GetContent()) { file -> + if (file != null) { + val fileName = contentResolver.query(file, null, null, null, null)?.use { cursor -> + cursor.moveToFirst() + cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME).let(cursor::getString) + }?.takeIf { it.isNotBlank() } ?: file.pathSegments.last() + .substringAfterLast('/') + .substringAfter(':') + + if (!fileName.endsWith(".dat")) { + alert(getString(R.string.route_not_asset, fileName)).show() + return@registerForActivityResult + } + val filesDir = getExternalFilesDir(null) ?: filesDir + + runOnDefaultDispatcher { + val outFile = File(filesDir, fileName).apply { + parentFile?.mkdirs() + } + + contentResolver.openInputStream(file)?.use(outFile.outputStream()) + + File(outFile.parentFile, outFile.nameWithoutExtension + ".version.txt").apply { + if (isFile) delete() + createNewFile() + val fw = FileWriter(this) + fw.write("Custom") + fw.close() + } + + adapter.reloadAssets() + } + + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_import_file -> { + startFilesForResult(importFile, "*/*") + return true + } + } + return false + } + + inner class AssetAdapter : RecyclerView.Adapter(), + UndoSnackbarManager.Interface { + + val assets = ArrayList() + + init { + reloadAssets() + } + + fun reloadAssets() { + val filesDir = getExternalFilesDir(null) ?: filesDir + val files = filesDir.listFiles() + ?.filter { it.isFile && it.name.endsWith(".db") && it.name !in assetNames } + assets.clear() + assets.add(File(filesDir, "geoip.db")) + assets.add(File(filesDir, "geosite.db")) + if (files != null) assets.addAll(files) + + layout.refreshLayout.post { + notifyDataSetChanged() + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AssetHolder { + return AssetHolder(LayoutAssetItemBinding.inflate(layoutInflater, parent, false)) + } + + override fun onBindViewHolder(holder: AssetHolder, position: Int) { + holder.bind(assets[position]) + } + + override fun getItemCount(): Int { + return assets.size + } + + fun remove(index: Int) { + assets.removeAt(index) + notifyItemRemoved(index) + } + + override fun undo(actions: List>) { + for ((index, item) in actions) { + assets.add(index, item) + notifyItemInserted(index) + } + } + + override fun commit(actions: List>) { + val groups = actions.map { it.second }.toTypedArray() + runOnDefaultDispatcher { + groups.forEach { it.deleteRecursively() } + } + } + + } + + val updating = AtomicInteger() + + inner class AssetHolder(val binding: LayoutAssetItemBinding) : + RecyclerView.ViewHolder(binding.root) { + lateinit var file: File + + fun bind(file: File) { + this.file = file + + binding.assetName.text = file.name + val versionFile = File(file.parentFile, "${file.nameWithoutExtension}.version.txt") + + val localVersion = if (file.isFile) { + if (versionFile.isFile) { + versionFile.readText().trim() + } else { + "Unknown-" + DateFormat.getDateFormat(app).format(Date(file.lastModified())) + } + } else { + "" + } + + binding.assetStatus.text = getString(R.string.route_asset_status, localVersion) + + binding.rulesUpdate.isInvisible = file.name !in assetNames + binding.rulesUpdate.setOnClickListener { + updating.incrementAndGet() + layout.refreshLayout.isEnabled = false + binding.subscriptionUpdateProgress.isInvisible = false + binding.rulesUpdate.isInvisible = true + runOnDefaultDispatcher { + runCatching { + updateAsset(file, versionFile, localVersion) + }.onFailure { + onMainDispatcher { + alert(it.readableMessage).show() + } + } + + onMainDispatcher { + binding.rulesUpdate.isInvisible = false + binding.subscriptionUpdateProgress.isInvisible = true + if (updating.decrementAndGet() == 0) { + layout.refreshLayout.isEnabled = true + } + } + } + } + + } + + } + + suspend fun updateAsset(file: File, versionFile: File, localVersion: String) { + val repo: String + var fileName = file.name + + if (DataStore.rulesProvider == 0) { + if (file.name == assetNames[0]) { + repo = "SagerNet/sing-geoip" + } else { + repo = "SagerNet/sing-geosite" + } + } else { + if (file.name == assetNames[0]) { + repo = "soffchen/sing-geoip" + } else { + repo = "soffchen/sing-geosite" + } + } + + val client = Libcore.newHttpClient().apply { + modernTLS() + keepAlive() + trySocks5(DataStore.mixedPort) + } + + try { + var response = client.newRequest().apply { + setURL("https://api.github.com/repos/$repo/releases/latest") + }.execute() + + val release = JSONObject(response.contentString) + val tagName = release.optString("tag_name") + + if (tagName == localVersion) { + onMainDispatcher { + snackbar(R.string.route_asset_no_update).show() + } + return + } + + val releaseAssets = release.getJSONArray("assets").filterIsInstance() + val assetToDownload = releaseAssets.find { it.getStr("name") == fileName } + ?: error("File $fileName not found in release ${release["url"]}") + val browserDownloadUrl = assetToDownload.getStr("browser_download_url") + + response = client.newRequest().apply { + setURL(browserDownloadUrl) + }.execute() + + val cacheFile = File(file.parentFile, file.name + ".tmp") + cacheFile.parentFile?.mkdirs() + + response.writeTo(cacheFile.canonicalPath) + + if (fileName.endsWith(".xz")) { + Libcore.unxz(cacheFile.absolutePath, file.absolutePath) + cacheFile.delete() + } else { + cacheFile.renameTo(file) + } + + versionFile.writeText(tagName) + + adapter.reloadAssets() + + onMainDispatcher { + snackbar(R.string.route_asset_updated).show() + } + } finally { + client.close() + } + } + + override fun onSupportNavigateUp(): Boolean { + finish() + return true + } + + override fun onBackPressed() { + finish() + } + + override fun onResume() { + super.onResume() + + if (::adapter.isInitialized) { + adapter.reloadAssets() + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt new file mode 100644 index 0000000..4205b69 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt @@ -0,0 +1,313 @@ +package io.nekohasekai.sagernet.ui + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.os.Parcel +import android.os.Parcelable +import android.provider.OpenableColumns +import android.view.View +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AlertDialog +import androidx.core.content.FileProvider +import androidx.core.view.isVisible +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.jakewharton.processphoenix.ProcessPhoenix +import io.nekohasekai.sagernet.BuildConfig +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.database.* +import io.nekohasekai.sagernet.database.preference.KeyValuePair +import io.nekohasekai.sagernet.database.preference.PublicDatabase +import io.nekohasekai.sagernet.databinding.LayoutBackupBinding +import io.nekohasekai.sagernet.databinding.LayoutImportBinding +import io.nekohasekai.sagernet.databinding.LayoutProgressBinding +import io.nekohasekai.sagernet.ktx.* +import moe.matsuri.nb4a.utils.Util +import org.json.JSONArray +import org.json.JSONObject +import java.io.File +import java.util.* + +class BackupFragment : NamedFragment(R.layout.layout_backup) { + + override fun name0() = app.getString(R.string.backup) + + var content = "" + private val exportSettings = registerForActivityResult(ActivityResultContracts.CreateDocument()) { data -> + if (data != null) { + runOnDefaultDispatcher { + try { + requireActivity().contentResolver.openOutputStream( + data + )!!.bufferedWriter().use { + it.write(content) + } + onMainDispatcher { + snackbar(getString(R.string.action_export_msg)).show() + } + } catch (e: Exception) { + Logs.w(e) + onMainDispatcher { + snackbar(e.readableMessage).show() + } + } + + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val binding = LayoutBackupBinding.bind(view) + binding.actionExport.setOnClickListener { + runOnDefaultDispatcher { + content = doBackup( + binding.backupConfigurations.isChecked, + binding.backupRules.isChecked, + binding.backupSettings.isChecked + ) + onMainDispatcher { + startFilesForResult( + exportSettings, "matsuri_backup_${Date().toLocaleString()}.json" + ) + } + } + } + + binding.actionShare.setOnClickListener { + runOnDefaultDispatcher { + content = doBackup( + binding.backupConfigurations.isChecked, + binding.backupRules.isChecked, + binding.backupSettings.isChecked + ) + app.cacheDir.mkdirs() + val cacheFile = File( + app.cacheDir, "matsuri_backup_${Date().toLocaleString()}.json" + ) + cacheFile.writeText(content) + onMainDispatcher { + startActivity( + Intent.createChooser( + Intent(Intent.ACTION_SEND).setType("application/json") + .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + .putExtra( + Intent.EXTRA_STREAM, FileProvider.getUriForFile( + app, BuildConfig.APPLICATION_ID + ".cache", cacheFile + ) + ), app.getString(R.string.abc_shareactionprovider_share_with) + ) + ) + } + + } + } + + binding.actionImportFile.setOnClickListener { + startFilesForResult(importFile, "*/*") + } + } + + fun Parcelable.toBase64Str(): String { + val parcel = Parcel.obtain() + writeToParcel(parcel, 0) + try { + return Util.b64EncodeUrlSafe(parcel.marshall()) + } finally { + parcel.recycle() + } + } + + fun doBackup(profile: Boolean, rule: Boolean, setting: Boolean): String { + val out = JSONObject().apply { + put("version", 1) + if (profile) { + put("profiles", JSONArray().apply { + SagerDatabase.proxyDao.getAll().forEach { + put(it.toBase64Str()) + } + }) + + put("groups", JSONArray().apply { + SagerDatabase.groupDao.allGroups().forEach { + put(it.toBase64Str()) + } + }) + } + if (rule) { + put("rules", JSONArray().apply { + SagerDatabase.rulesDao.allRules().forEach { + put(it.toBase64Str()) + } + }) + } + if (setting) { + put("settings", JSONArray().apply { + PublicDatabase.kvPairDao.all().forEach { + put(it.toBase64Str()) + } + }) + } + } + return out.toStringPretty() + } + + val importFile = registerForActivityResult(ActivityResultContracts.GetContent()) { file -> + if (file != null) { + runOnDefaultDispatcher { + startImport(file) + } + } + } + + suspend fun startImport(file: Uri) { + val fileName = requireContext().contentResolver.query(file, null, null, null, null) + ?.use { cursor -> + cursor.moveToFirst() + cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME).let(cursor::getString) + } + ?.takeIf { it.isNotBlank() } ?: file.pathSegments.last() + .substringAfterLast('/') + .substringAfter(':') + + if (!fileName.endsWith(".json")) { + onMainDispatcher { + snackbar(getString(R.string.backup_not_file, fileName)).show() + } + return + } + + suspend fun invalid() = onMainDispatcher { + onMainDispatcher { + snackbar(getString(R.string.invalid_backup_file)).show() + } + } + + val content = try { + JSONObject((requireContext().contentResolver.openInputStream(file) ?: return).use { + it.bufferedReader().readText() + }) + } catch (e: Exception) { + Logs.w(e) + invalid() + return + } + val version = content.optInt("version", 0) + if (version < 1 || version > 1) { + invalid() + return + } + + onMainDispatcher { + val import = LayoutImportBinding.inflate(layoutInflater) + if (!content.has("profiles")) { + import.backupConfigurations.isVisible = false + } + if (!content.has("rules")) { + import.backupRules.isVisible = false + } + if (!content.has("settings")) { + import.backupSettings.isVisible = false + } + MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.backup_import) + .setView(import.root) + .setPositiveButton(R.string.backup_import) { _, _ -> + SagerNet.stopService() + + val binding = LayoutProgressBinding.inflate(layoutInflater) + binding.content.text = getString(R.string.backup_importing) + val dialog = AlertDialog.Builder(requireContext()) + .setView(binding.root) + .setCancelable(false) + .show() + runOnDefaultDispatcher { + runCatching { + finishImport( + content, + import.backupConfigurations.isChecked, + import.backupRules.isChecked, + import.backupSettings.isChecked + ) + ProcessPhoenix.triggerRebirth( + requireContext(), Intent(requireContext(), MainActivity::class.java) + ) + }.onFailure { + Logs.w(it) + onMainDispatcher { + alert(it.readableMessage).show() + } + } + + onMainDispatcher { + dialog.dismiss() + } + } + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + } + + fun finishImport( + content: JSONObject, profile: Boolean, rule: Boolean, setting: Boolean + ) { + if (profile && content.has("profiles")) { + val profiles = mutableListOf() + val jsonProfiles = content.getJSONArray("profiles") + for (i in 0 until jsonProfiles.length()) { + val data = Util.b64Decode(jsonProfiles[i] as String) + val parcel = Parcel.obtain() + parcel.unmarshall(data, 0, data.size) + parcel.setDataPosition(0) + profiles.add(ProxyEntity.CREATOR.createFromParcel(parcel)) + parcel.recycle() + } + SagerDatabase.proxyDao.reset() + SagerDatabase.proxyDao.insert(profiles) + + val groups = mutableListOf() + val jsonGroups = content.getJSONArray("groups") + for (i in 0 until jsonGroups.length()) { + val data = Util.b64Decode(jsonGroups[i] as String) + val parcel = Parcel.obtain() + parcel.unmarshall(data, 0, data.size) + parcel.setDataPosition(0) + groups.add(ProxyGroup.CREATOR.createFromParcel(parcel)) + parcel.recycle() + } + SagerDatabase.groupDao.reset() + SagerDatabase.groupDao.insert(groups) + } + if (rule && content.has("rules")) { + val rules = mutableListOf() + val jsonRules = content.getJSONArray("rules") + for (i in 0 until jsonRules.length()) { + val data = Util.b64Decode(jsonRules[i] as String) + val parcel = Parcel.obtain() + parcel.unmarshall(data, 0, data.size) + parcel.setDataPosition(0) + rules.add(ParcelizeBridge.createRule(parcel)) + parcel.recycle() + } + SagerDatabase.rulesDao.reset() + SagerDatabase.rulesDao.insert(rules) + } + if (setting && content.has("settings")) { + val settings = mutableListOf() + val jsonSettings = content.getJSONArray("settings") + for (i in 0 until jsonSettings.length()) { + val data = Util.b64Decode(jsonSettings[i] as String) + val parcel = Parcel.obtain() + parcel.unmarshall(data, 0, data.size) + parcel.setDataPosition(0) + settings.add(KeyValuePair.CREATOR.createFromParcel(parcel)) + parcel.recycle() + } + PublicDatabase.kvPairDao.reset() + PublicDatabase.kvPairDao.insert(settings) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/BlankActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/BlankActivity.kt new file mode 100644 index 0000000..f77dc8c --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/BlankActivity.kt @@ -0,0 +1,20 @@ +package io.nekohasekai.sagernet.ui + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import moe.matsuri.nb4a.utils.SendLog + +class BlankActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // process crash log + intent?.getStringExtra("sendLog")?.apply { + SendLog.sendLog(this@BlankActivity, this) + } + + finish() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt new file mode 100644 index 0000000..0f0618a --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt @@ -0,0 +1,1574 @@ +package io.nekohasekai.sagernet.ui + +import android.content.Intent +import android.graphics.Color +import android.net.Uri +import android.os.Bundle +import android.os.SystemClock +import android.provider.OpenableColumns +import android.text.SpannableStringBuilder +import android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE +import android.text.format.Formatter +import android.text.style.ForegroundColorSpan +import android.view.* +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.PopupMenu +import androidx.appcompat.widget.SearchView +import androidx.appcompat.widget.Toolbar +import androidx.core.view.isGone +import androidx.core.view.isVisible +import androidx.core.view.size +import androidx.fragment.app.Fragment +import androidx.preference.PreferenceDataStore +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator +import io.nekohasekai.sagernet.* +import io.nekohasekai.sagernet.aidl.TrafficData +import io.nekohasekai.sagernet.bg.BaseService +import io.nekohasekai.sagernet.bg.proto.UrlTest +import io.nekohasekai.sagernet.database.* +import io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener +import io.nekohasekai.sagernet.databinding.LayoutAppsItemBinding +import io.nekohasekai.sagernet.databinding.LayoutProfileListBinding +import io.nekohasekai.sagernet.databinding.LayoutProgressListBinding +import io.nekohasekai.sagernet.fmt.AbstractBean +import io.nekohasekai.sagernet.fmt.toUniversalLink +import io.nekohasekai.sagernet.fmt.v2ray.toV2rayN +import io.nekohasekai.sagernet.group.RawUpdater +import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.plugin.PluginManager +import io.nekohasekai.sagernet.ui.profile.* +import io.nekohasekai.sagernet.utils.PackageCache +import io.nekohasekai.sagernet.widget.QRCodeDialog +import io.nekohasekai.sagernet.widget.UndoSnackbarManager +import kotlinx.coroutines.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import moe.matsuri.nb4a.Protocols +import moe.matsuri.nb4a.Protocols.getProtocolColor +import moe.matsuri.nb4a.proxy.config.ConfigSettingActivity +import moe.matsuri.nb4a.proxy.neko.NekoJSInterface +import moe.matsuri.nb4a.plugin.NekoPluginManager +import moe.matsuri.nb4a.proxy.neko.NekoSettingActivity +import moe.matsuri.nb4a.proxy.neko.canShare +import okhttp3.internal.closeQuietly +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.Socket +import java.net.UnknownHostException +import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.atomic.AtomicInteger +import java.util.zip.ZipInputStream +import kotlin.collections.set + +class ConfigurationFragment @JvmOverloads constructor( + val select: Boolean = false, val selectedItem: ProxyEntity? = null, val titleRes: Int = 0 +) : ToolbarFragment(R.layout.layout_group_list), + PopupMenu.OnMenuItemClickListener, + Toolbar.OnMenuItemClickListener, + SearchView.OnQueryTextListener, + OnPreferenceDataStoreChangeListener { + + interface SelectCallback { + fun returnProfile(profileId: Long) + } + + lateinit var adapter: GroupPagerAdapter + lateinit var tabLayout: TabLayout + lateinit var groupPager: ViewPager2 + + val alwaysShowAddress by lazy { DataStore.alwaysShowAddress } + + fun getCurrentGroupFragment(): GroupFragment? { + return childFragmentManager.findFragmentByTag("f" + DataStore.selectedGroup) as GroupFragment? + } + + val updateSelectedCallback = object : ViewPager2.OnPageChangeCallback() { + override fun onPageScrolled( + position: Int, positionOffset: Float, positionOffsetPixels: Int + ) { + if (adapter.groupList.size > position) { + DataStore.selectedGroup = adapter.groupList[position].id + } + } + } + + override fun onQueryTextChange(query: String): Boolean { + getCurrentGroupFragment()?.adapter?.filter(query) + return false + } + + override fun onQueryTextSubmit(query: String): Boolean = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + if (savedInstanceState != null) { + parentFragmentManager.beginTransaction() + .setReorderingAllowed(false) + .detach(this) + .attach(this) + .commit() + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + if (!select) { + toolbar.inflateMenu(R.menu.add_profile_menu) + toolbar.setOnMenuItemClickListener(this) + } else { + toolbar.setTitle(titleRes) + toolbar.setNavigationIcon(R.drawable.ic_navigation_close) + toolbar.setNavigationOnClickListener { + requireActivity().finish() + } + } + + val searchView = toolbar.findViewById(R.id.action_search) + if (searchView != null) { + searchView.setOnQueryTextListener(this) + searchView.maxWidth = Int.MAX_VALUE + } + + groupPager = view.findViewById(R.id.group_pager) + tabLayout = view.findViewById(R.id.group_tab) + adapter = GroupPagerAdapter() + ProfileManager.addListener(adapter) + GroupManager.addListener(adapter) + + groupPager.adapter = adapter + groupPager.offscreenPageLimit = 2 + + TabLayoutMediator(tabLayout, groupPager) { tab, position -> + if (adapter.groupList.size > position) { + tab.text = adapter.groupList[position].displayName() + } + tab.view.setOnLongClickListener { // clear toast + true + } + }.attach() + + toolbar.setOnClickListener { + val fragment = getCurrentGroupFragment() + + if (fragment != null) { + val selectedProxy = selectedItem?.id ?: DataStore.selectedProxy + val selectedProfileIndex = + fragment.adapter!!.configurationIdList.indexOf(selectedProxy) + if (selectedProfileIndex != -1) { + val layoutManager = fragment.layoutManager + val first = layoutManager.findFirstVisibleItemPosition() + val last = layoutManager.findLastVisibleItemPosition() + + if (selectedProfileIndex !in first..last) { + fragment.configurationListView.scrollTo(selectedProfileIndex, true) + return@setOnClickListener + } + + } + + fragment.configurationListView.scrollTo(0) + } + + } + + DataStore.profileCacheStore.registerChangeListener(this) + } + + override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) { + runOnMainDispatcher { + // editingGroup + if (key == Key.PROFILE_GROUP) { + val targetId = DataStore.editingGroup + if (targetId > 0 && targetId != DataStore.selectedGroup) { + DataStore.selectedGroup = targetId + val targetIndex = adapter.groupList.indexOfFirst { it.id == targetId } + if (targetIndex >= 0) { + groupPager.setCurrentItem(targetIndex, false) + } else { + adapter.reload() + } + } + } + } + } + + override fun onDestroy() { + DataStore.profileCacheStore.unregisterChangeListener(this) + + if (::adapter.isInitialized) { + GroupManager.removeListener(adapter) + ProfileManager.removeListener(adapter) + } + + super.onDestroy() + } + + override fun onKeyDown(ketCode: Int, event: KeyEvent): Boolean { + val fragment = getCurrentGroupFragment() + fragment?.configurationListView?.apply { + if (!hasFocus()) requestFocus() + } + return super.onKeyDown(ketCode, event) + } + + val importFile = registerForActivityResult(ActivityResultContracts.GetContent()) { file -> + if (file != null) runOnDefaultDispatcher { + try { + val fileName = requireContext().contentResolver.query(file, null, null, null, null) + ?.use { cursor -> + cursor.moveToFirst() + cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME) + .let(cursor::getString) + } + + val proxies = mutableListOf() + if (fileName != null && fileName.endsWith(".zip")) { + // try parse wireguard zip + + val zip = + ZipInputStream(requireContext().contentResolver.openInputStream(file)!!) + while (true) { + val entry = zip.nextEntry ?: break + if (entry.isDirectory) continue + val fileText = zip.bufferedReader().readText() + RawUpdater.parseRaw(fileText)?.let { pl -> proxies.addAll(pl) } + zip.closeEntry() + } + zip.closeQuietly() + } else { + val fileText = requireContext().contentResolver.openInputStream(file)!!.use { + it.bufferedReader().readText() + } + RawUpdater.parseRaw(fileText)?.let { pl -> proxies.addAll(pl) } + } + + if (proxies.isEmpty()) onMainDispatcher { + snackbar(getString(R.string.no_proxies_found_in_file)).show() + } else import(proxies) + } catch (e: SubscriptionFoundException) { + (requireActivity() as MainActivity).importSubscription(Uri.parse(e.link)) + } catch (e: Exception) { + Logs.w(e) + + onMainDispatcher { + snackbar(e.readableMessage).show() + } + } + } + } + + suspend fun import(proxies: List) { + val targetId = DataStore.selectedGroupForImport() + for (proxy in proxies) { + ProfileManager.createProfile(targetId, proxy) + } + onMainDispatcher { + DataStore.editingGroup = targetId + snackbar( + requireContext().resources.getQuantityString( + R.plurals.added, proxies.size, proxies.size + ) + ).show() + } + + } + + override fun onMenuItemClick(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_scan_qr_code -> { + startActivity(Intent(context, ScannerActivity::class.java)) + } + R.id.action_import_clipboard -> { + val text = SagerNet.getClipboardText() + if (text.isBlank()) { + snackbar(getString(R.string.clipboard_empty)).show() + } else runOnDefaultDispatcher { + try { + val proxies = RawUpdater.parseRaw(text) + if (proxies.isNullOrEmpty()) onMainDispatcher { + snackbar(getString(R.string.no_proxies_found_in_clipboard)).show() + } else import(proxies) + } catch (e: SubscriptionFoundException) { + (requireActivity() as MainActivity).importSubscription(Uri.parse(e.link)) + } catch (e: Exception) { + Logs.w(e) + + onMainDispatcher { + snackbar(e.readableMessage).show() + } + } + } + } + R.id.action_import_file -> { + startFilesForResult(importFile, "*/*") + } + R.id.action_new_socks -> { + startActivity(Intent(requireActivity(), SocksSettingsActivity::class.java)) + } + R.id.action_new_http -> { + startActivity(Intent(requireActivity(), HttpSettingsActivity::class.java)) + } + R.id.action_new_ss -> { + startActivity(Intent(requireActivity(), ShadowsocksSettingsActivity::class.java)) + } + R.id.action_new_vmess -> { + startActivity(Intent(requireActivity(), VMessSettingsActivity::class.java)) + } + R.id.action_new_vless -> { + startActivity(Intent(requireActivity(), VMessSettingsActivity::class.java).apply { + putExtra("vless", true) + }) + } + R.id.action_new_trojan -> { + startActivity(Intent(requireActivity(), TrojanSettingsActivity::class.java)) + } + R.id.action_new_trojan_go -> { + startActivity(Intent(requireActivity(), TrojanGoSettingsActivity::class.java)) + } + R.id.action_new_naive -> { + startActivity(Intent(requireActivity(), NaiveSettingsActivity::class.java)) + } + R.id.action_new_hysteria -> { + startActivity(Intent(requireActivity(), HysteriaSettingsActivity::class.java)) + } + R.id.action_new_tuic -> { + startActivity(Intent(requireActivity(), TuicSettingsActivity::class.java)) + } + R.id.action_new_ssh -> { + startActivity(Intent(requireActivity(), SSHSettingsActivity::class.java)) + } + R.id.action_new_wg -> { + startActivity(Intent(requireActivity(), WireGuardSettingsActivity::class.java)) + } + R.id.action_new_config -> { + startActivity(Intent(requireActivity(), ConfigSettingActivity::class.java)) + } + R.id.action_new_chain -> { + startActivity(Intent(requireActivity(), ChainSettingsActivity::class.java)) + } + R.id.action_new_neko -> { + val context = requireContext() + lateinit var dialog: AlertDialog + val linearLayout = LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + + NekoPluginManager.getProtocols().forEach { obj -> + LayoutAppsItemBinding.inflate(layoutInflater, this, true).apply { + itemcheck.isGone = true + button.isGone = false + itemicon.setImageDrawable( + PackageCache.installedApps[obj.plgId]?.loadIcon( + context.packageManager + ) + ) + title.text = obj.protocolId + desc.text = obj.plgId + button.setOnClickListener { + dialog.dismiss() + val intent = Intent(context, NekoSettingActivity::class.java) + intent.putExtra("plgId", obj.plgId) + intent.putExtra("protocolId", obj.protocolId) + startActivity(intent) + } + } + } + } + dialog = MaterialAlertDialogBuilder(context).setTitle(R.string.neko_plugin) + .setView(linearLayout) + .show() + } + R.id.action_clear_traffic_statistics -> { + runOnDefaultDispatcher { + val profiles = SagerDatabase.proxyDao.getByGroup(DataStore.currentGroupId()) + val toClear = mutableListOf() + if (profiles.isNotEmpty()) for (profile in profiles) { + if (profile.tx != 0L || profile.rx != 0L) { + profile.tx = 0 + profile.rx = 0 + toClear.add(profile) + } + } + if (toClear.isNotEmpty()) { + ProfileManager.updateProfile(toClear) + } + } + } + R.id.action_connection_test_clear_results -> { + runOnDefaultDispatcher { + val profiles = SagerDatabase.proxyDao.getByGroup(DataStore.currentGroupId()) + val toClear = mutableListOf() + if (profiles.isNotEmpty()) for (profile in profiles) { + if (profile.status != 0) { + profile.status = 0 + profile.ping = 0 + profile.error = null + toClear.add(profile) + } + } + if (toClear.isNotEmpty()) { + ProfileManager.updateProfile(toClear) + } + } + } + R.id.action_connection_test_delete_unavailable -> { + runOnDefaultDispatcher { + val profiles = SagerDatabase.proxyDao.getByGroup(DataStore.currentGroupId()) + val toClear = mutableListOf() + if (profiles.isNotEmpty()) for (profile in profiles) { + if (profile.status != 0 && profile.status != 1) { + toClear.add(profile) + } + } + if (toClear.isNotEmpty()) { + onMainDispatcher { + MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.confirm) + .setMessage(R.string.delete_confirm_prompt) + .setPositiveButton(R.string.yes) { _, _ -> + for (profile in toClear) { + adapter.groupFragments[DataStore.selectedGroup]?.adapter?.apply { + val index = configurationIdList.indexOf(profile.id) + if (index >= 0) { + configurationIdList.removeAt(index) + configurationList.remove(profile.id) + notifyItemRemoved(index) + } + } + } + runOnDefaultDispatcher { + for (profile in toClear) { + ProfileManager.deleteProfile2( + profile.groupId, profile.id + ) + } + } + } + .setNegativeButton(R.string.no, null) + .show() + } + } + } + } + R.id.action_connection_icmp_ping -> { + pingTest(true) + } + R.id.action_connection_tcp_ping -> { + pingTest(false) + } + R.id.action_connection_url_test -> { + urlTest() + } + } + return true + } + + inner class TestDialog { + val binding = LayoutProgressListBinding.inflate(layoutInflater) + val builder = MaterialAlertDialogBuilder(requireContext()).setView(binding.root) + .setNegativeButton(android.R.string.cancel) { _, _ -> + cancel() + } + .setOnDismissListener { + cancel() + } + .setCancelable(false) + + lateinit var cancel: () -> Unit + val fragment by lazy { getCurrentGroupFragment() } + val results = Collections.synchronizedList(mutableListOf()) + var proxyN = 0 + val finishedN = AtomicInteger(0) + + suspend fun insert(profile: ProxyEntity?) { + results.add(profile) + } + + suspend fun update(profile: ProxyEntity) { + fragment?.configurationListView?.post { + var profileStatusText: String? = null + var profileStatusColor = 0 + + when (profile.status) { + -1 -> { + profileStatusText = profile.error + profileStatusColor = + requireContext().getColorAttr(android.R.attr.textColorSecondary) + } + 0 -> { + profileStatusText = getString(R.string.connection_test_testing) + profileStatusColor = + requireContext().getColorAttr(android.R.attr.textColorSecondary) + } + 1 -> { + profileStatusText = getString(R.string.available, profile.ping) + profileStatusColor = requireContext().getColour(R.color.material_green_500) + } + 2 -> { + profileStatusText = profile.error + profileStatusColor = requireContext().getColour(R.color.material_red_500) + } + 3 -> { + val err = profile.error ?: "" + val msg = Protocols.genFriendlyMsg(err) + profileStatusText = if (msg != err) msg else getString(R.string.unavailable) + profileStatusColor = requireContext().getColour(R.color.material_red_500) + } + } + + val text = SpannableStringBuilder().apply { + append("\n" + profile.displayName()) + append("\n") + append( + profile.displayType(), + ForegroundColorSpan(requireContext().getProtocolColor(profile.type)), + SPAN_EXCLUSIVE_EXCLUSIVE + ) + append(" ") + append( + profileStatusText, + ForegroundColorSpan(profileStatusColor), + SPAN_EXCLUSIVE_EXCLUSIVE + ) + append("\n") + } + + binding.nowTesting.text = text + binding.progress.text = "${finishedN.addAndGet(1)} / $proxyN" + } + } + + } + + fun stopService() { + if (DataStore.serviceState.started) SagerNet.stopService() + } + + @Suppress("EXPERIMENTAL_API_USAGE") + fun pingTest(icmpPing: Boolean) { + stopService() + + val test = TestDialog() + val testJobs = mutableListOf() + val dialog = test.builder.show() + val mainJob = runOnDefaultDispatcher { + val group = DataStore.currentGroup() + var profilesUnfiltered = SagerDatabase.proxyDao.getByGroup(group.id) + test.proxyN = profilesUnfiltered.size + val profiles = ConcurrentLinkedQueue(profilesUnfiltered) + val testPool = newFixedThreadPoolContext(5, "Connection test pool") + repeat(5) { + testJobs.add(launch(testPool) { + while (isActive) { + val profile = profiles.poll() ?: break + + if (icmpPing) { + if (!profile.requireBean().canICMPing()) { + profile.status = -1 + profile.error = + app.getString(R.string.connection_test_icmp_ping_unavailable) + test.insert(profile) + continue + } + } else { + if (!profile.requireBean().canTCPing()) { + profile.status = -1 + profile.error = + app.getString(R.string.connection_test_tcp_ping_unavailable) + test.insert(profile) + continue + } + } + + profile.status = 0 + test.insert(profile) + var address = profile.requireBean().serverAddress + if (!address.isIpAddress()) { + try { + InetAddress.getAllByName(address).apply { + if (isNotEmpty()) { + address = this[0].hostAddress + } + } + } catch (ignored: UnknownHostException) { + } + } + if (!isActive) break + if (!address.isIpAddress()) { + profile.status = 2 + profile.error = app.getString(R.string.connection_test_domain_not_found) + test.update(profile) + continue + } + try { + if (icmpPing) { + // removed + } else { + val socket = Socket() + try { + socket.soTimeout = 3000 + socket.bind(InetSocketAddress(0)) + val start = SystemClock.elapsedRealtime() + socket.connect( + InetSocketAddress( + address, profile.requireBean().serverPort + ), 3000 + ) + if (!isActive) break + profile.status = 1 + profile.ping = (SystemClock.elapsedRealtime() - start).toInt() + test.update(profile) + } finally { + socket.closeQuietly() + } + } + } catch (e: Exception) { + if (!isActive) break + val message = e.readableMessage + + if (icmpPing) { + profile.status = 2 + profile.error = getString(R.string.connection_test_unreachable) + } else { + profile.status = 2 + when { + !message.contains("failed:") -> profile.error = + getString(R.string.connection_test_timeout) + else -> when { + message.contains("ECONNREFUSED") -> { + profile.error = + getString(R.string.connection_test_refused) + } + message.contains("ENETUNREACH") -> { + profile.error = + getString(R.string.connection_test_unreachable) + } + else -> { + profile.status = 3 + profile.error = message + } + } + } + } + test.update(profile) + } + } + }) + } + + testJobs.joinAll() + testPool.close() + + onMainDispatcher { + dialog.dismiss() + } + } + test.cancel = { + runOnDefaultDispatcher { + test.results.filterNotNull().forEach { + try { + ProfileManager.updateProfile(it) + } catch (e: Exception) { + Logs.w(e) + } + } + GroupManager.postReload(DataStore.currentGroupId()) + mainJob.cancel() + testJobs.forEach { it.cancel() } + } + } + } + + fun urlTest() { + stopService() + + val test = TestDialog() + val dialog = test.builder.show() + val testJobs = mutableListOf() + + val mainJob = runOnDefaultDispatcher { + val group = DataStore.currentGroup() + val profilesUnfiltered = SagerDatabase.proxyDao.getByGroup(group.id) + test.proxyN = profilesUnfiltered.size + val profiles = ConcurrentLinkedQueue(profilesUnfiltered) + val urlTest = UrlTest() // note: this is NOT in bg process + + repeat(5) { + testJobs.add(launch { + while (isActive) { + val profile = profiles.poll() ?: break + profile.status = 0 + test.insert(profile) + + try { + val result = urlTest.doTest(profile) + profile.status = 1 + profile.ping = result + } catch (e: PluginManager.PluginNotFoundException) { + profile.status = 2 + profile.error = e.readableMessage + } catch (e: Exception) { + profile.status = 3 + profile.error = e.readableMessage + } + + test.update(profile) + } + }) + } + + testJobs.joinAll() + + onMainDispatcher { + dialog.dismiss() + } + } + test.cancel = { + runOnDefaultDispatcher { + test.results.filterNotNull().forEach { + try { + ProfileManager.updateProfile(it) + } catch (e: Exception) { + Logs.w(e) + } + } + GroupManager.postReload(DataStore.currentGroupId()) + NekoJSInterface.Default.destroyAllJsi() + mainJob.cancel() + testJobs.forEach { it.cancel() } + } + } + } + + inner class GroupPagerAdapter : FragmentStateAdapter(this), + ProfileManager.Listener, + GroupManager.Listener { + + var selectedGroupIndex = 0 + var groupList: ArrayList = ArrayList() + var groupFragments: HashMap = HashMap() + + fun reload(now: Boolean = false) { + + if (!select) { + groupPager.unregisterOnPageChangeCallback(updateSelectedCallback) + } + + runOnDefaultDispatcher { + var newGroupList = ArrayList(SagerDatabase.groupDao.allGroups()) + if (newGroupList.isEmpty()) { + SagerDatabase.groupDao.createGroup(ProxyGroup(ungrouped = true)) + newGroupList = ArrayList(SagerDatabase.groupDao.allGroups()) + } + newGroupList.find { it.ungrouped }?.let { + if (SagerDatabase.proxyDao.countByGroup(it.id) == 0L) { + newGroupList.remove(it) + } + } + + var selectedGroup = selectedItem?.groupId ?: DataStore.currentGroupId() + var set = false + if (selectedGroup > 0L) { + selectedGroupIndex = newGroupList.indexOfFirst { it.id == selectedGroup } + set = true + } else if (groupList.size == 1) { + selectedGroup = groupList[0].id + if (DataStore.selectedGroup != selectedGroup) { + DataStore.selectedGroup = selectedGroup + } + } + + val runFunc = if (now) requireActivity()::runOnUiThread else groupPager::post + runFunc { + groupList = newGroupList + notifyDataSetChanged() + if (set) groupPager.setCurrentItem(selectedGroupIndex, false) + val hideTab = groupList.size < 2 + tabLayout.isGone = hideTab + toolbar.elevation = if (hideTab) 0F else dp2px(4).toFloat() + if (!select) { + groupPager.registerOnPageChangeCallback(updateSelectedCallback) + } + } + } + } + + init { + reload(true) + } + + override fun getItemCount(): Int { + return groupList.size + } + + override fun createFragment(position: Int): Fragment { + return GroupFragment().apply { + proxyGroup = groupList[position] + groupFragments[proxyGroup.id] = this + if (position == selectedGroupIndex) { + selected = true + } + } + } + + override fun getItemId(position: Int): Long { + return groupList[position].id + } + + override fun containsItem(itemId: Long): Boolean { + return groupList.any { it.id == itemId } + } + + override suspend fun groupAdd(group: ProxyGroup) { + tabLayout.post { + groupList.add(group) + + if (groupList.any { !it.ungrouped }) tabLayout.post { + tabLayout.visibility = View.VISIBLE + } + + notifyItemInserted(groupList.size - 1) + tabLayout.getTabAt(groupList.size - 1)?.select() + } + } + + override suspend fun groupRemoved(groupId: Long) { + val index = groupList.indexOfFirst { it.id == groupId } + if (index == -1) return + + tabLayout.post { + groupList.removeAt(index) + notifyItemRemoved(index) + } + } + + override suspend fun groupUpdated(group: ProxyGroup) { + val index = groupList.indexOfFirst { it.id == group.id } + if (index == -1) return + + tabLayout.post { + tabLayout.getTabAt(index)?.text = group.displayName() + } + } + + override suspend fun groupUpdated(groupId: Long) = Unit + + override suspend fun onAdd(profile: ProxyEntity) { + if (groupList.find { it.id == profile.groupId } == null) { + DataStore.selectedGroup = profile.groupId + reload() + } + } + + override suspend fun onUpdated(data: TrafficData) = Unit + + override suspend fun onUpdated(profile: ProxyEntity) = Unit + + override suspend fun onRemoved(groupId: Long, profileId: Long) { + val group = groupList.find { it.id == groupId } ?: return + if (group.ungrouped && SagerDatabase.proxyDao.countByGroup(groupId) == 0L) { + reload() + } + } + } + + class GroupFragment : Fragment() { + + lateinit var proxyGroup: ProxyGroup + var selected = false + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + return LayoutProfileListBinding.inflate(inflater).root + } + + lateinit var undoManager: UndoSnackbarManager + var adapter: ConfigurationAdapter? = null + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + + if (::proxyGroup.isInitialized) { + outState.putParcelable("proxyGroup", proxyGroup) + } + } + + override fun onViewStateRestored(savedInstanceState: Bundle?) { + super.onViewStateRestored(savedInstanceState) + + savedInstanceState?.getParcelable("proxyGroup")?.also { + proxyGroup = it + onViewCreated(requireView(), null) + } + } + + private val isEnabled: Boolean + get() { + return DataStore.serviceState.let { it.canStop || it == BaseService.State.Stopped } + } + + lateinit var layoutManager: LinearLayoutManager + lateinit var configurationListView: RecyclerView + + val select by lazy { + try { + (parentFragment as ConfigurationFragment).select + } catch (e: Exception) { + Logs.e(e) + false + } + } + val selectedItem by lazy { + try { + (parentFragment as ConfigurationFragment).selectedItem + } catch (e: Exception) { + Logs.e(e) + null + } + } + + override fun onResume() { + super.onResume() + + if (::configurationListView.isInitialized && configurationListView.size == 0) { + configurationListView.adapter = adapter + runOnDefaultDispatcher { + adapter?.reloadProfiles() + } + } else if (!::configurationListView.isInitialized) { + onViewCreated(requireView(), null) + } + checkOrderMenu() + configurationListView.requestFocus() + } + + fun checkOrderMenu() { + if (select) return + + val pf = requireParentFragment() as? ToolbarFragment ?: return + val menu = pf.toolbar.menu + val origin = menu.findItem(R.id.action_order_origin) + val byName = menu.findItem(R.id.action_order_by_name) + val byDelay = menu.findItem(R.id.action_order_by_delay) + when (proxyGroup.order) { + GroupOrder.ORIGIN -> { + origin.isChecked = true + } + GroupOrder.BY_NAME -> { + byName.isChecked = true + } + GroupOrder.BY_DELAY -> { + byDelay.isChecked = true + } + } + + fun updateTo(order: Int) { + if (proxyGroup.order == order) return + runOnDefaultDispatcher { + proxyGroup.order = order + GroupManager.updateGroup(proxyGroup) + } + } + + origin.setOnMenuItemClickListener { + it.isChecked = true + updateTo(GroupOrder.ORIGIN) + true + } + byName.setOnMenuItemClickListener { + it.isChecked = true + updateTo(GroupOrder.BY_NAME) + true + } + byDelay.setOnMenuItemClickListener { + it.isChecked = true + updateTo(GroupOrder.BY_DELAY) + true + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (!::proxyGroup.isInitialized) return + + configurationListView = view.findViewById(R.id.configuration_list) + layoutManager = FixedLinearLayoutManager(configurationListView) + configurationListView.layoutManager = layoutManager + adapter = ConfigurationAdapter() + ProfileManager.addListener(adapter!!) + GroupManager.addListener(adapter!!) + configurationListView.adapter = adapter + configurationListView.setItemViewCacheSize(20) + + if (!select) { + + undoManager = UndoSnackbarManager(activity as MainActivity, adapter!!) + + ItemTouchHelper(object : ItemTouchHelper.SimpleCallback( + ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.START + ) { + override fun getSwipeDirs( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + ): Int { + return 0 + } + + override fun getDragDirs( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + ) = if (isEnabled) super.getDragDirs(recyclerView, viewHolder) else 0 + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + } + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder, + ): Boolean { + adapter?.move( + viewHolder.bindingAdapterPosition, target.bindingAdapterPosition + ) + return true + } + + override fun clearView( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + ) { + super.clearView(recyclerView, viewHolder) + adapter?.commitMove() + } + }).attachToRecyclerView(configurationListView) + + } + + } + + override fun onDestroy() { + adapter?.let { + ProfileManager.removeListener(it) + GroupManager.removeListener(it) + } + + super.onDestroy() + + if (!::undoManager.isInitialized) return + undoManager.flush() + } + + inner class ConfigurationAdapter : RecyclerView.Adapter(), + ProfileManager.Listener, + GroupManager.Listener, + UndoSnackbarManager.Interface { + + init { + setHasStableIds(true) + } + + var configurationIdList: MutableList = mutableListOf() + val configurationList = HashMap() + + private fun getItem(profileId: Long): ProxyEntity { + var profile = configurationList[profileId] + if (profile == null) { + profile = ProfileManager.getProfile(profileId) + if (profile != null) { + configurationList[profileId] = profile + } + } + return profile!! + } + + private fun getItemAt(index: Int) = getItem(configurationIdList[index]) + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): ConfigurationHolder { + return ConfigurationHolder( + LayoutInflater.from(parent.context) + .inflate(R.layout.layout_profile, parent, false) + ) + } + + override fun getItemId(position: Int): Long { + return configurationIdList[position] + } + + override fun onBindViewHolder(holder: ConfigurationHolder, position: Int) { + try { + holder.bind(getItemAt(position)) + } catch (ignored: NullPointerException) { // when group deleted + } + } + + override fun getItemCount(): Int { + return configurationIdList.size + } + + private val updated = HashSet() + + fun filter(name: String) { + if (name.isEmpty()) { + reloadProfiles() + return + } + configurationIdList.clear() + val lower = name.lowercase() + configurationIdList.addAll(configurationList.filter { + it.value.displayName().lowercase().contains(lower) || + it.value.displayType().lowercase().contains(lower) || + it.value.displayAddress().lowercase().contains(lower) + }.keys) + notifyDataSetChanged() + } + + fun move(from: Int, to: Int) { + val first = getItemAt(from) + var previousOrder = first.userOrder + val (step, range) = if (from < to) Pair(1, from until to) else Pair( + -1, to + 1 downTo from + ) + for (i in range) { + val next = getItemAt(i + step) + val order = next.userOrder + next.userOrder = previousOrder + previousOrder = order + configurationIdList[i] = next.id + updated.add(next) + } + first.userOrder = previousOrder + configurationIdList[to] = first.id + updated.add(first) + notifyItemMoved(from, to) + } + + fun commitMove() = runOnDefaultDispatcher { + updated.forEach { SagerDatabase.proxyDao.updateProxy(it) } + updated.clear() + } + + fun remove(pos: Int) { + if (pos < 0) return + configurationIdList.removeAt(pos) + notifyItemRemoved(pos) + } + + override fun undo(actions: List>) { + for ((index, item) in actions) { + configurationListView.post { + configurationList[item.id] = item + configurationIdList.add(index, item.id) + notifyItemInserted(index) + } + } + } + + override fun commit(actions: List>) { + val profiles = actions.map { it.second } + runOnDefaultDispatcher { + for (entity in profiles) { + ProfileManager.deleteProfile(entity.groupId, entity.id) + } + } + } + + override suspend fun onAdd(profile: ProxyEntity) { + if (profile.groupId != proxyGroup.id) return + + configurationListView.post { + if (::undoManager.isInitialized) { + undoManager.flush() + } + val pos = itemCount + configurationList[profile.id] = profile + configurationIdList.add(profile.id) + notifyItemInserted(pos) + } + } + + override suspend fun onUpdated(profile: ProxyEntity) { + if (profile.groupId != proxyGroup.id) return + val index = configurationIdList.indexOf(profile.id) + if (index < 0) return + configurationListView.post { + if (::undoManager.isInitialized) { + undoManager.flush() + } + val oldProfile = configurationList[profile.id] + configurationList[profile.id] = profile + notifyItemChanged(index) + } + } + + override suspend fun onUpdated(data: TrafficData) { + val index = configurationIdList.indexOf(data.id) + if (index != -1) { + val holder = layoutManager.findViewByPosition(index) + ?.let { configurationListView.getChildViewHolder(it) } as ConfigurationHolder? + if (holder != null) { + onMainDispatcher { + holder.bind(holder.entity, data) + } + } + } + } + + override suspend fun onRemoved(groupId: Long, profileId: Long) { + if (groupId != proxyGroup.id) return + val index = configurationIdList.indexOf(profileId) + if (index < 0) return + + configurationListView.post { + configurationIdList.removeAt(index) + configurationList.remove(profileId) + notifyItemRemoved(index) + } + } + + override suspend fun groupAdd(group: ProxyGroup) = Unit + override suspend fun groupRemoved(groupId: Long) = Unit + + override suspend fun groupUpdated(group: ProxyGroup) { + if (group.id != proxyGroup.id) return + proxyGroup = group + reloadProfiles() + } + + override suspend fun groupUpdated(groupId: Long) { + if (groupId != proxyGroup.id) return + proxyGroup = SagerDatabase.groupDao.getById(groupId)!! + reloadProfiles() + } + + fun reloadProfiles() { + var newProfiles = SagerDatabase.proxyDao.getByGroup(proxyGroup.id) + val subscription = proxyGroup.subscription + when (proxyGroup.order) { + GroupOrder.BY_NAME -> { + newProfiles = newProfiles.sortedBy { it.displayName() } + + } + GroupOrder.BY_DELAY -> { + newProfiles = + newProfiles.sortedBy { if (it.status == 1) it.ping else 114514 } + } + } + + configurationList.clear() + configurationList.putAll(newProfiles.associateBy { it.id }) + val newProfileIds = newProfiles.map { it.id } + + var selectedProfileIndex = -1 + + if (selected) { + val selectedProxy = selectedItem?.id ?: DataStore.selectedProxy + selectedProfileIndex = newProfileIds.indexOf(selectedProxy) + } + + configurationListView.post { + configurationIdList.clear() + configurationIdList.addAll(newProfileIds) + notifyDataSetChanged() + + if (selectedProfileIndex != -1) { + configurationListView.scrollTo(selectedProfileIndex, true) + } else if (newProfiles.isNotEmpty()) { + configurationListView.scrollTo(0, true) + } + + } + } + + } + + val profileAccess = Mutex() + val reloadAccess = Mutex() + + inner class ConfigurationHolder(val view: View) : RecyclerView.ViewHolder(view), + PopupMenu.OnMenuItemClickListener { + + lateinit var entity: ProxyEntity + + val profileName: TextView = view.findViewById(R.id.profile_name) + val profileType: TextView = view.findViewById(R.id.profile_type) + val profileAddress: TextView = view.findViewById(R.id.profile_address) + val profileStatus: TextView = view.findViewById(R.id.profile_status) + + val trafficText: TextView = view.findViewById(R.id.traffic_text) + val selectedView: LinearLayout = view.findViewById(R.id.selected_view) + val editButton: ImageView = view.findViewById(R.id.edit) + val shareLayout: LinearLayout = view.findViewById(R.id.share) + val shareLayer: LinearLayout = view.findViewById(R.id.share_layer) + val shareButton: ImageView = view.findViewById(R.id.shareIcon) + val removeButton: ImageView = view.findViewById(R.id.remove) + + fun bind(proxyEntity: ProxyEntity, trafficData: TrafficData? = null) { + val pf = parentFragment as? ConfigurationFragment ?: return + + entity = proxyEntity + + if (select) { + view.setOnClickListener { + (requireActivity() as SelectCallback).returnProfile(proxyEntity.id) + } + } else { + view.setOnClickListener { + runOnDefaultDispatcher { + var update: Boolean + var lastSelected: Long + profileAccess.withLock { + update = DataStore.selectedProxy != proxyEntity.id + lastSelected = DataStore.selectedProxy + DataStore.selectedProxy = proxyEntity.id + onMainDispatcher { + selectedView.visibility = View.VISIBLE + } + } + + if (update) { + ProfileManager.postUpdate(lastSelected) + if (DataStore.serviceState.canStop && reloadAccess.tryLock()) { + SagerNet.reloadService() + reloadAccess.unlock() + } + } else if (SagerNet.isTv) { + if (DataStore.serviceState.started) { + SagerNet.stopService() + } else { + SagerNet.startService() + } + } + } + + } + } + + profileName.text = proxyEntity.displayName() + profileType.text = proxyEntity.displayType() + profileType.setTextColor(requireContext().getProtocolColor(proxyEntity.type)) + + var rx = proxyEntity.rx + var tx = proxyEntity.tx + if (trafficData != null) { + // use new data + tx = trafficData.tx + rx = trafficData.rx + } + + val showTraffic = rx + tx != 0L + trafficText.isVisible = showTraffic + if (showTraffic) { + trafficText.text = view.context.getString( + R.string.traffic, + Formatter.formatFileSize(view.context, tx), + Formatter.formatFileSize(view.context, rx) + ) + } + + var address = proxyEntity.displayAddress() + if (showTraffic && address.length >= 30) { + address = address.substring(0, 27) + "..." + } + + if (proxyEntity.requireBean().name.isBlank() || !pf.alwaysShowAddress) { + address = "" + } + + profileAddress.text = address + (trafficText.parent as View).isGone = + (!showTraffic || proxyEntity.status <= 0) && address.isBlank() + + if (proxyEntity.status <= 0) { + if (showTraffic) { + profileStatus.text = trafficText.text + profileStatus.setTextColor(requireContext().getColorAttr(android.R.attr.textColorSecondary)) + trafficText.text = "" + } else { + profileStatus.text = "" + } + } else if (proxyEntity.status == 1) { + profileStatus.text = getString(R.string.available, proxyEntity.ping) + profileStatus.setTextColor(requireContext().getColour(R.color.material_green_500)) + } else { + profileStatus.setTextColor(requireContext().getColour(R.color.material_red_500)) + if (proxyEntity.status == 2) { + profileStatus.text = proxyEntity.error + } + } + + if (proxyEntity.status == 3) { + val err = proxyEntity.error ?: "" + val msg = Protocols.genFriendlyMsg(err) + profileStatus.text = if (msg != err) msg else getString(R.string.unavailable) + profileStatus.setOnClickListener { + alert(err).show() + } + } else { + profileStatus.setOnClickListener(null) + } + + editButton.setOnClickListener { + it.context.startActivity( + proxyEntity.settingIntent( + it.context, proxyGroup.type == GroupType.SUBSCRIPTION + ) + ) + } + + removeButton.setOnClickListener { + adapter?.let { + val index = it.configurationIdList.indexOf(proxyEntity.id) + it.remove(index) + undoManager.remove(index to proxyEntity) + } + } + + val selectOrChain = select || proxyEntity.type == ProxyEntity.TYPE_CHAIN + shareLayout.isGone = selectOrChain + editButton.isGone = select + removeButton.isGone = select + + proxyEntity.nekoBean?.apply { + shareLayout.isGone = !canShare() + } + + runOnDefaultDispatcher { + val selected = (selectedItem?.id ?: DataStore.selectedProxy) == proxyEntity.id + val started = + selected && DataStore.serviceState.started && DataStore.currentProfile == proxyEntity.id + onMainDispatcher { + editButton.isEnabled = !started + removeButton.isEnabled = !started + selectedView.visibility = if (selected) View.VISIBLE else View.INVISIBLE + } + + fun showShare(anchor: View) { + val popup = PopupMenu(requireContext(), anchor) + popup.menuInflater.inflate(R.menu.profile_share_menu, popup.menu) + + if (proxyEntity.vmessBean == null || proxyEntity.vmessBean!!.isVLESS) { + popup.menu.findItem(R.id.action_group_qr).subMenu.removeItem(R.id.action_v2rayn_qr) + popup.menu.findItem(R.id.action_group_clipboard).subMenu.removeItem(R.id.action_v2rayn_clipboard) + } + + when { + !proxyEntity.haveStandardLink() -> { + popup.menu.findItem(R.id.action_group_qr).subMenu.removeItem(R.id.action_standard_qr) + popup.menu.findItem(R.id.action_group_clipboard).subMenu.removeItem( + R.id.action_standard_clipboard + ) + } + !proxyEntity.haveLink() -> { + popup.menu.removeItem(R.id.action_group_qr) + popup.menu.removeItem(R.id.action_group_clipboard) + } + } + + if (proxyEntity.nekoBean != null) { + popup.menu.removeItem(R.id.action_group_configuration) + } + + popup.setOnMenuItemClickListener(this@ConfigurationHolder) + popup.show() + } + + if (!(select || proxyEntity.type == ProxyEntity.TYPE_CHAIN)) { + onMainDispatcher { + shareLayer.setBackgroundColor(Color.TRANSPARENT) + shareButton.setImageResource(R.drawable.ic_social_share) + shareButton.setColorFilter(Color.GRAY) + shareButton.isVisible = true + + shareLayout.setOnClickListener { + showShare(it) + } + } + } + } + + } + + var currentName = "" + fun showCode(link: String) { + QRCodeDialog(link, currentName).showAllowingStateLoss(parentFragmentManager) + } + + fun export(link: String) { + val success = SagerNet.trySetPrimaryClip(link) + (activity as MainActivity).snackbar(if (success) R.string.action_export_msg else R.string.action_export_err) + .show() + } + + override fun onMenuItemClick(item: MenuItem): Boolean { + try { + currentName = entity.displayName()!! + when (item.itemId) { + R.id.action_standard_qr -> showCode(entity.toLink()!!) + R.id.action_standard_clipboard -> export(entity.toLink()!!) + R.id.action_universal_qr -> showCode(entity.requireBean().toUniversalLink()) + R.id.action_universal_clipboard -> export( + entity.requireBean().toUniversalLink() + ) + R.id.action_v2rayn_qr -> showCode(entity.vmessBean!!.toV2rayN()) + R.id.action_v2rayn_clipboard -> export(entity.vmessBean!!.toV2rayN()) + R.id.action_config_export_clipboard -> export(entity.exportConfig().first) + R.id.action_config_export_file -> { + val cfg = entity.exportConfig() + DataStore.serverConfig = cfg.first + startFilesForResult( + (parentFragment as ConfigurationFragment).exportConfig, cfg.second + ) + } + } + } catch (e: Exception) { + Logs.w(e) + (activity as MainActivity).snackbar(e.readableMessage).show() + return true + } + return true + } + } + + } + + private val exportConfig = + registerForActivityResult(ActivityResultContracts.CreateDocument()) { data -> + if (data != null) { + runOnDefaultDispatcher { + try { + (requireActivity() as MainActivity).contentResolver.openOutputStream(data)!! + .bufferedWriter() + .use { + it.write(DataStore.serverConfig) + } + onMainDispatcher { + snackbar(getString(R.string.action_export_msg)).show() + } + } catch (e: Exception) { + Logs.w(e) + onMainDispatcher { + snackbar(e.readableMessage).show() + } + } + + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/DebugFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/DebugFragment.kt new file mode 100644 index 0000000..a5d33a3 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/DebugFragment.kt @@ -0,0 +1,28 @@ +package io.nekohasekai.sagernet.ui + +import android.os.Bundle +import android.view.View +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.databinding.LayoutDebugBinding +import io.nekohasekai.sagernet.ktx.snackbar + +class DebugFragment : NamedFragment(R.layout.layout_debug) { + + override fun name0() = "Debug" + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val binding = LayoutDebugBinding.bind(view) + + binding.debugCrash.setOnClickListener { + error("test crash") + } + binding.resetSettings.setOnClickListener { + DataStore.configurationStore.reset() + snackbar("Cleared").show() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/GroupFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/GroupFragment.kt new file mode 100644 index 0000000..355e87f --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/GroupFragment.kt @@ -0,0 +1,529 @@ +package io.nekohasekai.sagernet.ui + +import android.content.Intent +import android.os.Bundle +import android.text.format.Formatter +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.widget.PopupMenu +import androidx.appcompat.widget.Toolbar +import androidx.core.view.* +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import io.nekohasekai.sagernet.GroupType +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.database.* +import io.nekohasekai.sagernet.databinding.LayoutGroupItemBinding +import io.nekohasekai.sagernet.fmt.toUniversalLink +import io.nekohasekai.sagernet.group.GroupUpdater +import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.widget.ListHolderListener +import io.nekohasekai.sagernet.widget.QRCodeDialog +import io.nekohasekai.sagernet.widget.UndoSnackbarManager +import kotlinx.coroutines.delay +import moe.matsuri.nb4a.utils.Util +import moe.matsuri.nb4a.utils.toBytesString +import java.util.* + +class GroupFragment : ToolbarFragment(R.layout.layout_group), + Toolbar.OnMenuItemClickListener { + + lateinit var activity: MainActivity + lateinit var groupListView: RecyclerView + lateinit var layoutManager: LinearLayoutManager + lateinit var groupAdapter: GroupAdapter + lateinit var undoManager: UndoSnackbarManager + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + activity = requireActivity() as MainActivity + + ViewCompat.setOnApplyWindowInsetsListener(view, ListHolderListener) + toolbar.setTitle(R.string.menu_group) + toolbar.inflateMenu(R.menu.add_group_menu) + toolbar.setOnMenuItemClickListener(this) + + groupListView = view.findViewById(R.id.group_list) + layoutManager = FixedLinearLayoutManager(groupListView) + groupListView.layoutManager = layoutManager + groupAdapter = GroupAdapter() + GroupManager.addListener(groupAdapter) + groupListView.adapter = groupAdapter + + undoManager = UndoSnackbarManager(activity, groupAdapter) + + ItemTouchHelper(object : ItemTouchHelper.SimpleCallback( + ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.START + ) { + override fun getSwipeDirs( + recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder + ): Int { + val proxyGroup = (viewHolder as GroupHolder).proxyGroup + if (proxyGroup.ungrouped || proxyGroup.id in GroupUpdater.updating) { + return 0 + } + return super.getSwipeDirs(recyclerView, viewHolder) + } + + override fun getDragDirs( + recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder + ): Int { + val proxyGroup = (viewHolder as GroupHolder).proxyGroup + if (proxyGroup.ungrouped || proxyGroup.id in GroupUpdater.updating) { + return 0 + } + return super.getDragDirs(recyclerView, viewHolder) + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val index = viewHolder.bindingAdapterPosition + groupAdapter.remove(index) + undoManager.remove(index to (viewHolder as GroupHolder).proxyGroup) + } + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder, + ): Boolean { + groupAdapter.move(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition) + return true + } + + override fun clearView( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + ) { + super.clearView(recyclerView, viewHolder) + groupAdapter.commitMove() + } + }).attachToRecyclerView(groupListView) + + } + + override fun onMenuItemClick(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_new_group -> { + startActivity(Intent(context, GroupSettingsActivity::class.java)) + } + R.id.action_update_all -> { + MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.confirm) + .setMessage(R.string.update_all_subscription) + .setPositiveButton(R.string.yes) { _, _ -> + SagerDatabase.groupDao.allGroups() + .filter { it.type == GroupType.SUBSCRIPTION } + .forEach { + GroupUpdater.startUpdate(it, true) + } + } + .setNegativeButton(R.string.no, null) + .show() + } + } + return true + } + + private lateinit var selectedGroup: ProxyGroup + + private val exportProfiles = registerForActivityResult(ActivityResultContracts.CreateDocument()) { data -> + if (data != null) { + runOnDefaultDispatcher { + val profiles = SagerDatabase.proxyDao.getByGroup(selectedGroup.id) + val links = profiles.mapNotNull { it.toLink(compact = true) }.joinToString("\n") + try { + (requireActivity() as MainActivity).contentResolver.openOutputStream( + data + )!!.bufferedWriter().use { + it.write(links) + } + onMainDispatcher { + snackbar(getString(R.string.action_export_msg)).show() + } + } catch (e: Exception) { + Logs.w(e) + onMainDispatcher { + snackbar(e.readableMessage).show() + } + } + + } + } + } + + inner class GroupAdapter : RecyclerView.Adapter(), + GroupManager.Listener, + UndoSnackbarManager.Interface { + + val groupList = ArrayList() + + suspend fun reload() { + val groups = SagerDatabase.groupDao.allGroups().toMutableList() + if (groups.size > 1 && SagerDatabase.proxyDao.countByGroup(groups.find { it.ungrouped }!!.id) == 0L) groups.removeAll { it.ungrouped } + groupList.clear() + groupList.addAll(groups) + groupListView.post { + notifyDataSetChanged() + } + } + + init { + setHasStableIds(true) + + runOnDefaultDispatcher { + reload() + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GroupHolder { + return GroupHolder(LayoutGroupItemBinding.inflate(layoutInflater, parent, false)) + } + + override fun onBindViewHolder(holder: GroupHolder, position: Int) { + holder.bind(groupList[position]) + } + + override fun getItemCount(): Int { + return groupList.size + } + + override fun getItemId(position: Int): Long { + return groupList[position].id + } + + private val updated = HashSet() + + fun move(from: Int, to: Int) { + val first = groupList[from] + var previousOrder = first.userOrder + val (step, range) = if (from < to) Pair(1, from until to) else Pair( + -1, to + 1 downTo from + ) + for (i in range) { + val next = groupList[i + step] + val order = next.userOrder + next.userOrder = previousOrder + previousOrder = order + groupList[i] = next + updated.add(next) + } + first.userOrder = previousOrder + groupList[to] = first + updated.add(first) + notifyItemMoved(from, to) + } + + fun commitMove() = runOnDefaultDispatcher { + updated.forEach { SagerDatabase.groupDao.updateGroup(it) } + updated.clear() + } + + fun remove(index: Int) { + groupList.removeAt(index) + notifyItemRemoved(index) + } + + override fun undo(actions: List>) { + for ((index, item) in actions) { + groupList.add(index, item) + notifyItemInserted(index) + } + } + + override fun commit(actions: List>) { + val groups = actions.map { it.second } + runOnDefaultDispatcher { + GroupManager.deleteGroup(groups) + reload() + } + } + + override suspend fun groupAdd(group: ProxyGroup) { + groupList.add(group) + delay(300L) + + onMainDispatcher { + undoManager.flush() + notifyItemInserted(groupList.size - 1) + + if (group.type == GroupType.SUBSCRIPTION) { + GroupUpdater.startUpdate(group, true) + } + } + } + + override suspend fun groupRemoved(groupId: Long) { + val index = groupList.indexOfFirst { it.id == groupId } + if (index == -1) return + onMainDispatcher { + undoManager.flush() + if (SagerDatabase.groupDao.allGroups().size <= 2) { + runOnDefaultDispatcher { + reload() + } + } else { + groupList.removeAt(index) + notifyItemRemoved(index) + } + } + } + + override suspend fun groupUpdated(group: ProxyGroup) { + val index = groupList.indexOfFirst { it.id == group.id } + if (index == -1) { + reload() + return + } + groupList[index] = group + onMainDispatcher { + undoManager.flush() + + notifyItemChanged(index) + } + } + + override suspend fun groupUpdated(groupId: Long) { + val index = groupList.indexOfFirst { it.id == groupId } + if (index == -1) { + reload() + return + } + onMainDispatcher { + notifyItemChanged(index) + } + } + + } + + override fun onDestroy() { + if (::groupAdapter.isInitialized) { + GroupManager.removeListener(groupAdapter) + } + + super.onDestroy() + + if (!::undoManager.isInitialized) return + undoManager.flush() + } + + inner class GroupHolder(binding: LayoutGroupItemBinding) : RecyclerView.ViewHolder(binding.root), + PopupMenu.OnMenuItemClickListener { + + lateinit var proxyGroup: ProxyGroup + val groupName = binding.groupName + val groupStatus = binding.groupStatus + val groupTraffic = binding.groupTraffic + val groupUser = binding.groupUser + val editButton = binding.edit + val optionsButton = binding.options + val updateButton = binding.groupUpdate + val subscriptionUpdateProgress = binding.subscriptionUpdateProgress + + override fun onMenuItemClick(item: MenuItem): Boolean { + + fun export(link: String) { + val success = SagerNet.trySetPrimaryClip(link) + activity.snackbar(if (success) R.string.action_export_msg else R.string.action_export_err) + .show() + } + + when (item.itemId) { + R.id.action_universal_qr -> { + QRCodeDialog( + proxyGroup.toUniversalLink(), proxyGroup.displayName() + ).showAllowingStateLoss(parentFragmentManager) + } + R.id.action_universal_clipboard -> { + export(proxyGroup.toUniversalLink()) + } + R.id.action_export_clipboard -> { + runOnDefaultDispatcher { + val profiles = SagerDatabase.proxyDao.getByGroup(selectedGroup.id) + val links = profiles.mapNotNull { it.toLink(compact = true) } + .joinToString("\n") + onMainDispatcher { + SagerNet.trySetPrimaryClip(links) + snackbar(getString(R.string.copy_toast_msg)).show() + } + } + } + R.id.action_export_file -> { + startFilesForResult(exportProfiles, "profiles_${proxyGroup.displayName()}.txt") + } + R.id.action_clear -> { + MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.confirm) + .setMessage(R.string.clear_profiles_message) + .setPositiveButton(R.string.yes) { _, _ -> + runOnDefaultDispatcher { + GroupManager.clearGroup(proxyGroup.id) + } + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + } + + return true + } + + + fun bind(group: ProxyGroup) { + proxyGroup = group + + itemView.setOnClickListener { } + + editButton.isGone = proxyGroup.ungrouped + updateButton.isInvisible = proxyGroup.type != GroupType.SUBSCRIPTION + groupName.text = proxyGroup.displayName() + + editButton.setOnClickListener { + startActivity(Intent(it.context, GroupSettingsActivity::class.java).apply { + putExtra(GroupSettingsActivity.EXTRA_GROUP_ID, group.id) + }) + } + + updateButton.setOnClickListener { + GroupUpdater.startUpdate(proxyGroup, true) + } + + optionsButton.setOnClickListener { + selectedGroup = proxyGroup + + val popup = PopupMenu(requireContext(), it) + popup.menuInflater.inflate(R.menu.group_action_menu, popup.menu) + + if (proxyGroup.type != GroupType.SUBSCRIPTION) { + popup.menu.removeItem(R.id.action_share) + } + popup.setOnMenuItemClickListener(this) + popup.show() + } + + if (proxyGroup.id in GroupUpdater.updating) { + (groupName.parent as LinearLayout).apply { + setPadding(paddingLeft, dp2px(11), paddingRight, paddingBottom) + } + + subscriptionUpdateProgress.isVisible = true + + if (!GroupUpdater.progress.containsKey(proxyGroup.id)) { + subscriptionUpdateProgress.isIndeterminate = true + } else { + subscriptionUpdateProgress.isIndeterminate = false + val progress = GroupUpdater.progress[proxyGroup.id]!! + subscriptionUpdateProgress.max = progress.max + subscriptionUpdateProgress.progress = progress.progress + } + + updateButton.isInvisible = true + editButton.isGone = true + } else { + (groupName.parent as LinearLayout).apply { + setPadding(paddingLeft, dp2px(15), paddingRight, paddingBottom) + } + + subscriptionUpdateProgress.isVisible = false + updateButton.isInvisible = proxyGroup.type != GroupType.SUBSCRIPTION + editButton.isGone = proxyGroup.ungrouped + } + + val subscription = proxyGroup.subscription + if (subscription != null && subscription.bytesUsed > 0L) { // SIP008 & Open Online Config + groupTraffic.isVisible = true + groupTraffic.text = if (subscription.bytesRemaining > 0L) { + getString( + R.string.subscription_traffic, Formatter.formatFileSize( + context, subscription.bytesUsed + ), Formatter.formatFileSize( + context, subscription.bytesRemaining + ) + ) + } else { + getString( + R.string.subscription_used, Formatter.formatFileSize( + context, subscription.bytesUsed + ) + ) + } + groupStatus.setPadding(0) + } else if (subscription != null && !subscription.subscriptionUserinfo.isNullOrBlank()) { // Raw + var text = ""; + + fun get(regex: String): String? { + return regex.toRegex().findAll(subscription.subscriptionUserinfo).mapNotNull { + if (it.groupValues.size > 1) it.groupValues[1] else null + }.firstOrNull(); + } + + var used: Long = 0 + get("upload=([0-9]+)")?.apply { + used += toLong() + } + get("download=([0-9]+)")?.apply { + used += toLong() + } + val total = get("total=([0-9]+)")?.toLong() ?: 0 + if (used > 0 || total > 0) { + text += getString( + R.string.subscription_traffic, + used.toBytesString(), + (total - used).toBytesString() + ) + } + get("expire=([0-9]+)")?.apply { + text += "\n" + text += getString( + R.string.subscription_expire, + Util.timeStamp2Text(this.toLong() * 1000) + ) + } + + if (text.isNotEmpty()) { + groupTraffic.isVisible = true + groupTraffic.text = text; + groupStatus.setPadding(0) + } + } else { + groupTraffic.isVisible = false + groupStatus.setPadding(0, 0, 0, dp2px(4)) + } + + groupUser.text = subscription?.username ?: "" + + runOnDefaultDispatcher { + val size = SagerDatabase.proxyDao.countByGroup(group.id) + onMainDispatcher { + @Suppress("DEPRECATION") when (group.type) { + GroupType.BASIC -> { + if (size == 0L) { + groupStatus.setText(R.string.group_status_empty) + } else { + groupStatus.text = getString(R.string.group_status_proxies, size) + } + } + GroupType.SUBSCRIPTION -> { + groupStatus.text = if (size == 0L) { + getString(R.string.group_status_empty_subscription) + } else { + val date = Date(group.subscription!!.lastUpdated * 1000L) + getString( + R.string.group_status_proxies_subscription, + size, + "${date.month + 1} - ${date.date}" + ) + } + + } + } + } + + } + + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt new file mode 100644 index 0000000..7773abc --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt @@ -0,0 +1,315 @@ +package io.nekohasekai.sagernet.ui + +import android.annotation.SuppressLint +import android.content.DialogInterface +import android.os.Bundle +import android.os.Parcelable +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.annotation.LayoutRes +import androidx.appcompat.app.AlertDialog +import androidx.core.view.ViewCompat +import androidx.preference.* +import com.github.shadowsocks.plugin.Empty +import com.github.shadowsocks.plugin.fragment.AlertDialogFragment +import com.takisoft.preferencex.PreferenceFragmentCompat +import com.takisoft.preferencex.SimpleMenuPreference +import io.nekohasekai.sagernet.GroupType +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.* +import io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener +import io.nekohasekai.sagernet.ktx.applyDefaultValues +import io.nekohasekai.sagernet.ktx.onMainDispatcher +import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher +import io.nekohasekai.sagernet.widget.ListListener +import io.nekohasekai.sagernet.widget.UserAgentPreference +import kotlinx.parcelize.Parcelize + +@Suppress("UNCHECKED_CAST") +class GroupSettingsActivity( + @LayoutRes resId: Int = R.layout.layout_config_settings, +) : ThemedActivity(resId), + OnPreferenceDataStoreChangeListener { + + fun ProxyGroup.init() { + DataStore.groupName = name ?: "" + DataStore.groupType = type + DataStore.groupOrder = order + val subscription = subscription ?: SubscriptionBean().applyDefaultValues() + DataStore.subscriptionLink = subscription.link + DataStore.subscriptionForceResolve = subscription.forceResolve + DataStore.subscriptionDeduplication = subscription.deduplication + DataStore.subscriptionUpdateWhenConnectedOnly = subscription.updateWhenConnectedOnly + DataStore.subscriptionUserAgent = subscription.customUserAgent + DataStore.subscriptionAutoUpdate = subscription.autoUpdate + DataStore.subscriptionAutoUpdateDelay = subscription.autoUpdateDelay + } + + fun ProxyGroup.serialize() { + name = DataStore.groupName.takeIf { it.isNotBlank() } ?: "My group" + type = DataStore.groupType + order = DataStore.groupOrder + + val isSubscription = type == GroupType.SUBSCRIPTION + if (isSubscription) { + subscription = (subscription ?: SubscriptionBean().applyDefaultValues()).apply { + link = DataStore.subscriptionLink + forceResolve = DataStore.subscriptionForceResolve + deduplication = DataStore.subscriptionDeduplication + updateWhenConnectedOnly = DataStore.subscriptionUpdateWhenConnectedOnly + customUserAgent = DataStore.subscriptionUserAgent + autoUpdate = DataStore.subscriptionAutoUpdate + autoUpdateDelay = DataStore.subscriptionAutoUpdateDelay + } + } + } + + fun needSave(): Boolean { + if (!DataStore.dirty) return false + return true + } + + fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + addPreferencesFromResource(R.xml.group_preferences) + + val groupType = findPreference(Key.GROUP_TYPE)!! + val groupSubscription = findPreference(Key.GROUP_SUBSCRIPTION)!! + val subscriptionUpdate = findPreference(Key.SUBSCRIPTION_UPDATE)!! + + fun updateGroupType(groupType: Int = DataStore.groupType) { + val isSubscription = groupType == GroupType.SUBSCRIPTION + groupSubscription.isVisible = isSubscription + subscriptionUpdate.isVisible = isSubscription + } + updateGroupType() + groupType.setOnPreferenceChangeListener { _, newValue -> + updateGroupType((newValue as String).toInt()) + true + } + + val subscriptionUserAgent = + findPreference(Key.SUBSCRIPTION_USER_AGENT)!! + val subscriptionAutoUpdate = + findPreference(Key.SUBSCRIPTION_AUTO_UPDATE)!! + val subscriptionAutoUpdateDelay = + findPreference(Key.SUBSCRIPTION_AUTO_UPDATE_DELAY)!! + + subscriptionAutoUpdateDelay.isEnabled = subscriptionAutoUpdate.isChecked + subscriptionAutoUpdateDelay.setOnPreferenceChangeListener { _, newValue -> + val delay = (newValue as String).toIntOrNull() + if (delay == null) { + false + } else { + delay >= 15 + } + } + subscriptionAutoUpdate.setOnPreferenceChangeListener { _, newValue -> + subscriptionAutoUpdateDelay.isEnabled = (newValue as Boolean) + true + } + } + + fun PreferenceFragmentCompat.viewCreated(view: View, savedInstanceState: Bundle?) { + } + + fun PreferenceFragmentCompat.displayPreferenceDialog(preference: Preference): Boolean { + return false + } + + class UnsavedChangesDialogFragment : AlertDialogFragment() { + override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) { + setTitle(R.string.unsaved_changes_prompt) + setPositiveButton(R.string.yes) { _, _ -> + runOnDefaultDispatcher { + (requireActivity() as GroupSettingsActivity).saveAndExit() + } + } + setNegativeButton(R.string.no) { _, _ -> + requireActivity().finish() + } + setNeutralButton(android.R.string.cancel, null) + } + } + + @Parcelize + data class GroupIdArg(val groupId: Long) : Parcelable + class DeleteConfirmationDialogFragment : AlertDialogFragment() { + override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) { + setTitle(R.string.delete_group_prompt) + setPositiveButton(R.string.yes) { _, _ -> + runOnDefaultDispatcher { + GroupManager.deleteGroup(arg.groupId) + } + requireActivity().finish() + } + setNegativeButton(R.string.no, null) + } + } + + companion object { + const val EXTRA_GROUP_ID = "id" + } + + @SuppressLint("CommitTransaction") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setSupportActionBar(findViewById(R.id.toolbar)) + supportActionBar?.apply { + setTitle(R.string.group_settings) + setDisplayHomeAsUpEnabled(true) + setHomeAsUpIndicator(R.drawable.ic_navigation_close) + } + + if (savedInstanceState == null) { + val editingId = intent.getLongExtra(EXTRA_GROUP_ID, 0L) + DataStore.editingId = editingId + runOnDefaultDispatcher { + if (editingId == 0L) { + ProxyGroup().init() + } else { + val entity = SagerDatabase.groupDao.getById(editingId) + if (entity == null) { + onMainDispatcher { + finish() + } + return@runOnDefaultDispatcher + } + entity.init() + } + + onMainDispatcher { + supportFragmentManager.beginTransaction() + .replace(R.id.settings, MyPreferenceFragmentCompat().apply { + activity = this@GroupSettingsActivity + }) + .commit() + + DataStore.dirty = false + DataStore.profileCacheStore.registerChangeListener(this@GroupSettingsActivity) + } + } + + } + + } + + suspend fun saveAndExit() { + + val editingId = DataStore.editingId + if (editingId == 0L) { + GroupManager.createGroup(ProxyGroup().apply { serialize() }) + } else if (needSave()) { + val entity = SagerDatabase.groupDao.getById(DataStore.editingId) + if (entity == null) { + finish() + return + } + entity.subscription?.subscriptionUserinfo = ""; + GroupManager.updateGroup(entity.apply { serialize() }) + } + + finish() + + } + + val child by lazy { supportFragmentManager.findFragmentById(R.id.settings) as MyPreferenceFragmentCompat } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.profile_config_menu, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem) = child.onOptionsItemSelected(item) + + override fun onBackPressed() { + if (needSave()) { + UnsavedChangesDialogFragment().apply { key() }.show(supportFragmentManager, null) + } else super.onBackPressed() + } + + override fun onSupportNavigateUp(): Boolean { + if (!super.onSupportNavigateUp()) finish() + return true + } + + override fun onDestroy() { + DataStore.profileCacheStore.unregisterChangeListener(this) + super.onDestroy() + } + + override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) { + if (key != Key.PROFILE_DIRTY) { + DataStore.dirty = true + } + } + + class MyPreferenceFragmentCompat : PreferenceFragmentCompat() { + + lateinit var activity: GroupSettingsActivity + + override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { + preferenceManager.preferenceDataStore = DataStore.profileCacheStore + activity.apply { + createPreferences(savedInstanceState, rootKey) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + ViewCompat.setOnApplyWindowInsetsListener(listView, ListListener) + + activity.apply { + viewCreated(view, savedInstanceState) + } + } + + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.action_delete -> { + if (DataStore.editingId == 0L) { + requireActivity().finish() + } else { + DeleteConfirmationDialogFragment().apply { + arg(GroupIdArg(DataStore.editingId)) + key() + }.show(parentFragmentManager, null) + } + true + } + R.id.action_apply -> { + runOnDefaultDispatcher { + activity.saveAndExit() + } + true + } + else -> false + } + + override fun onDisplayPreferenceDialog(preference: Preference) { + activity.apply { + if (displayPreferenceDialog(preference)) return + } + super.onDisplayPreferenceDialog(preference) + } + + } + + object PasswordSummaryProvider : Preference.SummaryProvider { + + override fun provideSummary(preference: EditTextPreference): CharSequence { + val text = preference.text + return if (text.isNullOrBlank()) { + preference.context.getString(androidx.preference.R.string.not_set) + } else { + "\u2022".repeat(text.length) + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/LogcatFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/LogcatFragment.kt new file mode 100644 index 0000000..18b1583 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/LogcatFragment.kt @@ -0,0 +1,127 @@ +package io.nekohasekai.sagernet.ui + +import android.annotation.SuppressLint +import android.graphics.Color +import android.os.Build +import android.os.Bundle +import android.text.SpannableString +import android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE +import android.text.style.ForegroundColorSpan +import android.view.MenuItem +import android.view.View +import android.widget.ScrollView +import androidx.appcompat.widget.Toolbar +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.databinding.LayoutLogcatBinding +import io.nekohasekai.sagernet.ktx.* +import libcore.Libcore +import moe.matsuri.nb4a.utils.SendLog + +class LogcatFragment : ToolbarFragment(R.layout.layout_logcat), + Toolbar.OnMenuItemClickListener { + + lateinit var binding: LayoutLogcatBinding + + @SuppressLint("RestrictedApi", "WrongConstant") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + toolbar.setTitle(R.string.menu_log) + + toolbar.inflateMenu(R.menu.logcat_menu) + toolbar.setOnMenuItemClickListener(this) + + binding = LayoutLogcatBinding.bind(view) + + if (Build.VERSION.SDK_INT >= 23) { + binding.textview.breakStrategy = 0 // simple + } + + reloadSession() + + DataStore.postLogListener = { + runOnMainDispatcher { + val color = getColorForLine(it) + val span = SpannableString(it) + span.setSpan(color, 0, it.length, SPAN_EXCLUSIVE_EXCLUSIVE) + binding.textview.append(span) + binding.scroolview.post { + binding.scroolview.fullScroll(ScrollView.FOCUS_DOWN) + } + } + } + } + + override fun onDestroy() { + DataStore.postLogListener = null + super.onDestroy() + } + + private fun getColorForLine(line: String): ForegroundColorSpan { + var color = ForegroundColorSpan(Color.GRAY) + when { + line.contains(" INFO[") || line.contains(" [Info]") -> { + color = ForegroundColorSpan((0xFF86C166).toInt()) + } + line.contains(" ERROR[") || line.contains(" [Error]") -> { + color = ForegroundColorSpan(Color.RED) + } + line.contains(" WARN[") || line.contains(" [Warning]") -> { + color = ForegroundColorSpan(Color.RED) + } + } + return color + } + + private fun reloadSession() { + val span = SpannableString( + String(SendLog.getNekoLog(50 * 1024)) + ) + var offset = 0 + for (line in span.lines()) { + val color = getColorForLine(line) + span.setSpan( + color, offset, offset + line.length, SPAN_EXCLUSIVE_EXCLUSIVE + ) + offset += line.length + 1 + } + binding.textview.text = span + + binding.scroolview.post { + binding.scroolview.fullScroll(ScrollView.FOCUS_DOWN) + } + } + + override fun onMenuItemClick(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_clear_logcat -> { + runOnDefaultDispatcher { + try { + Libcore.nekoLogClear() + Runtime.getRuntime().exec("/system/bin/logcat -c") + } catch (e: Exception) { + onMainDispatcher { + snackbar(e.readableMessage).show() + } + return@runOnDefaultDispatcher + } + onMainDispatcher { + binding.textview.text = "" + } + } + + } + R.id.action_send_logcat -> { + val context = requireContext() + runOnDefaultDispatcher { + SendLog.sendLog(context, "NB4A") + } + } + R.id.action_refresh -> { + reloadSession() + } + } + return true + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt new file mode 100644 index 0000000..1358d0d --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt @@ -0,0 +1,490 @@ +package io.nekohasekai.sagernet.ui + +import android.annotation.SuppressLint +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.os.RemoteException +import android.view.KeyEvent +import android.view.MenuItem +import android.widget.Toast +import androidx.annotation.IdRes +import androidx.core.view.ViewCompat +import androidx.preference.PreferenceDataStore +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.navigation.NavigationView +import com.google.android.material.snackbar.Snackbar +import io.nekohasekai.sagernet.* +import io.nekohasekai.sagernet.aidl.ISagerNetService +import io.nekohasekai.sagernet.aidl.SpeedDisplayData +import io.nekohasekai.sagernet.aidl.TrafficData +import io.nekohasekai.sagernet.bg.BaseService +import io.nekohasekai.sagernet.bg.SagerConnection +import io.nekohasekai.sagernet.database.* +import io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener +import io.nekohasekai.sagernet.databinding.LayoutMainBinding +import io.nekohasekai.sagernet.fmt.AbstractBean +import io.nekohasekai.sagernet.fmt.KryoConverters +import io.nekohasekai.sagernet.fmt.PluginEntry +import io.nekohasekai.sagernet.group.GroupInterfaceAdapter +import io.nekohasekai.sagernet.group.GroupUpdater +import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.widget.ListHolderListener +import kotlinx.coroutines.launch +import moe.matsuri.nb4a.utils.Util +import java.util.* + +class MainActivity : ThemedActivity(), + SagerConnection.Callback, + OnPreferenceDataStoreChangeListener, + NavigationView.OnNavigationItemSelectedListener { + + lateinit var binding: LayoutMainBinding + lateinit var navigation: NavigationView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = LayoutMainBinding.inflate(layoutInflater) + binding.fab.initProgress(binding.fabProgress) + if (themeResId !in intArrayOf( + R.style.Theme_SagerNet_Black + ) + ) { + navigation = binding.navView + binding.drawerLayout.removeView(binding.navViewBlack) + } else { + navigation = binding.navViewBlack + binding.drawerLayout.removeView(binding.navView) + } + navigation.setNavigationItemSelectedListener(this) + + if (savedInstanceState == null) { + displayFragmentWithId(R.id.nav_configuration) + } + + binding.fab.setOnClickListener { + if (DataStore.serviceState.canStop) SagerNet.stopService() else connect.launch( + null + ) + } + binding.stats.setOnClickListener { if (DataStore.serviceState.connected) binding.stats.testConnection() } + + setContentView(binding.root) + ViewCompat.setOnApplyWindowInsetsListener(binding.coordinator, ListHolderListener) + changeState(BaseService.State.Idle) + connection.connect(this, this) + DataStore.configurationStore.registerChangeListener(this) + GroupManager.userInterface = GroupInterfaceAdapter(this) + + if (intent?.action == Intent.ACTION_VIEW) { + onNewIntent(intent) + } + + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + + val uri = intent.data ?: return + + runOnDefaultDispatcher { + if (uri.scheme == "sn" && uri.host == "subscription" || uri.scheme == "clash") { + importSubscription(uri) + } else { + importProfile(uri) + } + } + } + + fun urlTest(): Int { + if (!DataStore.serviceState.connected || connection.service == null) { + error("not started") + } + return connection.service!!.urlTest() + } + + suspend fun importSubscription(uri: Uri) { + val group: ProxyGroup + + val url = uri.getQueryParameter("url") + if (!url.isNullOrBlank()) { + group = ProxyGroup(type = GroupType.SUBSCRIPTION) + val subscription = SubscriptionBean() + group.subscription = subscription + + // cleartext format + subscription.link = url + group.name = uri.getQueryParameter("name") + } else { + val data = uri.encodedQuery.takeIf { !it.isNullOrBlank() } ?: return + try { + group = KryoConverters.deserialize( + ProxyGroup().apply { export = true }, Util.zlibDecompress(Util.b64Decode(data)) + ).apply { + export = false + } + } catch (e: Exception) { + onMainDispatcher { + alert(e.readableMessage).show() + } + return + } + } + + val name = group.name.takeIf { !it.isNullOrBlank() } ?: group.subscription?.link + ?: group.subscription?.token + if (name.isNullOrBlank()) return + + group.name = group.name.takeIf { !it.isNullOrBlank() } + ?: ("Subscription #" + System.currentTimeMillis()) + + onMainDispatcher { + + displayFragmentWithId(R.id.nav_group) + + MaterialAlertDialogBuilder(this@MainActivity).setTitle(R.string.subscription_import) + .setMessage(getString(R.string.subscription_import_message, name)) + .setPositiveButton(R.string.yes) { _, _ -> + runOnDefaultDispatcher { + finishImportSubscription(group) + } + } + .setNegativeButton(android.R.string.cancel, null) + .show() + + } + + } + + private suspend fun finishImportSubscription(subscription: ProxyGroup) { + GroupManager.createGroup(subscription) + GroupUpdater.startUpdate(subscription, true) + } + + suspend fun importProfile(uri: Uri) { + val profile = try { + parseProxies(uri.toString()).getOrNull(0) ?: error(getString(R.string.no_proxies_found)) + } catch (e: Exception) { + onMainDispatcher { + alert(e.readableMessage).show() + } + return + } + + onMainDispatcher { + MaterialAlertDialogBuilder(this@MainActivity).setTitle(R.string.profile_import) + .setMessage(getString(R.string.profile_import_message, profile.displayName())) + .setPositiveButton(R.string.yes) { _, _ -> + runOnDefaultDispatcher { + finishImportProfile(profile) + } + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + + } + + private suspend fun finishImportProfile(profile: AbstractBean) { + val targetId = DataStore.selectedGroupForImport() + + ProfileManager.createProfile(targetId, profile) + + onMainDispatcher { + displayFragmentWithId(R.id.nav_configuration) + + snackbar(resources.getQuantityString(R.plurals.added, 1, 1)).show() + } + } + + override fun missingPlugin(profileName: String, pluginName: String) { + val pluginEntity = PluginEntry.find(pluginName) + + // unknown exe or neko plugin + if (pluginEntity == null) { + snackbar(getString(R.string.plugin_unknown, pluginName)).show() + return + } + + // official exe + + MaterialAlertDialogBuilder(this).setTitle(R.string.missing_plugin) + .setMessage( + getString( + R.string.profile_requiring_plugin, profileName, pluginEntity.displayName + ) + ) + .setPositiveButton(R.string.action_download) { _, _ -> + showDownloadDialog(pluginEntity) + } + .setNeutralButton(android.R.string.cancel, null) + .setNeutralButton(R.string.action_learn_more) { _, _ -> + launchCustomTab("https://matsuridayo.github.io/m-plugin/") + } + .show() + } + + private fun showDownloadDialog(pluginEntry: PluginEntry) { + var index = 0 + var playIndex = -1 + var fdroidIndex = -1 + + val items = mutableListOf() + if (pluginEntry.downloadSource.playStore) { + items.add(getString(R.string.install_from_play_store)) + playIndex = index++ + } + if (pluginEntry.downloadSource.fdroid) { + items.add(getString(R.string.install_from_fdroid)) + fdroidIndex = index++ + } + + items.add(getString(R.string.download)) + val downloadIndex = index + + MaterialAlertDialogBuilder(this).setTitle(pluginEntry.name) + .setItems(items.toTypedArray()) { _, which -> + when (which) { + playIndex -> launchCustomTab("https://play.google.com/store/apps/details?id=${pluginEntry.packageName}") + fdroidIndex -> launchCustomTab("https://f-droid.org/packages/${pluginEntry.packageName}/") + downloadIndex -> launchCustomTab(pluginEntry.downloadSource.downloadLink) + } + } + .show() + } + + override fun onNavigationItemSelected(item: MenuItem): Boolean { + if (item.isChecked) binding.drawerLayout.closeDrawers() else { + return displayFragmentWithId(item.itemId) + } + return true + } + + + @SuppressLint("CommitTransaction") + fun displayFragment(fragment: ToolbarFragment) { + if (fragment is ConfigurationFragment) { + binding.stats.allowShow = true + binding.fab.show() + } else if (!DataStore.showBottomBar) { + binding.stats.allowShow = false + binding.stats.performHide() + binding.fab.hide() + } + supportFragmentManager.beginTransaction() + .replace(R.id.fragment_holder, fragment) + .commitAllowingStateLoss() + binding.drawerLayout.closeDrawers() + } + + fun displayFragmentWithId(@IdRes id: Int): Boolean { + when (id) { + R.id.nav_configuration -> { + displayFragment(ConfigurationFragment()) + } + R.id.nav_group -> displayFragment(GroupFragment()) + R.id.nav_route -> displayFragment(RouteFragment()) + R.id.nav_settings -> displayFragment(SettingsFragment()) + R.id.nav_traffic -> displayFragment(WebviewFragment()) + R.id.nav_tools -> displayFragment(ToolsFragment()) + R.id.nav_logcat -> displayFragment(LogcatFragment()) + R.id.nav_faq -> { + launchCustomTab("https://matsuridayo.github.io/") + return false + } + R.id.nav_about -> displayFragment(AboutFragment()) + R.id.nav_tuiguang -> { + launchCustomTab("https://matsuricom.github.io/") + return false + } + else -> return false + } + navigation.menu.findItem(id).isChecked = true + return true + } + + @SuppressLint("CommitTransaction") + fun ruleCreated() { + navigation.menu.findItem(R.id.nav_route).isChecked = true + supportFragmentManager.beginTransaction() + .replace(R.id.fragment_holder, RouteFragment()) + .commitAllowingStateLoss() + if (DataStore.serviceState.started) { + snackbar(getString(R.string.restart)).setAction(R.string.apply) { + SagerNet.reloadService() + }.show() + } + } + + private fun changeState( + state: BaseService.State, + msg: String? = null, + animate: Boolean = false, + ) { + DataStore.serviceState = state + + binding.fab.changeState(state, DataStore.serviceState, animate) + binding.stats.changeState(state) + if (msg != null) snackbar(getString(R.string.vpn_error, msg)).show() + + when (state) { + BaseService.State.Stopped -> { + runOnDefaultDispatcher { + // refresh view + ProfileManager.postUpdate(DataStore.currentProfile) + } + } + else -> {} + } + } + + override fun snackbarInternal(text: CharSequence): Snackbar { + return Snackbar.make(binding.coordinator, text, Snackbar.LENGTH_LONG).apply { + if (binding.fab.isShown) { + anchorView = binding.fab + } + // TODO + } + } + + override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) { + changeState(state, msg, true) + } + + override fun routeAlert(type: Int, routeName: String) { + when (type) { + 0 -> { + // need vpn + + Toast.makeText( + this, getString(R.string.route_need_vpn, routeName), Toast.LENGTH_SHORT + ).show() + } + } + } + + val connection = SagerConnection(true) + override fun onServiceConnected(service: ISagerNetService) = changeState( + try { + BaseService.State.values()[service.state] + } catch (_: RemoteException) { + BaseService.State.Idle + } + ) + + override fun onServiceDisconnected() = changeState(BaseService.State.Idle) + override fun onBinderDied() { + connection.disconnect(this) + connection.connect(this, this) + } + + private val connect = registerForActivityResult(VpnRequestActivity.StartService()) { + if (it) snackbar(R.string.vpn_permission_denied).show() + } + + // may NOT called when app is in background + // ONLY do UI update here, write DB in bg process + override fun cbSpeedUpdate(stats: SpeedDisplayData) { + binding.stats.updateSpeed(stats.txRateProxy, stats.rxRateProxy) + } + + override fun cbTrafficUpdate(data: TrafficData) { + runOnDefaultDispatcher { + ProfileManager.postUpdate(data) + } + } + + override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) { + when (key) { + Key.SERVICE_MODE -> onBinderDied() + Key.PROXY_APPS, Key.BYPASS_MODE, Key.INDIVIDUAL -> { + if (DataStore.serviceState.canStop) { + snackbar(getString(R.string.restart)).setAction(R.string.apply) { + SagerNet.reloadService() + }.show() + } + } + } + } + + override fun onStart() { + super.onStart() + } + + override fun onStop() { + super.onStop() + } + + override fun onDestroy() { + super.onDestroy() + GroupManager.userInterface = null + DataStore.configurationStore.unregisterChangeListener(this) + connection.disconnect(this) + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + when (keyCode) { + KeyEvent.KEYCODE_DPAD_LEFT -> { + if (super.onKeyDown(keyCode, event)) return true + binding.drawerLayout.open() + navigation.requestFocus() + } + KeyEvent.KEYCODE_DPAD_RIGHT -> { + if (binding.drawerLayout.isOpen) { + binding.drawerLayout.close() + return true + } + } + } + + if (super.onKeyDown(keyCode, event)) return true + if (binding.drawerLayout.isOpen) return false + + val fragment = + supportFragmentManager.findFragmentById(R.id.fragment_holder) as? ToolbarFragment + return fragment != null && fragment.onKeyDown(keyCode, event) + } + + @SuppressLint("SimpleDateFormat") + override fun onResume() { + super.onResume() + + // TODO nb4a release + /* + val sdf = SimpleDateFormat("yyyy-MM-dd") + val now = System.currentTimeMillis() + val expire = Libcore.getExpireTime() * 1000 + val dateExpire = Date(expire) + val build = Libcore.getBuildTime() * 1000 + val dateBuild = Date(build) + + var text: String? = null + if (now > expire) { + text = getString( + R.string.please_update_force, sdf.format(dateBuild), sdf.format(dateExpire) + ) + } else if (now > (expire - 2592000000)) { + // 30 days remind :D + text = getString( + R.string.please_update, sdf.format(dateBuild), sdf.format(dateExpire) + ) + } + + + if (text != null) { + MaterialAlertDialogBuilder(this@MainActivity).setTitle(R.string.insecure) + .setMessage(text) + .setPositiveButton(R.string.action_download) { _, _ -> + launchCustomTab( + "https://github.com/MatsuriDayo/NekoBoxForAndroid/releases" + ) + } + .setNegativeButton(android.R.string.cancel, null) + .setCancelable(false) + .show() + } + */ + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/NamedFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/NamedFragment.kt new file mode 100644 index 0000000..cecf547 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/NamedFragment.kt @@ -0,0 +1,14 @@ +package io.nekohasekai.sagernet.ui + +import androidx.fragment.app.Fragment + +abstract class NamedFragment : Fragment { + + constructor() : super() + constructor(contentLayoutId: Int) : super(contentLayoutId) + + private val name by lazy { name0() } + fun name() = name + protected abstract fun name0(): String + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/NetworkFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/NetworkFragment.kt new file mode 100644 index 0000000..6ef29c1 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/NetworkFragment.kt @@ -0,0 +1,84 @@ +package io.nekohasekai.sagernet.ui + +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AlertDialog +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.ProfileManager +import io.nekohasekai.sagernet.databinding.LayoutNetworkBinding +import io.nekohasekai.sagernet.databinding.LayoutProgressBinding +import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.utils.Cloudflare +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.runBlocking + +class NetworkFragment : NamedFragment(R.layout.layout_network) { + + override fun name0() = app.getString(R.string.tools_network) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val binding = LayoutNetworkBinding.bind(view) + binding.stunTest.setOnClickListener { + startActivity(Intent(requireContext(), StunActivity::class.java)) + } + + //Markwon.create(requireContext()) + // .setMarkdown(binding.wrapLicense, getString(R.string.warp_license)) + + binding.warpGenerate.setOnClickListener { + runBlocking { + generateWarpConfiguration() + } + } + } + + suspend fun generateWarpConfiguration() { + val activity = requireActivity() as MainActivity + val binding = LayoutProgressBinding.inflate(layoutInflater).apply { + content.setText(R.string.generating) + } + var job: Job? = null + val dialog = AlertDialog.Builder(requireContext()) + .setView(binding.root) + .setCancelable(false) + .setNegativeButton(android.R.string.cancel) { _, _ -> + job?.cancel() + } + .show() + job = runOnDefaultDispatcher { + try { + val bean = Cloudflare.makeWireGuardConfiguration() + if (isActive) { + val groupId = DataStore.selectedGroupForImport() + if (DataStore.selectedGroup != groupId) { + DataStore.selectedGroup = groupId + } + onMainDispatcher { + activity.displayFragmentWithId(R.id.nav_configuration) + } + delay(1000L) + onMainDispatcher { + dialog.dismiss() + } + ProfileManager.createProfile(groupId, bean) + } + } catch (e: Exception) { + Logs.w(e) + onMainDispatcher { + if (isActive) { + dialog.dismiss() + activity.snackbar(e.readableMessage).show() + } + } + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ProfileSelectActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ProfileSelectActivity.kt new file mode 100644 index 0000000..20edf1a --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ProfileSelectActivity.kt @@ -0,0 +1,36 @@ +package io.nekohasekai.sagernet.ui + +import android.content.Intent +import android.os.Bundle +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.ProxyEntity + +class ProfileSelectActivity : ThemedActivity(R.layout.layout_empty), + ConfigurationFragment.SelectCallback { + + companion object { + const val EXTRA_SELECTED = "selected" + const val EXTRA_PROFILE_ID = "id" + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val selected = intent.getParcelableExtra(EXTRA_SELECTED) + + supportFragmentManager.beginTransaction() + .replace( + R.id.fragment_holder, + ConfigurationFragment(true, selected, R.string.select_profile) + ) + .commitAllowingStateLoss() + } + + override fun returnProfile(profileId: Long) { + setResult(RESULT_OK, Intent().apply { + putExtra(EXTRA_PROFILE_ID, profileId) + }) + finish() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/RouteFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/RouteFragment.kt new file mode 100644 index 0000000..6fce4b0 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/RouteFragment.kt @@ -0,0 +1,310 @@ +package io.nekohasekai.sagernet.ui + +import android.content.Intent +import android.os.Bundle +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.widget.Toolbar +import androidx.core.view.ViewCompat +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.ProfileManager +import io.nekohasekai.sagernet.database.RuleEntity +import io.nekohasekai.sagernet.database.SagerDatabase +import io.nekohasekai.sagernet.databinding.LayoutEmptyRouteBinding +import io.nekohasekai.sagernet.databinding.LayoutRouteItemBinding +import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.widget.ListHolderListener +import io.nekohasekai.sagernet.widget.UndoSnackbarManager + +class RouteFragment : ToolbarFragment(R.layout.layout_route), Toolbar.OnMenuItemClickListener { + + lateinit var activity: MainActivity + lateinit var ruleListView: RecyclerView + lateinit var ruleAdapter: RuleAdapter + lateinit var undoManager: UndoSnackbarManager + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + activity = requireActivity() as MainActivity + + ViewCompat.setOnApplyWindowInsetsListener(view, ListHolderListener) + toolbar.setTitle(R.string.menu_route) + toolbar.inflateMenu(R.menu.add_route_menu) + toolbar.setOnMenuItemClickListener(this) + + ruleListView = view.findViewById(R.id.route_list) + ruleListView.layoutManager = FixedLinearLayoutManager(ruleListView) + ruleAdapter = RuleAdapter() + ProfileManager.addListener(ruleAdapter) + ruleListView.adapter = ruleAdapter + undoManager = UndoSnackbarManager(activity, ruleAdapter) + + ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.START) { + + override fun getSwipeDirs( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + ) = if (viewHolder is RuleAdapter.DocumentHolder) { + 0 + } else { + super.getSwipeDirs(recyclerView, viewHolder) + } + + override fun getDragDirs( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + ) = if (viewHolder is RuleAdapter.DocumentHolder) { + 0 + } else { + super.getDragDirs(recyclerView, viewHolder) + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val index = viewHolder.bindingAdapterPosition + ruleAdapter.remove(index) + undoManager.remove(index to (viewHolder as RuleAdapter.RuleHolder).rule) + } + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder, + ): Boolean { + return if (target is RuleAdapter.DocumentHolder) { + false + } else { + ruleAdapter.move(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition) + true + } + } + + override fun clearView( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + ) { + super.clearView(recyclerView, viewHolder) + ruleAdapter.commitMove() + } + }).attachToRecyclerView(ruleListView) + } + + override fun onDestroy() { + if (::ruleAdapter.isInitialized) { + ProfileManager.removeListener(ruleAdapter) + } + super.onDestroy() + } + + override fun onMenuItemClick(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_new_route -> { + startActivity(Intent(context, RouteSettingsActivity::class.java)) + } + R.id.action_reset_route -> { + MaterialAlertDialogBuilder(activity).setTitle(R.string.confirm) + .setMessage(R.string.clear_profiles_message) + .setPositiveButton(R.string.yes) { _, _ -> + runOnDefaultDispatcher { + SagerDatabase.rulesDao.reset() + DataStore.rulesFirstCreate = false + ruleAdapter.reload() + } + } + .setNegativeButton(R.string.no, null) + .show() + } + R.id.action_manage_assets -> { + startActivity(Intent(requireContext(), AssetsActivity::class.java)) + } + } + return true + } + + inner class RuleAdapter : RecyclerView.Adapter(), ProfileManager.RuleListener, UndoSnackbarManager.Interface { + + val ruleList = ArrayList() + suspend fun reload() { + val rules = ProfileManager.getRules() + ruleListView.post { + ruleList.clear() + ruleList.addAll(rules) + ruleAdapter.notifyDataSetChanged() + } + } + + init { + runOnDefaultDispatcher { + reload() + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): RecyclerView.ViewHolder { + return if (viewType == 0) { + DocumentHolder(LayoutEmptyRouteBinding.inflate(layoutInflater, parent, false)) + } else { + RuleHolder(LayoutRouteItemBinding.inflate(layoutInflater, parent, false)) + } + } + + override fun getItemViewType(position: Int): Int { + if (position == 0) return 0 + return 1 + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is DocumentHolder) { + holder.bind() + } else if (holder is RuleHolder) { + holder.bind(ruleList[position - 1]) + } + } + + override fun getItemCount(): Int { + return ruleList.size + 1 + } + + override fun getItemId(position: Int): Long { + if (position == 0) return 0L + return ruleList[position - 1].id + } + + private val updated = HashSet() + fun move(from: Int, to: Int) { + val first = ruleList[from - 1] + var previousOrder = first.userOrder + val (step, range) = if (from < to) Pair(1, from - 1 until to - 1) else Pair(-1, to downTo from - 1) + for (i in range) { + val next = ruleList[i + step] + val order = next.userOrder + next.userOrder = previousOrder + previousOrder = order + ruleList[i] = next + updated.add(next) + } + first.userOrder = previousOrder + ruleList[to - 1] = first + updated.add(first) + notifyItemMoved(from, to) + } + + fun commitMove() = runOnDefaultDispatcher { + if (updated.isNotEmpty()) { + SagerDatabase.rulesDao.updateRules(updated.toList()) + updated.clear() + needReload() + } + } + + fun remove(index: Int) { + ruleList.removeAt(index - 1) + notifyItemRemoved(index) + } + + override fun undo(actions: List>) { + for ((index, item) in actions) { + ruleList.add(index - 1, item) + notifyItemInserted(index) + } + } + + override fun commit(actions: List>) { + val rules = actions.map { it.second } + runOnDefaultDispatcher { + ProfileManager.deleteRules(rules) + } + } + + override suspend fun onAdd(rule: RuleEntity) { + ruleListView.post { + ruleList.add(rule) + ruleAdapter.notifyItemInserted(ruleList.size) + needReload() + } + } + + override suspend fun onUpdated(rule: RuleEntity) { + val index = ruleList.indexOfFirst { it.id == rule.id } + if (index == -1) return + ruleListView.post { + ruleList[index] = rule + ruleAdapter.notifyItemChanged(index + 1) + needReload() + } + } + + override suspend fun onRemoved(ruleId: Long) { + val index = ruleList.indexOfFirst { it.id == ruleId } + if (index == -1) { + onMainDispatcher { + needReload() + } + } else ruleListView.post { + ruleList.removeAt(index) + ruleAdapter.notifyItemRemoved(index + 1) + needReload() + } + } + + override suspend fun onCleared() { + ruleListView.post { + ruleList.clear() + ruleAdapter.notifyDataSetChanged() + needReload() + } + } + + inner class DocumentHolder(binding: LayoutEmptyRouteBinding) : RecyclerView.ViewHolder(binding.root) { + fun bind() { + itemView.setOnClickListener { + it.context.launchCustomTab("https://sing-box.sagernet.org/configuration/route/rule/") + } + } + } + + inner class RuleHolder(binding: LayoutRouteItemBinding) : RecyclerView.ViewHolder(binding.root) { + + lateinit var rule: RuleEntity + val profileName = binding.profileName + val profileType = binding.profileType + val routeOutbound = binding.routeOutbound + val editButton = binding.edit + val shareLayout = binding.share + val enableSwitch = binding.enable + + fun bind(ruleEntity: RuleEntity) { + rule = ruleEntity + profileName.text = rule.displayName() + profileType.text = rule.mkSummary() + routeOutbound.text = rule.displayOutbound() + itemView.setOnClickListener { + enableSwitch.performClick() + } + enableSwitch.isChecked = rule.enabled + enableSwitch.setOnCheckedChangeListener { _, isChecked -> + runOnDefaultDispatcher { + rule.enabled = isChecked + SagerDatabase.rulesDao.updateRule(rule) + onMainDispatcher { + needReload() + } + } + } + editButton.setOnClickListener { + startActivity(Intent(it.context, RouteSettingsActivity::class.java).apply { + putExtra(RouteSettingsActivity.EXTRA_ROUTE_ID, rule.id) + }) + } + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt new file mode 100644 index 0000000..433a65c --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt @@ -0,0 +1,371 @@ +package io.nekohasekai.sagernet.ui + +import android.app.Activity +import android.content.DialogInterface +import android.content.Intent +import android.os.Bundle +import android.os.Parcelable +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.activity.result.component1 +import androidx.activity.result.component2 +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.LayoutRes +import androidx.appcompat.app.AlertDialog +import androidx.core.view.ViewCompat +import androidx.preference.EditTextPreference +import androidx.preference.Preference +import androidx.preference.PreferenceDataStore +import com.github.shadowsocks.plugin.Empty +import com.github.shadowsocks.plugin.fragment.AlertDialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.takisoft.preferencex.PreferenceFragmentCompat +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.ProfileManager +import io.nekohasekai.sagernet.database.RuleEntity +import io.nekohasekai.sagernet.database.SagerDatabase +import io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener +import io.nekohasekai.sagernet.ktx.app +import io.nekohasekai.sagernet.ktx.onMainDispatcher +import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher +import io.nekohasekai.sagernet.utils.PackageCache +import io.nekohasekai.sagernet.widget.AppListPreference +import io.nekohasekai.sagernet.widget.ListListener +import io.nekohasekai.sagernet.widget.OutboundPreference +import kotlinx.parcelize.Parcelize + +@Suppress("UNCHECKED_CAST") +class RouteSettingsActivity( + @LayoutRes resId: Int = R.layout.layout_settings_activity, +) : ThemedActivity(resId), + OnPreferenceDataStoreChangeListener { + + fun init(packageName: String?) { + RuleEntity().apply { + if (!packageName.isNullOrBlank()) { + packages = listOf(packageName) + name = app.getString(R.string.route_for, PackageCache.loadLabel(packageName)) + } + }.init() + } + + fun RuleEntity.init() { + DataStore.routeName = name + DataStore.routeDomain = domains + DataStore.routeIP = ip + DataStore.routePort = port + DataStore.routeSourcePort = sourcePort + DataStore.routeNetwork = network + DataStore.routeSource = source + DataStore.routeProtocol = protocol + DataStore.routeOutboundRule = outbound + DataStore.routeOutbound = when (outbound) { + 0L -> 0 + -1L -> 1 + -2L -> 2 + else -> 3 + } + DataStore.routePackages = packages.joinToString("\n") + } + + fun RuleEntity.serialize() { + name = DataStore.routeName + domains = DataStore.routeDomain + ip = DataStore.routeIP + port = DataStore.routePort + sourcePort = DataStore.routeSourcePort + network = DataStore.routeNetwork + source = DataStore.routeSource + protocol = DataStore.routeProtocol + outbound = when (DataStore.routeOutbound) { + 0 -> 0L + 1 -> -1L + 2 -> -2L + else -> DataStore.routeOutboundRule + } + packages = DataStore.routePackages.split("\n").filter { it.isNotBlank() } + + if (DataStore.editingId == 0L) { + enabled = true + } + } + + fun needSave(): Boolean { + if (!DataStore.dirty) return false + if (DataStore.routePackages.isBlank() && DataStore.routeDomain.isBlank() && DataStore.routeIP.isBlank() && DataStore.routePort.isBlank() && DataStore.routeSourcePort.isBlank() && DataStore.routeNetwork.isBlank() && DataStore.routeSource.isBlank() && DataStore.routeProtocol.isBlank()) { + return false + } + return true + } + + fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + addPreferencesFromResource(R.xml.route_preferences) + } + + val selectProfileForAdd = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { (resultCode, data) -> + if (resultCode == Activity.RESULT_OK) runOnDefaultDispatcher { + val profile = ProfileManager.getProfile( + data!!.getLongExtra( + ProfileSelectActivity.EXTRA_PROFILE_ID, 0 + ) + ) ?: return@runOnDefaultDispatcher + DataStore.routeOutboundRule = profile.id + onMainDispatcher { + outbound.value = "3" + } + } + } + + val selectAppList = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { (_, _) -> + apps.postUpdate() + } + + lateinit var outbound: OutboundPreference + lateinit var apps: AppListPreference + + fun PreferenceFragmentCompat.viewCreated(view: View, savedInstanceState: Bundle?) { + outbound = findPreference(Key.ROUTE_OUTBOUND)!! + apps = findPreference(Key.ROUTE_PACKAGES)!! + + outbound.setOnPreferenceChangeListener { _, newValue -> + if (newValue.toString() == "3") { + selectProfileForAdd.launch( + Intent( + this@RouteSettingsActivity, ProfileSelectActivity::class.java + ) + ) + false + } else { + true + } + } + + apps.setOnPreferenceClickListener { + selectAppList.launch( + Intent( + this@RouteSettingsActivity, AppListActivity::class.java + ) + ) + true + } + } + + fun PreferenceFragmentCompat.displayPreferenceDialog(preference: Preference): Boolean { + return false + } + + class UnsavedChangesDialogFragment : AlertDialogFragment() { + override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) { + setTitle(R.string.unsaved_changes_prompt) + setPositiveButton(R.string.yes) { _, _ -> + runOnDefaultDispatcher { + (requireActivity() as RouteSettingsActivity).saveAndExit() + } + } + setNegativeButton(R.string.no) { _, _ -> + requireActivity().finish() + } + setNeutralButton(android.R.string.cancel, null) + } + } + + @Parcelize + data class ProfileIdArg(val ruleId: Long) : Parcelable + class DeleteConfirmationDialogFragment : AlertDialogFragment() { + override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) { + setTitle(R.string.delete_route_prompt) + setPositiveButton(R.string.yes) { _, _ -> + runOnDefaultDispatcher { + ProfileManager.deleteRule(arg.ruleId) + } + requireActivity().finish() + } + setNegativeButton(R.string.no, null) + } + } + + companion object { + const val EXTRA_ROUTE_ID = "id" + const val EXTRA_PACKAGE_NAME = "pkg" + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setSupportActionBar(findViewById(R.id.toolbar)) + supportActionBar?.apply { + setTitle(R.string.cag_route) + setDisplayHomeAsUpEnabled(true) + setHomeAsUpIndicator(R.drawable.ic_navigation_close) + } + + if (savedInstanceState == null) { + val editingId = intent.getLongExtra(EXTRA_ROUTE_ID, 0L) + DataStore.editingId = editingId + runOnDefaultDispatcher { + if (editingId == 0L) { + init(intent.getStringExtra(EXTRA_PACKAGE_NAME)) + } else { + val ruleEntity = SagerDatabase.rulesDao.getById(editingId) + if (ruleEntity == null) { + onMainDispatcher { + finish() + } + return@runOnDefaultDispatcher + } + ruleEntity.init() + } + + onMainDispatcher { + supportFragmentManager.beginTransaction() + .replace(R.id.settings, MyPreferenceFragmentCompat().apply { + activity = this@RouteSettingsActivity + }) + .commit() + + DataStore.dirty = false + DataStore.profileCacheStore.registerChangeListener(this@RouteSettingsActivity) + } + } + + + } + + } + + suspend fun saveAndExit() { + + if (!needSave()) { + onMainDispatcher { + MaterialAlertDialogBuilder(this@RouteSettingsActivity).setTitle(R.string.empty_route) + .setMessage(R.string.empty_route_notice) + .setPositiveButton(android.R.string.ok, null) + .show() + } + return + } + + val editingId = DataStore.editingId + if (editingId == 0L) { + if (intent.hasExtra(EXTRA_PACKAGE_NAME)) { + setResult(RESULT_OK, Intent()) + } + + ProfileManager.createRule(RuleEntity().apply { serialize() }) + } else { + val entity = SagerDatabase.rulesDao.getById(DataStore.editingId) + if (entity == null) { + finish() + return + } + ProfileManager.updateRule(entity.apply { serialize() }) + } + finish() + + } + + val child by lazy { supportFragmentManager.findFragmentById(R.id.settings) as MyPreferenceFragmentCompat } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.profile_config_menu, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem) = child.onOptionsItemSelected(item) + + override fun onBackPressed() { + if (needSave()) { + UnsavedChangesDialogFragment().apply { key() }.show(supportFragmentManager, null) + } else super.onBackPressed() + } + + override fun onSupportNavigateUp(): Boolean { + if (!super.onSupportNavigateUp()) finish() + return true + } + + override fun onDestroy() { + DataStore.profileCacheStore.unregisterChangeListener(this) + super.onDestroy() + } + + override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) { + if (key != Key.PROFILE_DIRTY) { + DataStore.dirty = true + } + } + + class MyPreferenceFragmentCompat : PreferenceFragmentCompat() { + + lateinit var activity: RouteSettingsActivity + + override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { + preferenceManager.preferenceDataStore = DataStore.profileCacheStore + activity.apply { + createPreferences(savedInstanceState, rootKey) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + ViewCompat.setOnApplyWindowInsetsListener(listView, ListListener) + + activity.apply { + viewCreated(view, savedInstanceState) + } + } + + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.action_delete -> { + if (DataStore.editingId == 0L) { + requireActivity().finish() + } else { + DeleteConfirmationDialogFragment().apply { + arg(ProfileIdArg(DataStore.editingId)) + key() + }.show(parentFragmentManager, null) + } + true + } + R.id.action_apply -> { + runOnDefaultDispatcher { + activity.saveAndExit() + } + true + } + else -> false + } + + override fun onDisplayPreferenceDialog(preference: Preference) { + activity.apply { + if (displayPreferenceDialog(preference)) return + } + super.onDisplayPreferenceDialog(preference) + } + + } + + object PasswordSummaryProvider : Preference.SummaryProvider { + + override fun provideSummary(preference: EditTextPreference): CharSequence { + val text = preference.text + return if (text.isNullOrBlank()) { + preference.context.getString(androidx.preference.R.string.not_set) + } else { + "\u2022".repeat(text.length) + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ScannerActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ScannerActivity.kt new file mode 100644 index 0000000..c8477f5 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ScannerActivity.kt @@ -0,0 +1,238 @@ +package io.nekohasekai.sagernet.ui + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.pm.ShortcutManager +import android.graphics.ImageDecoder +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.MediaStore +import android.view.Menu +import android.view.MenuItem +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.getSystemService +import com.google.zxing.Result +import com.king.zxing.CameraScan +import com.king.zxing.DefaultCameraScan +import com.king.zxing.analyze.QRCodeAnalyzer +import com.king.zxing.util.CodeUtils +import com.king.zxing.util.LogUtils +import com.king.zxing.util.PermissionUtils +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.ProfileManager +import io.nekohasekai.sagernet.databinding.LayoutScannerBinding +import io.nekohasekai.sagernet.group.RawUpdater +import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.widget.ListHolderListener +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicInteger + + +class ScannerActivity : ThemedActivity(), + CameraScan.OnScanResultCallback { + + lateinit var binding: LayoutScannerBinding + lateinit var cameraScan: CameraScan + + @SuppressLint("SourceLockedOrientationActivity") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O) { + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } + if (Build.VERSION.SDK_INT >= 25) getSystemService()!!.reportShortcutUsed("scan") + binding = LayoutScannerBinding.inflate(layoutInflater) + setContentView(binding.root) + ListHolderListener.setup(this) + setSupportActionBar(findViewById(R.id.toolbar)) + supportActionBar?.apply { + setDisplayHomeAsUpEnabled(true) + setHomeAsUpIndicator(R.drawable.ic_navigation_close) + } + + // 二维码库 + initCameraScan() + startCamera() + binding.ivFlashlight.setOnClickListener { toggleTorchState() } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.scanner_menu, menu) + return true + } + + val importCodeFile = registerForActivityResult(ActivityResultContracts.GetMultipleContents()) { + runOnDefaultDispatcher { + try { + it.forEachTry { uri -> + val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + ImageDecoder.decodeBitmap( + ImageDecoder.createSource( + contentResolver, uri + ) + ) { decoder, _, _ -> + decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE + decoder.isMutableRequired = true + } + } else { + @Suppress("DEPRECATION") MediaStore.Images.Media.getBitmap( + contentResolver, uri + ) + } + val result = CodeUtils.parseCodeResult(bitmap) + onMainDispatcher { + onScanResultCallback(result, true) + } + } + finish() + } catch (e: Exception) { + Logs.w(e) + onMainDispatcher { + Toast.makeText(app, e.readableMessage, Toast.LENGTH_LONG).show() + } + } + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return if (item.itemId == R.id.action_import_file) { + startFilesForResult(importCodeFile, "image/*") + true + } else { + super.onOptionsItemSelected(item) + } + } + + var finished = AtomicBoolean(false) + var importedN = AtomicInteger(0) + + /** + * 接收扫码结果回调 + * @param result 扫码结果 + * @return 返回true表示拦截,将不自动执行后续逻辑,为false表示不拦截,默认不拦截 + */ + override fun onScanResultCallback(result: Result?): Boolean { + return onScanResultCallback(result, false) + } + + fun onScanResultCallback(result: Result?, multi: Boolean): Boolean { + if (!multi && finished.getAndSet(true)) return true + if (!multi) finish() + runOnDefaultDispatcher { + try { + val text = result?.text ?: throw Exception("QR code not found") + val results = RawUpdater.parseRaw(text) + if (!results.isNullOrEmpty()) { + val currentGroupId = DataStore.selectedGroupForImport() + if (DataStore.selectedGroup != currentGroupId) { + DataStore.selectedGroup = currentGroupId + } + + for (profile in results) { + ProfileManager.createProfile(currentGroupId, profile) + importedN.addAndGet(1) + } + } else { + onMainDispatcher { + Toast.makeText(app, R.string.action_import_err, Toast.LENGTH_SHORT).show() + } + } + } catch (e: SubscriptionFoundException) { + startActivity(Intent(this@ScannerActivity, MainActivity::class.java).apply { + action = Intent.ACTION_VIEW + data = Uri.parse(e.link) + }) + } catch (e: Throwable) { + Logs.w(e) + onMainDispatcher { + var text = getString(R.string.action_import_err) + text += "\n" + e.readableMessage + Toast.makeText(app, text, Toast.LENGTH_SHORT).show() + } + } + } + return true + } + + /** + * 初始化CameraScan + */ + fun initCameraScan() { + cameraScan = DefaultCameraScan(this, binding.previewView) + cameraScan.setAnalyzer(QRCodeAnalyzer()) + cameraScan.setOnScanResultCallback(this) + cameraScan.setNeedAutoZoom(true) + } + + /** + * 启动相机预览 + */ + fun startCamera() { + if (PermissionUtils.checkPermission(this, Manifest.permission.CAMERA)) { + cameraScan.startCamera() + } else { + LogUtils.d("checkPermissionResult != PERMISSION_GRANTED") + PermissionUtils.requestPermission( + this, Manifest.permission.CAMERA, CAMERA_PERMISSION_REQUEST_CODE + ) + } + } + + /** + * 释放相机 + */ + private fun releaseCamera() { + cameraScan.release() + } + + /** + * 切换闪光灯状态(开启/关闭) + */ + protected fun toggleTorchState() { + val isTorch = cameraScan.isTorchEnabled + cameraScan.enableTorch(!isTorch) + binding.ivFlashlight.isSelected = !isTorch + } + + val CAMERA_PERMISSION_REQUEST_CODE = 0X86 + + override fun onRequestPermissionsResult( + requestCode: Int, permissions: Array, grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) { + requestCameraPermissionResult(permissions, grantResults) + } + } + + /** + * 请求Camera权限回调结果 + * @param permissions + * @param grantResults + */ + fun requestCameraPermissionResult(permissions: Array, grantResults: IntArray) { + if (PermissionUtils.requestPermissionsResult( + Manifest.permission.CAMERA, permissions, grantResults + ) + ) { + startCamera() + } else { + finish() + } + } + + override fun onDestroy() { + releaseCamera() + super.onDestroy() + if (importedN.get() > 0) { + var text = getString(R.string.action_import_msg) + text += "\n" + importedN.get() + " profile(s)" + Toast.makeText(app, text, Toast.LENGTH_LONG).show() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsFragment.kt new file mode 100644 index 0000000..0bc105a --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsFragment.kt @@ -0,0 +1,22 @@ +package io.nekohasekai.sagernet.ui + +import android.os.Bundle +import android.view.View +import androidx.core.view.ViewCompat +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.widget.ListHolderListener + +class SettingsFragment : ToolbarFragment(R.layout.layout_config_settings) { + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + ViewCompat.setOnApplyWindowInsetsListener(view, ListHolderListener) + toolbar.setTitle(R.string.settings) + + parentFragmentManager.beginTransaction() + .replace(R.id.settings, SettingsPreferenceFragment()) + .commitAllowingStateLoss() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt new file mode 100644 index 0000000..d075438 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt @@ -0,0 +1,254 @@ +package io.nekohasekai.sagernet.ui + +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.EditText +import androidx.core.app.ActivityCompat +import androidx.preference.EditTextPreference +import androidx.preference.MultiSelectListPreference +import androidx.preference.Preference +import androidx.preference.SwitchPreference +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.takisoft.preferencex.PreferenceFragmentCompat +import com.takisoft.preferencex.SimpleMenuPreference +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers +import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.utils.Theme +import io.nekohasekai.sagernet.widget.AppListPreference +import libcore.Libcore +import moe.matsuri.nb4a.Protocols +import moe.matsuri.nb4a.ui.ColorPickerPreference +import moe.matsuri.nb4a.ui.LongClickSwitchPreference +import moe.matsuri.nb4a.ui.MTUPreference + +class SettingsPreferenceFragment : PreferenceFragmentCompat() { + + private lateinit var isProxyApps: SwitchPreference + private lateinit var nekoPlugins: AppListPreference + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + listView.layoutManager = FixedLinearLayoutManager(listView) + } + + val reloadListener = Preference.OnPreferenceChangeListener { _, _ -> + needReload() + true + } + + override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { + preferenceManager.preferenceDataStore = DataStore.configurationStore + DataStore.initGlobal() + addPreferencesFromResource(R.xml.global_preferences) + + DataStore.routePackages = DataStore.nekoPlugins + nekoPlugins = findPreference(Key.NEKO_PLUGIN_MANAGED)!! + nekoPlugins.setOnPreferenceClickListener { + // borrow from route app settings + startActivity(Intent( + context, AppListActivity::class.java + ).apply { putExtra(Key.NEKO_PLUGIN_MANAGED, true) }) + true + } + + val appTheme = findPreference(Key.APP_THEME)!! + appTheme.setOnPreferenceChangeListener { _, newTheme -> + if (DataStore.serviceState.started) { + SagerNet.reloadService() + } + val theme = Theme.getTheme(newTheme as Int) + app.setTheme(theme) + requireActivity().apply { + setTheme(theme) + ActivityCompat.recreate(this) + } + true + } + + val nightTheme = findPreference(Key.NIGHT_THEME)!! + nightTheme.setOnPreferenceChangeListener { _, newTheme -> + Theme.currentNightMode = (newTheme as String).toInt() + Theme.applyNightTheme() + true + } + val mixedPort = findPreference(Key.MIXED_PORT)!! + val speedInterval = findPreference(Key.SPEED_INTERVAL)!! + val serviceMode = findPreference(Key.SERVICE_MODE)!! + val allowAccess = findPreference(Key.ALLOW_ACCESS)!! + val appendHttpProxy = findPreference(Key.APPEND_HTTP_PROXY)!! + + val portLocalDns = findPreference(Key.LOCAL_DNS_PORT)!! + val showDirectSpeed = findPreference(Key.SHOW_DIRECT_SPEED)!! + val ipv6Mode = findPreference(Key.IPV6_MODE)!! +// val domainStrategy = findPreference(Key.DOMAIN_STRATEGY)!! + val trafficSniffing = findPreference(Key.TRAFFIC_SNIFFING)!! + + val muxConcurrency = findPreference(Key.MUX_CONCURRENCY)!! + val tcpKeepAliveInterval = findPreference(Key.TCP_KEEP_ALIVE_INTERVAL)!! + tcpKeepAliveInterval.isVisible = false + + val bypassLan = findPreference(Key.BYPASS_LAN)!! + val bypassLanInCoreOnly = findPreference(Key.BYPASS_LAN_IN_CORE_ONLY)!! + + bypassLanInCoreOnly.isEnabled = bypassLan.isChecked + bypassLan.setOnPreferenceChangeListener { _, newValue -> + bypassLanInCoreOnly.isEnabled = newValue as Boolean + needReload() + true + } + + val remoteDns = findPreference(Key.REMOTE_DNS)!! + val directDns = findPreference(Key.DIRECT_DNS)!! + val directDnsUseSystem = findPreference(Key.DIRECT_DNS_USE_SYSTEM)!! + val enableDnsRouting = findPreference(Key.ENABLE_DNS_ROUTING)!! + val enableFakeDns = findPreference(Key.ENABLE_FAKEDNS)!! + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + DataStore.directDnsUseSystem = false + directDnsUseSystem.remove() + } else { + directDns.isEnabled = !directDnsUseSystem.isChecked + directDnsUseSystem.setOnPreferenceChangeListener { _, newValue -> + directDns.isEnabled = !(newValue as Boolean) + needReload() + true + } + } + + val requireTransproxy = findPreference(Key.REQUIRE_TRANSPROXY)!! + val transproxyPort = findPreference(Key.TRANSPROXY_PORT)!! + val transproxyMode = findPreference(Key.TRANSPROXY_MODE)!! + val enableLog = findPreference(Key.ENABLE_LOG)!! + val mtu = findPreference(Key.MTU)!! + + enableLog.setOnPreferenceChangeListener { _, _ -> + needRestart() + true + } + enableLog.setOnLongClickListener { + if (context == null) return@setOnLongClickListener true + + val view = EditText(context).apply { + inputType = EditorInfo.TYPE_CLASS_NUMBER + var size = DataStore.logBufSize + if (size == 0) size = 50 + setText(size.toString()) + } + + MaterialAlertDialogBuilder(requireContext()).setTitle("Log buffer size (kb)") + .setView(view) + .setPositiveButton(android.R.string.ok) { _, _ -> + DataStore.logBufSize = view.text.toString().toInt() + if (DataStore.logBufSize <= 0) DataStore.logBufSize = 50 + needRestart() + } + .setNegativeButton(android.R.string.cancel, null) + .show() + true + } + + transproxyPort.isEnabled = requireTransproxy.isChecked + transproxyMode.isEnabled = requireTransproxy.isChecked + + requireTransproxy.setOnPreferenceChangeListener { _, newValue -> + transproxyPort.isEnabled = newValue as Boolean + transproxyMode.isEnabled = newValue + needReload() + true + } + + val muxProtocols = findPreference(Key.MUX_PROTOCOLS)!! + + muxProtocols.apply { + val e = Protocols.getCanMuxList().toTypedArray() + entries = e + entryValues = e + } + + val dnsNetwork = findPreference(Key.DNS_NETWORK)!! + + portLocalDns.setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + muxConcurrency.setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + mixedPort.setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + + val metedNetwork = findPreference(Key.METERED_NETWORK)!! + if (Build.VERSION.SDK_INT < 28) { + metedNetwork.remove() + } + isProxyApps = findPreference(Key.PROXY_APPS)!! + isProxyApps.setOnPreferenceChangeListener { _, newValue -> + startActivity(Intent(activity, AppManagerActivity::class.java)) + if (newValue as Boolean) DataStore.dirty = true + newValue + } + + val profileTrafficStatistics = + findPreference(Key.PROFILE_TRAFFIC_STATISTICS)!! + speedInterval.isEnabled = profileTrafficStatistics.isChecked + profileTrafficStatistics.setOnPreferenceChangeListener { _, newValue -> + speedInterval.isEnabled = newValue as Boolean + needReload() + true + } + + serviceMode.setOnPreferenceChangeListener { _, _ -> + if (DataStore.serviceState.started) SagerNet.stopService() + true + } + + val tunImplementation = findPreference(Key.TUN_IMPLEMENTATION)!! + val resolveDestination = findPreference(Key.RESOLVE_DESTINATION)!! + val acquireWakeLock = findPreference(Key.ACQUIRE_WAKE_LOCK)!! + val enableClashAPI = findPreference(Key.ENABLE_CLASH_API)!! + + speedInterval.onPreferenceChangeListener = reloadListener + mixedPort.onPreferenceChangeListener = reloadListener + appendHttpProxy.onPreferenceChangeListener = reloadListener + showDirectSpeed.onPreferenceChangeListener = reloadListener +// domainStrategy.onPreferenceChangeListener = reloadListener + trafficSniffing.onPreferenceChangeListener = reloadListener + muxConcurrency.onPreferenceChangeListener = reloadListener + tcpKeepAliveInterval.onPreferenceChangeListener = reloadListener + bypassLanInCoreOnly.onPreferenceChangeListener = reloadListener + mtu.onPreferenceChangeListener = reloadListener + + enableFakeDns.onPreferenceChangeListener = reloadListener + remoteDns.onPreferenceChangeListener = reloadListener + directDns.onPreferenceChangeListener = reloadListener + enableDnsRouting.onPreferenceChangeListener = reloadListener + dnsNetwork.onPreferenceChangeListener = reloadListener + + portLocalDns.onPreferenceChangeListener = reloadListener + ipv6Mode.onPreferenceChangeListener = reloadListener + allowAccess.onPreferenceChangeListener = reloadListener + + transproxyPort.onPreferenceChangeListener = reloadListener + transproxyMode.onPreferenceChangeListener = reloadListener + + resolveDestination.onPreferenceChangeListener = reloadListener + tunImplementation.onPreferenceChangeListener = reloadListener + acquireWakeLock.onPreferenceChangeListener = reloadListener + enableClashAPI.onPreferenceChangeListener = reloadListener + + } + + override fun onResume() { + super.onResume() + + if (::isProxyApps.isInitialized) { + isProxyApps.isChecked = DataStore.proxyApps + } + if (::nekoPlugins.isInitialized) { + nekoPlugins.postUpdate() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/StunActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/StunActivity.kt new file mode 100644 index 0000000..c910e71 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/StunActivity.kt @@ -0,0 +1,65 @@ +package io.nekohasekai.sagernet.ui + +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import androidx.core.view.isVisible +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.databinding.LayoutStunBinding +import io.nekohasekai.sagernet.ktx.onMainDispatcher +import io.nekohasekai.sagernet.ktx.readableMessage +import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher +import libcore.Libcore + +class StunActivity : ThemedActivity() { + + private lateinit var binding: LayoutStunBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = LayoutStunBinding.inflate(layoutInflater) + setContentView(binding.root) + setSupportActionBar(findViewById(R.id.toolbar)) + supportActionBar?.apply { + setTitle(R.string.stun_test) + setDisplayHomeAsUpEnabled(true) + setHomeAsUpIndicator(R.drawable.baseline_arrow_back_24) + } + binding.stunTest.setOnClickListener { + doTest() + } + } + + fun doTest() { + binding.waitLayout.isVisible = true + runOnDefaultDispatcher { + val result = try { + val _result = Libcore.stunTest(binding.natStunServer.text.toString()) + if (_result!!.success) { + _result.text + } else { + throw Exception(_result.text) + } + } catch (e: Exception) { + onMainDispatcher { + AlertDialog.Builder(this@StunActivity) + .setTitle(R.string.error_title) + .setMessage(e.readableMessage) + .setPositiveButton(android.R.string.ok) { _, _ -> + finish() + } + .setOnCancelListener { + finish() + } + .runCatching { show() } + } + return@runOnDefaultDispatcher + } + onMainDispatcher { + binding.waitLayout.isVisible = false + binding.natResult.text = result + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/SwitchActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/SwitchActivity.kt new file mode 100644 index 0000000..44dddb3 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/SwitchActivity.kt @@ -0,0 +1,33 @@ +package io.nekohasekai.sagernet.ui + +import android.os.Bundle +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.ProfileManager +import io.nekohasekai.sagernet.ktx.runOnMainDispatcher + +class SwitchActivity : ThemedActivity(R.layout.layout_empty), + ConfigurationFragment.SelectCallback { + + override val isDialog = true + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + supportFragmentManager.beginTransaction() + .replace(R.id.fragment_holder, ConfigurationFragment(true, null, R.string.action_switch)) + .commitAllowingStateLoss() + } + + override fun returnProfile(profileId: Long) { + val old = DataStore.selectedProxy + DataStore.selectedProxy = profileId + runOnMainDispatcher { + ProfileManager.postUpdate(old) + ProfileManager.postUpdate(profileId) + } + SagerNet.reloadService() + finish() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ThemedActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ThemedActivity.kt new file mode 100644 index 0000000..4c57f20 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ThemedActivity.kt @@ -0,0 +1,56 @@ +package io.nekohasekai.sagernet.ui + +import android.content.res.Configuration +import android.os.Bundle +import android.widget.TextView +import androidx.annotation.StringRes +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import com.google.android.material.snackbar.Snackbar +import io.nekohasekai.sagernet.utils.Theme + +abstract class ThemedActivity : AppCompatActivity { + constructor() : super() + constructor(contentLayoutId: Int) : super(contentLayoutId) + + var themeResId = 0 + var uiMode = 0 + open val isDialog = false + + override fun onCreate(savedInstanceState: Bundle?) { + if (!isDialog) { + Theme.apply(this) + } else { + Theme.applyDialog(this) + } + Theme.applyNightTheme() + + super.onCreate(savedInstanceState) + + uiMode = resources.configuration.uiMode + } + + override fun setTheme(resId: Int) { + super.setTheme(resId) + + themeResId = resId + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + + if (newConfig.uiMode != uiMode) { + uiMode = newConfig.uiMode + ActivityCompat.recreate(this) + } + } + + fun snackbar(@StringRes resId: Int): Snackbar = snackbar("").setText(resId) + fun snackbar(text: CharSequence): Snackbar = snackbarInternal(text).apply { + view.findViewById(com.google.android.material.R.id.snackbar_text).apply { + maxLines = 10 + } + } + internal open fun snackbarInternal(text: CharSequence): Snackbar = throw NotImplementedError() + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ToolbarFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ToolbarFragment.kt new file mode 100644 index 0000000..563a222 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ToolbarFragment.kt @@ -0,0 +1,29 @@ +package io.nekohasekai.sagernet.ui + +import android.os.Bundle +import android.view.KeyEvent +import android.view.View +import androidx.appcompat.widget.Toolbar +import androidx.core.view.GravityCompat +import androidx.fragment.app.Fragment +import io.nekohasekai.sagernet.R + +open class ToolbarFragment : Fragment { + + constructor() : super() + constructor(contentLayoutId: Int) : super(contentLayoutId) + + lateinit var toolbar: Toolbar + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + toolbar = view.findViewById(R.id.toolbar) + toolbar.setNavigationIcon(R.drawable.ic_navigation_menu) + toolbar.setNavigationOnClickListener { + (activity as MainActivity).binding.drawerLayout.openDrawer(GravityCompat.START) + } + } + + open fun onKeyDown(ketCode: Int, event: KeyEvent) = false + open fun onBackPressed(): Boolean = false +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/ToolsFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/ToolsFragment.kt new file mode 100644 index 0000000..bf73ca1 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/ToolsFragment.kt @@ -0,0 +1,42 @@ +package io.nekohasekai.sagernet.ui + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.google.android.material.tabs.TabLayoutMediator +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.databinding.LayoutToolsBinding +import io.nekohasekai.sagernet.ktx.isExpert + +class ToolsFragment : ToolbarFragment(R.layout.layout_tools) { + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + toolbar.setTitle(R.string.menu_tools) + + val tools = mutableListOf() + tools.add(NetworkFragment()) + tools.add(BackupFragment()) + + if (isExpert) tools.add(DebugFragment()) + + val binding = LayoutToolsBinding.bind(view) + binding.toolsPager.adapter = ToolsAdapter(tools) + + TabLayoutMediator(binding.toolsTab, binding.toolsPager) { tab, position -> + tab.text = tools[position].name() + tab.view.setOnLongClickListener { // clear toast + true + } + }.attach() + } + + inner class ToolsAdapter(val tools: List) : FragmentStateAdapter(this) { + + override fun getItemCount() = tools.size + + override fun createFragment(position: Int) = tools[position] + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/VpnRequestActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/VpnRequestActivity.kt new file mode 100644 index 0000000..55a7b33 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/VpnRequestActivity.kt @@ -0,0 +1,72 @@ +package io.nekohasekai.sagernet.ui + +import android.app.Activity +import android.app.KeyguardManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.VpnService +import android.os.Bundle +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContract +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.getSystemService +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.ktx.broadcastReceiver + +class VpnRequestActivity : AppCompatActivity() { + private var receiver: BroadcastReceiver? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (getSystemService()!!.isKeyguardLocked) { + receiver = broadcastReceiver { _, _ -> connect.launch(null) } + registerReceiver(receiver, IntentFilter(Intent.ACTION_USER_PRESENT)) + } else connect.launch(null) + } + + private val connect = registerForActivityResult(StartService()) { + if (it) Toast.makeText(this, R.string.vpn_permission_denied, Toast.LENGTH_LONG).show() + finish() + } + + override fun onDestroy() { + super.onDestroy() + if (receiver != null) unregisterReceiver(receiver) + } + + class StartService : ActivityResultContract() { + private var cachedIntent: Intent? = null + + override fun getSynchronousResult( + context: Context, + input: Void?, + ): SynchronousResult? { + if (DataStore.serviceMode == Key.MODE_VPN) VpnService.prepare(context)?.let { intent -> + cachedIntent = intent + return null + } + SagerNet.startService() + return SynchronousResult(false) + } + + override fun createIntent(context: Context, input: Void?) = + cachedIntent!!.also { cachedIntent = null } + + override fun parseResult(resultCode: Int, intent: Intent?) = + if (resultCode == Activity.RESULT_OK) { + SagerNet.startService() + false + } else { + Logs.e("Failed to start VpnService: $intent") + true + } + } + + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/WebviewFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/WebviewFragment.kt new file mode 100644 index 0000000..c538181 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/WebviewFragment.kt @@ -0,0 +1,77 @@ +package io.nekohasekai.sagernet.ui + +import android.annotation.SuppressLint +import android.os.Bundle +import android.text.InputType +import android.view.MenuItem +import android.view.View +import android.webkit.* +import androidx.appcompat.widget.Toolbar +import com.afollestad.materialdialogs.MaterialDialog +import com.afollestad.materialdialogs.input.input +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.databinding.LayoutWebviewBinding +import moe.matsuri.nb4a.utils.WebViewUtil + +// Fragment必须有一个无参public的构造函数,否则在数据恢复的时候,会报crash + +class WebviewFragment : ToolbarFragment(R.layout.layout_webview), Toolbar.OnMenuItemClickListener { + + lateinit var mWebView: WebView + + @SuppressLint("SetJavaScriptEnabled") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + // layout + toolbar.setTitle(R.string.menu_dashboard) + toolbar.inflateMenu(R.menu.yacd_menu) + toolbar.setOnMenuItemClickListener(this) + + val binding = LayoutWebviewBinding.bind(view) + + // webview + mWebView = binding.webview + mWebView.settings.javaScriptEnabled = true + mWebView.webViewClient = object : WebViewClient() { + override fun onReceivedError( + view: WebView?, request: WebResourceRequest?, error: WebResourceError? + ) { + WebViewUtil.onReceivedError(view, request, error) + } + + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + } + } + mWebView.loadUrl(DataStore.yacdURL) + } + + override fun onDestroy() { + super.onDestroy() + // mWebView.onPause() + // mWebView.removeAllViews() + // mWebView.destroy() + } + + @SuppressLint("CheckResult") + override fun onMenuItemClick(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_set_url -> { + MaterialDialog(requireContext()).show { + title(R.string.set_panel_url) + input( + prefill = DataStore.yacdURL, + inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI + ) { _, str -> + DataStore.yacdURL = str.toString() + mWebView.loadUrl(DataStore.yacdURL) + } + positiveButton(R.string.save) + } + } + } + return true + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ChainSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ChainSettingsActivity.kt new file mode 100644 index 0000000..32542e2 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ChainSettingsActivity.kt @@ -0,0 +1,295 @@ +package io.nekohasekai.sagernet.ui.profile + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.text.format.Formatter +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import androidx.activity.result.component1 +import androidx.activity.result.component2 +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.view.isVisible +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.takisoft.preferencex.PreferenceFragmentCompat +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.ProfileManager +import io.nekohasekai.sagernet.database.ProxyEntity +import io.nekohasekai.sagernet.databinding.LayoutAddEntityBinding +import io.nekohasekai.sagernet.databinding.LayoutProfileBinding +import io.nekohasekai.sagernet.fmt.internal.ChainBean +import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.ui.ProfileSelectActivity +import moe.matsuri.nb4a.Protocols.getProtocolColor + +class ChainSettingsActivity : ProfileSettingsActivity(R.layout.layout_chain_settings) { + + override fun createEntity() = ChainBean() + + val proxyList = ArrayList() + + override fun ChainBean.init() { + DataStore.profileName = name + DataStore.serverProtocol = proxies.joinToString(",") + } + + override fun ChainBean.serialize() { + name = DataStore.profileName + proxies = proxyList.map { it.id } + initializeDefaultValues() + } + + override fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + addPreferencesFromResource(R.xml.name_preferences) + } + + lateinit var configurationList: RecyclerView + lateinit var configurationAdapter: ProxiesAdapter + lateinit var layoutManager: LinearLayoutManager + + @SuppressLint("InlinedApi") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + supportActionBar!!.setTitle(R.string.chain_settings) + configurationList = findViewById(R.id.configuration_list) + layoutManager = FixedLinearLayoutManager(configurationList) + configurationList.layoutManager = layoutManager + configurationAdapter = ProxiesAdapter() + configurationList.adapter = configurationAdapter + + ItemTouchHelper(object : ItemTouchHelper.SimpleCallback( + ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.START + ) { + override fun getSwipeDirs( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + ) = if (viewHolder is ProfileHolder) { + super.getSwipeDirs(recyclerView, viewHolder) + } else 0 + + override fun getDragDirs( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + ) = if (viewHolder is ProfileHolder) { + super.getDragDirs(recyclerView, viewHolder) + } else 0 + + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder, + ): Boolean { + return if (target !is ProfileHolder) false else { + configurationAdapter.move( + viewHolder.bindingAdapterPosition, target.bindingAdapterPosition + ) + true + } + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + configurationAdapter.remove(viewHolder.bindingAdapterPosition) + } + + }).attachToRecyclerView(configurationList) + } + + override fun PreferenceFragmentCompat.viewCreated(view: View, savedInstanceState: Bundle?) { + view.rootView.findViewById(R.id.recycler_view).apply { + (layoutParams ?: LinearLayout.LayoutParams(-1, -2)).apply { + height = -2 + layoutParams = this + } + } + + runOnDefaultDispatcher { + configurationAdapter.reload() + } + } + + inner class ProxiesAdapter : RecyclerView.Adapter() { + + suspend fun reload() { + val idList = DataStore.serverProtocol.split(",") + .mapNotNull { it.takeIf { it.isNotBlank() }?.toLong() } + if (idList.isNotEmpty()) { + val profiles = ProfileManager.getProfiles(idList).map { it.id to it }.toMap() + for (id in idList) { + proxyList.add(profiles[id] ?: continue) + } + } + onMainDispatcher { + notifyDataSetChanged() + } + } + + fun move(from: Int, to: Int) { + val toMove = proxyList[to - 1] + proxyList[to - 1] = proxyList[from - 1] + proxyList[from - 1] = toMove + notifyItemMoved(from, to) + DataStore.dirty = true + } + + fun remove(index: Int) { + proxyList.removeAt(index - 1) + notifyItemRemoved(index) + DataStore.dirty = true + } + + override fun getItemId(position: Int): Long { + return if (position == 0) 0 else proxyList[position - 1].id + } + + override fun getItemViewType(position: Int): Int { + return if (position == 0) 0 else 1 + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return if (viewType == 0) { + AddHolder(LayoutAddEntityBinding.inflate(layoutInflater, parent, false)) + } else { + ProfileHolder(LayoutProfileBinding.inflate(layoutInflater, parent, false)) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is AddHolder) { + holder.bind() + } else if (holder is ProfileHolder) { + holder.bind(proxyList[position - 1]) + } + } + + override fun getItemCount(): Int { + return proxyList.size + 1 + } + + } + + fun testProfileAllowed(profile: ProxyEntity): Boolean { + if (profile.id == DataStore.editingId) return false + + for (entity in proxyList) { + if (testProfileContains(entity, profile)) return false + } + + return true + } + + fun testProfileContains(profile: ProxyEntity, anotherProfile: ProxyEntity): Boolean { + if (profile.type != 8 || anotherProfile.type != 8) return false + if (profile.id == anotherProfile.id) return true + val proxies = profile.chainBean!!.proxies + if (proxies.contains(anotherProfile.id)) return true + if (proxies.isNotEmpty()) { + for (entity in ProfileManager.getProfiles(proxies)) { + if (testProfileContains(entity, anotherProfile)) { + return true + } + } + } + return false + } + + var replacing = 0 + + val selectProfileForAdd = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { (resultCode, data) -> + if (resultCode == Activity.RESULT_OK) runOnDefaultDispatcher { + DataStore.dirty = true + + val profile = ProfileManager.getProfile( + data!!.getLongExtra( + ProfileSelectActivity.EXTRA_PROFILE_ID, 0 + ) + )!! + + if (!testProfileAllowed(profile)) { + onMainDispatcher { + MaterialAlertDialogBuilder(this@ChainSettingsActivity).setTitle(R.string.circular_reference) + .setMessage(R.string.circular_reference_sum) + .setPositiveButton(android.R.string.ok, null).show() + } + } else { + configurationList.post { + if (replacing != 0) { + proxyList[replacing - 1] = profile + configurationAdapter.notifyItemChanged(replacing) + } else { + proxyList.add(profile) + configurationAdapter.notifyItemInserted(proxyList.size) + } + } + } + } + } + + inner class AddHolder(val binding: LayoutAddEntityBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind() { + binding.root.setOnClickListener { + replacing = 0 + selectProfileForAdd.launch( + Intent( + this@ChainSettingsActivity, ProfileSelectActivity::class.java + ) + ) + } + } + } + + inner class ProfileHolder(binding: LayoutProfileBinding) : + RecyclerView.ViewHolder(binding.root) { + + val profileName = binding.profileName + val profileType = binding.profileType + val trafficText: TextView = binding.trafficText + val editButton = binding.edit + val shareLayout = binding.share + + fun bind(proxyEntity: ProxyEntity) { + + profileName.text = proxyEntity.displayName() + profileType.text = proxyEntity.displayType() + profileType.setTextColor(getProtocolColor(proxyEntity.type)) + + val rx = proxyEntity.rx + val tx = proxyEntity.tx + + val showTraffic = rx + tx != 0L + trafficText.isVisible = showTraffic + if (showTraffic) { + trafficText.text = itemView.context.getString( + R.string.traffic, + Formatter.formatFileSize(itemView.context, tx), + Formatter.formatFileSize(itemView.context, rx) + ) + } + + editButton.setOnClickListener { + replacing = bindingAdapterPosition + selectProfileForAdd.launch(Intent( + this@ChainSettingsActivity, ProfileSelectActivity::class.java + ).apply { + putExtra(ProfileSelectActivity.EXTRA_SELECTED, proxyEntity) + }) + } + + shareLayout.isVisible = false + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/HttpSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/HttpSettingsActivity.kt new file mode 100644 index 0000000..b148953 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/HttpSettingsActivity.kt @@ -0,0 +1,9 @@ +package io.nekohasekai.sagernet.ui.profile + +import io.nekohasekai.sagernet.fmt.http.HttpBean + +class HttpSettingsActivity : StandardV2RaySettingsActivity() { + + override fun createEntity() = HttpBean() + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/HysteriaSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/HysteriaSettingsActivity.kt new file mode 100644 index 0000000..6282a21 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/HysteriaSettingsActivity.kt @@ -0,0 +1,98 @@ +package io.nekohasekai.sagernet.ui.profile + +import android.os.Bundle +import androidx.preference.EditTextPreference +import com.takisoft.preferencex.PreferenceFragmentCompat +import com.takisoft.preferencex.SimpleMenuPreference +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers +import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean +import io.nekohasekai.sagernet.ktx.applyDefaultValues + +class HysteriaSettingsActivity : ProfileSettingsActivity() { + + override fun createEntity() = HysteriaBean().applyDefaultValues() + + override fun HysteriaBean.init() { + DataStore.profileName = name + DataStore.serverAddress = serverAddress + DataStore.serverPort = serverPort + DataStore.serverObfs = obfuscation + DataStore.serverAuthType = authPayloadType + DataStore.serverProtocolVersion = protocol + DataStore.serverPassword = authPayload + DataStore.serverSNI = sni + DataStore.serverALPN = alpn + DataStore.serverCertificates = caText + DataStore.serverAllowInsecure = allowInsecure + DataStore.serverUploadSpeed = uploadMbps + DataStore.serverDownloadSpeed = downloadMbps + DataStore.serverStreamReceiveWindow = streamReceiveWindow + DataStore.serverConnectionReceiveWindow = connectionReceiveWindow + DataStore.serverDisableMtuDiscovery = disableMtuDiscovery + DataStore.serverHopInterval = hopInterval + } + + override fun HysteriaBean.serialize() { + name = DataStore.profileName + serverAddress = DataStore.serverAddress + serverPort = DataStore.serverPort + obfuscation = DataStore.serverObfs + authPayloadType = DataStore.serverAuthType + authPayload = DataStore.serverPassword + protocol = DataStore.serverProtocolVersion + sni = DataStore.serverSNI + alpn = DataStore.serverALPN + caText = DataStore.serverCertificates + allowInsecure = DataStore.serverAllowInsecure + uploadMbps = DataStore.serverUploadSpeed + downloadMbps = DataStore.serverDownloadSpeed + streamReceiveWindow = DataStore.serverStreamReceiveWindow + connectionReceiveWindow = DataStore.serverConnectionReceiveWindow + disableMtuDiscovery = DataStore.serverDisableMtuDiscovery + hopInterval = DataStore.serverHopInterval + } + + override fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + addPreferencesFromResource(R.xml.hysteria_preferences) + + val authType = findPreference(Key.SERVER_AUTH_TYPE)!! + val authPayload = findPreference(Key.SERVER_PASSWORD)!! + authPayload.isVisible = authType.value != "${HysteriaBean.TYPE_NONE}" + authType.setOnPreferenceChangeListener { _, newValue -> + authPayload.isVisible = newValue != "${HysteriaBean.TYPE_NONE}" + true + } + + findPreference(Key.SERVER_UPLOAD_SPEED)!!.apply { + setOnBindEditTextListener(EditTextPreferenceModifiers.Number) + } + findPreference(Key.SERVER_DOWNLOAD_SPEED)!!.apply { + setOnBindEditTextListener(EditTextPreferenceModifiers.Number) + } + findPreference(Key.SERVER_STREAM_RECEIVE_WINDOW)!!.apply { + setOnBindEditTextListener(EditTextPreferenceModifiers.Number) + } + findPreference(Key.SERVER_CONNECTION_RECEIVE_WINDOW)!!.apply { + setOnBindEditTextListener(EditTextPreferenceModifiers.Number) + } + + findPreference(Key.SERVER_PORT)!!.apply { + setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + } + + findPreference(Key.SERVER_PASSWORD)!!.apply { + summaryProvider = PasswordSummaryProvider + } + + findPreference(Key.SERVER_HOP_INTERVAL)!!.apply { + setOnBindEditTextListener(EditTextPreferenceModifiers.Number) + } + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/NaiveSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/NaiveSettingsActivity.kt new file mode 100644 index 0000000..39f6c1f --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/NaiveSettingsActivity.kt @@ -0,0 +1,67 @@ +package io.nekohasekai.sagernet.ui.profile + +import android.os.Bundle +import androidx.preference.EditTextPreference +import com.takisoft.preferencex.PreferenceFragmentCompat +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers +import io.nekohasekai.sagernet.fmt.naive.NaiveBean + +class NaiveSettingsActivity : ProfileSettingsActivity() { + + override fun createEntity() = NaiveBean() + + override fun NaiveBean.init() { + DataStore.profileName = name + DataStore.serverAddress = serverAddress + DataStore.serverPort = serverPort + DataStore.serverUsername = username + DataStore.serverPassword = password + DataStore.serverProtocol = proto + DataStore.serverSNI = sni + DataStore.serverCertificates = certificates + DataStore.serverHeaders = extraHeaders + DataStore.serverInsecureConcurrency = insecureConcurrency + } + + override fun NaiveBean.serialize() { + name = DataStore.profileName + serverAddress = DataStore.serverAddress + serverPort = DataStore.serverPort + username = DataStore.serverUsername + password = DataStore.serverPassword + proto = DataStore.serverProtocol + sni = DataStore.serverSNI + certificates = DataStore.serverCertificates + extraHeaders = DataStore.serverHeaders.replace("\r\n", "\n") + insecureConcurrency = DataStore.serverInsecureConcurrency + } + + override fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + addPreferencesFromResource(R.xml.naive_preferences) + findPreference(Key.SERVER_PORT)!!.apply { + setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + } + findPreference(Key.SERVER_PASSWORD)!!.apply { + summaryProvider = PasswordSummaryProvider + } + findPreference(Key.SERVER_INSECURE_CONCURRENCY)!!.apply { + setOnBindEditTextListener(EditTextPreferenceModifiers.Number) + } + } + + override fun finish() { + if (DataStore.profileName == "喵要打开隐藏功能") { + DataStore.isExpert = true + } else if (DataStore.profileName == "喵要关闭隐藏功能") { + DataStore.isExpert = false + } + super.finish() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt new file mode 100644 index 0000000..baf9246 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt @@ -0,0 +1,374 @@ +package io.nekohasekai.sagernet.ui.profile + +import android.annotation.SuppressLint +import android.content.DialogInterface +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.os.Parcelable +import android.text.InputType +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.LinearLayout +import androidx.annotation.LayoutRes +import androidx.appcompat.app.AlertDialog +import androidx.core.content.pm.ShortcutInfoCompat +import androidx.core.content.pm.ShortcutManagerCompat +import androidx.core.graphics.drawable.IconCompat +import androidx.core.view.ViewCompat +import androidx.core.view.isVisible +import androidx.preference.EditTextPreference +import androidx.preference.Preference +import androidx.preference.PreferenceDataStore +import com.afollestad.materialdialogs.MaterialDialog +import com.afollestad.materialdialogs.input.input +import com.github.shadowsocks.plugin.Empty +import com.github.shadowsocks.plugin.fragment.AlertDialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.takisoft.preferencex.PreferenceFragmentCompat +import io.nekohasekai.sagernet.* +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.GroupManager +import io.nekohasekai.sagernet.database.ProfileManager +import io.nekohasekai.sagernet.database.SagerDatabase +import io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener +import io.nekohasekai.sagernet.databinding.LayoutGroupItemBinding +import io.nekohasekai.sagernet.fmt.AbstractBean +import io.nekohasekai.sagernet.ktx.applyDefaultValues +import io.nekohasekai.sagernet.ktx.onMainDispatcher +import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher +import io.nekohasekai.sagernet.ktx.runOnMainDispatcher +import io.nekohasekai.sagernet.ui.ThemedActivity +import io.nekohasekai.sagernet.widget.ListListener +import kotlinx.parcelize.Parcelize +import kotlin.properties.Delegates + +@Suppress("UNCHECKED_CAST") +abstract class ProfileSettingsActivity( + @LayoutRes resId: Int = R.layout.layout_config_settings, +) : ThemedActivity(resId), OnPreferenceDataStoreChangeListener { + + class UnsavedChangesDialogFragment : AlertDialogFragment() { + override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) { + setTitle(R.string.unsaved_changes_prompt) + setPositiveButton(R.string.yes) { _, _ -> + runOnDefaultDispatcher { + (requireActivity() as ProfileSettingsActivity<*>).saveAndExit() + } + } + setNegativeButton(R.string.no) { _, _ -> + requireActivity().finish() + } + setNeutralButton(android.R.string.cancel, null) + } + } + + @Parcelize + data class ProfileIdArg(val profileId: Long, val groupId: Long) : Parcelable + class DeleteConfirmationDialogFragment : AlertDialogFragment() { + override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) { + setTitle(R.string.delete_confirm_prompt) + setPositiveButton(R.string.yes) { _, _ -> + runOnDefaultDispatcher { + ProfileManager.deleteProfile(arg.groupId, arg.profileId) + } + requireActivity().finish() + } + setNegativeButton(R.string.no, null) + } + } + + companion object { + const val EXTRA_PROFILE_ID = "id" + const val EXTRA_IS_SUBSCRIPTION = "sub" + } + + abstract fun createEntity(): T + abstract fun T.init() + abstract fun T.serialize() + + val proxyEntity by lazy { SagerDatabase.proxyDao.getById(DataStore.editingId) } + protected var isSubscription by Delegates.notNull() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setSupportActionBar(findViewById(R.id.toolbar)) + supportActionBar?.apply { + setTitle(R.string.profile_config) + setDisplayHomeAsUpEnabled(true) + setHomeAsUpIndicator(R.drawable.ic_navigation_close) + } + + if (savedInstanceState == null) { + val editingId = intent.getLongExtra(EXTRA_PROFILE_ID, 0L) + isSubscription = intent.getBooleanExtra(EXTRA_IS_SUBSCRIPTION, false) + DataStore.editingId = editingId + runOnDefaultDispatcher { + if (editingId == 0L) { + DataStore.editingGroup = DataStore.selectedGroupForImport() + createEntity().applyDefaultValues().init() + } else { + if (proxyEntity == null) { + onMainDispatcher { + finish() + } + return@runOnDefaultDispatcher + } + DataStore.editingGroup = proxyEntity!!.groupId + (proxyEntity!!.requireBean() as T).init() + } + + onMainDispatcher { + supportFragmentManager.beginTransaction() + .replace(R.id.settings, MyPreferenceFragmentCompat().apply { + activity = this@ProfileSettingsActivity + }).commit() + } + } + + + } + + } + + open suspend fun saveAndExit() { + + val editingId = DataStore.editingId + if (editingId == 0L) { + val editingGroup = DataStore.editingGroup + ProfileManager.createProfile(editingGroup, createEntity().apply { serialize() }) + } else { + if (proxyEntity == null) { + finish() + return + } + if (proxyEntity!!.id == DataStore.selectedProxy) { + SagerNet.stopService() + } + ProfileManager.updateProfile(proxyEntity!!.apply { (requireBean() as T).serialize() }) + } + finish() + + } + + val child by lazy { supportFragmentManager.findFragmentById(R.id.settings) as MyPreferenceFragmentCompat } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.profile_config_menu, menu) + menu.findItem(R.id.action_move)?.apply { + if (DataStore.editingId != 0L // not new profile + && SagerDatabase.groupDao.getById(DataStore.editingGroup)?.type == GroupType.BASIC // not in subscription group + && SagerDatabase.groupDao.allGroups() + .filter { it.type == GroupType.BASIC }.size > 1 // have other basic group + ) isVisible = true + } + menu.findItem(R.id.action_create_shortcut)?.apply { + if (Build.VERSION.SDK_INT >= 26 && DataStore.editingId != 0L) { + isVisible = true // not new profile + } + } + // shared menu item + menu.findItem(R.id.action_custom_outbound_json)?.isVisible = true + menu.findItem(R.id.action_custom_config_json)?.isVisible = true + return true + } + + override fun onOptionsItemSelected(item: MenuItem) = child.onOptionsItemSelected(item) + + override fun onBackPressed() { + if (DataStore.dirty) UnsavedChangesDialogFragment().apply { key() } + .show(supportFragmentManager, null) else super.onBackPressed() + } + + override fun onSupportNavigateUp(): Boolean { + if (!super.onSupportNavigateUp()) finish() + return true + } + + override fun onDestroy() { + DataStore.profileCacheStore.unregisterChangeListener(this) + super.onDestroy() + } + + override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) { + if (key != Key.PROFILE_DIRTY) { + DataStore.dirty = true + } + } + + abstract fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) + + open fun PreferenceFragmentCompat.viewCreated(view: View, savedInstanceState: Bundle?) { + } + + open fun PreferenceFragmentCompat.displayPreferenceDialog(preference: Preference): Boolean { + return false + } + + class MyPreferenceFragmentCompat : PreferenceFragmentCompat() { + + lateinit var activity: ProfileSettingsActivity<*> + + override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { + preferenceManager.preferenceDataStore = DataStore.profileCacheStore + activity.apply { + createPreferences(savedInstanceState, rootKey) + + if (isSubscription) { +// findPreference(Key.PROFILE_NAME)?.isEnabled = false + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + ViewCompat.setOnApplyWindowInsetsListener(listView, ListListener) + + activity.apply { + viewCreated(view, savedInstanceState) + } + + DataStore.dirty = false + DataStore.profileCacheStore.registerChangeListener(activity) + } + + @SuppressLint("CheckResult") + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.action_delete -> { + if (DataStore.editingId == 0L) { + requireActivity().finish() + } else { + DeleteConfirmationDialogFragment().apply { + arg( + ProfileIdArg( + DataStore.editingId, DataStore.editingGroup + ) + ) + key() + }.show(parentFragmentManager, null) + } + true + } + R.id.action_apply -> { + runOnDefaultDispatcher { + activity.saveAndExit() + } + true + } + R.id.action_custom_outbound_json -> { + activity.proxyEntity?.apply { + val bean = requireBean() + MaterialDialog(activity).show { + title(R.string.custom_outbound_json) + input( + prefill = bean.customOutboundJson, + inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE, + allowEmpty = true + ) { _, str -> + bean.customOutboundJson = str.toString() + DataStore.dirty = true + } + positiveButton(R.string.save) + } + } + true + } + R.id.action_custom_config_json -> { + activity.proxyEntity?.apply { + val bean = requireBean() + MaterialDialog(activity).show { + title(R.string.custom_config_json) + input( + prefill = bean.customConfigJson, + inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_MULTI_LINE, + allowEmpty = true + ) { _, str -> + bean.customConfigJson = str.toString() + DataStore.dirty = true + } + positiveButton(R.string.save) + } + } + true + } + R.id.action_create_shortcut -> { + val ent = activity.proxyEntity!! + val shortcut = ShortcutInfoCompat.Builder(activity, "shortcut-profile-${ent.id}") + .setShortLabel(ent.displayName()) + .setLongLabel(ent.displayName()) + .setIcon( + IconCompat.createWithResource( + activity, R.drawable.ic_qu_shadowsocks_launcher + ) + ).setIntent(Intent( + context, QuickToggleShortcut::class.java + ).apply { + action = Intent.ACTION_MAIN + putExtra("profile", ent.id) + }).build() + ShortcutManagerCompat.requestPinShortcut(activity, shortcut, null) + } + R.id.action_move -> { + val view = LinearLayout(context).apply { + val ent = activity.proxyEntity!! + orientation = LinearLayout.VERTICAL + + SagerDatabase.groupDao.allGroups() + .filter { it.type == GroupType.BASIC && it.id != ent.groupId } + .forEach { group -> + LayoutGroupItemBinding.inflate(layoutInflater, this, true).apply { + edit.isVisible = false + options.isVisible = false + groupName.text = group.displayName() + groupUpdate.text = getString(R.string.move) + groupUpdate.setOnClickListener { + runOnDefaultDispatcher { + val oldGroupId = ent.groupId + val newGroupId = group.id + ent.groupId = newGroupId + ProfileManager.updateProfile(ent) + GroupManager.postUpdate(oldGroupId) // reload + GroupManager.postUpdate(newGroupId) + DataStore.editingGroup = newGroupId // post switch animation + runOnMainDispatcher { + activity.finish() + } + } + } + } + } + } + MaterialAlertDialogBuilder(activity).setView(view).show() + true + } + else -> false + } + + override fun onDisplayPreferenceDialog(preference: Preference) { + activity.apply { + if (displayPreferenceDialog(preference)) return + } + super.onDisplayPreferenceDialog(preference) + } + + } + + object PasswordSummaryProvider : Preference.SummaryProvider { + + override fun provideSummary(preference: EditTextPreference): CharSequence { + val text = preference.text + return if (text.isNullOrBlank()) { + preference.context.getString(androidx.preference.R.string.not_set) + } else { + "\u2022".repeat(text.length) + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/SSHSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/SSHSettingsActivity.kt new file mode 100644 index 0000000..dcead13 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/SSHSettingsActivity.kt @@ -0,0 +1,77 @@ +package io.nekohasekai.sagernet.ui.profile + +import android.os.Bundle +import androidx.preference.EditTextPreference +import com.takisoft.preferencex.PreferenceFragmentCompat +import com.takisoft.preferencex.SimpleMenuPreference +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers +import io.nekohasekai.sagernet.fmt.ssh.SSHBean + +class SSHSettingsActivity : ProfileSettingsActivity() { + + override fun createEntity() = SSHBean() + + override fun SSHBean.init() { + DataStore.profileName = name + DataStore.serverAddress = serverAddress + DataStore.serverPort = serverPort + DataStore.serverUsername = username + DataStore.serverAuthType = authType + DataStore.serverPassword = password + DataStore.serverPrivateKey = privateKey + DataStore.serverPassword1 = privateKeyPassphrase + DataStore.serverCertificates = publicKey + } + + override fun SSHBean.serialize() { + name = DataStore.profileName + serverAddress = DataStore.serverAddress + serverPort = DataStore.serverPort + username = DataStore.serverUsername + authType = DataStore.serverAuthType + when (authType) { + SSHBean.AUTH_TYPE_NONE -> { + } + SSHBean.AUTH_TYPE_PASSWORD -> { + password = DataStore.serverPassword + } + SSHBean.AUTH_TYPE_PRIVATE_KEY -> { + privateKey = DataStore.serverPrivateKey + privateKeyPassphrase = DataStore.serverPassword1 + } + } + publicKey = DataStore.serverCertificates + } + + override fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + addPreferencesFromResource(R.xml.ssh_preferences) + findPreference(Key.SERVER_PORT)!!.apply { + setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + } + val password = findPreference(Key.SERVER_PASSWORD)!!.apply { + summaryProvider = PasswordSummaryProvider + } + val privateKey = findPreference(Key.SERVER_PRIVATE_KEY)!! + val privateKeyPassphrase = findPreference(Key.SERVER_PASSWORD1)!!.apply { + summaryProvider = PasswordSummaryProvider + } + val authType = findPreference(Key.SERVER_AUTH_TYPE)!! + fun updateAuthType(type: Int = DataStore.serverAuthType) { + password.isVisible = type == SSHBean.AUTH_TYPE_PASSWORD + privateKey.isVisible = type == SSHBean.AUTH_TYPE_PRIVATE_KEY + privateKeyPassphrase.isVisible = type == SSHBean.AUTH_TYPE_PRIVATE_KEY + } + updateAuthType() + authType.setOnPreferenceChangeListener { _, newValue -> + updateAuthType((newValue as String).toInt()) + true + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ShadowsocksSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ShadowsocksSettingsActivity.kt new file mode 100644 index 0000000..bb6de7e --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/ShadowsocksSettingsActivity.kt @@ -0,0 +1,62 @@ +package io.nekohasekai.sagernet.ui.profile + +import android.os.Bundle +import androidx.preference.EditTextPreference +import com.takisoft.preferencex.PreferenceFragmentCompat +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers +import io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean +import moe.matsuri.nb4a.proxy.PreferenceBinding +import moe.matsuri.nb4a.proxy.PreferenceBindingManager +import moe.matsuri.nb4a.proxy.Type + +class ShadowsocksSettingsActivity : ProfileSettingsActivity() { + + override fun createEntity() = ShadowsocksBean() + + private val pbm = PreferenceBindingManager() + private val name = pbm.add(PreferenceBinding(Type.Text, "name")) + private val serverAddress = pbm.add(PreferenceBinding(Type.Text, "serverAddress")) + private val serverPort = pbm.add(PreferenceBinding(Type.TextToInt, "serverPort")) + private val password = pbm.add(PreferenceBinding(Type.Text, "password")) + private val method = pbm.add(PreferenceBinding(Type.Text, "method")) + private val pluginName = + pbm.add(PreferenceBinding(Type.Text, "pluginName").apply { disable = true }) + private val pluginConfig = + pbm.add(PreferenceBinding(Type.Text, "pluginConfig").apply { disable = true }) + private val sUoT = pbm.add(PreferenceBinding(Type.Bool, "sUoT")) + + override fun ShadowsocksBean.init() { + pbm.writeToCacheAll(this) + + DataStore.profileCacheStore.putString("pluginName", plugin.substringBefore(";")) + DataStore.profileCacheStore.putString("pluginConfig", plugin.substringAfter(";")) + } + + override fun ShadowsocksBean.serialize() { + pbm.fromCacheAll(this) + + val pn = pluginName.readStringFromCache() + val pc = pluginConfig.readStringFromCache() + plugin = if (pn.isNotBlank() && pc.isNotBlank()) "$pn;$pc" else "" + } + + override fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + addPreferencesFromResource(R.xml.shadowsocks_preferences) + pbm.setPreferenceFragment(this) + + serverPort.preference.apply { + this as EditTextPreference + setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + } + password.preference.apply { + this as EditTextPreference + summaryProvider = PasswordSummaryProvider + } + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/SocksSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/SocksSettingsActivity.kt new file mode 100644 index 0000000..286ecad --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/SocksSettingsActivity.kt @@ -0,0 +1,60 @@ +package io.nekohasekai.sagernet.ui.profile + +import android.os.Bundle +import androidx.preference.EditTextPreference +import androidx.preference.PreferenceCategory +import com.takisoft.preferencex.PreferenceFragmentCompat +import com.takisoft.preferencex.SimpleMenuPreference +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers +import io.nekohasekai.sagernet.fmt.socks.SOCKSBean + +class SocksSettingsActivity : ProfileSettingsActivity() { + override fun createEntity() = SOCKSBean() + + override fun SOCKSBean.init() { + DataStore.profileName = name + DataStore.serverAddress = serverAddress + DataStore.serverPort = serverPort + + DataStore.serverProtocolVersion = protocol + DataStore.serverUsername = username + DataStore.serverPassword = password + } + + override fun SOCKSBean.serialize() { + name = DataStore.profileName + serverAddress = DataStore.serverAddress + serverPort = DataStore.serverPort + + protocol = DataStore.serverProtocolVersion + username = DataStore.serverUsername + password = DataStore.serverPassword + } + + override fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + addPreferencesFromResource(R.xml.socks_preferences) + findPreference(Key.SERVER_PORT)!!.apply { + setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + } + val password = findPreference(Key.SERVER_PASSWORD)!!.apply { + summaryProvider = PasswordSummaryProvider + } + val protocol = findPreference(Key.SERVER_PROTOCOL)!! + + fun updateProtocol(version: Int) { + password.isVisible = version == SOCKSBean.PROTOCOL_SOCKS5 + } + + updateProtocol(DataStore.serverProtocolVersion) + protocol.setOnPreferenceChangeListener { _, newValue -> + updateProtocol((newValue as String).toInt()) + true + } + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/StandardV2RaySettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/StandardV2RaySettingsActivity.kt new file mode 100644 index 0000000..bd3d560 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/StandardV2RaySettingsActivity.kt @@ -0,0 +1,166 @@ +package io.nekohasekai.sagernet.ui.profile + +import android.os.Bundle +import androidx.preference.EditTextPreference +import androidx.preference.PreferenceCategory +import com.takisoft.preferencex.PreferenceFragmentCompat +import com.takisoft.preferencex.SimpleMenuPreference +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers +import io.nekohasekai.sagernet.fmt.http.HttpBean +import io.nekohasekai.sagernet.fmt.trojan.TrojanBean +import io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean +import io.nekohasekai.sagernet.fmt.v2ray.VMessBean +import io.nekohasekai.sagernet.ktx.Logs +import moe.matsuri.nb4a.proxy.PreferenceBinding +import moe.matsuri.nb4a.proxy.PreferenceBindingManager +import moe.matsuri.nb4a.proxy.Type + +abstract class StandardV2RaySettingsActivity : ProfileSettingsActivity() { + + var tmpBean: StandardV2RayBean? = null + + private val pbm = PreferenceBindingManager() + private val name = pbm.add(PreferenceBinding(Type.Text, "name")) + private val serverAddress = pbm.add(PreferenceBinding(Type.Text, "serverAddress")) + private val serverPort = pbm.add(PreferenceBinding(Type.TextToInt, "serverPort")) + private val uuid = pbm.add(PreferenceBinding(Type.Text, "uuid")) + private val username = pbm.add(PreferenceBinding(Type.Text, "username")) + private val password = pbm.add(PreferenceBinding(Type.Text, "password")) + private val alterId = pbm.add(PreferenceBinding(Type.TextToInt, "alterId")) + private val encryption = pbm.add(PreferenceBinding(Type.Text, "encryption")) + private val type = pbm.add(PreferenceBinding(Type.Text, "type")) + private val host = pbm.add(PreferenceBinding(Type.Text, "host")) + private val path = pbm.add(PreferenceBinding(Type.Text, "path")) + private val packetEncoding = pbm.add(PreferenceBinding(Type.TextToInt, "packetEncoding")) + private val security = pbm.add(PreferenceBinding(Type.Text, "security")) + private val sni = pbm.add(PreferenceBinding(Type.Text, "sni")) + private val alpn = pbm.add(PreferenceBinding(Type.Text, "alpn")) + private val certificates = pbm.add(PreferenceBinding(Type.Text, "certificates")) + private val allowInsecure = pbm.add(PreferenceBinding(Type.Bool, "allowInsecure")) + private val utlsFingerprint = pbm.add(PreferenceBinding(Type.Text, "utlsFingerprint")) + private val realityPubKey = pbm.add(PreferenceBinding(Type.Text, "realityPubKey")) + private val realityShortId = pbm.add(PreferenceBinding(Type.Text, "realityShortId")) + + override fun StandardV2RayBean.init() { + if (this is VMessBean) { + if (intent?.getBooleanExtra("vless", false) == true) { + alterId = -1 + } + } else if (this is TrojanBean) { + this@StandardV2RaySettingsActivity.uuid.fieldName = "password" + this@StandardV2RaySettingsActivity.password.disable = true + } + + tmpBean = this // copy bean + pbm.writeToCacheAll(this) + } + + override fun StandardV2RayBean.serialize() { + pbm.fromCacheAll(this) + } + + lateinit var securityCategory: PreferenceCategory + + override fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + addPreferencesFromResource(R.xml.standard_v2ray_preferences) + pbm.setPreferenceFragment(this) + securityCategory = findPreference(Key.SERVER_SECURITY_CATEGORY)!! + + serverPort.preference.apply { + this as EditTextPreference + setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + } + + alterId.preference.apply { + this as EditTextPreference + setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + } + + uuid.preference.summaryProvider = PasswordSummaryProvider + + type.preference.isVisible = tmpBean !is HttpBean + uuid.preference.isVisible = tmpBean !is HttpBean + packetEncoding.preference.isVisible = tmpBean !is HttpBean + alterId.preference.isVisible = tmpBean is VMessBean && tmpBean?.isVLESS == false + encryption.preference.isVisible = tmpBean is VMessBean + type.preference.isVisible = tmpBean !is HttpBean + username.preference.isVisible = tmpBean is HttpBean + password.preference.isVisible = tmpBean is HttpBean + + if (tmpBean is TrojanBean) { + uuid.preference.title = resources.getString(R.string.password) + } + + encryption.preference.apply { + this as SimpleMenuPreference + if (tmpBean!!.isVLESS) { + title = resources.getString(R.string.xtls_flow) + setIcon(R.drawable.ic_baseline_stream_24) + setEntries(R.array.xtls_flow_value) + setEntryValues(R.array.xtls_flow_value) + } else { + setEntries(R.array.vmess_encryption_value) + setEntryValues(R.array.vmess_encryption_value) + } + } + + // menu with listener + + type.preference.apply { + updateView(type.readStringFromCache()) + this as SimpleMenuPreference + setOnPreferenceChangeListener { _, newValue -> + updateView(newValue as String) + true + } + } + + security.preference.apply { + updateTle(security.readStringFromCache()) + this as SimpleMenuPreference + setOnPreferenceChangeListener { _, newValue -> + updateTle(newValue as String) + true + } + } + } + + fun updateView(network: String) { + host.preference.isVisible = false + path.preference.isVisible = false + + when (network) { + "tcp" -> { + host.preference.setTitle(R.string.http_host) + path.preference.setTitle(R.string.http_path) + } + "http" -> { + host.preference.setTitle(R.string.http_host) + path.preference.setTitle(R.string.http_path) + host.preference.isVisible = true + path.preference.isVisible = true + } + "ws" -> { + host.preference.setTitle(R.string.ws_host) + path.preference.setTitle(R.string.ws_path) + host.preference.isVisible = true + path.preference.isVisible = true + } + "grpc" -> { + path.preference.setTitle(R.string.grpc_service_name) + path.preference.isVisible = true + } + } + } + + fun updateTle(tle: String) { + val isTLS = tle == "tls" + securityCategory.isVisible = isTLS + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/TrojanGoSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/TrojanGoSettingsActivity.kt new file mode 100644 index 0000000..45aa98d --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/TrojanGoSettingsActivity.kt @@ -0,0 +1,130 @@ +package io.nekohasekai.sagernet.ui.profile + +import android.os.Bundle +import androidx.preference.EditTextPreference +import androidx.preference.PreferenceCategory +import com.takisoft.preferencex.PreferenceFragmentCompat +import com.takisoft.preferencex.SimpleMenuPreference +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers +import io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean +import io.nekohasekai.sagernet.ktx.app + +class TrojanGoSettingsActivity : ProfileSettingsActivity() { + + override fun createEntity() = TrojanGoBean() + + override fun TrojanGoBean.init() { + DataStore.profileName = name + DataStore.serverAddress = serverAddress + DataStore.serverPort = serverPort + DataStore.serverPassword = password + DataStore.serverSNI = sni + DataStore.serverAllowInsecure = allowInsecure + DataStore.serverNetwork = type + DataStore.serverHost = host + DataStore.serverPath = path + if (encryption.startsWith("ss;")) { + DataStore.serverEncryption = "ss" + DataStore.serverMethod = encryption.substringAfter(";").substringBefore(":") + DataStore.serverPassword1 = encryption.substringAfter(":") + } else { + DataStore.serverEncryption = encryption + } + } + + override fun TrojanGoBean.serialize() { + name = DataStore.profileName + serverAddress = DataStore.serverAddress + serverPort = DataStore.serverPort + password = DataStore.serverPassword + sni = DataStore.serverSNI + allowInsecure = DataStore.serverAllowInsecure + type = DataStore.serverNetwork + host = DataStore.serverHost + path = DataStore.serverPath + encryption = when (val security = DataStore.serverEncryption) { + "ss" -> { + "ss;" + DataStore.serverMethod + ":" + DataStore.serverPassword1 + } + else -> { + security + } + } + } + + lateinit var network: SimpleMenuPreference + lateinit var encryprtion: SimpleMenuPreference + lateinit var wsCategory: PreferenceCategory + lateinit var ssCategory: PreferenceCategory + lateinit var method: SimpleMenuPreference + + val trojanGoMethods = app.resources.getStringArray(R.array.trojan_go_methods) + val trojanGoNetworks = app.resources.getStringArray(R.array.trojan_go_networks_value) + + override fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + addPreferencesFromResource(R.xml.trojan_go_preferences) + findPreference(Key.SERVER_PORT)!!.apply { + setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + } + findPreference(Key.SERVER_PASSWORD)!!.apply { + summaryProvider = PasswordSummaryProvider + } + findPreference(Key.SERVER_PASSWORD1)!!.apply { + summaryProvider = PasswordSummaryProvider + } + wsCategory = findPreference(Key.SERVER_WS_CATEGORY)!! + ssCategory = findPreference(Key.SERVER_SS_CATEGORY)!! + method = findPreference(Key.SERVER_METHOD)!! + + network = findPreference(Key.SERVER_NETWORK)!! + + if (network.value !in trojanGoNetworks) { + network.value = trojanGoNetworks[0] + } + + updateNetwork(network.value) + network.setOnPreferenceChangeListener { _, newValue -> + updateNetwork(newValue as String) + true + } + encryprtion = findPreference(Key.SERVER_ENCRYPTION)!! + updateEncryption(encryprtion.value) + encryprtion.setOnPreferenceChangeListener { _, newValue -> + updateEncryption(newValue as String) + true + } + } + + fun updateNetwork(newNet: String) { + when (newNet) { + "ws" -> { + wsCategory.isVisible = true + } + else -> { + wsCategory.isVisible = false + } + } + } + + fun updateEncryption(encryption: String) { + when (encryption) { + "ss" -> { + ssCategory.isVisible = true + + if (method.value !in trojanGoMethods) { + method.value = trojanGoMethods[0] + } + } + else -> { + ssCategory.isVisible = false + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/TrojanSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/TrojanSettingsActivity.kt new file mode 100644 index 0000000..6210ec5 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/TrojanSettingsActivity.kt @@ -0,0 +1,9 @@ +package io.nekohasekai.sagernet.ui.profile + +import io.nekohasekai.sagernet.fmt.trojan.TrojanBean + +class TrojanSettingsActivity : StandardV2RaySettingsActivity() { + + override fun createEntity() = TrojanBean() + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/TuicSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/TuicSettingsActivity.kt new file mode 100644 index 0000000..5f81a6c --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/TuicSettingsActivity.kt @@ -0,0 +1,72 @@ +package io.nekohasekai.sagernet.ui.profile + +import android.os.Bundle +import android.widget.Switch +import androidx.preference.EditTextPreference +import androidx.preference.SwitchPreference +import com.takisoft.preferencex.PreferenceFragmentCompat +import com.takisoft.preferencex.SimpleMenuPreference +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.fmt.tuic.TuicBean +import io.nekohasekai.sagernet.ktx.applyDefaultValues + +class TuicSettingsActivity : ProfileSettingsActivity() { + + override fun createEntity() = TuicBean().applyDefaultValues() + + override fun TuicBean.init() { + DataStore.profileName = name + DataStore.serverAddress = serverAddress + DataStore.serverPort = serverPort + DataStore.serverPassword = token + DataStore.serverALPN = alpn + DataStore.serverCertificates = caText + DataStore.serverUDPRelayMode = udpRelayMode + DataStore.serverCongestionController = congestionController + DataStore.serverDisableSNI = disableSNI + DataStore.serverSNI = sni + DataStore.serverReduceRTT = reduceRTT + DataStore.serverMTU = mtu + DataStore.serverFastConnect = fastConnect + DataStore.serverAllowInsecure = allowInsecure + } + + override fun TuicBean.serialize() { + name = DataStore.profileName + serverAddress = DataStore.serverAddress + serverPort = DataStore.serverPort + token = DataStore.serverPassword + alpn = DataStore.serverALPN + caText = DataStore.serverCertificates + udpRelayMode = DataStore.serverUDPRelayMode + congestionController = DataStore.serverCongestionController + disableSNI = DataStore.serverDisableSNI + sni = DataStore.serverSNI + reduceRTT = DataStore.serverReduceRTT + mtu = DataStore.serverMTU + fastConnect = DataStore.serverFastConnect + allowInsecure = DataStore.serverAllowInsecure + } + + override fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + addPreferencesFromResource(R.xml.tuic_preferences) + + val disableSNI = findPreference(Key.SERVER_DISABLE_SNI)!! + val sni = findPreference(Key.SERVER_SNI)!! + sni.isEnabled = !disableSNI.isChecked + disableSNI.setOnPreferenceChangeListener { _, newValue -> + sni.isEnabled = !(newValue as Boolean) + true + } + + findPreference(Key.SERVER_PASSWORD)!!.apply { + summaryProvider = PasswordSummaryProvider + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/VMessSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/VMessSettingsActivity.kt new file mode 100644 index 0000000..33d3e74 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/VMessSettingsActivity.kt @@ -0,0 +1,9 @@ +package io.nekohasekai.sagernet.ui.profile + +import io.nekohasekai.sagernet.fmt.v2ray.VMessBean + +class VMessSettingsActivity : StandardV2RaySettingsActivity() { + + override fun createEntity() = VMessBean() + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/profile/WireGuardSettingsActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/WireGuardSettingsActivity.kt new file mode 100644 index 0000000..0673aa7 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/profile/WireGuardSettingsActivity.kt @@ -0,0 +1,49 @@ +package io.nekohasekai.sagernet.ui.profile + +import android.os.Bundle +import androidx.preference.EditTextPreference +import com.takisoft.preferencex.PreferenceFragmentCompat +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers +import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean +import moe.matsuri.nb4a.proxy.PreferenceBinding +import moe.matsuri.nb4a.proxy.PreferenceBindingManager +import moe.matsuri.nb4a.proxy.Type + +class WireGuardSettingsActivity : ProfileSettingsActivity() { + + override fun createEntity() = WireGuardBean() + + private val pbm = PreferenceBindingManager() + private val name = pbm.add(PreferenceBinding(Type.Text, "name")) + private val serverAddress = pbm.add(PreferenceBinding(Type.Text, "serverAddress")) + private val serverPort = pbm.add(PreferenceBinding(Type.TextToInt, "serverPort")) + private val localAddress = pbm.add(PreferenceBinding(Type.Text, "localAddress")) + private val privateKey = pbm.add(PreferenceBinding(Type.Text, "privateKey")) + private val peerPublicKey = pbm.add(PreferenceBinding(Type.Text, "peerPublicKey")) + private val peerPreSharedKey = pbm.add(PreferenceBinding(Type.Text, "peerPreSharedKey")) + private val mtu = pbm.add(PreferenceBinding(Type.TextToInt, "mtu")) + private val reserved = pbm.add(PreferenceBinding(Type.Text, "reserved")) + + override fun WireGuardBean.init() { + pbm.writeToCacheAll(this) + } + + override fun WireGuardBean.serialize() { + pbm.fromCacheAll(this) + } + + override fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + addPreferencesFromResource(R.xml.wireguard_preferences) + pbm.setPreferenceFragment(this) + + (serverPort.preference as EditTextPreference) + .setOnBindEditTextListener(EditTextPreferenceModifiers.Port) + (privateKey.preference as EditTextPreference).summaryProvider = PasswordSummaryProvider + (mtu.preference as EditTextPreference).setOnBindEditTextListener(EditTextPreferenceModifiers.Number) + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/utils/Cloudflare.kt b/app/src/main/java/io/nekohasekai/sagernet/utils/Cloudflare.kt new file mode 100644 index 0000000..eee031f --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/utils/Cloudflare.kt @@ -0,0 +1,72 @@ +package io.nekohasekai.sagernet.utils + +import com.wireguard.crypto.KeyPair +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.utils.cf.DeviceResponse +import io.nekohasekai.sagernet.utils.cf.RegisterRequest +import io.nekohasekai.sagernet.utils.cf.UpdateDeviceRequest +import libcore.Libcore +import moe.matsuri.nb4a.utils.JavaUtil.gson + +// kang from wgcf +object Cloudflare { + + private const val API_URL = "https://api.cloudflareclient.com" + private const val API_VERSION = "v0a1922" + + private const val CLIENT_VERSION_KEY = "CF-Client-Version" + private const val CLIENT_VERSION = "a-6.3-1922" + + fun makeWireGuardConfiguration(): WireGuardBean { + val keyPair = KeyPair() + val client = Libcore.newHttpClient().apply { + pinnedTLS12() + trySocks5(DataStore.mixedPort) + } + + try { + val response = client.newRequest().apply { + setMethod("POST") + setURL("$API_URL/$API_VERSION/reg") + setHeader(CLIENT_VERSION_KEY, CLIENT_VERSION) + setHeader("Accept", "application/json") + setHeader("Content-Type", "application/json") + setContentString(RegisterRequest.newRequest(keyPair.publicKey)) + setUserAgent("okhttp/3.12.1") + }.execute() + + Logs.d(response.contentString) + val device = gson.fromJson(response.contentString, DeviceResponse::class.java) + val accessToken = device.token + + client.newRequest().apply { + setMethod("PATCH") + setURL(API_URL + "/" + API_VERSION + "/reg/" + device.id + "/account/reg/" + device.id) + setHeader("Accept", "application/json") + setHeader("Content-Type", "application/json") + setHeader("Authorization", "Bearer $accessToken") + setHeader(CLIENT_VERSION_KEY, CLIENT_VERSION) + setContentString(UpdateDeviceRequest.newRequest()) + setUserAgent("okhttp/3.12.1") + }.execute() + + val peer = device.config.peers[0] + val localAddresses = device.config.interfaceX.addresses + return WireGuardBean().apply { + name = "CloudFlare Warp ${device.account.id}" + privateKey = keyPair.privateKey.toBase64() + peerPublicKey = peer.publicKey + serverAddress = peer.endpoint.host.substringBeforeLast(":") + serverPort = peer.endpoint.host.substringAfterLast(":").toInt() + localAddress = localAddresses.v4 + "/32" + "\n" + localAddresses.v6 + "/128" + mtu = 1280 + reserved = device.config.clientId + } + } finally { + client.close() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/utils/Commandline.kt b/app/src/main/java/io/nekohasekai/sagernet/utils/Commandline.kt new file mode 100644 index 0000000..03c32f6 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/utils/Commandline.kt @@ -0,0 +1,140 @@ +package io.nekohasekai.sagernet.utils + +import java.util.* + +/** + * Commandline objects help handling command lines specifying processes to + * execute. + * + * The class can be used to define a command line as nested elements or as a + * helper to define a command line by an application. + * + * + * ` + *

+ *   

+ *     

+ *     

+ *     

+ *   


+ *


+` * + * + * Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java + * + * Adds support for escape character '\'. + */ +object Commandline { + + /** + * Quote the parts of the given array in way that makes them + * usable as command line arguments. + * @param args the list of arguments to quote. + * @return empty string for null or no command, else every argument split + * by spaces and quoted by quoting rules. + */ + fun toString(args: Iterable?): String { + // empty path return empty string + args ?: return "" + // path containing one or more elements + val result = StringBuilder() + for (arg in args) { + if (result.isNotEmpty()) result.append(' ') + arg.indices.map { arg[it] }.forEach { + when (it) { + ' ', '\\', '"', '\'' -> { + result.append('\\') // intentionally no break + result.append(it) + } + else -> result.append(it) + } + } + } + return result.toString() + } + + /** + * Quote the parts of the given array in way that makes them + * usable as command line arguments. + * @param args the list of arguments to quote. + * @return empty string for null or no command, else every argument split + * by spaces and quoted by quoting rules. + */ + fun toString(args: Array) = + toString(args.asIterable()) // thanks to Java, arrays aren't iterable + + /** + * Crack a command line. + * @param toProcess the command line to process. + * @return the command line broken into strings. + * An empty or null toProcess parameter results in a zero sized array. + */ + fun translateCommandline(toProcess: String?): Array { + if (toProcess == null || toProcess.isEmpty()) { + //no command? no string + return arrayOf() + } + // parse with a simple finite state machine + + val normal = 0 + val inQuote = 1 + val inDoubleQuote = 2 + var state = normal + val tok = StringTokenizer(toProcess, "\\\"\' ", true) + val result = ArrayList() + val current = StringBuilder() + var lastTokenHasBeenQuoted = false + var lastTokenIsSlash = false + + while (tok.hasMoreTokens()) { + val nextTok = tok.nextToken() + when (state) { + inQuote -> if ("\'" == nextTok) { + lastTokenHasBeenQuoted = true + state = normal + } else current.append(nextTok) + inDoubleQuote -> when (nextTok) { + "\"" -> if (lastTokenIsSlash) { + current.append(nextTok) + lastTokenIsSlash = false + } else { + lastTokenHasBeenQuoted = true + state = normal + } + "\\" -> lastTokenIsSlash = if (lastTokenIsSlash) { + current.append(nextTok) + false + } else true + else -> { + if (lastTokenIsSlash) { + current.append("\\") // unescaped + lastTokenIsSlash = false + } + current.append(nextTok) + } + } + else -> { + when { + lastTokenIsSlash -> { + current.append(nextTok) + lastTokenIsSlash = false + } + "\\" == nextTok -> lastTokenIsSlash = true + "\'" == nextTok -> state = inQuote + "\"" == nextTok -> state = inDoubleQuote + " " == nextTok -> if (lastTokenHasBeenQuoted || current.isNotEmpty()) { + result.add(current.toString()) + current.setLength(0) + } + else -> current.append(nextTok) + } + lastTokenHasBeenQuoted = false + } + } + } + if (lastTokenHasBeenQuoted || current.isNotEmpty()) result.add(current.toString()) + require(state != inQuote && state != inDoubleQuote) { "unbalanced quotes in $toProcess" } + require(!lastTokenIsSlash) { "escape character following nothing in $toProcess" } + return result.toTypedArray() + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/utils/CrashHandler.kt b/app/src/main/java/io/nekohasekai/sagernet/utils/CrashHandler.kt new file mode 100644 index 0000000..292f929 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/utils/CrashHandler.kt @@ -0,0 +1,172 @@ +package io.nekohasekai.sagernet.utils + +import android.annotation.SuppressLint +import android.content.Intent +import android.os.Build +import android.util.Log +import com.jakewharton.processphoenix.ProcessPhoenix +import io.nekohasekai.sagernet.BuildConfig +import io.nekohasekai.sagernet.database.preference.PublicDatabase +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.ktx.app +import io.nekohasekai.sagernet.ui.BlankActivity +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStreamReader +import java.text.SimpleDateFormat +import java.util.* +import java.util.regex.Pattern + +object CrashHandler : Thread.UncaughtExceptionHandler { + + @Suppress("UNNECESSARY_SAFE_CALL") + override fun uncaughtException(thread: Thread, throwable: Throwable) { + // note: libc / go panic is in android log + + try { + Log.e(thread.toString(), throwable.stackTraceToString()) + } catch (e: Exception) { + } + + try { + Logs.e(thread.toString()) + Logs.e(throwable.stackTraceToString()) + } catch (e: Exception) { + } + + ProcessPhoenix.triggerRebirth(app, Intent(app, BlankActivity::class.java).apply { + putExtra("sendLog", "NB4A Crash") + }) + } + + fun formatThrowable(throwable: Throwable): String { + var format = throwable.javaClass.name + val message = throwable.message + if (!message.isNullOrBlank()) { + format += ": $message" + } + format += "\n" + + format += throwable.stackTrace.joinToString("\n") { + " at ${it.className}.${it.methodName}(${it.fileName}:${if (it.isNativeMethod) "native" else it.lineNumber})" + } + + val cause = throwable.cause + if (cause != null) { + format += "\n\nCaused by: " + formatThrowable(cause) + } + + return format + } + + fun buildReportHeader(): String { + var report = "" + report += "NekoBox for Andoird ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) ${BuildConfig.FLAVOR.uppercase()}\n" + report += "Date: ${getCurrentMilliSecondUTCTimeStamp()}\n\n" + report += "OS_VERSION: ${getSystemPropertyWithAndroidAPI("os.version")}\n" + report += "SDK_INT: ${Build.VERSION.SDK_INT}\n" + report += if ("REL" == Build.VERSION.CODENAME) { + "RELEASE: ${Build.VERSION.RELEASE}" + } else { + "CODENAME: ${Build.VERSION.CODENAME}" + } + "\n" + report += "ID: ${Build.ID}\n" + report += "DISPLAY: ${Build.DISPLAY}\n" + report += "INCREMENTAL: ${Build.VERSION.INCREMENTAL}\n" + + val systemProperties = getSystemProperties() + + report += "SECURITY_PATCH: ${systemProperties.getProperty("ro.build.version.security_patch")}\n" + report += "IS_DEBUGGABLE: ${systemProperties.getProperty("ro.debuggable")}\n" + report += "IS_EMULATOR: ${systemProperties.getProperty("ro.boot.qemu")}\n" + report += "IS_TREBLE_ENABLED: ${systemProperties.getProperty("ro.treble.enabled")}\n" + + report += "TYPE: ${Build.TYPE}\n" + report += "TAGS: ${Build.TAGS}\n\n" + + report += "MANUFACTURER: ${Build.MANUFACTURER}\n" + report += "BRAND: ${Build.BRAND}\n" + report += "MODEL: ${Build.MODEL}\n" + report += "PRODUCT: ${Build.PRODUCT}\n" + report += "BOARD: ${Build.BOARD}\n" + report += "HARDWARE: ${Build.HARDWARE}\n" + report += "DEVICE: ${Build.DEVICE}\n" + report += "SUPPORTED_ABIS: ${ + Build.SUPPORTED_ABIS.filter { it.isNotBlank() }.joinToString(", ") + }\n\n" + + + try { + report += "Settings: \n" + for (pair in PublicDatabase.kvPairDao.all()) { + report += "\n" + report += pair.key + ": " + pair.toString() + } + }catch (e: Exception) { + report += "Export settings failed: " + formatThrowable(e) + } + + report += "\n\n" + + return report + } + + private fun getSystemProperties(): Properties { + val systemProperties = Properties() + + // getprop commands returns values in the format `[key]: [value]` + // Regex matches string starting with a literal `[`, + // followed by one or more characters that do not match a closing square bracket as the key, + // followed by a literal `]: [`, + // followed by one or more characters as the value, + // followed by string ending with literal `]` + // multiline values will be ignored + val propertiesPattern = Pattern.compile("^\\[([^]]+)]: \\[(.+)]$") + try { + val process = ProcessBuilder().command("/system/bin/getprop") + .redirectErrorStream(true) + .start() + val inputStream = process.inputStream + val bufferedReader = BufferedReader(InputStreamReader(inputStream)) + var line: String? + var key: String + var value: String + while (bufferedReader.readLine().also { line = it } != null) { + val matcher = propertiesPattern.matcher(line) + if (matcher.matches()) { + key = matcher.group(1) + value = matcher.group(2) + if (key != null && value != null && !key.isEmpty() && !value.isEmpty()) systemProperties[key] = value + } + } + bufferedReader.close() + process.destroy() + } catch (e: IOException) { + Logs.e( + "Failed to get run \"/system/bin/getprop\" to get system properties.", e + ) + } + + //for (String key : systemProperties.stringPropertyNames()) { + // Logger.logVerbose(key + ": " + systemProperties.get(key)); + //} + return systemProperties + } + + private fun getSystemPropertyWithAndroidAPI(property: String): String? { + return try { + System.getProperty(property) + } catch (e: Exception) { + Logs.e("Failed to get system property \"" + property + "\":" + e.message) + null + } + } + + @SuppressLint("SimpleDateFormat") + private fun getCurrentMilliSecondUTCTimeStamp(): String { + val df = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS z") + df.timeZone = TimeZone.getTimeZone("UTC") + return df.format(Date()) + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/utils/DefaultNetworkListener.kt b/app/src/main/java/io/nekohasekai/sagernet/utils/DefaultNetworkListener.kt new file mode 100644 index 0000000..6af79fd --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/utils/DefaultNetworkListener.kt @@ -0,0 +1,154 @@ +package io.nekohasekai.sagernet.utils + +import android.annotation.TargetApi +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import android.os.Build +import android.os.Handler +import android.os.Looper +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.ktx.Logs +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.channels.actor +import kotlinx.coroutines.runBlocking +import java.net.UnknownHostException + +object DefaultNetworkListener { + private sealed class NetworkMessage { + class Start(val key: Any, val listener: (Network?) -> Unit) : NetworkMessage() + class Get : NetworkMessage() { + val response = CompletableDeferred() + } + + class Stop(val key: Any) : NetworkMessage() + + class Put(val network: Network) : NetworkMessage() + class Update(val network: Network) : NetworkMessage() + class Lost(val network: Network) : NetworkMessage() + } + + private val networkActor = GlobalScope.actor(Dispatchers.Unconfined) { + val listeners = mutableMapOf Unit>() + var network: Network? = null + val pendingRequests = arrayListOf() + for (message in channel) when (message) { + is NetworkMessage.Start -> { + if (listeners.isEmpty()) register() + listeners[message.key] = message.listener + if (network != null) message.listener(network) + } + is NetworkMessage.Get -> { + check(listeners.isNotEmpty()) { "Getting network without any listeners is not supported" } + if (network == null) pendingRequests += message else message.response.complete( + network + ) + } + is NetworkMessage.Stop -> if (listeners.isNotEmpty() && // was not empty + listeners.remove(message.key) != null && listeners.isEmpty() + ) { + network = null + unregister() + } + + is NetworkMessage.Put -> { + network = message.network + pendingRequests.forEach { it.response.complete(message.network) } + pendingRequests.clear() + listeners.values.forEach { it(network) } + } + is NetworkMessage.Update -> if (network == message.network) listeners.values.forEach { + it( + network + ) + } + is NetworkMessage.Lost -> if (network == message.network) { + network = null + listeners.values.forEach { it(null) } + } + } + } + + suspend fun start(key: Any, listener: (Network?) -> Unit) = + networkActor.send(NetworkMessage.Start(key, listener)) + + suspend fun get() = if (fallback) @TargetApi(23) { + SagerNet.connectivity.activeNetwork + ?: throw UnknownHostException() // failed to listen, return current if available + } else NetworkMessage.Get().run { + networkActor.send(this) + response.await() + } + + suspend fun stop(key: Any) = networkActor.send(NetworkMessage.Stop(key)) + + // NB: this runs in ConnectivityThread, and this behavior cannot be changed until API 26 + private object Callback : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) = + runBlocking { networkActor.send(NetworkMessage.Put(network)) } + + override fun onCapabilitiesChanged( + network: Network, networkCapabilities: NetworkCapabilities + ) { // it's a good idea to refresh capabilities + runBlocking { networkActor.send(NetworkMessage.Update(network)) } + } + + override fun onLost(network: Network) = + runBlocking { networkActor.send(NetworkMessage.Lost(network)) } + } + + private var fallback = false + private val request = NetworkRequest.Builder().apply { + addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + if (Build.VERSION.SDK_INT == 23) { // workarounds for OEM bugs + removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + removeCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL) + } + }.build() + private val mainHandler = Handler(Looper.getMainLooper()) + + /** + * Unfortunately registerDefaultNetworkCallback is going to return VPN interface since Android P DP1: + * https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e + * + * This makes doing a requestNetwork with REQUEST necessary so that we don't get ALL possible networks that + * satisfies default network capabilities but only THE default network. Unfortunately, we need to have + * android.permission.CHANGE_NETWORK_STATE to be able to call requestNetwork. + * + * Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887 + */ + private fun register() { + try { + fallback = false + when (Build.VERSION.SDK_INT) { + in 31..Int.MAX_VALUE -> @TargetApi(31) { + SagerNet.connectivity.registerBestMatchingNetworkCallback( + request, Callback, mainHandler + ) + } + in 28 until 31 -> @TargetApi(28) { // we want REQUEST here instead of LISTEN + SagerNet.connectivity.requestNetwork(request, Callback, mainHandler) + } + in 26 until 28 -> @TargetApi(26) { + SagerNet.connectivity.registerDefaultNetworkCallback(Callback, mainHandler) + } + in 24 until 26 -> @TargetApi(24) { + SagerNet.connectivity.registerDefaultNetworkCallback(Callback) + } + else -> { + SagerNet.connectivity.requestNetwork(request, Callback) + // known bug on API 23: https://stackoverflow.com/a/33509180/2245107 + } + } + } catch (e: Exception) { + Logs.w(e) + fallback = true + } + } + + private fun unregister() = SagerNet.connectivity.unregisterNetworkCallback(Callback) +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/utils/DeviceStorageApp.kt b/app/src/main/java/io/nekohasekai/sagernet/utils/DeviceStorageApp.kt new file mode 100644 index 0000000..fc4f632 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/utils/DeviceStorageApp.kt @@ -0,0 +1,20 @@ +package io.nekohasekai.sagernet.utils + +import android.annotation.SuppressLint +import android.annotation.TargetApi +import android.app.Application +import android.content.Context + +@SuppressLint("Registered") +@TargetApi(24) +class DeviceStorageApp(context: Context) : Application() { + init { + attachBaseContext(context.createDeviceProtectedStorageContext()) + } + + /** + * Thou shalt not get the REAL underlying application context which would no longer be operating under device + * protected storage. + */ + override fun getApplicationContext() = this +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/utils/PackageCache.kt b/app/src/main/java/io/nekohasekai/sagernet/utils/PackageCache.kt new file mode 100644 index 0000000..136563d --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/utils/PackageCache.kt @@ -0,0 +1,97 @@ +package io.nekohasekai.sagernet.utils + +import android.Manifest +import android.annotation.SuppressLint +import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import io.nekohasekai.sagernet.ktx.app +import io.nekohasekai.sagernet.ktx.listenForPackageChanges +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import moe.matsuri.nb4a.plugin.Plugins + +object PackageCache { + + lateinit var installedPackages: Map + lateinit var installedPluginPackages: Map + lateinit var installedApps: Map + lateinit var packageMap: Map + val uidMap = HashMap>() + val loaded = Mutex(true) + + // called from init (suspend) + fun register() { + reload() + app.listenForPackageChanges(false) { + reload() + labelMap.clear() + } + loaded.unlock() + } + + @SuppressLint("InlinedApi") + fun reload() { + val rawPackageInfo = app.packageManager.getInstalledPackages( + PackageManager.MATCH_UNINSTALLED_PACKAGES + or PackageManager.GET_PERMISSIONS + or PackageManager.GET_PROVIDERS + or PackageManager.GET_META_DATA + ) + + installedPackages = rawPackageInfo.filter { + when (it.packageName) { + "android" -> true + else -> it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true + } + }.associateBy { it.packageName } + + installedPluginPackages = rawPackageInfo.filter { + Plugins.isExeOrPlugin(it) + }.associateBy { it.packageName } + + val installed = app.packageManager.getInstalledApplications(PackageManager.GET_META_DATA) + installedApps = installed.associateBy { it.packageName } + packageMap = installed.associate { it.packageName to it.uid } + uidMap.clear() + for (info in installed) { + val uid = info.uid + uidMap.getOrPut(uid) { HashSet() }.add(info.packageName) + } + } + + operator fun get(uid: Int) = uidMap[uid] + operator fun get(packageName: String) = packageMap[packageName] + + suspend fun awaitLoad() { + if (::packageMap.isInitialized) { + return + } + loaded.withLock { + // just await + } + } + + fun awaitLoadSync() { + if (::packageMap.isInitialized) { + return + } + runBlocking { + loaded.withLock { + // just await + } + } + } + + private val labelMap = mutableMapOf() + fun loadLabel(packageName: String): String { + var label = labelMap[packageName] + if (label != null) return label + val info = installedApps[packageName] ?: return packageName + label = info.loadLabel(app.packageManager).toString() + labelMap[packageName] = label + return label + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/utils/Subnet.kt b/app/src/main/java/io/nekohasekai/sagernet/utils/Subnet.kt new file mode 100644 index 0000000..80af40a --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/utils/Subnet.kt @@ -0,0 +1,86 @@ +package io.nekohasekai.sagernet.utils + +import io.nekohasekai.sagernet.ktx.parseNumericAddress +import java.net.InetAddress +import java.util.* + +class Subnet(val address: InetAddress, val prefixSize: Int) : Comparable { + companion object { + fun fromString(value: String, lengthCheck: Int = -1): Subnet? { + val parts = value.split('/', limit = 2) + val addr = parts[0].parseNumericAddress() ?: return null + check(lengthCheck < 0 || addr.address.size == lengthCheck) + return if (parts.size == 2) try { + val prefixSize = parts[1].toInt() + if (prefixSize < 0 || prefixSize > addr.address.size shl 3) null else Subnet(addr, + prefixSize) + } catch (_: NumberFormatException) { + null + } else Subnet(addr, addr.address.size shl 3) + } + } + + private val addressLength get() = address.address.size shl 3 + + init { + require(prefixSize in 0..addressLength) { "prefixSize $prefixSize not in 0..$addressLength" } + } + + class Immutable(private val a: ByteArray, private val prefixSize: Int = 0) { + companion object : Comparator { + override fun compare(a: Immutable, b: Immutable): Int { + check(a.a.size == b.a.size) + for (i in a.a.indices) { + val result = a.a[i].compareTo(b.a[i]) + if (result != 0) return result + } + return 0 + } + } + + fun matches(b: Immutable) = matches(b.a) + fun matches(b: ByteArray): Boolean { + if (a.size != b.size) return false + var i = 0 + while (i * 8 < prefixSize && i * 8 + 8 <= prefixSize) { + if (a[i] != b[i]) return false + ++i + } + return i * 8 == prefixSize || a[i] == (b[i].toInt() and -(1 shl i * 8 + 8 - prefixSize)).toByte() + } + } + + fun toImmutable() = Immutable(address.address.also { + var i = prefixSize / 8 + if (prefixSize % 8 > 0) { + it[i] = (it[i].toInt() and -(1 shl i * 8 + 8 - prefixSize)).toByte() + ++i + } + while (i < it.size) it[i++] = 0 + }, prefixSize) + + override fun toString(): String = + if (prefixSize == addressLength) address.hostAddress else address.hostAddress + '/' + prefixSize + + private fun Byte.unsigned() = toInt() and 0xFF + override fun compareTo(other: Subnet): Int { + val addrThis = address.address + val addrThat = other.address.address + var result = + addrThis.size.compareTo(addrThat.size) // IPv4 address goes first + if (result != 0) return result + for (i in addrThis.indices) { + result = addrThis[i].unsigned() + .compareTo(addrThat[i].unsigned()) // undo sign extension of signed byte + if (result != 0) return result + } + return prefixSize.compareTo(other.prefixSize) + } + + override fun equals(other: Any?): Boolean { + val that = other as? Subnet + return address == that?.address && prefixSize == that.prefixSize + } + + override fun hashCode(): Int = Objects.hash(address, prefixSize) +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/utils/Theme.kt b/app/src/main/java/io/nekohasekai/sagernet/utils/Theme.kt new file mode 100644 index 0000000..86457c4 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/utils/Theme.kt @@ -0,0 +1,135 @@ +package io.nekohasekai.sagernet.utils + +import android.content.Context +import android.content.res.Configuration +import androidx.appcompat.app.AppCompatDelegate +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.ktx.app + +object Theme { + + const val RED = 1 + const val PINK_SSR = 2 + const val PINK = 3 + const val PURPLE = 4 + const val DEEP_PURPLE = 5 + const val INDIGO = 6 + const val BLUE = 7 + const val LIGHT_BLUE = 8 + const val CYAN = 9 + const val TEAL = 10 + const val GREEN = 11 + const val LIGHT_GREEN = 12 + const val LIME = 13 + const val YELLOW = 14 + const val AMBER = 15 + const val ORANGE = 16 + const val DEEP_ORANGE = 17 + const val BROWN = 18 + const val GREY = 19 + const val BLUE_GREY = 20 + const val BLACK = 21 + + private fun defaultTheme() = PINK_SSR + + fun apply(context: Context) { + context.setTheme(getTheme()) + } + + fun applyDialog(context: Context) { + context.setTheme(getDialogTheme()) + } + + fun getTheme(): Int { + return getTheme(DataStore.appTheme) + } + + fun getDialogTheme(): Int { + return getDialogTheme(DataStore.appTheme) + } + + fun getTheme(theme: Int): Int { + return when (theme) { + RED -> R.style.Theme_SagerNet_Red + PINK -> R.style.Theme_SagerNet + PINK_SSR -> R.style.Theme_SagerNet_Pink_SSR + PURPLE -> R.style.Theme_SagerNet_Purple + DEEP_PURPLE -> R.style.Theme_SagerNet_DeepPurple + INDIGO -> R.style.Theme_SagerNet_Indigo + BLUE -> R.style.Theme_SagerNet_Blue + LIGHT_BLUE -> R.style.Theme_SagerNet_LightBlue + CYAN -> R.style.Theme_SagerNet_Cyan + TEAL -> R.style.Theme_SagerNet_Teal + GREEN -> R.style.Theme_SagerNet_Green + LIGHT_GREEN -> R.style.Theme_SagerNet_LightGreen + LIME -> R.style.Theme_SagerNet_Lime + YELLOW -> R.style.Theme_SagerNet_Yellow + AMBER -> R.style.Theme_SagerNet_Amber + ORANGE -> R.style.Theme_SagerNet_Orange + DEEP_ORANGE -> R.style.Theme_SagerNet_DeepOrange + BROWN -> R.style.Theme_SagerNet_Brown + GREY -> R.style.Theme_SagerNet_Grey + BLUE_GREY -> R.style.Theme_SagerNet_BlueGrey + BLACK -> R.style.Theme_SagerNet_Black + else -> getTheme(defaultTheme()) + } + } + + fun getDialogTheme(theme: Int): Int { + return when (theme) { + RED -> R.style.Theme_SagerNet_Dialog_Red + PINK -> R.style.Theme_SagerNet_Dialog + PINK_SSR -> R.style.Theme_SagerNet_Dialog_Pink_SSR + PURPLE -> R.style.Theme_SagerNet_Dialog_Purple + DEEP_PURPLE -> R.style.Theme_SagerNet_Dialog_DeepPurple + INDIGO -> R.style.Theme_SagerNet_Dialog_Indigo + BLUE -> R.style.Theme_SagerNet_Dialog_Blue + LIGHT_BLUE -> R.style.Theme_SagerNet_Dialog_LightBlue + CYAN -> R.style.Theme_SagerNet_Dialog_Cyan + TEAL -> R.style.Theme_SagerNet_Dialog_Teal + GREEN -> R.style.Theme_SagerNet_Dialog_Green + LIGHT_GREEN -> R.style.Theme_SagerNet_Dialog_LightGreen + LIME -> R.style.Theme_SagerNet_Dialog_Lime + YELLOW -> R.style.Theme_SagerNet_Dialog_Yellow + AMBER -> R.style.Theme_SagerNet_Dialog_Amber + ORANGE -> R.style.Theme_SagerNet_Dialog_Orange + DEEP_ORANGE -> R.style.Theme_SagerNet_Dialog_DeepOrange + BROWN -> R.style.Theme_SagerNet_Dialog_Brown + GREY -> R.style.Theme_SagerNet_Dialog_Grey + BLUE_GREY -> R.style.Theme_SagerNet_Dialog_BlueGrey + BLACK -> R.style.Theme_SagerNet_Dialog_Black + else -> getDialogTheme(defaultTheme()) + } + } + + var currentNightMode = -1 + fun getNightMode(): Int { + if (currentNightMode == -1) { + currentNightMode = DataStore.nightTheme + } + return getNightMode(currentNightMode) + } + + fun getNightMode(mode: Int): Int { + return when (mode) { + 0 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + 1 -> AppCompatDelegate.MODE_NIGHT_YES + 2 -> AppCompatDelegate.MODE_NIGHT_NO + else -> AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY + } + } + + fun usingNightMode(): Boolean { + return when (DataStore.nightTheme) { + 1 -> true + 2 -> false + else -> (app.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES + } + } + + fun applyNightTheme() { + AppCompatDelegate.setDefaultNightMode(getNightMode()) + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/utils/cf/DeviceResponse.kt b/app/src/main/java/io/nekohasekai/sagernet/utils/cf/DeviceResponse.kt new file mode 100644 index 0000000..874304d --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/utils/cf/DeviceResponse.kt @@ -0,0 +1,114 @@ +package io.nekohasekai.sagernet.utils.cf + + +import com.google.gson.annotations.SerializedName + +data class DeviceResponse( + @SerializedName("created") + var created: String = "", + @SerializedName("type") + var type: String = "", + @SerializedName("locale") + var locale: String = "", + @SerializedName("enabled") + var enabled: Boolean = false, + @SerializedName("token") + var token: String = "", + @SerializedName("waitlist_enabled") + var waitlistEnabled: Boolean = false, + @SerializedName("install_id") + var installId: String = "", + @SerializedName("warp_enabled") + var warpEnabled: Boolean = false, + @SerializedName("name") + var name: String = "", + @SerializedName("fcm_token") + var fcmToken: String = "", + @SerializedName("tos") + var tos: String = "", + @SerializedName("model") + var model: String = "", + @SerializedName("id") + var id: String = "", + @SerializedName("place") + var place: Int = 0, + @SerializedName("config") + var config: Config = Config(), + @SerializedName("updated") + var updated: String = "", + @SerializedName("key") + var key: String = "", + @SerializedName("account") + var account: Account = Account() +) { + data class Config( + @SerializedName("peers") + var peers: List = listOf(), + @SerializedName("services") + var services: Services = Services(), + @SerializedName("interface") + var interfaceX: Interface = Interface(), + @SerializedName("client_id") + var clientId: String = "" + ) { + data class Peer( + @SerializedName("public_key") + var publicKey: String = "", + @SerializedName("endpoint") + var endpoint: Endpoint = Endpoint() + ) { + data class Endpoint( + @SerializedName("v6") + var v6: String = "", + @SerializedName("host") + var host: String = "", + @SerializedName("v4") + var v4: String = "" + ) + } + + data class Services( + @SerializedName("http_proxy") + var httpProxy: String = "" + ) + + data class Interface( + @SerializedName("addresses") + var addresses: Addresses = Addresses() + ) { + data class Addresses( + @SerializedName("v6") + var v6: String = "", + @SerializedName("v4") + var v4: String = "" + ) + } + } + + data class Account( + @SerializedName("account_type") + var accountType: String = "", + @SerializedName("role") + var role: String = "", + @SerializedName("referral_renewal_countdown") + var referralRenewalCountdown: Int = 0, + @SerializedName("created") + var created: String = "", + @SerializedName("usage") + var usage: Int = 0, + @SerializedName("warp_plus") + var warpPlus: Boolean = false, + @SerializedName("referral_count") + var referralCount: Int = 0, + @SerializedName("license") + var license: String = "", + @SerializedName("quota") + var quota: Int = 0, + @SerializedName("premium_data") + var premiumData: Int = 0, + @SerializedName("id") + var id: String = "", + @SerializedName("updated") + var updated: String = "" + ) +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/utils/cf/RegisterRequest.kt b/app/src/main/java/io/nekohasekai/sagernet/utils/cf/RegisterRequest.kt new file mode 100644 index 0000000..34bbe7c --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/utils/cf/RegisterRequest.kt @@ -0,0 +1,33 @@ +package io.nekohasekai.sagernet.utils.cf + +import com.google.gson.Gson +import com.google.gson.annotations.SerializedName +import com.wireguard.crypto.Key +import java.text.SimpleDateFormat +import java.util.* + +data class RegisterRequest( + @SerializedName("fcm_token") var fcmToken: String = "", + @SerializedName("install_id") var installedId: String = "", + var key: String = "", + var locale: String = "", + var model: String = "", + var tos: String = "", + var type: String = "" +) { + + companion object { + fun newRequest(publicKey: Key): String { + val request = RegisterRequest() + request.fcmToken = "" + request.installedId = "" + request.key = publicKey.toBase64() + request.locale = "en_US" + request.model = "PC" + val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'000000'+08:00", Locale.US) + request.tos = format.format(Date()) + request.type = "Android" + return Gson().toJson(request) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/utils/cf/UpdateDeviceRequest.kt b/app/src/main/java/io/nekohasekai/sagernet/utils/cf/UpdateDeviceRequest.kt new file mode 100644 index 0000000..e12915b --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/utils/cf/UpdateDeviceRequest.kt @@ -0,0 +1,12 @@ +package io.nekohasekai.sagernet.utils.cf + +import com.google.gson.Gson + +data class UpdateDeviceRequest( + var name: String, var active: Boolean +) { + companion object { + fun newRequest(name: String = "SagerNet Client", active: Boolean = true) = + Gson().toJson(UpdateDeviceRequest(name, active)) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/AppListPreference.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/AppListPreference.kt new file mode 100644 index 0000000..14fd23e --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/AppListPreference.kt @@ -0,0 +1,41 @@ +package io.nekohasekai.sagernet.widget + +import android.content.Context +import android.util.AttributeSet +import androidx.preference.Preference +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.ktx.app +import io.nekohasekai.sagernet.utils.PackageCache + +class AppListPreference : Preference { + + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super( + context, attrs, defStyle + ) + + constructor( + context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) + + override fun getSummary(): CharSequence { + val packages = DataStore.routePackages.split("\n").filter { it.isNotBlank() }.map { + PackageCache.installedPackages[it]?.applicationInfo?.loadLabel(app.packageManager) + ?: PackageCache.installedPluginPackages[it]?.applicationInfo?.loadLabel(app.packageManager) + ?: it + } + if (packages.isEmpty()) { + return context.getString(androidx.preference.R.string.not_set) + } + val count = packages.size + if (count <= 5) return packages.joinToString("\n") + return context.getString(R.string.apps_message, count) + } + + fun postUpdate() { + notifyChanged() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/AutoCollapseTextView.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/AutoCollapseTextView.kt new file mode 100644 index 0000000..8b5b63a --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/AutoCollapseTextView.kt @@ -0,0 +1,39 @@ +package io.nekohasekai.sagernet.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Rect +import android.util.AttributeSet +import android.view.MotionEvent +import androidx.appcompat.widget.AppCompatTextView +import androidx.core.view.isGone + +class AutoCollapseTextView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : + AppCompatTextView(context, attrs, defStyleAttr) { + override fun onTextChanged( + text: CharSequence?, + start: Int, + lengthBefore: Int, + lengthAfter: Int, + ) { + super.onTextChanged(text, start, lengthBefore, lengthAfter) + isGone = text.isNullOrEmpty() + } + + // #1874 + override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) = + try { + super.onFocusChanged(focused, direction, previouslyFocusedRect) + } catch (e: IndexOutOfBoundsException) { + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent?) = try { + super.onTouchEvent(event) + } catch (e: IndexOutOfBoundsException) { + false + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/FabProgressBehavior.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/FabProgressBehavior.kt new file mode 100644 index 0000000..ae0855e --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/FabProgressBehavior.kt @@ -0,0 +1,29 @@ +package io.nekohasekai.sagernet.widget + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import androidx.coordinatorlayout.widget.CoordinatorLayout +import com.google.android.material.progressindicator.CircularProgressIndicator + +class FabProgressBehavior(context: Context, attrs: AttributeSet?) : + CoordinatorLayout.Behavior(context, attrs) { + override fun layoutDependsOn( + parent: CoordinatorLayout, + child: CircularProgressIndicator, + dependency: View, + ): Boolean { + return dependency.id == (child.layoutParams as CoordinatorLayout.LayoutParams).anchorId + } + + override fun onLayoutChild( + parent: CoordinatorLayout, child: CircularProgressIndicator, + layoutDirection: Int, + ): Boolean { + val size = parent.getDependencies(child).single().measuredHeight + child.trackThickness + return if (child.indicatorSize != size) { + child.indicatorSize = size + true + } else false + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/GroupPreference.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/GroupPreference.kt new file mode 100644 index 0000000..e27dff9 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/GroupPreference.kt @@ -0,0 +1,35 @@ +package io.nekohasekai.sagernet.widget + +import android.content.Context +import android.util.AttributeSet +import com.takisoft.preferencex.SimpleMenuPreference +import io.nekohasekai.sagernet.database.SagerDatabase + +class GroupPreference : SimpleMenuPreference { + + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super( + context, attrs, defStyle + ) + + constructor( + context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) + + init { + val groups = SagerDatabase.groupDao.allGroups() + + entries = groups.map { it.displayName() }.toTypedArray() + entryValues = groups.map { "${it.id}" }.toTypedArray() + } + + override fun getSummary(): CharSequence? { + if (!value.isNullOrBlank() && value != "0") { + return SagerDatabase.groupDao.getById(value.toLong())?.displayName() + ?: super.getSummary() + } + return super.getSummary() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/LinkOrContentPreference.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/LinkOrContentPreference.kt new file mode 100644 index 0000000..8fdf1f2 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/LinkOrContentPreference.kt @@ -0,0 +1,64 @@ +package io.nekohasekai.sagernet.widget + +import android.content.Context +import android.net.Uri +import android.util.AttributeSet +import androidx.core.widget.addTextChangedListener +import com.google.android.material.textfield.TextInputLayout +import com.takisoft.preferencex.EditTextPreference +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.ktx.app +import io.nekohasekai.sagernet.ktx.readableMessage +import okhttp3.HttpUrl.Companion.toHttpUrl + +class LinkOrContentPreference : EditTextPreference { + + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) + + constructor( + context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) + + + init { + dialogLayoutResource = R.layout.layout_link_dialog + + setOnBindEditTextListener { + val linkLayout = it.rootView.findViewById(R.id.input_layout) + fun validate() { + val link = it.text + if (link.isBlank()) { + linkLayout.isErrorEnabled = false + return + } + + try { + if (Uri.parse(link.toString()).scheme == "content") { + linkLayout.isErrorEnabled = false + return + } + val url = link.toString().toHttpUrl() + if ("http".equals(url.scheme, true)) { + linkLayout.error = app.getString(R.string.cleartext_http_warning) + linkLayout.isErrorEnabled = true + } else { + linkLayout.isErrorEnabled = false + } + } catch (e: Exception) { + linkLayout.error = e.readableMessage + linkLayout.isErrorEnabled = true + } + + } + validate() + it.addTextChangedListener { + validate() + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/LinkPreference.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/LinkPreference.kt new file mode 100644 index 0000000..9397669 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/LinkPreference.kt @@ -0,0 +1,93 @@ +package io.nekohasekai.sagernet.widget + +import android.content.Context +import android.util.AttributeSet +import androidx.core.widget.addTextChangedListener +import com.google.android.material.textfield.TextInputLayout +import com.takisoft.preferencex.EditTextPreference +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.ktx.app +import io.nekohasekai.sagernet.ktx.readableMessage +import okhttp3.HttpUrl.Companion.toHttpUrl + +class LinkPreference : EditTextPreference { + + var defaultValue: String? = null + + constructor(context: Context) : this(context, null) + + constructor( + context: Context, + attrs: AttributeSet?, + ) : this(context, attrs, com.takisoft.preferencex.R.attr.editTextPreferenceStyle) + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + ) : this(context, attrs, defStyleAttr, 0) + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int, + ) : super(context, attrs, defStyleAttr, defStyleRes) { + val a = context.obtainStyledAttributes( + attrs, R.styleable.Preference, defStyleAttr, defStyleRes + ) + if (a.hasValue(androidx.preference.R.styleable.Preference_defaultValue)) { + defaultValue = onGetDefaultValue( + a, androidx.preference.R.styleable.Preference_defaultValue + )?.toString() + } else if (a.hasValue(androidx.preference.R.styleable.Preference_android_defaultValue)) { + defaultValue = onGetDefaultValue( + a, androidx.preference.R.styleable.Preference_android_defaultValue + )?.toString() + } + } + + init { + dialogLayoutResource = R.layout.layout_link_dialog + + setOnBindEditTextListener { + val linkLayout = it.rootView.findViewById(R.id.input_layout) + fun validate() { + val link = it.text + if (link.isBlank()) { + linkLayout.isErrorEnabled = false + return + } + try { + val url = link.toString().toHttpUrl() + if ("http".equals(url.scheme, true)) { + linkLayout.error = app.getString(R.string.cleartext_http_warning) + linkLayout.isErrorEnabled = true + } else { + linkLayout.isErrorEnabled = false + } + } catch (e: Exception) { + linkLayout.error = e.readableMessage + linkLayout.isErrorEnabled = true + } + } + validate() + it.addTextChangedListener { + validate() + } + } + + setOnPreferenceChangeListener { _, newValue -> + if ((newValue as String).isBlank()) { + text = defaultValue + false + } else try { + newValue.toHttpUrl() + true + } catch (ignored: Exception) { + false + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/OutboundPreference.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/OutboundPreference.kt new file mode 100644 index 0000000..6adf26b --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/OutboundPreference.kt @@ -0,0 +1,44 @@ +package io.nekohasekai.sagernet.widget + +import android.content.Context +import android.util.AttributeSet +import com.takisoft.preferencex.SimpleMenuPreference +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.ProfileManager + +class OutboundPreference : SimpleMenuPreference { + + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super( + context, + attrs, + defStyle + ) + + constructor( + context: Context?, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) + + init { + setEntries(R.array.outbound_entry) + setEntryValues(R.array.outbound_value) + } + + override fun getSummary(): CharSequence? { + if (value == "3") { + val routeOutbound = DataStore.routeOutboundRule + if (routeOutbound > 0) { + ProfileManager.getProfile(routeOutbound)?.displayName()?.let { + return it + } + } + } + return super.getSummary() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/QRCodeDialog.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/QRCodeDialog.kt new file mode 100644 index 0000000..fa72f90 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/QRCodeDialog.kt @@ -0,0 +1,103 @@ +package io.nekohasekai.sagernet.widget + +import android.graphics.Bitmap +import android.graphics.Color +import android.os.Bundle +import android.util.DisplayMetrics +import android.view.Gravity +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.os.bundleOf +import androidx.fragment.app.DialogFragment +import com.google.zxing.BarcodeFormat +import com.google.zxing.EncodeHintType +import com.google.zxing.MultiFormatWriter +import com.google.zxing.WriterException +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.ktx.readableMessage +import io.nekohasekai.sagernet.ui.MainActivity +import java.nio.charset.StandardCharsets +import kotlin.math.roundToInt + +class QRCodeDialog() : DialogFragment() { + + companion object { + private const val KEY_URL = "io.nekohasekai.sagernet.QRCodeDialog.KEY_URL" + private const val KEY_NAME = "io.nekohasekai.sagernet.QRCodeDialog.KEY_NAME" + private val iso88591 = StandardCharsets.ISO_8859_1.newEncoder() + } + + constructor(url: String, displayName: String) : this() { + arguments = bundleOf( + Pair(KEY_URL, url), Pair(KEY_NAME, displayName) + ) + } + + /** + * Based on: + * https://android.googlesource.com/platform/ + packages/apps/Settings/+/0d706f0/src/com/android/settings/wifi/qrcode/QrCodeGenerator.java + * https://android.googlesource.com/platform/ + packages/apps/Settings/+/8a9ccfd/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java#153 + */ + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ) = try { + // get display size + var pixelMin = 0 + + try { + val displayMetrics: DisplayMetrics = requireContext().resources.displayMetrics + val height: Int = displayMetrics.heightPixels + val width: Int = displayMetrics.widthPixels + pixelMin = if (height > width) width else height + pixelMin = (pixelMin * 0.8).roundToInt() + } catch (e: Exception) { + } + + val size = if (pixelMin > 0) pixelMin else resources.getDimensionPixelSize(R.dimen.qrcode_size) + + // draw QR Code + val url = arguments?.getString(KEY_URL)!! + val displayName = arguments?.getString(KEY_NAME)!! + + val hints = mutableMapOf() + if (!iso88591.canEncode(url)) hints[EncodeHintType.CHARACTER_SET] = StandardCharsets.UTF_8.name() + val qrBits = MultiFormatWriter().encode(url, BarcodeFormat.QR_CODE, size, size, hints) + LinearLayout(context).apply { + // Layout + orientation = LinearLayout.VERTICAL + gravity = Gravity.CENTER + + // QR Code Image View + addView(ImageView(context).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT + ) + setImageBitmap(Bitmap.createBitmap(size, size, Bitmap.Config.RGB_565).apply { + for (x in 0 until size) for (y in 0 until size) { + setPixel(x, y, if (qrBits.get(x, y)) Color.BLACK else Color.WHITE) + } + }) + }) + + // Text View + addView(TextView(context).apply { + gravity = Gravity.CENTER + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT + ) + text = displayName + }) + } + } catch (e: WriterException) { + Logs.w(e) + (activity as MainActivity).snackbar(e.readableMessage).show() + dismiss() + null + } +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt new file mode 100644 index 0000000..b86659e --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt @@ -0,0 +1,150 @@ +package io.nekohasekai.sagernet.widget + +import android.content.Context +import android.graphics.drawable.Drawable +import android.os.Build +import android.util.AttributeSet +import android.view.PointerIcon +import android.view.View +import androidx.annotation.DrawableRes +import androidx.appcompat.widget.TooltipCompat +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.vectordrawable.graphics.drawable.Animatable2Compat +import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat +import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.google.android.material.progressindicator.BaseProgressIndicator +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.bg.BaseService +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import java.util.* + +class ServiceButton @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : + FloatingActionButton(context, attrs, defStyleAttr), DynamicAnimation.OnAnimationEndListener { + + private val callback = object : Animatable2Compat.AnimationCallback() { + override fun onAnimationEnd(drawable: Drawable) { + super.onAnimationEnd(drawable) + var next = animationQueue.peek() ?: return + if (next.icon.current == drawable) { + animationQueue.pop() + next = animationQueue.peek() ?: return + } + next.start() + } + } + + private inner class AnimatedState( + @DrawableRes resId: Int, + private val onStart: BaseProgressIndicator<*>.() -> Unit = { hideProgress() } + ) { + val icon: AnimatedVectorDrawableCompat = + AnimatedVectorDrawableCompat.create(context, resId)!!.apply { + registerAnimationCallback(this@ServiceButton.callback) + } + + fun start() { + setImageDrawable(icon) + icon.start() + progress.onStart() + } + + fun stop() = icon.stop() + } + + private val iconStopped by lazy { AnimatedState(R.drawable.ic_service_stopped) } + private val iconConnecting by lazy { + AnimatedState(R.drawable.ic_service_connecting) { + hideProgress() + delayedAnimation = (context as LifecycleOwner).lifecycleScope.launchWhenStarted { + delay(context.resources.getInteger(android.R.integer.config_mediumAnimTime) + 1000L) + isIndeterminate = true + show() + } + } + } + private val iconConnected by lazy { + AnimatedState(R.drawable.ic_service_connected) { + delayedAnimation?.cancel() + setProgressCompat(1, true) + } + } + private val iconStopping by lazy { AnimatedState(R.drawable.ic_service_stopping) } + private val animationQueue = ArrayDeque() + + private var checked = false + private var delayedAnimation: Job? = null + private lateinit var progress: BaseProgressIndicator<*> + fun initProgress(progress: BaseProgressIndicator<*>) { + this.progress = progress + progress.progressDrawable?.addSpringAnimationEndListener(this) + } + + override fun onAnimationEnd( + animation: DynamicAnimation>?, canceled: Boolean, value: Float, + velocity: Float + ) { + if (!canceled) progress.hide() + } + + private fun hideProgress() { + delayedAnimation?.cancel() + progress.hide() + } + + override fun onCreateDrawableState(extraSpace: Int): IntArray { + val drawableState = super.onCreateDrawableState(extraSpace + 1) + if (checked) View.mergeDrawableStates( + drawableState, + intArrayOf(android.R.attr.state_checked) + ) + return drawableState + } + + fun changeState(state: BaseService.State, previousState: BaseService.State, animate: Boolean) { + when (state) { + BaseService.State.Connecting -> changeState(iconConnecting, animate) + BaseService.State.Connected -> changeState(iconConnected, animate) + BaseService.State.Stopping -> { + changeState(iconStopping, animate && previousState == BaseService.State.Connected) + } + else -> changeState(iconStopped, animate) + } + checked = state == BaseService.State.Connected + refreshDrawableState() + val description = context.getText(if (state.canStop) R.string.stop else R.string.connect) + contentDescription = description + TooltipCompat.setTooltipText(this, description) + val enabled = state.canStop || state == BaseService.State.Stopped + isEnabled = enabled + if (Build.VERSION.SDK_INT >= 24) pointerIcon = PointerIcon.getSystemIcon( + context, + if (enabled) PointerIcon.TYPE_HAND else PointerIcon.TYPE_WAIT + ) + } + + private fun changeState(icon: AnimatedState, animate: Boolean) { + fun counters(a: AnimatedState, b: AnimatedState): Boolean = + a == iconStopped && b == iconConnecting || + a == iconConnecting && b == iconStopped || + a == iconConnected && b == iconStopping || + a == iconStopping && b == iconConnected + if (animate) { + if (animationQueue.size < 2 || !counters(animationQueue.last, icon)) { + animationQueue.add(icon) + if (animationQueue.size == 1) icon.start() + } else animationQueue.removeLast() + } else { + animationQueue.peekFirst()?.stop() + animationQueue.clear() + icon.start() // force ensureAnimatorSet to be called so that stop() will work + icon.stop() + } + } +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt new file mode 100644 index 0000000..2116c23 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt @@ -0,0 +1,161 @@ +package io.nekohasekai.sagernet.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.text.format.Formatter +import android.util.AttributeSet +import android.view.View +import android.widget.TextView +import androidx.appcompat.widget.TooltipCompat +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.whenStarted +import com.google.android.material.bottomappbar.BottomAppBar +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.bg.BaseService +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.ui.MainActivity +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class StatsBar @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, + defStyleAttr: Int = R.attr.bottomAppBarStyle, +) : BottomAppBar(context, attrs, defStyleAttr) { + private lateinit var statusText: TextView + private lateinit var txText: TextView + private lateinit var rxText: TextView + private lateinit var behavior: YourBehavior + + var allowShow = true + + override fun getBehavior(): YourBehavior { + if (!this::behavior.isInitialized) behavior = YourBehavior { allowShow } + return behavior + } + + class YourBehavior(val getAllowShow: () -> Boolean) : Behavior() { + + override fun onNestedScroll( + coordinatorLayout: CoordinatorLayout, child: BottomAppBar, target: View, + dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, + type: Int, consumed: IntArray, + ) { + super.onNestedScroll( + coordinatorLayout, + child, + target, + dxConsumed, + dyConsumed + dyUnconsumed, + dxUnconsumed, + 0, + type, + consumed + ) + } + + override fun slideUp(child: BottomAppBar) { + if (!getAllowShow()) return + super.slideUp(child) + } + + override fun slideDown(child: BottomAppBar) { + if (!getAllowShow()) return + super.slideDown(child) + } + } + + + override fun setOnClickListener(l: OnClickListener?) { + statusText = findViewById(R.id.status) + txText = findViewById(R.id.tx) + rxText = findViewById(R.id.rx) + super.setOnClickListener(l) + } + + private fun setStatus(text: CharSequence) { + statusText.text = text + TooltipCompat.setTooltipText(this, text) + } + + fun changeState(state: BaseService.State) { + val activity = context as MainActivity + fun postWhenStarted(what: () -> Unit) = activity.lifecycleScope.launch(Dispatchers.Main) { + delay(100L) + activity.whenStarted { what() } + } + if ((state == BaseService.State.Connected).also { hideOnScroll = it }) { + postWhenStarted { + if (allowShow) performShow() + setStatus(app.getText(R.string.vpn_connected)) + } + } else { + postWhenStarted { + performHide() + } + updateSpeed(0, 0) + setStatus( + context.getText( + when (state) { + BaseService.State.Connecting -> R.string.connecting + BaseService.State.Stopping -> R.string.stopping + else -> R.string.not_connected + } + ) + ) + } + } + + @SuppressLint("SetTextI18n") + fun updateSpeed(txRate: Long, rxRate: Long) { + txText.text = "▲ ${ + context.getString( + R.string.speed, Formatter.formatFileSize(context, txRate) + ) + }" + rxText.text = "▼ ${ + context.getString( + R.string.speed, Formatter.formatFileSize(context, rxRate) + ) + }" + } + + fun testConnection() { + val activity = context as MainActivity + isEnabled = false + setStatus(app.getText(R.string.connection_test_testing)) + runOnDefaultDispatcher { + try { + val elapsed = activity.urlTest() + onMainDispatcher { + isEnabled = true + setStatus( + app.getString( + if (DataStore.connectionTestURL.startsWith("https://")) { + R.string.connection_test_available + } else { + R.string.connection_test_available_http + }, elapsed + ) + ) + } + + } catch (e: Exception) { + Logs.w(e.toString()) + onMainDispatcher { + isEnabled = true + setStatus(app.getText(R.string.connection_test_testing)) + + activity.snackbar( + app.getString( + R.string.connection_test_error, e.readableMessage + ) + ).show() + } + } + } + } + +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/UndoSnackbarManager.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/UndoSnackbarManager.kt new file mode 100644 index 0000000..ad6e129 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/UndoSnackbarManager.kt @@ -0,0 +1,55 @@ +package io.nekohasekai.sagernet.widget + +import com.google.android.material.snackbar.Snackbar +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.ui.ThemedActivity + +/** + * @param activity ThemedActivity. + * //@param view The view to find a parent from. + * @param undo Callback for undoing removals. + * @param commit Callback for committing removals. + * @tparam T Item type. + */ +class UndoSnackbarManager( + private val activity: ThemedActivity, + private val callback: Interface, +) { + + interface Interface { + fun undo(actions: List>) + fun commit(actions: List>) + } + + private val recycleBin = ArrayList>() + private val removedCallback = object : Snackbar.Callback() { + override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { + if (last === transientBottomBar && event != DISMISS_EVENT_ACTION) { + callback.commit(recycleBin) + recycleBin.clear() + last = null + } + } + } + + private var last: Snackbar? = null + + fun remove(items: Collection>) { + recycleBin.addAll(items) + val count = recycleBin.size + activity.snackbar(activity.resources.getQuantityString(R.plurals.removed, count, count)) + .apply { + addCallback(removedCallback) + setAction(R.string.undo) { + callback.undo(recycleBin.reversed()) + recycleBin.clear() + } + last = this + show() + } + } + + fun remove(vararg items: Pair) = remove(items.toList()) + + fun flush() = last?.dismiss() +} diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/UserAgentPreference.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/UserAgentPreference.kt new file mode 100644 index 0000000..2ec9ffc --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/UserAgentPreference.kt @@ -0,0 +1,31 @@ +package io.nekohasekai.sagernet.widget + +import android.content.Context +import android.util.AttributeSet +import com.takisoft.preferencex.EditTextPreference +import io.nekohasekai.sagernet.ktx.USER_AGENT + +class UserAgentPreference : EditTextPreference { + + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super( + context, attrs, defStyle + ) + + constructor( + context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) + + public override fun notifyChanged() { + super.notifyChanged() + } + + override fun getSummary(): CharSequence? { + if (text.isNullOrBlank()) { + return USER_AGENT + } + return super.getSummary() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/widget/WindowInsetsListeners.kt b/app/src/main/java/io/nekohasekai/sagernet/widget/WindowInsetsListeners.kt new file mode 100644 index 0000000..fc35dd0 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sagernet/widget/WindowInsetsListeners.kt @@ -0,0 +1,40 @@ +package io.nekohasekai.sagernet.widget + +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.core.graphics.Insets +import androidx.core.view.* +import io.nekohasekai.sagernet.R + +object ListHolderListener : OnApplyWindowInsetsListener { + override fun onApplyWindowInsets(view: View, insets: WindowInsetsCompat): WindowInsetsCompat { + val statusBarInsets = insets.getInsets(WindowInsetsCompat.Type.statusBars()) + view.setPadding(statusBarInsets.left, + statusBarInsets.top, + statusBarInsets.right, + statusBarInsets.bottom) + return WindowInsetsCompat.Builder(insets).apply { + setInsets(WindowInsetsCompat.Type.statusBars(), Insets.NONE) + /*setInsets(WindowInsetsCompat.Type.navigationBars(), + insets.getInsets(WindowInsetsCompat.Type.navigationBars()))*/ + }.build() + } + + fun setup(activity: AppCompatActivity) = activity.findViewById(android.R.id.content).let { + ViewCompat.setOnApplyWindowInsetsListener(it, ListHolderListener) + WindowCompat.setDecorFitsSystemWindows(activity.window, false) + } +} + +object MainListListener : OnApplyWindowInsetsListener { + override fun onApplyWindowInsets(view: View, insets: WindowInsetsCompat) = insets.apply { + view.updatePadding(bottom = view.resources.getDimensionPixelOffset(R.dimen.main_list_padding_bottom) + + insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom) + } +} + +object ListListener : OnApplyWindowInsetsListener { + override fun onApplyWindowInsets(view: View, insets: WindowInsetsCompat) = insets.apply { + view.updatePadding(bottom = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom) + } +} diff --git a/app/src/main/java/moe/matsuri/nb4a/DNS.kt b/app/src/main/java/moe/matsuri/nb4a/DNS.kt new file mode 100644 index 0000000..db9902b --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/DNS.kt @@ -0,0 +1,74 @@ +package moe.matsuri.nb4a + +import io.nekohasekai.sagernet.database.DataStore + +object DNS { + fun SingBoxOptions.DNSServerOptions.applyDNSNetworkSettings(isDirect: Boolean) { + if (isDirect) { + if (DataStore.dnsNetwork.contains("NoDirectIPv4")) this.strategy = "ipv6_only" + if (DataStore.dnsNetwork.contains("NoDirectIPv6")) this.strategy = "ipv4_only" + } else { + if (DataStore.dnsNetwork.contains("NoRemoteIPv4")) this.strategy = "ipv6_only" + if (DataStore.dnsNetwork.contains("NoRemoteIPv6")) this.strategy = "ipv4_only" + } + } + + fun SingBoxOptions.DNSRule_DefaultOptions.makeSingBoxRule(list: List) { + geosite = mutableListOf() + domain = mutableListOf() + domain_suffix = mutableListOf() + domain_regex = mutableListOf() + domain_keyword = mutableListOf() + list.forEach { + if (it.startsWith("geosite:")) { + geosite.plusAssign(it.removePrefix("geosite:")) + } else if (it.startsWith("full:")) { + domain.plusAssign(it.removePrefix("full:")) + } else if (it.startsWith("domain:")) { + domain_suffix.plusAssign(it.removePrefix("domain:")) + } else if (it.startsWith("regexp:")) { + domain_regex.plusAssign(it.removePrefix("regexp:")) + } else if (it.startsWith("keyword:")) { + domain_keyword.plusAssign(it.removePrefix("keyword:")) + } else { + domain.plusAssign(it) + } + } + } + + fun SingBoxOptions.Rule_DefaultOptions.makeSingBoxRule(list: List, isIP: Boolean) { + if (isIP) { + ip_cidr = mutableListOf() + geoip = mutableListOf() + } else { + geosite = mutableListOf() + domain = mutableListOf() + domain_suffix = mutableListOf() + domain_regex = mutableListOf() + domain_keyword = mutableListOf() + } + list.forEach { + if (isIP) { + if (it.startsWith("geoip:")) { + geoip.plusAssign(it.removePrefix("geoip:")) + } else { + ip_cidr.plusAssign(it) + } + return@forEach + } + if (it.startsWith("geosite:")) { + geosite.plusAssign(it.removePrefix("geosite:")) + } else if (it.startsWith("full:")) { + domain.plusAssign(it.removePrefix("full:")) + } else if (it.startsWith("domain:")) { + domain_suffix.plusAssign(it.removePrefix("domain:")) + } else if (it.startsWith("regexp:")) { + domain_regex.plusAssign(it.removePrefix("regexp:")) + } else if (it.startsWith("keyword:")) { + domain_keyword.plusAssign(it.removePrefix("keyword:")) + } else { + domain.plusAssign(it) + } + } + } +} diff --git a/app/src/main/java/moe/matsuri/nb4a/Protocols.kt b/app/src/main/java/moe/matsuri/nb4a/Protocols.kt new file mode 100644 index 0000000..8148511 --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/Protocols.kt @@ -0,0 +1,83 @@ +package moe.matsuri.nb4a + +import android.content.Context +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.ProxyEntity.Companion.TYPE_NEKO +import io.nekohasekai.sagernet.fmt.AbstractBean +import io.nekohasekai.sagernet.ktx.app +import io.nekohasekai.sagernet.ktx.getColorAttr +import moe.matsuri.nb4a.plugin.NekoPluginManager + +// Settings for all protocols, built-in or plugin +object Protocols { + // Mux + + fun shouldEnableMux(protocol: String): Boolean { + return DataStore.muxProtocols.contains(protocol) + } + + fun getCanMuxList(): List { + // built-in and support mux + // sing-box support ss & vmess & trojan smux + val list = mutableListOf("vmess", "trojan", "trojan-go", "shadowsocks") + + NekoPluginManager.getProtocols().forEach { + if (it.protocolConfig.optBoolean("canMux")) { + list.add(it.protocolId) + } + } + + return list + } + + // Deduplication + + class Deduplication( + val bean: AbstractBean + ) { + + fun hash(): String { + return bean.serverAddress + bean.serverPort + } + + override fun hashCode(): Int { + return hash().toByteArray().contentHashCode() + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Deduplication + + return hash() == other.hash() + } + + } + + // Display + + fun Context.getProtocolColor(type: Int): Int { + return when (type) { + TYPE_NEKO -> getColorAttr(android.R.attr.textColorPrimary) + else -> getColorAttr(R.attr.accentOrTextSecondary) + } + } + + // Test + + fun genFriendlyMsg(msg: String): String { + val msgL = msg.lowercase() + return when { + msgL.contains("timeout") || msgL.contains("deadline") -> { + app.getString(R.string.connection_test_timeout) + } + msgL.contains("refused") || msgL.contains("closed pipe") -> { + app.getString(R.string.connection_test_refused) + } + else -> msg + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java b/app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java new file mode 100644 index 0000000..a57466c --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java @@ -0,0 +1,3873 @@ +package moe.matsuri.nb4a; + +import static moe.matsuri.nb4a.utils.JavaUtil.gson; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; +import java.util.Map; + +public class SingBoxOptions { + + // base + + public static class SingBoxOption { + public Map asMap() { + return gson.fromJson(gson.toJson(this), Map.class); + } + } + + // custom classes + + public static class User { + public String username; + public String password; + } + + public static class MyOptions extends SingBoxOption { + public LogOptions log; + + public DNSOptions dns; + + public NTPOptions ntp; + + public List inbounds; + + public List> outbounds; + + public RouteOptions route; + + public ExperimentalOptions experimental; + } + + public static class Outbound_WireGuardOptions_Fix extends Outbound { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public Boolean system_interface; + + public String interface_name; + + // Generate note: Listable + public List local_address; + + public String private_key; + + public String peer_public_key; + + public String pre_shared_key; + + public String reserved; // fixed, can fill a base64 str + + public Integer workers; + + public Integer mtu; + + public String network; + + } + + // paste generate output here + + public static class ClashAPIOptions extends SingBoxOption { + + public String external_controller; + + public String external_ui; + + public String secret; + + public String default_mode; + + public Boolean store_selected; + + public String cache_file; + + } + + public static class SelectorOutboundOptions extends SingBoxOption { + + public List outbounds; + + @SerializedName("default") + public String default_; + + } + + public static class URLTestOutboundOptions extends SingBoxOption { + + public List outbounds; + + public String url; + + public Long interval; + + public Integer tolerance; + + } + + + public static class Options extends SingBoxOption { + + public LogOptions log; + + public DNSOptions dns; + + public NTPOptions ntp; + + public List inbounds; + + public List outbounds; + + public RouteOptions route; + + public ExperimentalOptions experimental; + + } + + public static class LogOptions extends SingBoxOption { + + public Boolean disabled; + + public String level; + + public String output; + + public Boolean timestamp; + + // Generate note: option type: public Boolean DisableColor; + + } + + public static class DirectInboundOptions extends SingBoxOption { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public String network; + + public String override_address; + + public Integer override_port; + + } + + public static class DirectOutboundOptions extends SingBoxOption { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + public String override_address; + + public Integer override_port; + + public Integer proxy_protocol; + + } + + public static class DNSOptions extends SingBoxOption { + + public List servers; + + public List rules; + + @SerializedName("final") + public String final_; + + // Generate note: nested type DNSClientOptions + public String strategy; + + public Boolean disable_cache; + + public Boolean disable_expire; + + // End of public DNSClientOptions ; + + } + + public static class DNSClientOptions extends SingBoxOption { + + public String strategy; + + public Boolean disable_cache; + + public Boolean disable_expire; + + } + + public static class DNSServerOptions extends SingBoxOption { + + public String tag; + + public String address; + + public String address_resolver; + + public String address_strategy; + + public Long address_fallback_delay; + + public String strategy; + + public String detour; + + } + + + public static class DNSRule extends SingBoxOption { + + public String type; + + // Generate note: option type: public DefaultDNSRule DefaultOptions; + + // Generate note: option type: public LogicalDNSRule LogicalOptions; + + } + + public static class DefaultDNSRule extends SingBoxOption { + + // Generate note: Listable + public List inbound; + + public Integer ip_version; + + // Generate note: Listable + public List query_type; + + public String network; + + // Generate note: Listable + public List auth_user; + + // Generate note: Listable + public List protocol; + + // Generate note: Listable + public List domain; + + // Generate note: Listable + public List domain_suffix; + + // Generate note: Listable + public List domain_keyword; + + // Generate note: Listable + public List domain_regex; + + // Generate note: Listable + public List geosite; + + // Generate note: Listable + public List source_geoip; + + // Generate note: Listable + public List source_ip_cidr; + + // Generate note: Listable + public List source_port; + + // Generate note: Listable + public List source_port_range; + + // Generate note: Listable + public List port; + + // Generate note: Listable + public List port_range; + + // Generate note: Listable + public List process_name; + + // Generate note: Listable + public List process_path; + + // Generate note: Listable + public List package_name; + + // Generate note: Listable + public List user; + + // Generate note: Listable + public List user_id; + + // Generate note: Listable + public List outbound; + + public String clash_mode; + + public Boolean invert; + + public String server; + + public Boolean disable_cache; + + } + + public static class LogicalDNSRule extends SingBoxOption { + + public String mode; + + public List rules; + + public Boolean invert; + + public String server; + + public Boolean disable_cache; + + } + + public static class ExperimentalOptions extends SingBoxOption { + + public ClashAPIOptions clash_api; + + public V2RayAPIOptions v2ray_api; + + } + + public static class HysteriaInboundOptions extends SingBoxOption { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public String up; + + public Integer up_mbps; + + public String down; + + public Integer down_mbps; + + public String obfs; + + public List users; + + public Long recv_window_conn; + + public Long recv_window_client; + + public Integer max_conn_client; + + public Boolean disable_mtu_discovery; + + public InboundTLSOptions tls; + + } + + public static class HysteriaUser extends SingBoxOption { + + public String name; + + public List auth; + + public String auth_str; + + } + + public static class HysteriaOutboundOptions extends SingBoxOption { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String up; + + public Integer up_mbps; + + public String down; + + public Integer down_mbps; + + public String obfs; + + public List auth; + + public String auth_str; + + public Long recv_window_conn; + + public Long recv_window; + + public Boolean disable_mtu_discovery; + + public String network; + + public OutboundTLSOptions tls; + + } + + + public static class Inbound extends SingBoxOption { + + public String type; + + public String tag; + + // Generate note: option type: public TunInboundOptions TunOptions; + + // Generate note: option type: public RedirectInboundOptions RedirectOptions; + + // Generate note: option type: public TProxyInboundOptions TProxyOptions; + + // Generate note: option type: public DirectInboundOptions DirectOptions; + + // Generate note: option type: public SocksInboundOptions SocksOptions; + + // Generate note: option type: public HTTPMixedInboundOptions HTTPOptions; + + // Generate note: option type: public HTTPMixedInboundOptions MixedOptions; + + // Generate note: option type: public ShadowsocksInboundOptions ShadowsocksOptions; + + // Generate note: option type: public VMessInboundOptions VMessOptions; + + // Generate note: option type: public TrojanInboundOptions TrojanOptions; + + // Generate note: option type: public NaiveInboundOptions NaiveOptions; + + // Generate note: option type: public HysteriaInboundOptions HysteriaOptions; + + // Generate note: option type: public ShadowTLSInboundOptions ShadowTLSOptions; + + // Generate note: option type: public VLESSInboundOptions VLESSOptions; + + } + + public static class InboundOptions extends SingBoxOption { + + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + } + + public static class ListenOptions extends SingBoxOption { + + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + } + + public static class NaiveInboundOptions extends SingBoxOption { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public List users; + + public String network; + + public InboundTLSOptions tls; + + } + + public static class NTPOptions extends SingBoxOption { + + public Boolean enabled; + + public Long interval; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + } + + + public static class Outbound extends SingBoxOption { + + public String type; + + public String tag; + + // Generate note: option type: public DirectOutboundOptions DirectOptions; + + // Generate note: option type: public SocksOutboundOptions SocksOptions; + + // Generate note: option type: public HTTPOutboundOptions HTTPOptions; + + // Generate note: option type: public ShadowsocksOutboundOptions ShadowsocksOptions; + + // Generate note: option type: public VMessOutboundOptions VMessOptions; + + // Generate note: option type: public TrojanOutboundOptions TrojanOptions; + + // Generate note: option type: public WireGuardOutboundOptions WireGuardOptions; + + // Generate note: option type: public HysteriaOutboundOptions HysteriaOptions; + + // Generate note: option type: public TorOutboundOptions TorOptions; + + // Generate note: option type: public SSHOutboundOptions SSHOptions; + + // Generate note: option type: public ShadowTLSOutboundOptions ShadowTLSOptions; + + // Generate note: option type: public ShadowsocksROutboundOptions ShadowsocksROptions; + + // Generate note: option type: public VLESSOutboundOptions VLESSOptions; + + // Generate note: option type: public SelectorOutboundOptions SelectorOptions; + + // Generate note: option type: public URLTestOutboundOptions URLTestOptions; + + } + + public static class DialerOptions extends SingBoxOption { + + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + } + + public static class ServerOptions extends SingBoxOption { + + public String server; + + public Integer server_port; + + } + + public static class MultiplexOptions extends SingBoxOption { + + public Boolean enabled; + + public String protocol; + + public Integer max_connections; + + public Integer min_streams; + + public Integer max_streams; + + } + + public static class OnDemandOptions extends SingBoxOption { + + public Boolean enabled; + + public List rules; + + } + + public static class OnDemandRule extends SingBoxOption { + + public String action; + + // Generate note: Listable + public List dns_search_domain_match; + + // Generate note: Listable + public List dns_server_address_match; + + public String interface_type_match; + + // Generate note: Listable + public List ssid_match; + + public String probe_url; + + } + + + public static class RedirectInboundOptions extends SingBoxOption { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + } + + public static class TProxyInboundOptions extends SingBoxOption { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public String network; + + } + + public static class RouteOptions extends SingBoxOption { + + public GeoIPOptions geoip; + + public GeositeOptions geosite; + + public List rules; + + @SerializedName("final") + public String final_; + + public Boolean find_process; + + public Boolean auto_detect_interface; + + public Boolean override_android_vpn; + + public String default_interface; + + public Integer default_mark; + + } + + public static class GeoIPOptions extends SingBoxOption { + + public String path; + + public String download_url; + + public String download_detour; + + } + + public static class GeositeOptions extends SingBoxOption { + + public String path; + + public String download_url; + + public String download_detour; + + } + + + public static class Rule extends SingBoxOption { + + public String type; + + // Generate note: option type: public DefaultRule DefaultOptions; + + // Generate note: option type: public LogicalRule LogicalOptions; + + } + + public static class DefaultRule extends SingBoxOption { + + // Generate note: Listable + public List inbound; + + public Integer ip_version; + + public String network; + + // Generate note: Listable + public List auth_user; + + // Generate note: Listable + public List protocol; + + // Generate note: Listable + public List domain; + + // Generate note: Listable + public List domain_suffix; + + // Generate note: Listable + public List domain_keyword; + + // Generate note: Listable + public List domain_regex; + + // Generate note: Listable + public List geosite; + + // Generate note: Listable + public List source_geoip; + + // Generate note: Listable + public List geoip; + + // Generate note: Listable + public List source_ip_cidr; + + // Generate note: Listable + public List ip_cidr; + + // Generate note: Listable + public List source_port; + + // Generate note: Listable + public List source_port_range; + + // Generate note: Listable + public List port; + + // Generate note: Listable + public List port_range; + + // Generate note: Listable + public List process_name; + + // Generate note: Listable + public List process_path; + + // Generate note: Listable + public List package_name; + + // Generate note: Listable + public List user; + + // Generate note: Listable + public List user_id; + + public String clash_mode; + + public Boolean invert; + + public String outbound; + + } + + public static class LogicalRule extends SingBoxOption { + + public String mode; + + public List rules; + + public Boolean invert; + + public String outbound; + + } + + public static class ShadowsocksInboundOptions extends SingBoxOption { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public String network; + + public String method; + + public String password; + + public List users; + + public List destinations; + + } + + public static class ShadowsocksUser extends SingBoxOption { + + public String name; + + public String password; + + } + + public static class ShadowsocksDestination extends SingBoxOption { + + public String name; + + public String password; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + } + + public static class ShadowsocksOutboundOptions extends SingBoxOption { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String method; + + public String password; + + public String plugin; + + public String plugin_opts; + + public String network; + + public Boolean udp_over_tcp; + + public MultiplexOptions multiplex; + + } + + public static class ShadowsocksROutboundOptions extends SingBoxOption { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String method; + + public String password; + + public String obfs; + + public String obfs_param; + + public String protocol; + + public String protocol_param; + + public String network; + + } + + public static class ShadowTLSInboundOptions extends SingBoxOption { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public Integer version; + + public String password; + + public List users; + + public ShadowTLSHandshakeOptions handshake; + + public Map handshake_for_server_name; + + public Boolean strict_mode; + + } + + public static class ShadowTLSUser extends SingBoxOption { + + public String name; + + public String password; + + } + + public static class ShadowTLSHandshakeOptions extends SingBoxOption { + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + } + + public static class ShadowTLSOutboundOptions extends SingBoxOption { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public Integer version; + + public String password; + + public OutboundTLSOptions tls; + + } + + public static class SocksInboundOptions extends SingBoxOption { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public List users; + + } + + public static class HTTPMixedInboundOptions extends SingBoxOption { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public List users; + + public Boolean set_system_proxy; + + public InboundTLSOptions tls; + + } + + public static class SocksOutboundOptions extends SingBoxOption { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String version; + + public String username; + + public String password; + + public String network; + + public Boolean udp_over_tcp; + + } + + public static class HTTPOutboundOptions extends SingBoxOption { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String username; + + public String password; + + public OutboundTLSOptions tls; + + } + + public static class SSHOutboundOptions extends SingBoxOption { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String user; + + public String password; + + public String private_key; + + public String private_key_path; + + public String private_key_passphrase; + + // Generate note: Listable + public List host_key; + + // Generate note: Listable + public List host_key_algorithms; + + public String client_version; + + } + + public static class InboundTLSOptions extends SingBoxOption { + + public Boolean enabled; + + public String server_name; + + public Boolean insecure; + + // Generate note: Listable + public List alpn; + + public String min_version; + + public String max_version; + + // Generate note: Listable + public List cipher_suites; + + public String certificate; + + public String certificate_path; + + public String key; + + public String key_path; + + public InboundACMEOptions acme; + + public InboundRealityOptions reality; + + } + + public static class OutboundTLSOptions extends SingBoxOption { + + public Boolean enabled; + + public Boolean disable_sni; + + public String server_name; + + public Boolean insecure; + + // Generate note: Listable + public List alpn; + + public String min_version; + + public String max_version; + + // Generate note: Listable + public List cipher_suites; + + public String certificate; + + public String certificate_path; + + public OutboundECHOptions ech; + + public OutboundUTLSOptions utls; + + public OutboundRealityOptions reality; + + } + + public static class InboundRealityOptions extends SingBoxOption { + + public Boolean enabled; + + public InboundRealityHandshakeOptions handshake; + + public String private_key; + + // Generate note: Listable + public List short_id; + + public Long max_time_difference; + + } + + public static class InboundRealityHandshakeOptions extends SingBoxOption { + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + } + + public static class OutboundECHOptions extends SingBoxOption { + + public Boolean enabled; + + public Boolean pq_signature_schemes_enabled; + + public Boolean dynamic_record_sizing_disabled; + + public String config; + + } + + public static class OutboundUTLSOptions extends SingBoxOption { + + public Boolean enabled; + + public String fingerprint; + + } + + public static class OutboundRealityOptions extends SingBoxOption { + + public Boolean enabled; + + public String public_key; + + public String short_id; + + } + + public static class InboundACMEOptions extends SingBoxOption { + + // Generate note: Listable + public List domain; + + public String data_directory; + + public String default_server_name; + + public String email; + + public String provider; + + public Boolean disable_http_challenge; + + public Boolean disable_tls_alpn_challenge; + + public Integer alternative_http_port; + + public Integer alternative_tls_port; + + public ACMEExternalAccountOptions external_account; + + } + + public static class ACMEExternalAccountOptions extends SingBoxOption { + + public String key_id; + + public String mac_key; + + } + + public static class TorOutboundOptions extends SingBoxOption { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + public String executable_path; + + public List extra_args; + + public String data_directory; + + public Map torrc; + + } + + public static class TrojanInboundOptions extends SingBoxOption { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public List users; + + public InboundTLSOptions tls; + + public ServerOptions fallback; + + public Map fallback_for_alpn; + + public V2RayTransportOptions transport; + + } + + public static class TrojanUser extends SingBoxOption { + + public String name; + + public String password; + + } + + public static class TrojanOutboundOptions extends SingBoxOption { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String password; + + public String network; + + public OutboundTLSOptions tls; + + public MultiplexOptions multiplex; + + public V2RayTransportOptions transport; + + } + + public static class TunInboundOptions extends SingBoxOption { + + public String interface_name; + + public Integer mtu; + + // Generate note: Listable + public List inet4_address; + + // Generate note: Listable + public List inet6_address; + + public Boolean auto_route; + + public Boolean strict_route; + + // Generate note: Listable + public List inet4_route_address; + + // Generate note: Listable + public List inet6_route_address; + + // Generate note: Listable + public List include_uid; + + // Generate note: Listable + public List include_uid_range; + + // Generate note: Listable + public List exclude_uid; + + // Generate note: Listable + public List exclude_uid_range; + + // Generate note: Listable + public List include_android_user; + + // Generate note: Listable + public List include_package; + + // Generate note: Listable + public List exclude_package; + + public Boolean endpoint_independent_nat; + + public Long udp_timeout; + + public String stack; + + public TunPlatformOptions platform; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + } + + public static class TunPlatformOptions extends SingBoxOption { + + public HTTPProxyOptions http_proxy; + + } + + public static class HTTPProxyOptions extends SingBoxOption { + + public Boolean enabled; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + } + + + public static class V2RayAPIOptions extends SingBoxOption { + + public String listen; + + public V2RayStatsServiceOptions stats; + + } + + public static class V2RayStatsServiceOptions extends SingBoxOption { + + public Boolean enabled; + + public List inbounds; + + public List outbounds; + + public List users; + + } + + + public static class V2RayTransportOptions extends SingBoxOption { + + public String type; + + // Generate note: option type: public V2RayHTTPOptions HTTPOptions; + + // Generate note: option type: public V2RayWebsocketOptions WebsocketOptions; + + // Generate note: option type: public V2RayQUICOptions QUICOptions; + + // Generate note: option type: public V2RayGRPCOptions GRPCOptions; + + } + + public static class V2RayHTTPOptions extends SingBoxOption { + + // Generate note: Listable + public List host; + + public String path; + + public String method; + + public Map headers; + + } + + public static class V2RayWebsocketOptions extends SingBoxOption { + + public String path; + + public Map headers; + + public Integer max_early_data; + + public String early_data_header_name; + + } + + + public static class V2RayGRPCOptions extends SingBoxOption { + + public String service_name; + + // Generate note: option type: public Boolean ForceLite; + + } + + public static class VLESSInboundOptions extends SingBoxOption { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public List users; + + public InboundTLSOptions tls; + + public V2RayTransportOptions transport; + + } + + public static class VLESSUser extends SingBoxOption { + + public String name; + + public String uuid; + + public String flow; + + } + + public static class VLESSOutboundOptions extends SingBoxOption { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String uuid; + + public String flow; + + public String network; + + public OutboundTLSOptions tls; + + public V2RayTransportOptions transport; + + public String packet_encoding; + + } + + public static class VMessInboundOptions extends SingBoxOption { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public List users; + + public InboundTLSOptions tls; + + public V2RayTransportOptions transport; + + } + + public static class VMessUser extends SingBoxOption { + + public String name; + + public String uuid; + + public Integer alterId; + + } + + public static class VMessOutboundOptions extends SingBoxOption { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String uuid; + + public String security; + + public Integer alter_id; + + public Boolean global_padding; + + public Boolean authenticated_length; + + public String network; + + public OutboundTLSOptions tls; + + public String packet_encoding; + + public MultiplexOptions multiplex; + + public V2RayTransportOptions transport; + + } + + public static class WireGuardOutboundOptions extends SingBoxOption { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + // Generate note: option type: public Boolean UDPFragmentDefault; + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public Boolean system_interface; + + public String interface_name; + + // Generate note: Listable + public List local_address; + + public String private_key; + + public String peer_public_key; + + public String pre_shared_key; + + public List reserved; + + public Integer workers; + + public Integer mtu; + + public String network; + + } + + public static class DNSRule_DefaultOptions extends DNSRule { + + // Generate note: Listable + public List inbound; + + public Integer ip_version; + + // Generate note: Listable + public List query_type; + + public String network; + + // Generate note: Listable + public List auth_user; + + // Generate note: Listable + public List protocol; + + // Generate note: Listable + public List domain; + + // Generate note: Listable + public List domain_suffix; + + // Generate note: Listable + public List domain_keyword; + + // Generate note: Listable + public List domain_regex; + + // Generate note: Listable + public List geosite; + + // Generate note: Listable + public List source_geoip; + + // Generate note: Listable + public List source_ip_cidr; + + // Generate note: Listable + public List source_port; + + // Generate note: Listable + public List source_port_range; + + // Generate note: Listable + public List port; + + // Generate note: Listable + public List port_range; + + // Generate note: Listable + public List process_name; + + // Generate note: Listable + public List process_path; + + // Generate note: Listable + public List package_name; + + // Generate note: Listable + public List user; + + // Generate note: Listable + public List user_id; + + // Generate note: Listable + public List outbound; + + public String clash_mode; + + public Boolean invert; + + public String server; + + public Boolean disable_cache; + + } + + public static class DNSRule_LogicalOptions extends DNSRule { + + public String mode; + + public List rules; + + public Boolean invert; + + public String server; + + public Boolean disable_cache; + + } + + public static class Inbound_TunOptions extends Inbound { + + public String interface_name; + + public Integer mtu; + + // Generate note: Listable + public List inet4_address; + + // Generate note: Listable + public List inet6_address; + + public Boolean auto_route; + + public Boolean strict_route; + + // Generate note: Listable + public List inet4_route_address; + + // Generate note: Listable + public List inet6_route_address; + + // Generate note: Listable + public List include_uid; + + // Generate note: Listable + public List include_uid_range; + + // Generate note: Listable + public List exclude_uid; + + // Generate note: Listable + public List exclude_uid_range; + + // Generate note: Listable + public List include_android_user; + + // Generate note: Listable + public List include_package; + + // Generate note: Listable + public List exclude_package; + + public Boolean endpoint_independent_nat; + + public Long udp_timeout; + + public String stack; + + public TunPlatformOptions platform; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + } + + public static class Inbound_RedirectOptions extends Inbound { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + } + + public static class Inbound_TProxyOptions extends Inbound { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public String network; + + } + + public static class Inbound_DirectOptions extends Inbound { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public String network; + + public String override_address; + + public Integer override_port; + + } + + public static class Inbound_SocksOptions extends Inbound { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public List users; + + } + + public static class Inbound_HTTPOptions extends Inbound { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public List users; + + public Boolean set_system_proxy; + + public InboundTLSOptions tls; + + } + + public static class Inbound_MixedOptions extends Inbound { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public List users; + + public Boolean set_system_proxy; + + public InboundTLSOptions tls; + + } + + public static class Inbound_ShadowsocksOptions extends Inbound { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public String network; + + public String method; + + public String password; + + public List users; + + public List destinations; + + } + + public static class Inbound_VMessOptions extends Inbound { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public List users; + + public InboundTLSOptions tls; + + public V2RayTransportOptions transport; + + } + + public static class Inbound_TrojanOptions extends Inbound { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public List users; + + public InboundTLSOptions tls; + + public ServerOptions fallback; + + public Map fallback_for_alpn; + + public V2RayTransportOptions transport; + + } + + public static class Inbound_NaiveOptions extends Inbound { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public List users; + + public String network; + + public InboundTLSOptions tls; + + } + + public static class Inbound_HysteriaOptions extends Inbound { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public String up; + + public Integer up_mbps; + + public String down; + + public Integer down_mbps; + + public String obfs; + + public List users; + + public Long recv_window_conn; + + public Long recv_window_client; + + public Integer max_conn_client; + + public Boolean disable_mtu_discovery; + + public InboundTLSOptions tls; + + } + + public static class Inbound_ShadowTLSOptions extends Inbound { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public Integer version; + + public String password; + + public List users; + + public ShadowTLSHandshakeOptions handshake; + + public Map handshake_for_server_name; + + public Boolean strict_mode; + + } + + public static class Inbound_VLESSOptions extends Inbound { + + // Generate note: nested type ListenOptions + public String listen; + + public Integer listen_port; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public Long udp_timeout; + + public Boolean proxy_protocol; + + public Boolean proxy_protocol_accept_no_header; + + public String detour; + + // Generate note: nested type InboundOptions + public Boolean sniff; + + public Boolean sniff_override_destination; + + public Long sniff_timeout; + + public String domain_strategy; + + // End of public InboundOptions ; + + // End of public ListenOptions ; + + public List users; + + public InboundTLSOptions tls; + + public V2RayTransportOptions transport; + + } + + public static class Outbound_DirectOptions extends Outbound { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + public String override_address; + + public Integer override_port; + + public Integer proxy_protocol; + + } + + public static class Outbound_SocksOptions extends Outbound { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String version; + + public String username; + + public String password; + + public String network; + + public Boolean udp_over_tcp; + + } + + public static class Outbound_HTTPOptions extends Outbound { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String username; + + public String password; + + public OutboundTLSOptions tls; + + } + + public static class Outbound_ShadowsocksOptions extends Outbound { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String method; + + public String password; + + public String plugin; + + public String plugin_opts; + + public String network; + + public Boolean udp_over_tcp; + + public MultiplexOptions multiplex; + + } + + public static class Outbound_VMessOptions extends Outbound { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String uuid; + + public String security; + + public Integer alter_id; + + public Boolean global_padding; + + public Boolean authenticated_length; + + public String network; + + public OutboundTLSOptions tls; + + public String packet_encoding; + + public MultiplexOptions multiplex; + + public V2RayTransportOptions transport; + + } + + public static class Outbound_TrojanOptions extends Outbound { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String password; + + public String network; + + public OutboundTLSOptions tls; + + public MultiplexOptions multiplex; + + public V2RayTransportOptions transport; + + } + + public static class Outbound_WireGuardOptions extends Outbound { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public Boolean system_interface; + + public String interface_name; + + // Generate note: Listable + public List local_address; + + public String private_key; + + public String peer_public_key; + + public String pre_shared_key; + + public List reserved; + + public Integer workers; + + public Integer mtu; + + public String network; + + } + + public static class Outbound_HysteriaOptions extends Outbound { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String up; + + public Integer up_mbps; + + public String down; + + public Integer down_mbps; + + public String obfs; + + public List auth; + + public String auth_str; + + public Long recv_window_conn; + + public Long recv_window; + + public Boolean disable_mtu_discovery; + + public String network; + + public OutboundTLSOptions tls; + + } + + public static class Outbound_TorOptions extends Outbound { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + public String executable_path; + + public List extra_args; + + public String data_directory; + + public Map torrc; + + } + + public static class Outbound_SSHOptions extends Outbound { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String user; + + public String password; + + public String private_key; + + public String private_key_path; + + public String private_key_passphrase; + + // Generate note: Listable + public List host_key; + + // Generate note: Listable + public List host_key_algorithms; + + public String client_version; + + } + + public static class Outbound_ShadowTLSOptions extends Outbound { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public Integer version; + + public String password; + + public OutboundTLSOptions tls; + + } + + public static class Outbound_ShadowsocksROptions extends Outbound { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String method; + + public String password; + + public String obfs; + + public String obfs_param; + + public String protocol; + + public String protocol_param; + + public String network; + + } + + public static class Outbound_VLESSOptions extends Outbound { + + // Generate note: nested type DialerOptions + public String detour; + + public String bind_interface; + + public String inet4_bind_address; + + public String inet6_bind_address; + + public String protect_path; + + public Integer routing_mark; + + public Boolean reuse_addr; + + public Long connect_timeout; + + public Boolean tcp_fast_open; + + public Boolean udp_fragment; + + + public String domain_strategy; + + public Long fallback_delay; + + // End of public DialerOptions ; + + // Generate note: nested type ServerOptions + public String server; + + public Integer server_port; + + // End of public ServerOptions ; + + public String uuid; + + public String flow; + + public String network; + + public OutboundTLSOptions tls; + + public V2RayTransportOptions transport; + + public String packet_encoding; + + } + + public static class Outbound_SelectorOptions extends Outbound { + + public List outbounds; + + @SerializedName("default") + public String default_; + + } + + public static class Outbound_URLTestOptions extends Outbound { + + public List outbounds; + + public String url; + + public Long interval; + + public Integer tolerance; + + } + + public static class Rule_DefaultOptions extends Rule { + + // Generate note: Listable + public List inbound; + + public Integer ip_version; + + public String network; + + // Generate note: Listable + public List auth_user; + + // Generate note: Listable + public List protocol; + + // Generate note: Listable + public List domain; + + // Generate note: Listable + public List domain_suffix; + + // Generate note: Listable + public List domain_keyword; + + // Generate note: Listable + public List domain_regex; + + // Generate note: Listable + public List geosite; + + // Generate note: Listable + public List source_geoip; + + // Generate note: Listable + public List geoip; + + // Generate note: Listable + public List source_ip_cidr; + + // Generate note: Listable + public List ip_cidr; + + // Generate note: Listable + public List source_port; + + // Generate note: Listable + public List source_port_range; + + // Generate note: Listable + public List port; + + // Generate note: Listable + public List port_range; + + // Generate note: Listable + public List process_name; + + // Generate note: Listable + public List process_path; + + // Generate note: Listable + public List package_name; + + // Generate note: Listable + public List user; + + // Generate note: Listable + public List user_id; + + public String clash_mode; + + public Boolean invert; + + public String outbound; + + } + + public static class Rule_LogicalOptions extends Rule { + + public String mode; + + public List rules; + + public Boolean invert; + + public String outbound; + + } + + public static class V2RayTransportOptions_HTTPOptions extends V2RayTransportOptions { + + // Generate note: Listable + public List host; + + public String path; + + public String method; + + public Map headers; + + } + + public static class V2RayTransportOptions_WebsocketOptions extends V2RayTransportOptions { + + public String path; + + public Map headers; + + public Integer max_early_data; + + public String early_data_header_name; + + } + + + public static class V2RayTransportOptions_GRPCOptions extends V2RayTransportOptions { + + public String service_name; + + + } + +} diff --git a/app/src/main/java/moe/matsuri/nb4a/TempDatabase.kt b/app/src/main/java/moe/matsuri/nb4a/TempDatabase.kt new file mode 100644 index 0000000..4070cdb --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/TempDatabase.kt @@ -0,0 +1,30 @@ +package moe.matsuri.nb4a + +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.database.preference.KeyValuePair +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +@Database(entities = [KeyValuePair::class], version = 1) +abstract class TempDatabase : RoomDatabase() { + + companion object { + @Suppress("EXPERIMENTAL_API_USAGE") + private val instance by lazy { + Room.inMemoryDatabaseBuilder(SagerNet.application, TempDatabase::class.java) + .allowMainThreadQueries() + .enableMultiInstanceInvalidation() + .fallbackToDestructiveMigration() + .setQueryExecutor { GlobalScope.launch { it.run() } } + .build() + } + + val profileCacheDao get() = instance.profileCacheDao() + + } + + abstract fun profileCacheDao(): KeyValuePair.Dao +} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/net/LocalResolverImpl.kt b/app/src/main/java/moe/matsuri/nb4a/net/LocalResolverImpl.kt new file mode 100644 index 0000000..8a5a219 --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/net/LocalResolverImpl.kt @@ -0,0 +1,80 @@ +package moe.matsuri.nb4a.net + +import android.net.DnsResolver +import android.os.Build +import android.os.CancellationSignal +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.ktx.tryResume +import io.nekohasekai.sagernet.ktx.tryResumeWithException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.runBlocking +import java.net.InetAddress +import kotlin.coroutines.suspendCoroutine + +object LocalResolverImpl : libcore.LocalResolver { + + override fun lookupIP(network: String, domain: String): String { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return runBlocking { + suspendCoroutine { continuation -> + val signal = CancellationSignal() + val callback = object : DnsResolver.Callback> { + @Suppress("ThrowableNotThrown") + override fun onAnswer(answer: Collection, rcode: Int) { + // libcore/v2ray.go + when { + answer.isNotEmpty() -> { + continuation.tryResume((answer as Collection).mapNotNull { it?.hostAddress } + .joinToString(",")) + } + rcode == 0 -> { + // fuck AAAA no record + // features/dns/client.go + continuation.tryResume("") + } + else -> { + // Need return rcode + // proxy/dns/dns.go + continuation.tryResumeWithException(Exception("$rcode")) + } + } + } + + override fun onError(error: DnsResolver.DnsException) { + continuation.tryResumeWithException(error) + } + } + val type = when { + network.endsWith("4") -> DnsResolver.TYPE_A + network.endsWith("6") -> DnsResolver.TYPE_AAAA + else -> null + } + if (type != null) { + DnsResolver.getInstance().query( + SagerNet.underlyingNetwork, + domain, + type, + DnsResolver.FLAG_EMPTY, + Dispatchers.IO.asExecutor(), + signal, + callback + ) + } else { + DnsResolver.getInstance().query( + SagerNet.underlyingNetwork, + domain, + DnsResolver.FLAG_EMPTY, + Dispatchers.IO.asExecutor(), + signal, + callback + ) + } + } + } + } else { + throw Exception("114514") + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/plugin/NekoPluginManager.kt b/app/src/main/java/moe/matsuri/nb4a/plugin/NekoPluginManager.kt new file mode 100644 index 0000000..d7ae50e --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/plugin/NekoPluginManager.kt @@ -0,0 +1,153 @@ +package moe.matsuri.nb4a.plugin + +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.bg.BaseService +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.ktx.forEach +import io.nekohasekai.sagernet.utils.PackageCache +import moe.matsuri.nb4a.proxy.neko.NekoJSInterface +import okhttp3.internal.closeQuietly +import org.json.JSONObject +import java.io.File +import java.util.zip.CRC32 +import java.util.zip.ZipFile + +object NekoPluginManager { + const val managerVersion = 2 + + val plugins get() = DataStore.nekoPlugins.split("\n").filter { it.isNotBlank() } + + // plgID to plgConfig object + fun getManagedPlugins(): Map { + val ret = mutableMapOf() + plugins.forEach { + tryGetPlgConfig(it)?.apply { + ret[it] = this + } + } + return ret + } + + class Protocol( + val protocolId: String, val plgId: String, val protocolConfig: JSONObject + ) + + fun getProtocols(): List { + val ret = mutableListOf() + getManagedPlugins().forEach { (t, u) -> + u.optJSONArray("protocols")?.forEach { _, any -> + if (any is JSONObject) { + val name = any.optString("protocolId") + ret.add(Protocol(name, t, any)) + } + } + } + return ret + } + + fun findProtocol(protocolId: String): Protocol? { + getManagedPlugins().forEach { (t, u) -> + u.optJSONArray("protocols")?.forEach { _, any -> + if (any is JSONObject) { + if (protocolId == any.optString("protocolId")) { + return Protocol(protocolId, t, any) + } + } + } + } + return null + } + + fun removeManagedPlugin(plgId: String) { + DataStore.configurationStore.remove(plgId) + val dir = File(SagerNet.application.filesDir.absolutePath + "/plugins/" + plgId) + if (dir.exists()) { + dir.deleteRecursively() + } + } + + fun extractPlugin(plgId: String, install: Boolean) { + val app = PackageCache.installedApps[plgId] ?: return + val apk = File(app.publicSourceDir) + if (!apk.exists()) { + return + } + if (!install && !plugins.contains(plgId)) { + return + } + + val zipFile = ZipFile(apk) + val unzipDir = File(SagerNet.application.filesDir.absolutePath + "/plugins/" + plgId) + unzipDir.mkdirs() + for (entry in zipFile.entries()) { + if (entry.name.startsWith("assets/")) { + val relativePath = entry.name.removePrefix("assets/") + val outFile = File(unzipDir, relativePath) + if (entry.isDirectory) { + outFile.mkdirs() + continue + } + + if (outFile.isDirectory) { + outFile.delete() + } else if (outFile.exists()) { + val checksum = CRC32() + checksum.update(outFile.readBytes()) + if (checksum.value == entry.crc) { + continue + } + } + + val input = zipFile.getInputStream(entry) + outFile.outputStream().use { + input.copyTo(it) + } + } + } + zipFile.closeQuietly() + } + + suspend fun installPlugin(plgId: String) { + if (plgId == "moe.matsuri.plugin.singbox" || plgId == "moe.matsuri.plugin.xray") { + throw Exception("This plugin is deprecated") + } + extractPlugin(plgId, true) + NekoJSInterface.Default.destroyJsi(plgId) + NekoJSInterface.Default.requireJsi(plgId).init() + NekoJSInterface.Default.destroyJsi(plgId) + } + + const val PLUGIN_APP_VERSION = "_v_vc" + const val PLUGIN_APP_VERSION_NAME = "_v_vn" + + // Return null if not managed + fun tryGetPlgConfig(plgId: String): JSONObject? { + return try { + JSONObject(DataStore.configurationStore.getString(plgId)!!) + } catch (e: Exception) { + null + } + } + + fun updatePlgConfig(plgId: String, plgConfig: JSONObject) { + PackageCache.installedPluginPackages[plgId]?.apply { + // longVersionCode requires API 28 +// plgConfig.put(PLUGIN_APP_VERSION, versionCode) + plgConfig.put(PLUGIN_APP_VERSION_NAME, versionName) + } + DataStore.configurationStore.putString(plgId, plgConfig.toString()) + } + + fun htmlPath(plgId: String): String { + val htmlFile = File(SagerNet.application.filesDir.absolutePath + "/plugins/" + plgId) + return htmlFile.absolutePath + } + + class PluginInternalException(val protocolId: String) : Exception(), + BaseService.ExpectedException { + override fun getLocalizedMessage() = + SagerNet.application.getString(R.string.neko_plugin_internal_error, protocolId) + } + +} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/plugin/Plugins.kt b/app/src/main/java/moe/matsuri/nb4a/plugin/Plugins.kt new file mode 100644 index 0000000..e07adc2 --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/plugin/Plugins.kt @@ -0,0 +1,115 @@ +package moe.matsuri.nb4a.plugin + +import android.content.Intent +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.pm.ProviderInfo +import android.net.Uri +import android.os.Build +import android.widget.Toast +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.plugin.PluginManager.loadString +import io.nekohasekai.sagernet.utils.PackageCache + +object Plugins { + const val AUTHORITIES_PREFIX_SEKAI_EXE = "io.nekohasekai.sagernet.plugin." + const val AUTHORITIES_PREFIX_NEKO_EXE = "moe.matsuri.exe." + const val AUTHORITIES_PREFIX_NEKO_PLUGIN = "moe.matsuri.plugin." + + const val ACTION_NATIVE_PLUGIN = "io.nekohasekai.sagernet.plugin.ACTION_NATIVE_PLUGIN" + + const val METADATA_KEY_ID = "io.nekohasekai.sagernet.plugin.id" + const val METADATA_KEY_EXECUTABLE_PATH = "io.nekohasekai.sagernet.plugin.executable_path" + + fun isExeOrPlugin(pkg: PackageInfo): Boolean { + if (pkg.providers == null || pkg.providers.isEmpty()) return false + val provider = pkg.providers[0] ?: return false + val auth = provider.authority ?: return false + return auth.startsWith(AUTHORITIES_PREFIX_SEKAI_EXE) + || auth.startsWith(AUTHORITIES_PREFIX_NEKO_EXE) + || auth.startsWith(AUTHORITIES_PREFIX_NEKO_PLUGIN) + } + + fun preferExePrefix(): String { + return AUTHORITIES_PREFIX_NEKO_EXE + } + + fun isUsingMatsuriExe(pluginId: String): Boolean { + getPlugin(pluginId)?.apply { + if (authority.startsWith(AUTHORITIES_PREFIX_NEKO_EXE)) { + return true + } + } + return false; + } + + fun displayExeProvider(pkgName: String): String { + return if (pkgName.startsWith(AUTHORITIES_PREFIX_SEKAI_EXE)) { + "SagerNet" + } else if (pkgName.startsWith(AUTHORITIES_PREFIX_NEKO_EXE)) { + "Matsuri" + } else { + "Unknown" + } + } + + fun getPlugin(pluginId: String): ProviderInfo? { + if (pluginId.isBlank()) return null + + // try queryIntentContentProviders + var providers = getPluginOld(pluginId) + + // try PackageCache + if (providers.isEmpty()) providers = getPluginNew(pluginId) + + // not found + if (providers.isEmpty()) return null + + if (providers.size > 1) { + val prefer = providers.filter { + it.authority.startsWith(preferExePrefix()) + } + if (prefer.size == 1) providers = prefer + } + + if (providers.size > 1) { + val message = + "Conflicting plugins found from: ${providers.joinToString { it.packageName }}" + Toast.makeText(SagerNet.application, message, Toast.LENGTH_LONG).show() + } + + return providers[0] + } + + fun getPluginNew(pluginId: String): List { + PackageCache.awaitLoadSync() + val pkgs = PackageCache.installedPluginPackages + .map { it.value } + .filter { it.providers[0].loadString(METADATA_KEY_ID) == pluginId } + return pkgs.map { it.providers[0] } + } + + private fun buildUri(id: String, auth: String) = Uri.Builder() + .scheme("plugin") + .authority(auth) + .path("/$id") + .build() + + private fun getPluginOld(pluginId: String): List { + var flags = PackageManager.GET_META_DATA + if (Build.VERSION.SDK_INT >= 24) { + flags = + flags or PackageManager.MATCH_DIRECT_BOOT_UNAWARE or PackageManager.MATCH_DIRECT_BOOT_AWARE + } + val list1 = SagerNet.application.packageManager.queryIntentContentProviders( + Intent(ACTION_NATIVE_PLUGIN, buildUri(pluginId, "io.nekohasekai.sagernet")), flags + ) + val list2 = SagerNet.application.packageManager.queryIntentContentProviders( + Intent(ACTION_NATIVE_PLUGIN, buildUri(pluginId, "moe.matsuri.lite")), flags + ) + return (list1 + list2).mapNotNull { + it.providerInfo + }.filter { it.exported } + } +} diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/PreferenceBinding.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/PreferenceBinding.kt new file mode 100644 index 0000000..185a103 --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/proxy/PreferenceBinding.kt @@ -0,0 +1,100 @@ +package moe.matsuri.nb4a.proxy + +import androidx.preference.Preference +import com.takisoft.preferencex.EditTextPreference +import com.takisoft.preferencex.PreferenceFragmentCompat +import com.takisoft.preferencex.SimpleMenuPreference +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.ktx.readableMessage + +object Type { + const val Text = 0 + const val TextToInt = 1 + const val Int = 2 + const val Bool = 3 +} + +class PreferenceBinding( + val type: Int = Type.Text, + var fieldName: String, + var bean: Any? = null, + var pf: PreferenceFragmentCompat? = null +) { + + var cacheName = fieldName + var disable = false + + fun readStringFromCache(): String { + return DataStore.profileCacheStore.getString(cacheName) ?: "" + } + + fun readBoolFromCache(): Boolean { + return DataStore.profileCacheStore.getBoolean(cacheName, false) + } + + fun readIntFromCache(): Int { + return DataStore.profileCacheStore.getInt(cacheName, 0) + } + + fun readStringToIntFromCache(): Int { + val value = DataStore.profileCacheStore.getString(cacheName)?.toIntOrNull() ?: 0 +// Logs.d("readStringToIntFromCache $value $cacheName -> $fieldName") + return value + } + + fun fromCache() { + if (disable) return + val f = try { + bean!!.javaClass.getField(fieldName) + } catch (e: Exception) { + Logs.d("binding no field: ${e.readableMessage}") + return + } + when (type) { + Type.Text -> f.set(bean, readStringFromCache()) + Type.TextToInt -> f.set(bean, readStringToIntFromCache()) + Type.Int -> f.set(bean, readIntFromCache()) + Type.Bool -> f.set(bean, readBoolFromCache()) + } + } + + fun writeToCache() { + if (disable) return + val f = try { + bean!!.javaClass.getField(fieldName) ?: return + } catch (e: Exception) { + Logs.d("binding no field: ${e.readableMessage}") + return + } + val value = f.get(bean) + when (type) { + Type.Text -> { + if (value is String) { +// Logs.d("writeToCache TEXT $value $cacheName -> $fieldName") + DataStore.profileCacheStore.putString(cacheName, value) + } + } + Type.TextToInt -> { + if (value is Int) { +// Logs.d("writeToCache TEXT2INT $value $cacheName -> $fieldName") + DataStore.profileCacheStore.putString(cacheName, value.toString()) + } + } + Type.Int -> { + if (value is Int) { + DataStore.profileCacheStore.putInt(cacheName, value) + } + } + Type.Bool -> { + if (value is Boolean) { + DataStore.profileCacheStore.putBoolean(cacheName, value) + } + } + } + } + + val preference by lazy { + pf!!.findPreference(cacheName)!! + } +} diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/PreferenceBindingManager.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/PreferenceBindingManager.kt new file mode 100644 index 0000000..5da4856 --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/proxy/PreferenceBindingManager.kt @@ -0,0 +1,33 @@ +package moe.matsuri.nb4a.proxy + +import com.takisoft.preferencex.PreferenceFragmentCompat + +class PreferenceBindingManager { + val items = mutableListOf() + + fun add(b: PreferenceBinding): PreferenceBinding { + items.add(b) + return b + } + + fun fromCacheAll(bean: Any) { + items.forEach { + it.bean = bean + it.fromCache() + } + } + + fun writeToCacheAll(bean: Any) { + items.forEach { + it.bean = bean + it.writeToCache() + } + } + + fun setPreferenceFragment(pf: PreferenceFragmentCompat) { + items.forEach { + it.pf = pf + } + } + +} diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/config/ConfigBean.java b/app/src/main/java/moe/matsuri/nb4a/proxy/config/ConfigBean.java new file mode 100644 index 0000000..b3f18a0 --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/proxy/config/ConfigBean.java @@ -0,0 +1,73 @@ +package moe.matsuri.nb4a.proxy.config; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; + +import io.nekohasekai.sagernet.fmt.KryoConverters; +import io.nekohasekai.sagernet.fmt.internal.InternalBean; +import moe.matsuri.nb4a.utils.JavaUtil; + +public class ConfigBean extends InternalBean { + + public Integer type; // 0=config 1=outbound + public String config; + + @Override + public void initializeDefaultValues() { + super.initializeDefaultValues(); + if (type == null) type = 0; + if (config == null) config = ""; + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(0); + super.serialize(output); + output.writeInt(type); + output.writeString(config); + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + super.deserialize(input); + type = input.readInt(); + config = input.readString(); + } + + @Override + public String displayName() { + if (JavaUtil.isNotBlank(name)) { + return name; + } else { + return "Custom " + Math.abs(hashCode()); + } + } + + public String displayType() { + return type == 0 ? "sing-box config" : "sing-box outbound"; + } + + @NotNull + @Override + public ConfigBean clone() { + return KryoConverters.deserialize(new ConfigBean(), KryoConverters.serialize(this)); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public ConfigBean newInstance() { + return new ConfigBean(); + } + + @Override + public ConfigBean[] newArray(int size) { + return new ConfigBean[size]; + } + }; +} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/config/ConfigSettingActivity.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/config/ConfigSettingActivity.kt new file mode 100644 index 0000000..246ec65 --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/proxy/config/ConfigSettingActivity.kt @@ -0,0 +1,67 @@ +package moe.matsuri.nb4a.proxy.config + +import android.os.Bundle +import androidx.preference.PreferenceDataStore +import androidx.preference.SwitchPreference +import com.takisoft.preferencex.EditTextPreference +import com.takisoft.preferencex.PreferenceFragmentCompat +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener +import io.nekohasekai.sagernet.ui.profile.ProfileSettingsActivity + +class ConfigSettingActivity : + ProfileSettingsActivity(), + OnPreferenceDataStoreChangeListener { + + var beanType: Int = 0 + + lateinit var configPreference: EditTextPreference + + override fun createEntity() = ConfigBean() + + override fun ConfigBean.init() { + // CustomBean to input + beanType = type + DataStore.profileName = name + DataStore.serverConfig = config + } + + override fun ConfigBean.serialize() { + // CustomBean from input + type = beanType + name = DataStore.profileName + config = DataStore.serverConfig + } + + override fun onCreate(savedInstanceState: Bundle?) { + intent?.getIntExtra("type", 0)?.apply { beanType = this } + super.onCreate(savedInstanceState) + } + + override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) { + if (key != Key.PROFILE_DIRTY) { + DataStore.dirty = true + } + if (key == Key.SERVER_CONFIG) { + if (::configPreference.isInitialized) { + configPreference.text = store.getString(key, "") + } + } else if (key == "isOutboundOnly") { + beanType = if (store.getBoolean(key, false)) 1 else 0 + } + } + + override fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + addPreferencesFromResource(R.xml.config_preferences) + + configPreference = findPreference(Key.SERVER_CONFIG)!! + + findPreference("isOutboundOnly")!!.isChecked = beanType == 1 + } + +} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoBean.java b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoBean.java new file mode 100644 index 0000000..3a86b7f --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoBean.java @@ -0,0 +1,110 @@ +package moe.matsuri.nb4a.proxy.neko; + +import androidx.annotation.NonNull; + +import com.esotericsoftware.kryo.io.ByteBufferInput; +import com.esotericsoftware.kryo.io.ByteBufferOutput; + +import org.jetbrains.annotations.NotNull; +import org.json.JSONObject; + +import io.nekohasekai.sagernet.R; +import io.nekohasekai.sagernet.SagerNet; +import io.nekohasekai.sagernet.fmt.AbstractBean; +import io.nekohasekai.sagernet.fmt.KryoConverters; +import io.nekohasekai.sagernet.ktx.Logs; +import moe.matsuri.nb4a.plugin.NekoPluginManager; + +public class NekoBean extends AbstractBean { + + // BoxInstance use this + public JSONObject allConfig = null; + + public String plgId; + public String protocolId; + public JSONObject sharedStorage = new JSONObject(); + + @Override + public void initializeDefaultValues() { + super.initializeDefaultValues(); + if (protocolId == null) protocolId = ""; + if (plgId == null) plgId = "moe.matsuri.plugin.donotexist"; + } + + @Override + public void serialize(ByteBufferOutput output) { + output.writeInt(0); + super.serialize(output); + output.writeString(plgId); + output.writeString(protocolId); + output.writeString(sharedStorage.toString()); + } + + @Override + public void deserialize(ByteBufferInput input) { + int version = input.readInt(); + super.deserialize(input); + plgId = input.readString(); + protocolId = input.readString(); + sharedStorage = tryParseJSON(input.readString()); + } + + @NotNull + public static JSONObject tryParseJSON(String input) { + JSONObject ret; + try { + ret = new JSONObject(input); + } catch (Exception e) { + ret = new JSONObject(); + Logs.INSTANCE.e(e); + } + return ret; + } + + public String displayType() { + NekoPluginManager.Protocol p = NekoPluginManager.INSTANCE.findProtocol(protocolId); + String neko = SagerNet.application.getResources().getString(R.string.neko_plugin); + if (p == null) return neko; + return p.getProtocolId(); + } + + @Override + public boolean canMapping() { + NekoPluginManager.Protocol p = NekoPluginManager.INSTANCE.findProtocol(protocolId); + if (p == null) return false; + return p.getProtocolConfig().optBoolean("canMapping"); + } + + @Override + public boolean canICMPing() { + NekoPluginManager.Protocol p = NekoPluginManager.INSTANCE.findProtocol(protocolId); + if (p == null) return false; + return p.getProtocolConfig().optBoolean("canICMPing"); + } + + @Override + public boolean canTCPing() { + NekoPluginManager.Protocol p = NekoPluginManager.INSTANCE.findProtocol(protocolId); + if (p == null) return false; + return p.getProtocolConfig().optBoolean("canTCPing"); + } + + @NotNull + @Override + public NekoBean clone() { + return KryoConverters.deserialize(new NekoBean(), KryoConverters.serialize(this)); + } + + public static final Creator CREATOR = new CREATOR() { + @NonNull + @Override + public NekoBean newInstance() { + return new NekoBean(); + } + + @Override + public NekoBean[] newArray(int size) { + return new NekoBean[size]; + } + }; +} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoFmt.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoFmt.kt new file mode 100644 index 0000000..88e45ec --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoFmt.kt @@ -0,0 +1,123 @@ +package moe.matsuri.nb4a.proxy.neko + +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.ktx.getStr +import io.nekohasekai.sagernet.ktx.runOnIoDispatcher +import libcore.Libcore +import moe.matsuri.nb4a.Protocols +import moe.matsuri.nb4a.plugin.NekoPluginManager +import org.json.JSONObject +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +suspend fun parseShareLink(plgId: String, protocolId: String, link: String): NekoBean = + suspendCoroutine { + runOnIoDispatcher { + val jsi = NekoJSInterface.Default.requireJsi(plgId) + jsi.lock() + + try { + jsi.init() + + val jsip = jsi.switchProtocol(protocolId) + val sharedStorage = jsip.parseShareLink(link) + + // NekoBean from link + val bean = NekoBean() + bean.plgId = plgId + bean.protocolId = protocolId + bean.sharedStorage = NekoBean.tryParseJSON(sharedStorage) + bean.onSharedStorageSet() + + it.resume(bean) + } catch (e: Exception) { + Logs.e(e) + it.resume(NekoBean().apply { + this.plgId = plgId + this.protocolId = protocolId + }) + } + + jsi.unlock() + // destroy when all link parsed + } + } + +fun NekoBean.shareLink(): String { + return sharedStorage.optString("shareLink") +} + +// Only run in bg process +// seems no concurrent +suspend fun NekoBean.updateAllConfig(port: Int) = suspendCoroutine { + allConfig = null + + runOnIoDispatcher { + val jsi = NekoJSInterface.Default.requireJsi(plgId) + jsi.lock() + + try { + jsi.init() + val jsip = jsi.switchProtocol(protocolId) + + // runtime arguments + val otherArgs = mutableMapOf() + otherArgs["finalAddress"] = finalAddress + otherArgs["finalPort"] = finalPort + otherArgs["muxEnabled"] = Protocols.shouldEnableMux(protocolId) + otherArgs["muxConcurrency"] = DataStore.muxConcurrency + + val ret = jsip.buildAllConfig(port, this@updateAllConfig, otherArgs) + + // result + allConfig = JSONObject(ret) + } catch (e: Exception) { + Logs.e(e) + } + + jsi.unlock() + it.resume(Unit) + // destroy when config generated / all tests finished + } +} + +fun NekoBean.cacheGet(id: String): String? { + return DataStore.profileCacheStore.getString("neko_${hash()}_$id") +} + +fun NekoBean.cacheSet(id: String, value: String) { + DataStore.profileCacheStore.putString("neko_${hash()}_$id", value) +} + +fun NekoBean.hash(): String { + var a = plgId + a += protocolId + a += sharedStorage.toString() + return Libcore.sha256Hex(a.toByteArray()) +} + +// must call it to update something like serverAddress +fun NekoBean.onSharedStorageSet() { + serverAddress = sharedStorage.getStr("serverAddress") + serverPort = sharedStorage.getStr("serverPort")?.toInt() ?: 1080 + if (serverAddress == null || serverAddress.isBlank()) { + serverAddress = "127.0.0.1" + } + name = sharedStorage.optString("name") +} + +fun NekoBean.needBypassRootUid(): Boolean { + val p = NekoPluginManager.findProtocol(protocolId) ?: return false + return p.protocolConfig.optBoolean("needBypassRootUid") +} + +fun NekoBean.haveStandardLink(): Boolean { + val p = NekoPluginManager.findProtocol(protocolId) ?: return false + return p.protocolConfig.optBoolean("haveStandardLink") +} + +fun NekoBean.canShare(): Boolean { + val p = NekoPluginManager.findProtocol(protocolId) ?: return false + return p.protocolConfig.optBoolean("canShare") +} diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoJSInterface.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoJSInterface.kt new file mode 100644 index 0000000..35b5712 --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoJSInterface.kt @@ -0,0 +1,388 @@ +package moe.matsuri.nb4a.proxy.neko + +import android.annotation.SuppressLint +import android.webkit.* +import android.widget.Toast +import androidx.preference.Preference +import androidx.preference.PreferenceScreen +import com.takisoft.preferencex.SimpleMenuPreference +import io.nekohasekai.sagernet.BuildConfig +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.ktx.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.withContext +import moe.matsuri.nb4a.plugin.NekoPluginManager +import moe.matsuri.nb4a.utils.JavaUtil +import moe.matsuri.nb4a.utils.Util +import moe.matsuri.nb4a.utils.WebViewUtil +import org.json.JSONObject +import java.io.File +import java.io.FileInputStream +import java.util.* +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +class NekoJSInterface(val plgId: String) { + + private val mutex = Mutex() + private var webView: WebView? = null + val jsObject = JsObject() + var plgConfig: JSONObject? = null + var plgConfigException: Exception? = null + val protocols = mutableMapOf() + val loaded = AtomicBoolean() + + suspend fun lock() { + mutex.lock(null) + } + + fun unlock() { + mutex.unlock(null) + } + + // load webview and js + // Return immediately when already loaded + // Return plgConfig or throw exception + suspend fun init() = withContext(Dispatchers.Main) { + initInternal() + } + + @SuppressLint("SetJavaScriptEnabled") + private suspend fun initInternal() = suspendCoroutine { + if (loaded.get()) { + plgConfig?.apply { + it.resume(this) + return@suspendCoroutine + } + plgConfigException?.apply { + it.resumeWithException(this) + return@suspendCoroutine + } + it.resumeWithException(Exception("wtf")) + return@suspendCoroutine + } + + WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG) + NekoPluginManager.extractPlugin(plgId, false) + + webView = WebView(SagerNet.application.applicationContext) + webView!!.settings.javaScriptEnabled = true + webView!!.addJavascriptInterface(jsObject, "neko") + webView!!.webViewClient = object : WebViewClient() { + // provide files + override fun shouldInterceptRequest( + view: WebView?, request: WebResourceRequest? + ): WebResourceResponse { + return WebViewUtil.interceptRequest( + { res -> + val f = File(NekoPluginManager.htmlPath(plgId), res) + if (f.exists()) { + FileInputStream(f) + } else { + null + } + }, + view, + request + ) + } + + override fun onReceivedError( + view: WebView?, request: WebResourceRequest?, error: WebResourceError? + ) { + WebViewUtil.onReceivedError(view, request, error) + } + + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + if (loaded.getAndSet(true)) return + + runOnIoDispatcher { + // Process nekoInit + var ret = "" + try { + ret = nekoInit() + val obj = JSONObject(ret) + if (!obj.getBoolean("ok")) { + throw Exception("plugin refuse to run: ${obj.optString("reason")}") + } + val min = obj.getInt("minVersion") + if (min > NekoPluginManager.managerVersion) { + throw Exception("manager version ${NekoPluginManager.managerVersion} too old, this plugin requires >= $min") + } + plgConfig = obj + NekoPluginManager.updatePlgConfig(plgId, obj) + it.resume(obj) + } catch (e: Exception) { + val e2 = Exception("nekoInit: " + e.readableMessage + "\n\n" + ret) + plgConfigException = e2 + it.resumeWithException(e2) + } + } + } + } + webView!!.loadUrl("http://$plgId/plugin.html") + } + + // Android call JS + + private suspend fun callJS(script: String): String = suspendCoroutine { + val jsLatch = CountDownLatch(1) + var jsReceivedValue = "" + + runOnMainDispatcher { + if (webView != null) { + webView!!.evaluateJavascript(script) { value -> + jsReceivedValue = value + jsLatch.countDown() + } + } else { + jsReceivedValue = "webView is null" + jsLatch.countDown() + } + } + + jsLatch.await(5, TimeUnit.SECONDS) + + // evaluateJavascript escapes Javascript's String + jsReceivedValue = JavaUtil.unescapeString(jsReceivedValue.removeSurrounding("\"")) + if (BuildConfig.DEBUG) Logs.d("$script: $jsReceivedValue") + it.resume(jsReceivedValue) + } + + // call once + private suspend fun nekoInit(): String { + val sendData = JSONObject() + sendData.put("lang", Locale.getDefault().toLanguageTag()) + sendData.put("plgId", plgId) + sendData.put("managerVersion", NekoPluginManager.managerVersion) + + return callJS( + "nekoInit(\"${ + Util.b64EncodeUrlSafe( + sendData.toString().toByteArray() + ) + }\")" + ) + } + + fun switchProtocol(id: String): NekoProtocol { + lateinit var p: NekoProtocol + if (protocols.containsKey(id)) { + p = protocols[id]!! + } else { + p = NekoProtocol(id) { callJS(it) } + protocols[id] = p + } + jsObject.protocol = p + return p + } + + suspend fun getAbout(): String { + return callJS("nekoAbout()") + } + + inner class NekoProtocol(val protocolId: String, val callJS: suspend (String) -> String) { + private suspend fun callProtocol(method: String, b64Str: String?): String { + var arg = "" + if (b64Str != null) { + arg = "\"" + b64Str + "\"" + } + return callJS("nekoProtocol(\"$protocolId\").$method($arg)") + } + + suspend fun buildAllConfig( + port: Int, bean: NekoBean, otherArgs: Map? + ): String { + val sendData = JSONObject() + sendData.put("port", port) + sendData.put( + "sharedStorage", + Util.b64EncodeUrlSafe(bean.sharedStorage.toString().toByteArray()) + ) + otherArgs?.forEach { (t, u) -> sendData.put(t, u) } + + return callProtocol( + "buildAllConfig", Util.b64EncodeUrlSafe(sendData.toString().toByteArray()) + ) + } + + suspend fun parseShareLink(shareLink: String): String { + val sendData = JSONObject() + sendData.put("shareLink", shareLink) + + return callProtocol( + "parseShareLink", Util.b64EncodeUrlSafe(sendData.toString().toByteArray()) + ) + } + + // UI Interface + + suspend fun setSharedStorage(sharedStorage: String) { + callProtocol( + "setSharedStorage", + Util.b64EncodeUrlSafe(sharedStorage.toByteArray()) + ) + } + + suspend fun requireSetProfileCache() { + callProtocol("requireSetProfileCache", null) + } + + suspend fun requirePreferenceScreenConfig(): String { + return callProtocol("requirePreferenceScreenConfig", null) + } + + suspend fun sharedStorageFromProfileCache(): String { + return callProtocol("sharedStorageFromProfileCache", null) + } + + suspend fun onPreferenceCreated() { + callProtocol("onPreferenceCreated", null) + } + + suspend fun onPreferenceChanged(key: String, v: Any) { + val sendData = JSONObject() + sendData.put("key", key) + sendData.put("newValue", v) + + callProtocol( + "onPreferenceChanged", + Util.b64EncodeUrlSafe(sendData.toString().toByteArray()) + ) + } + + } + + inner class JsObject { + var preferenceScreen: PreferenceScreen? = null + var protocol: NekoProtocol? = null + + // JS call Android + + @JavascriptInterface + fun toast(s: String) { + Toast.makeText(SagerNet.application.applicationContext, s, Toast.LENGTH_SHORT).show() + } + + @JavascriptInterface + fun logError(s: String) { + Logs.e("logError: $s") + } + + @JavascriptInterface + fun setPreferenceVisibility(key: String, isVisible: Boolean) { + runBlockingOnMainDispatcher { + preferenceScreen?.findPreference(key)?.isVisible = isVisible + } + } + + @JavascriptInterface + fun setPreferenceTitle(key: String, title: String) { + runBlockingOnMainDispatcher { + preferenceScreen?.findPreference(key)?.title = title + } + } + + @JavascriptInterface + fun setMenu(key: String, entries: String) { + runBlockingOnMainDispatcher { + preferenceScreen?.findPreference(key)?.apply { + NekoPreferenceInflater.setMenu(this, JSONObject(entries)) + } + } + } + + @JavascriptInterface + fun listenOnPreferenceChanged(key: String) { + preferenceScreen?.findPreference(key) + ?.setOnPreferenceChangeListener { preference, newValue -> + runOnIoDispatcher { + protocol?.onPreferenceChanged(preference.key, newValue) + } + true + } + } + + @JavascriptInterface + fun setKV(type: Int, key: String, jsonStr: String) { + try { + val v = JSONObject(jsonStr) + when (type) { + 0 -> DataStore.profileCacheStore.putBoolean(key, v.getBoolean("v")) + 1 -> DataStore.profileCacheStore.putFloat(key, v.getDouble("v").toFloat()) + 2 -> DataStore.profileCacheStore.putInt(key, v.getInt("v")) + 3 -> DataStore.profileCacheStore.putLong(key, v.getLong("v")) + 4 -> DataStore.profileCacheStore.putString(key, v.getString("v")) + } + } catch (e: Exception) { + Logs.e("setKV: $e") + } + } + + @JavascriptInterface + fun getKV(type: Int, key: String): String { + val v = JSONObject() + try { + when (type) { + 0 -> v.put("v", DataStore.profileCacheStore.getBoolean(key)) + 1 -> v.put("v", DataStore.profileCacheStore.getFloat(key)) + 2 -> v.put("v", DataStore.profileCacheStore.getInt(key)) + 3 -> v.put("v", DataStore.profileCacheStore.getLong(key)) + 4 -> v.put("v", DataStore.profileCacheStore.getString(key)) + } + } catch (e: Exception) { + Logs.e("getKV: $e") + } + return v.toString() + } + + } + + fun destroy() { + webView?.onPause() + webView?.removeAllViews() + webView?.destroy() + webView = null + } + + suspend fun destorySuspend() = withContext(Dispatchers.Main) { + destroy() + } + + object Default { + val map = mutableMapOf() + + suspend fun destroyJsi(plgId: String) = withContext(Dispatchers.Main) { + if (map.containsKey(plgId)) { + map[plgId]!!.destroy() + map.remove(plgId) + } + } + + // now it's manually managed + suspend fun destroyAllJsi() = withContext(Dispatchers.Main) { + map.forEach { (t, u) -> + u.destroy() + map.remove(t) + } + } + + suspend fun requireJsi(plgId: String): NekoJSInterface = withContext(Dispatchers.Main) { + lateinit var jsi: NekoJSInterface + if (map.containsKey(plgId)) { + jsi = map[plgId]!! + } else { + jsi = NekoJSInterface(plgId) + map[plgId] = jsi + } + return@withContext jsi + } + } +} diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoPreferenceInflater.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoPreferenceInflater.kt new file mode 100644 index 0000000..5453f5e --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoPreferenceInflater.kt @@ -0,0 +1,92 @@ +package moe.matsuri.nb4a.proxy.neko + +import androidx.preference.Preference +import androidx.preference.PreferenceScreen +import androidx.preference.SwitchPreference +import com.takisoft.preferencex.EditTextPreference +import com.takisoft.preferencex.PreferenceCategory +import com.takisoft.preferencex.SimpleMenuPreference +import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers +import io.nekohasekai.sagernet.ktx.forEach +import io.nekohasekai.sagernet.ktx.getStr +import io.nekohasekai.sagernet.ui.profile.ProfileSettingsActivity +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import moe.matsuri.nb4a.utils.getDrawableByName +import org.json.JSONArray +import org.json.JSONObject + +object NekoPreferenceInflater { + suspend fun inflate(pref: JSONArray, preferenceScreen: PreferenceScreen) = + withContext(Dispatchers.Main) { + val context = preferenceScreen.context + pref.forEach { _, category -> + category as JSONObject + + val preferenceCategory = PreferenceCategory(context) + preferenceScreen.addPreference(preferenceCategory) + + category.getStr("key")?.apply { preferenceCategory.key = this } + category.getStr("title")?.apply { preferenceCategory.title = this } + + category.optJSONArray("preferences")?.forEach { _, any -> + if (any is JSONObject) { + lateinit var p: Preference + // Create Preference + when (any.getStr("type")) { + "EditTextPreference" -> { + p = EditTextPreference(context).apply { + when (any.getStr("summaryProvider")) { + null -> summaryProvider = androidx.preference.EditTextPreference.SimpleSummaryProvider.getInstance() + "PasswordSummaryProvider" -> summaryProvider = ProfileSettingsActivity.PasswordSummaryProvider + } + when (any.getStr("EditTextPreferenceModifiers")) { + "Monospace" -> onBindEditTextListener = EditTextPreferenceModifiers.Monospace + "Hosts" -> onBindEditTextListener = EditTextPreferenceModifiers.Hosts + "Port" -> onBindEditTextListener = EditTextPreferenceModifiers.Port + "Number" -> onBindEditTextListener = EditTextPreferenceModifiers.Number + } + } + } + "SwitchPreference" -> { + p = SwitchPreference(context) + } + "SimpleMenuPreference" -> { + p = SimpleMenuPreference(context).apply { + summaryProvider = androidx.preference.ListPreference.SimpleSummaryProvider.getInstance() + val entries = any.optJSONObject("entries") + if (entries != null) setMenu(this, entries) + } + } + } + // Set key & title + p.key = any.getString("key") + any.getStr("title")?.apply { p.title = this } + // Set icon + any.getStr("icon")?.apply { + p.icon = context.getDrawableByName(this) + } + // Set summary + any.getStr("summary")?.apply { + p.summary = this + } + // Add to category + preferenceCategory.addPreference(p) + } + } + } + } + + fun setMenu(p: SimpleMenuPreference, entries: JSONObject) { + val menuEntries = mutableListOf() + val menuEntryValues = mutableListOf() + entries.forEach { s, b -> + menuEntryValues.add(s) + menuEntries.add(b as String) + } + entries.apply { + p.setEntries(menuEntries.toTypedArray()) + p.setEntryValues(menuEntryValues.toTypedArray()) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoSettingActivity.kt b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoSettingActivity.kt new file mode 100644 index 0000000..0f3eb7c --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoSettingActivity.kt @@ -0,0 +1,102 @@ +package moe.matsuri.nb4a.proxy.neko + +import android.os.Bundle +import android.view.View +import androidx.core.view.isVisible +import androidx.preference.PreferenceDataStore +import com.takisoft.preferencex.PreferenceFragmentCompat +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.ktx.runOnIoDispatcher +import io.nekohasekai.sagernet.ui.profile.ProfileSettingsActivity +import moe.matsuri.nb4a.ui.Dialogs +import org.json.JSONArray + +class NekoSettingActivity : ProfileSettingsActivity() { + + lateinit var jsi: NekoJSInterface + lateinit var jsip: NekoJSInterface.NekoProtocol + lateinit var plgId: String + lateinit var protocolId: String + var loaded = false + + override fun createEntity() = NekoBean() + + override fun NekoBean.init() { + if (!this@NekoSettingActivity::plgId.isInitialized) this@NekoSettingActivity.plgId = plgId + if (!this@NekoSettingActivity::protocolId.isInitialized) this@NekoSettingActivity.protocolId = protocolId + DataStore.profileCacheStore.putString("name", name) + DataStore.sharedStorage = sharedStorage.toString() + } + + override fun NekoBean.serialize() { + // NekoBean from input + plgId = this@NekoSettingActivity.plgId + protocolId = this@NekoSettingActivity.protocolId + + sharedStorage = NekoBean.tryParseJSON(DataStore.sharedStorage) + onSharedStorageSet() + } + + override fun onCreate(savedInstanceState: Bundle?) { + intent?.getStringExtra("plgId")?.apply { plgId = this } + intent?.getStringExtra("protocolId")?.apply { protocolId = this } + super.onCreate(savedInstanceState) + } + + override fun PreferenceFragmentCompat.viewCreated(view: View, savedInstanceState: Bundle?) { + listView.isVisible = false + } + + override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) { + if (loaded && key != Key.PROFILE_DIRTY) { + DataStore.dirty = true + } + } + + override fun PreferenceFragmentCompat.createPreferences( + savedInstanceState: Bundle?, + rootKey: String?, + ) { + addPreferencesFromResource(R.xml.neko_preferences) + + // Create a jsi + jsi = NekoJSInterface(plgId) + runOnIoDispatcher { + try { + jsi.init() + jsip = jsi.switchProtocol(protocolId) + jsi.jsObject.preferenceScreen = preferenceScreen + + // Because of the Preference problem, first require the KV and then inflate the UI + jsip.setSharedStorage(DataStore.sharedStorage) + jsip.requireSetProfileCache() + + val config = jsip.requirePreferenceScreenConfig() + val pref = JSONArray(config) + + NekoPreferenceInflater.inflate(pref, preferenceScreen) + jsip.onPreferenceCreated() + + runOnUiThread { + loaded = true + listView.isVisible = true + } + } catch (e: Exception) { + Dialogs.logExceptionAndShow(this@NekoSettingActivity, e) { finish() } + } + } + } + + override suspend fun saveAndExit() { + DataStore.sharedStorage = jsip.sharedStorageFromProfileCache() + super.saveAndExit() // serialize & finish + } + + override fun onDestroy() { + jsi.destroy() + super.onDestroy() + } + +} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/ui/ColorPickerPreference.kt b/app/src/main/java/moe/matsuri/nb4a/ui/ColorPickerPreference.kt new file mode 100644 index 0000000..ee0728e --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/ui/ColorPickerPreference.kt @@ -0,0 +1,120 @@ +package moe.matsuri.nb4a.ui + +import android.content.Context +import android.content.res.Resources +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.GridLayout +import android.widget.ImageView +import android.widget.LinearLayout +import androidx.appcompat.app.AlertDialog +import androidx.core.content.res.ResourcesCompat +import androidx.core.content.res.TypedArrayUtils +import androidx.core.graphics.drawable.DrawableCompat +import androidx.core.view.setPadding +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.ktx.getColorAttr +import io.nekohasekai.sagernet.ktx.isExpertFlavor +import kotlin.math.roundToInt + +class ColorPickerPreference +@JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyle: Int = TypedArrayUtils.getAttr( + context, + androidx.preference.R.attr.editTextPreferenceStyle, + android.R.attr.editTextPreferenceStyle + ) +) : Preference( + context, attrs, defStyle +) { + + var inited = false + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + + val widgetFrame = holder.findViewById(android.R.id.widget_frame) as LinearLayout + + if (!inited) { + inited = true + + widgetFrame.addView( + getNekoImageViewAtColor( + context.getColorAttr(R.attr.colorPrimary), + 48, + 0 + ) + ) + widgetFrame.visibility = View.VISIBLE + } + } + + fun getNekoImageViewAtColor(color: Int, sizeDp: Int, paddingDp: Int): ImageView { + // dp to pixel + val factor = context.resources.displayMetrics.density + val size = (sizeDp * factor).roundToInt() + val paddingSize = (paddingDp * factor).roundToInt() + + return ImageView(context).apply { + layoutParams = ViewGroup.LayoutParams(size, size) + setPadding(paddingSize) + setImageDrawable(getNekoAtColor(resources, color)) + } + } + + fun getNekoAtColor(res: Resources, color: Int): Drawable { + val neko = ResourcesCompat.getDrawable( + res, + R.drawable.ic_baseline_fiber_manual_record_24, + null + )!! + DrawableCompat.setTint(neko.mutate(), color) + return neko + } + + override fun onClick() { + super.onClick() + + lateinit var dialog: AlertDialog + + val grid = GridLayout(context).apply { + columnCount = 4 + + val colors = context.resources.getIntArray(R.array.material_colors) + var i = 0 + + for (color in colors) { + i++ //Theme.kt + if (!isExpertFlavor && i in listOf(21)) continue + + val themeId = i + val view = getNekoImageViewAtColor(color, 64, 0).apply { + setOnClickListener { + persistInt(themeId) + dialog.dismiss() + callChangeListener(themeId) + } + } + addView(view) + } + + } + + dialog = MaterialAlertDialogBuilder(context).setTitle(title) + .setView(LinearLayout(context).apply { + gravity = Gravity.CENTER + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT + ) + addView(grid) + }) + .setNegativeButton(android.R.string.cancel, null) + .show() + } +} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/ui/Dialogs.kt b/app/src/main/java/moe/matsuri/nb4a/ui/Dialogs.kt new file mode 100644 index 0000000..0bc7de4 --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/ui/Dialogs.kt @@ -0,0 +1,38 @@ +package moe.matsuri.nb4a.ui + +import android.content.Context +import android.widget.TextView +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.ktx.readableMessage +import io.nekohasekai.sagernet.ktx.runOnMainDispatcher + +object Dialogs { + fun logExceptionAndShow(context: Context, e: Exception, callback: Runnable) { + Logs.e(e) + runOnMainDispatcher { + MaterialAlertDialogBuilder(context) + .setTitle(R.string.error_title) + .setMessage(e.readableMessage) + .setCancelable(false) + .setPositiveButton(android.R.string.ok) { _, _ -> + callback.run() + } + .show() + } + } + + fun message(context: Context, title: String, message: String) { + runOnMainDispatcher { + val dialog = MaterialAlertDialogBuilder(context) + .setTitle(title) + .setMessage(message) + .setCancelable(true) + .show() + dialog.findViewById(android.R.id.message)?.apply { + setTextIsSelectable(true) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/ui/LongClickSwitchPreference.kt b/app/src/main/java/moe/matsuri/nb4a/ui/LongClickSwitchPreference.kt new file mode 100644 index 0000000..aee8dfe --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/ui/LongClickSwitchPreference.kt @@ -0,0 +1,33 @@ +package moe.matsuri.nb4a.ui + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import androidx.core.content.res.TypedArrayUtils +import androidx.preference.PreferenceViewHolder +import androidx.preference.R +import androidx.preference.SwitchPreference + +class LongClickSwitchPreference +@JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = TypedArrayUtils.getAttr( + context, R.attr.switchPreferenceStyle, android.R.attr.switchPreferenceStyle + ), defStyleRes: Int = 0 +) : SwitchPreference( + context, attrs, defStyleAttr, defStyleRes +) { + private var mLongClickListener: View.OnLongClickListener? = null + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + val itemView: View = holder.itemView + itemView.setOnLongClickListener { + mLongClickListener?.onLongClick(it) ?: true + } + } + + fun setOnLongClickListener(longClickListener: View.OnLongClickListener) { + this.mLongClickListener = longClickListener + } + +} diff --git a/app/src/main/java/moe/matsuri/nb4a/ui/MTUPreference.kt b/app/src/main/java/moe/matsuri/nb4a/ui/MTUPreference.kt new file mode 100644 index 0000000..cbcdf8d --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/ui/MTUPreference.kt @@ -0,0 +1,51 @@ +package moe.matsuri.nb4a.ui + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.EditText +import androidx.preference.PreferenceViewHolder +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.takisoft.preferencex.SimpleMenuPreference + +class MTUPreference : SimpleMenuPreference { + constructor(context: Context?) : super(context) + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) + + constructor( + context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) + + init { + setSummaryProvider { + value.toString() + } + } + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + val itemView: View = holder.itemView + itemView.setOnLongClickListener { + val view = EditText(context).apply { + inputType = EditorInfo.TYPE_CLASS_NUMBER + setText(preferenceDataStore?.getString(key, "") ?: "") + } + + MaterialAlertDialogBuilder(context).setTitle("MTU") + .setView(view) + .setPositiveButton(android.R.string.ok) { _, _ -> + val mtu = view.text.toString().toInt() + if (mtu < 1000 || mtu > 10000) return@setPositiveButton + value = mtu.toString() + } + .setNegativeButton(android.R.string.cancel, null) + .show() + true + } + } + +} diff --git a/app/src/main/java/moe/matsuri/nb4a/utils/JavaUtil.java b/app/src/main/java/moe/matsuri/nb4a/utils/JavaUtil.java new file mode 100644 index 0000000..1e36147 --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/utils/JavaUtil.java @@ -0,0 +1,200 @@ +package moe.matsuri.nb4a.utils; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Application; +import android.content.Context; +import android.os.Build; +import android.text.TextUtils; +import android.webkit.WebView; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.ToNumberPolicy; + +import java.io.File; +import java.io.RandomAccessFile; +import java.lang.reflect.Method; +import java.nio.channels.FileLock; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.nekohasekai.sagernet.BuildConfig; +import io.nekohasekai.sagernet.ktx.Logs; +import kotlin.text.StringsKt; + +public class JavaUtil { + + // The encoded character of each character escape. + // This array functions as the keys of a sorted map, from encoded characters to decoded characters. + static final char[] ENCODED_ESCAPES = {'\"', '\'', '\\', 'b', 'f', 'n', 'r', 't'}; + + // The decoded character of each character escape. + // This array functions as the values of a sorted map, from encoded characters to decoded characters. + static final char[] DECODED_ESCAPES = {'\"', '\'', '\\', '\b', '\f', '\n', '\r', '\t'}; + + // A pattern that matches an escape. + // What follows the escape indicator is captured by group 1=character 2=octal 3=Unicode. + static final Pattern PATTERN = Pattern.compile("\\\\(?:(b|t|n|f|r|\\\"|\\\'|\\\\)|((?:[0-3]?[0-7])?[0-7])|u+(\\p{XDigit}{4}))"); + + // Process the return of webView.evaluateJavascript + public static String unescapeString(CharSequence encodedString) { + Matcher matcher = PATTERN.matcher(encodedString); + StringBuffer decodedString = new StringBuffer(); + // Find each escape of the encoded string in succession. + while (matcher.find()) { + char ch; + if (matcher.start(1) >= 0) { + // Decode a character escape. + ch = DECODED_ESCAPES[Arrays.binarySearch(ENCODED_ESCAPES, matcher.group(1).charAt(0))]; + } else if (matcher.start(2) >= 0) { + // Decode an octal escape. + ch = (char) (Integer.parseInt(matcher.group(2), 8)); + } else /* if (matcher.start(3) >= 0) */ { + // Decode a Unicode escape. + ch = (char) (Integer.parseInt(matcher.group(3), 16)); + } + // Replace the escape with the decoded character. + matcher.appendReplacement(decodedString, Matcher.quoteReplacement(String.valueOf(ch))); + } + // Append the remainder of the encoded string to the decoded string. + // The remainder is the longest suffix of the encoded string such that the suffix contains no escapes. + matcher.appendTail(decodedString); + return new String(decodedString); + } + + // Webview Utils + + public static void handleWebviewDir(Context context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + return; + } + try { + Set pathSet = new HashSet<>(); + String suffix; + String dataPath = context.getDataDir().getAbsolutePath(); + String webViewDir = "/app_webview"; + String huaweiWebViewDir = "/app_hws_webview"; + String lockFile = "/webview_data.lock"; + String processName = Application.getProcessName(); + if (!BuildConfig.APPLICATION_ID.equals(processName)) {//判断不等于默认进程名称 + suffix = TextUtils.isEmpty(processName) ? context.getPackageName() : processName; + WebView.setDataDirectorySuffix(suffix); + suffix = "_" + suffix; + pathSet.add(dataPath + webViewDir + suffix + lockFile); + if (checkIsHuaweiRom()) { + pathSet.add(dataPath + huaweiWebViewDir + suffix + lockFile); + } + } else { + //主进程 + suffix = "_" + processName; + pathSet.add(dataPath + webViewDir + lockFile);//默认未添加进程名后缀 + pathSet.add(dataPath + webViewDir + suffix + lockFile);//系统自动添加了进程名后缀 + if (checkIsHuaweiRom()) {//部分华为手机更改了webview目录名 + pathSet.add(dataPath + huaweiWebViewDir + lockFile); + pathSet.add(dataPath + huaweiWebViewDir + suffix + lockFile); + } + } + for (String path : pathSet) { + File file = new File(path); + if (file.exists()) { + tryLockOrRecreateFile(file); + break; + } + } + } catch (Exception e) { + Logs.INSTANCE.e(e); + } + } + + @TargetApi(Build.VERSION_CODES.P) + private static void tryLockOrRecreateFile(File file) { + try { + FileLock tryLock = new RandomAccessFile(file, "rw").getChannel().tryLock(); + if (tryLock != null) { + tryLock.close(); + } else { + createFile(file, file.delete()); + } + } catch (Exception e) { + e.printStackTrace(); + boolean deleted = false; + if (file.exists()) { + deleted = file.delete(); + } + createFile(file, deleted); + } + } + + private static void createFile(File file, boolean deleted) { + try { + if (deleted && !file.exists()) { + file.createNewFile(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static boolean checkIsHuaweiRom() { + return Build.MANUFACTURER.contains("HUAWEI"); + } + + @SuppressLint("PrivateApi") + public static String getProcessName() { + if (Build.VERSION.SDK_INT >= 28) + return Application.getProcessName(); + + // Using the same technique as Application.getProcessName() for older devices + // Using reflection since ActivityThread is an internal API + + try { + Class activityThread = Class.forName("android.app.ActivityThread"); + String methodName = "currentProcessName"; + Method getProcessName = activityThread.getDeclaredMethod(methodName); + return (String) getProcessName.invoke(null); + } catch (Exception e) { + return BuildConfig.APPLICATION_ID; + } + } + + // Old hutool Utils + + public static boolean isNullOrBlank(String str) { + return str == null || StringsKt.isBlank(str); + } + + public static boolean isNotBlank(String str) { + return !isNullOrBlank(str); + } + + private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray(); + + public static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars); + } + + public static boolean isEmpty(byte[] array) { + return array == null || array.length == 0; + } + + // gson + + public static final Gson gson = new GsonBuilder() + .setPrettyPrinting() + .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE) + .setLenient() + .disableHtmlEscaping() + .create(); + +} diff --git a/app/src/main/java/moe/matsuri/nb4a/utils/KotlinUtil.kt b/app/src/main/java/moe/matsuri/nb4a/utils/KotlinUtil.kt new file mode 100644 index 0000000..0b45095 --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/utils/KotlinUtil.kt @@ -0,0 +1,53 @@ +package moe.matsuri.nb4a.utils + +import android.content.Context +import android.graphics.drawable.Drawable +import androidx.appcompat.content.res.AppCompatResources +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.ktx.Logs +import java.io.File + +// SagerNet Class + +fun SagerNet.cleanWebview() { + var pathToClean = "app_webview" + if (isBgProcess) pathToClean += "_$process" + try { + val dataDir = filesDir.parentFile!! + File(dataDir, "$pathToClean/BrowserMetrics").recreate(true) + File(dataDir, "$pathToClean/BrowserMetrics-spare.pma").recreate(false) + } catch (e: Exception) { + Logs.e(e) + } +} + +fun File.recreate(dir: Boolean) { + if (parentFile?.isDirectory != true) return + if (dir && !isFile) { + if (exists()) deleteRecursively() + createNewFile() + } else if (!dir && !isDirectory) { + if (exists()) delete() + mkdir() + } +} + +// Context utils + +fun Context.getDrawableByName(name: String?): Drawable? { + val resourceId: Int = resources.getIdentifier(name, "drawable", packageName) + return AppCompatResources.getDrawable(this, resourceId) +} + +// Traffic display + +fun Long.toBytesString(): String { + return when { + this > 1024 * 1024 * 1024 -> String.format( + "%.2f GiB", (this.toDouble() / 1024 / 1024 / 1024) + ) + this > 1024 * 1024 -> String.format("%.2f MiB", (this.toDouble() / 1024 / 1024)) + this > 1024 -> String.format("%.2f KiB", (this.toDouble() / 1024)) + else -> "$this Bytes" + } +} diff --git a/app/src/main/java/moe/matsuri/nb4a/utils/NGUtil.kt b/app/src/main/java/moe/matsuri/nb4a/utils/NGUtil.kt new file mode 100644 index 0000000..7035104 --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/utils/NGUtil.kt @@ -0,0 +1,237 @@ +package moe.matsuri.nb4a.utils + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.text.Editable +import android.util.Base64 +import io.nekohasekai.sagernet.ktx.Logs +import java.net.URLDecoder +import java.net.URLEncoder +import java.util.* + +// Copy form v2rayNG to parse their stupid format + +object NGUtil { + + /** + * convert string to editalbe for kotlin + * + * @param text + * @return + */ + fun getEditable(text: String): Editable { + return Editable.Factory.getInstance().newEditable(text) + } + + /** + * find value in array position + */ + fun arrayFind(array: Array, value: String): Int { + for (i in array.indices) { + if (array[i] == value) { + return i + } + } + return -1 + } + + /** + * parseInt + */ + fun parseInt(str: String): Int { + return try { + Integer.parseInt(str) + } catch (e: Exception) { + e.printStackTrace() + 0 + } + } + + /** + * get text from clipboard + */ + fun getClipboard(context: Context): String { + return try { + val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + cmb.primaryClip?.getItemAt(0)?.text.toString() + } catch (e: Exception) { + e.printStackTrace() + "" + } + } + + /** + * set text to clipboard + */ + fun setClipboard(context: Context, content: String) { + try { + val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clipData = ClipData.newPlainText(null, content) + cmb.setPrimaryClip(clipData) + } catch (e: Exception) { + e.printStackTrace() + } + } + + /** + * base64 decode + */ + fun decode(text: String): String { + tryDecodeBase64(text)?.let { return it } + if (text.endsWith('=')) { + // try again for some loosely formatted base64 + tryDecodeBase64(text.trimEnd('='))?.let { return it } + } + return "" + } + + fun tryDecodeBase64(text: String): String? { + try { + return Base64.decode(text, Base64.NO_WRAP).toString(charset("UTF-8")) + } catch (e: Exception) { + Logs.i( "Parse base64 standard failed $e") + } + try { + return Base64.decode(text, Base64.NO_WRAP.or(Base64.URL_SAFE)).toString(charset("UTF-8")) + } catch (e: Exception) { + Logs.i( "Parse base64 url safe failed $e") + } + return null + } + + /** + * base64 encode + */ + fun encode(text: String): String { + return try { + Base64.encodeToString(text.toByteArray(charset("UTF-8")), Base64.NO_WRAP) + } catch (e: Exception) { + e.printStackTrace() + "" + } + } + + /** + * is ip address + */ + fun isIpAddress(value: String): Boolean { + try { + var addr = value + if (addr.isEmpty() || addr.isBlank()) { + return false + } + //CIDR + if (addr.indexOf("/") > 0) { + val arr = addr.split("/") + if (arr.count() == 2 && Integer.parseInt(arr[1]) > 0) { + addr = arr[0] + } + } + + // "::ffff:192.168.173.22" + // "[::ffff:192.168.173.22]:80" + if (addr.startsWith("::ffff:") && '.' in addr) { + addr = addr.drop(7) + } else if (addr.startsWith("[::ffff:") && '.' in addr) { + addr = addr.drop(8).replace("]", "") + } + + // addr = addr.toLowerCase() + val octets = addr.split('.').toTypedArray() + if (octets.size == 4) { + if(octets[3].indexOf(":") > 0) { + addr = addr.substring(0, addr.indexOf(":")) + } + return isIpv4Address(addr) + } + + // Ipv6addr [2001:abc::123]:8080 + return isIpv6Address(addr) + } catch (e: Exception) { + e.printStackTrace() + return false + } + } + + fun isPureIpAddress(value: String): Boolean { + return (isIpv4Address(value) || isIpv6Address(value)) + } + + fun isIpv4Address(value: String): Boolean { + val regV4 = Regex("^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$") + return regV4.matches(value) + } + + fun isIpv6Address(value: String): Boolean { + var addr = value + if (addr.indexOf("[") == 0 && addr.lastIndexOf("]") > 0) { + addr = addr.drop(1) + addr = addr.dropLast(addr.count() - addr.lastIndexOf("]")) + } + val regV6 = Regex("^((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7}$") + return regV6.matches(addr) + } + + private fun isCoreDNSAddress(s: String): Boolean { + return s.startsWith("https") || s.startsWith("tcp") || s.startsWith("quic") + } + + fun openUri(context: Context, uriString: String) { + val uri = Uri.parse(uriString) + context.startActivity(Intent(Intent.ACTION_VIEW, uri)) + } + + /** + * uuid + */ + fun getUuid(): String { + return try { + UUID.randomUUID().toString().replace("-", "") + } catch (e: Exception) { + e.printStackTrace() + "" + } + } + + fun urlDecode(url: String): String { + return try { + URLDecoder.decode(url, "UTF-8") + } catch (e: Exception) { + url + } + } + + fun urlEncode(url: String): String { + return try { + URLEncoder.encode(url, "UTF-8") + } catch (e: Exception) { + e.printStackTrace() + url + } + } + + /** + * package path + */ + fun packagePath(context: Context): String { + var path = context.filesDir.toString() + path = path.replace("files", "") + //path += "tun2socks" + + return path + } + + /** + * readTextFromAssets + */ + fun readTextFromAssets(context: Context, fileName: String): String { + val content = context.assets.open(fileName).bufferedReader().use { + it.readText() + } + return content + } + +} \ No newline at end of file diff --git a/app/src/main/java/moe/matsuri/nb4a/utils/SendLog.kt b/app/src/main/java/moe/matsuri/nb4a/utils/SendLog.kt new file mode 100644 index 0000000..b37384e --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/utils/SendLog.kt @@ -0,0 +1,73 @@ +package moe.matsuri.nb4a.utils + +import android.content.Context +import android.content.Intent +import androidx.core.content.FileProvider +import io.nekohasekai.sagernet.BuildConfig +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.ktx.app +import io.nekohasekai.sagernet.ktx.use +import io.nekohasekai.sagernet.utils.CrashHandler +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.IOException + +object SendLog { + // Create full log and send + fun sendLog(context: Context, title: String) { + val logFile = File.createTempFile( + "$title ", + ".log", + File(app.cacheDir, "log").also { it.mkdirs() }) + + var report = CrashHandler.buildReportHeader() + + report += "Logcat: \n\n" + + logFile.writeText(report) + + try { + Runtime.getRuntime().exec(arrayOf("logcat", "-d")).inputStream.use( + FileOutputStream( + logFile, true + ) + ) + logFile.appendText("\n") + } catch (e: IOException) { + Logs.w(e) + logFile.appendText("Export logcat error: " + CrashHandler.formatThrowable(e)) + } + + logFile.appendText("\n") + logFile.appendBytes(getNekoLog(0)) + + context.startActivity( + Intent.createChooser( + Intent(Intent.ACTION_SEND).setType("text/x-log") + .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + .putExtra( + Intent.EXTRA_STREAM, FileProvider.getUriForFile( + context, BuildConfig.APPLICATION_ID + ".cache", logFile + ) + ), context.getString(R.string.abc_shareactionprovider_share_with) + ) + ) + } + + // Get log bytes from neko.log + fun getNekoLog(max: Long): ByteArray { + val file = File( + SagerNet.application.cacheDir, + "neko.log" + ) + val len = file.length() + val stream = FileInputStream(file) + if (max in 1 until len) { + stream.skip(len - max) // TODO string? + } + return stream.readBytes() + } +} diff --git a/app/src/main/java/moe/matsuri/nb4a/utils/Util.kt b/app/src/main/java/moe/matsuri/nb4a/utils/Util.kt new file mode 100644 index 0000000..1a077bc --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/utils/Util.kt @@ -0,0 +1,137 @@ +package moe.matsuri.nb4a.utils + +import android.annotation.SuppressLint +import android.util.Base64 +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement +import com.google.gson.JsonParseException +import java.io.ByteArrayOutputStream +import java.lang.reflect.Type +import java.text.NumberFormat +import java.text.SimpleDateFormat +import java.util.* +import java.util.zip.Deflater +import java.util.zip.Inflater + +object Util { + + /** + * 取两个文本之间的文本值 + * + * @param text 源文本 比如:欲取全文本为 12345 + * @param left 文本前面 + * @param right 后面文本 + * @return 返回 String + */ + fun getSubString(text: String, left: String?, right: String?): String { + var zLen: Int + if (left == null || left.isEmpty()) { + zLen = 0 + } else { + zLen = text.indexOf(left) + if (zLen > -1) { + zLen += left.length + } else { + zLen = 0 + } + } + var yLen = if (right == null) -1 else text.indexOf(right, zLen) + if (yLen < 0 || right == null || right.isEmpty()) { + yLen = text.length + } + return text.substring(zLen, yLen) + } + + // Base64 for all + + fun b64EncodeUrlSafe(s: String): String { + return b64EncodeUrlSafe(s.toByteArray()) + } + + fun b64EncodeUrlSafe(b: ByteArray): String { + return String(Base64.encode(b, Base64.NO_PADDING or Base64.NO_WRAP or Base64.URL_SAFE)) + } + + // v2rayN Style + fun b64EncodeOneLine(b: ByteArray): String { + return String(Base64.encode(b, Base64.NO_WRAP)) + } + + fun b64EncodeDefault(b: ByteArray): String { + return String(Base64.encode(b, Base64.DEFAULT)) + } + + fun b64Decode(b: String): ByteArray { + var ret: ByteArray? = null + + // padding 自动处理,不用理 + // URLSafe 需要替换这两个,不要用 URL_SAFE 否则处理非 Safe 的时候会乱码 + val str = b.replace("-", "+").replace("_", "/") + + val flags = listOf( + Base64.DEFAULT, // 多行 + Base64.NO_WRAP, // 单行 + ) + + for (flag in flags) { + try { + ret = Base64.decode(str, flag) + } catch (e: Exception) { + } + if (ret != null) return ret + } + + throw IllegalStateException("Cannot decode base64") + } + + fun zlibCompress(input: ByteArray, level: Int): ByteArray { + // Compress the bytes + // 1 to 4 bytes/char for UTF-8 + val output = ByteArray(input.size * 4) + val compressor = Deflater(level).apply { + setInput(input) + finish() + } + val compressedDataLength: Int = compressor.deflate(output) + compressor.end() + return output.copyOfRange(0, compressedDataLength) + } + + fun zlibDecompress(input: ByteArray): ByteArray { + val inflater = Inflater() + val outputStream = ByteArrayOutputStream() + + return outputStream.use { + val buffer = ByteArray(1024) + + inflater.setInput(input) + + var count = -1 + while (count != 0) { + count = inflater.inflate(buffer) + outputStream.write(buffer, 0, count) + } + + inflater.end() + outputStream.toByteArray() + } + } + + // Format Time + + @SuppressLint("SimpleDateFormat") + val sdf1 = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + + fun timeStamp2Text(t: Long): String { + return sdf1.format(Date(t)) + } + + fun tryToSetField(o: Any, name: String, value: Any) { + try { + o.javaClass.getField(name).set(o, value) + } catch (_: Exception) { + } + } + +} diff --git a/app/src/main/java/moe/matsuri/nb4a/utils/WebViewUtil.kt b/app/src/main/java/moe/matsuri/nb4a/utils/WebViewUtil.kt new file mode 100644 index 0000000..ebce693 --- /dev/null +++ b/app/src/main/java/moe/matsuri/nb4a/utils/WebViewUtil.kt @@ -0,0 +1,38 @@ +package moe.matsuri.nb4a.utils + +import android.os.Build +import android.webkit.WebResourceError +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView +import io.nekohasekai.sagernet.ktx.Logs +import java.io.ByteArrayInputStream +import java.io.InputStream + +object WebViewUtil { + fun onReceivedError( + view: WebView?, request: WebResourceRequest?, error: WebResourceError? + ) { + if (Build.VERSION.SDK_INT >= 23 && error != null) { + Logs.e("WebView error description: ${error.description}") + } + Logs.e("WebView error: ${error.toString()}") + } + + fun interceptRequest( + res: (String) -> InputStream?, view: WebView?, request: WebResourceRequest? + ): WebResourceResponse { + val path = request?.url?.path ?: "404" + val input = res(path) + var mime = "text/plain" + if (path.endsWith(".js")) mime = "application/javascript" + if (path.endsWith(".html")) mime = "text/html" + return if (input != null) { + WebResourceResponse(mime, "UTF-8", input) + } else { + WebResourceResponse( + "text/plain", "UTF-8", ByteArrayInputStream("".toByteArray()) + ) + } + } +} diff --git a/app/src/main/res/color/chip_background.xml b/app/src/main/res/color/chip_background.xml new file mode 100644 index 0000000..01a73d6 --- /dev/null +++ b/app/src/main/res/color/chip_background.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/src/main/res/color/chip_ripple_color.xml b/app/src/main/res/color/chip_ripple_color.xml new file mode 100644 index 0000000..24e6fb4 --- /dev/null +++ b/app/src/main/res/color/chip_ripple_color.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/app/src/main/res/color/chip_text_color.xml b/app/src/main/res/color/chip_text_color.xml new file mode 100644 index 0000000..c1319e0 --- /dev/null +++ b/app/src/main/res/color/chip_text_color.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/color/navigation_icon.xml b/app/src/main/res/color/navigation_icon.xml new file mode 100644 index 0000000..99c3f94 --- /dev/null +++ b/app/src/main/res/color/navigation_icon.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/navigation_item.xml b/app/src/main/res/color/navigation_item.xml new file mode 100644 index 0000000..f21c999 --- /dev/null +++ b/app/src/main/res/color/navigation_item.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v26/ic_qu_camera_launcher.xml b/app/src/main/res/drawable-v26/ic_qu_camera_launcher.xml new file mode 100644 index 0000000..93d5410 --- /dev/null +++ b/app/src/main/res/drawable-v26/ic_qu_camera_launcher.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/app/src/main/res/drawable-v26/ic_qu_shadowsocks_launcher.xml b/app/src/main/res/drawable-v26/ic_qu_shadowsocks_launcher.xml new file mode 100644 index 0000000..c039c2b --- /dev/null +++ b/app/src/main/res/drawable-v26/ic_qu_shadowsocks_launcher.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/baseline_arrow_back_24.xml b/app/src/main/res/drawable/baseline_arrow_back_24.xml new file mode 100644 index 0000000..2a31b2e --- /dev/null +++ b/app/src/main/res/drawable/baseline_arrow_back_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/baseline_construction_24.xml b/app/src/main/res/drawable/baseline_construction_24.xml new file mode 100644 index 0000000..1828ec8 --- /dev/null +++ b/app/src/main/res/drawable/baseline_construction_24.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/baseline_delete_sweep_24.xml b/app/src/main/res/drawable/baseline_delete_sweep_24.xml new file mode 100644 index 0000000..22560a4 --- /dev/null +++ b/app/src/main/res/drawable/baseline_delete_sweep_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/baseline_developer_board_24.xml b/app/src/main/res/drawable/baseline_developer_board_24.xml new file mode 100644 index 0000000..100c0e9 --- /dev/null +++ b/app/src/main/res/drawable/baseline_developer_board_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/baseline_flight_takeoff_24.xml b/app/src/main/res/drawable/baseline_flight_takeoff_24.xml new file mode 100644 index 0000000..081726c --- /dev/null +++ b/app/src/main/res/drawable/baseline_flight_takeoff_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/baseline_public_24.xml b/app/src/main/res/drawable/baseline_public_24.xml new file mode 100644 index 0000000..19fb425 --- /dev/null +++ b/app/src/main/res/drawable/baseline_public_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/baseline_save_24.xml b/app/src/main/res/drawable/baseline_save_24.xml new file mode 100644 index 0000000..1a8d86d --- /dev/null +++ b/app/src/main/res/drawable/baseline_save_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/baseline_send_24.xml b/app/src/main/res/drawable/baseline_send_24.xml new file mode 100644 index 0000000..f0d63e1 --- /dev/null +++ b/app/src/main/res/drawable/baseline_send_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/baseline_translate_24.xml b/app/src/main/res/drawable/baseline_translate_24.xml new file mode 100644 index 0000000..4e7e364 --- /dev/null +++ b/app/src/main/res/drawable/baseline_translate_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/baseline_widgets_24.xml b/app/src/main/res/drawable/baseline_widgets_24.xml new file mode 100644 index 0000000..05b871b --- /dev/null +++ b/app/src/main/res/drawable/baseline_widgets_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/baseline_wrap_text_24.xml b/app/src/main/res/drawable/baseline_wrap_text_24.xml new file mode 100644 index 0000000..c9de17b --- /dev/null +++ b/app/src/main/res/drawable/baseline_wrap_text_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_action_copyright.xml b/app/src/main/res/drawable/ic_action_copyright.xml new file mode 100644 index 0000000..fd76192 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_copyright.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/drawable/ic_action_delete.xml b/app/src/main/res/drawable/ic_action_delete.xml new file mode 100644 index 0000000..0e9d1eb --- /dev/null +++ b/app/src/main/res/drawable/ic_action_delete.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_action_description.xml b/app/src/main/res/drawable/ic_action_description.xml new file mode 100644 index 0000000..98bda1a --- /dev/null +++ b/app/src/main/res/drawable/ic_action_description.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_action_dns.xml b/app/src/main/res/drawable/ic_action_dns.xml new file mode 100644 index 0000000..dd725be --- /dev/null +++ b/app/src/main/res/drawable/ic_action_dns.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_action_done.xml b/app/src/main/res/drawable/ic_action_done.xml new file mode 100644 index 0000000..8bf04e3 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_done.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_action_lock.xml b/app/src/main/res/drawable/ic_action_lock.xml new file mode 100644 index 0000000..811a5b6 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_lock.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_action_lock_open.xml b/app/src/main/res/drawable/ic_action_lock_open.xml new file mode 100644 index 0000000..169e93a --- /dev/null +++ b/app/src/main/res/drawable/ic_action_lock_open.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/ic_action_note_add.xml b/app/src/main/res/drawable/ic_action_note_add.xml new file mode 100644 index 0000000..c8ffa88 --- /dev/null +++ b/app/src/main/res/drawable/ic_action_note_add.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_action_settings.xml b/app/src/main/res/drawable/ic_action_settings.xml new file mode 100644 index 0000000..1f0802a --- /dev/null +++ b/app/src/main/res/drawable/ic_action_settings.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_app_shortcut_background.xml b/app/src/main/res/drawable/ic_app_shortcut_background.xml new file mode 100644 index 0000000..d949e9f --- /dev/null +++ b/app/src/main/res/drawable/ic_app_shortcut_background.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_av_playlist_add.xml b/app/src/main/res/drawable/ic_av_playlist_add.xml new file mode 100644 index 0000000..7296673 --- /dev/null +++ b/app/src/main/res/drawable/ic_av_playlist_add.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_add_road_24.xml b/app/src/main/res/drawable/ic_baseline_add_road_24.xml new file mode 100644 index 0000000..0b8771e --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_add_road_24.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_baseline_airplanemode_active_24.xml b/app/src/main/res/drawable/ic_baseline_airplanemode_active_24.xml new file mode 100644 index 0000000..0ad3ad1 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_airplanemode_active_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_android_24.xml b/app/src/main/res/drawable/ic_baseline_android_24.xml new file mode 100644 index 0000000..9a2e721 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_android_24.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_bug_report_24.xml b/app/src/main/res/drawable/ic_baseline_bug_report_24.xml new file mode 100644 index 0000000..7853f61 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_bug_report_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_camera_24.xml b/app/src/main/res/drawable/ic_baseline_camera_24.xml new file mode 100644 index 0000000..c0d3501 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_camera_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_card_giftcard_24.xml b/app/src/main/res/drawable/ic_baseline_card_giftcard_24.xml new file mode 100644 index 0000000..d5b3e1c --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_card_giftcard_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_cast_connected_24.xml b/app/src/main/res/drawable/ic_baseline_cast_connected_24.xml new file mode 100644 index 0000000..0e44ec6 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_cast_connected_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_center_focus_weak_24.xml b/app/src/main/res/drawable/ic_baseline_center_focus_weak_24.xml new file mode 100644 index 0000000..eb56c21 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_center_focus_weak_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_color_lens_24.xml b/app/src/main/res/drawable/ic_baseline_color_lens_24.xml new file mode 100644 index 0000000..4bf1550 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_color_lens_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_compare_arrows_24.xml b/app/src/main/res/drawable/ic_baseline_compare_arrows_24.xml new file mode 100644 index 0000000..9f2ab69 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_compare_arrows_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_domain_24.xml b/app/src/main/res/drawable/ic_baseline_domain_24.xml new file mode 100644 index 0000000..06b4137 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_domain_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_download_24.xml b/app/src/main/res/drawable/ic_baseline_download_24.xml new file mode 100644 index 0000000..1f61509 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_download_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_emoji_emotions_24.xml b/app/src/main/res/drawable/ic_baseline_emoji_emotions_24.xml new file mode 100644 index 0000000..6a85a99 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_emoji_emotions_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_fast_forward_24.xml b/app/src/main/res/drawable/ic_baseline_fast_forward_24.xml new file mode 100644 index 0000000..e3f30c6 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_fast_forward_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_fiber_manual_record_24.xml b/app/src/main/res/drawable/ic_baseline_fiber_manual_record_24.xml new file mode 100644 index 0000000..d191830 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_fiber_manual_record_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_fingerprint_24.xml b/app/src/main/res/drawable/ic_baseline_fingerprint_24.xml new file mode 100644 index 0000000..4ad0310 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_fingerprint_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_flip_camera_android_24.xml b/app/src/main/res/drawable/ic_baseline_flip_camera_android_24.xml new file mode 100644 index 0000000..951aa1f --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_flip_camera_android_24.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_baseline_format_align_left_24.xml b/app/src/main/res/drawable/ic_baseline_format_align_left_24.xml new file mode 100644 index 0000000..4ff0c3a --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_format_align_left_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_grid_3x3_24.xml b/app/src/main/res/drawable/ic_baseline_grid_3x3_24.xml new file mode 100644 index 0000000..bdc2232 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_grid_3x3_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_home_24.xml b/app/src/main/res/drawable/ic_baseline_home_24.xml new file mode 100644 index 0000000..3a4c7da --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_home_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_http_24.xml b/app/src/main/res/drawable/ic_baseline_http_24.xml new file mode 100644 index 0000000..b93de26 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_http_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_https_24.xml b/app/src/main/res/drawable/ic_baseline_https_24.xml new file mode 100644 index 0000000..d619102 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_https_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_import_contacts_24.xml b/app/src/main/res/drawable/ic_baseline_import_contacts_24.xml new file mode 100644 index 0000000..99a23c4 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_import_contacts_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_info_24.xml b/app/src/main/res/drawable/ic_baseline_info_24.xml new file mode 100644 index 0000000..17255b7 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_info_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_layers_24.xml b/app/src/main/res/drawable/ic_baseline_layers_24.xml new file mode 100644 index 0000000..478fe9d --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_layers_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_legend_toggle_24.xml b/app/src/main/res/drawable/ic_baseline_legend_toggle_24.xml new file mode 100644 index 0000000..ea17c5d --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_legend_toggle_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_link_24.xml b/app/src/main/res/drawable/ic_baseline_link_24.xml new file mode 100644 index 0000000..2c0a73f --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_link_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_local_bar_24.xml b/app/src/main/res/drawable/ic_baseline_local_bar_24.xml new file mode 100644 index 0000000..2a55e49 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_local_bar_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_location_on_24.xml b/app/src/main/res/drawable/ic_baseline_location_on_24.xml new file mode 100644 index 0000000..e6dfeb4 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_location_on_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_lock_24.xml b/app/src/main/res/drawable/ic_baseline_lock_24.xml new file mode 100644 index 0000000..d619102 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_lock_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_low_priority_24.xml b/app/src/main/res/drawable/ic_baseline_low_priority_24.xml new file mode 100644 index 0000000..c8fb025 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_low_priority_24.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_manage_search_24.xml b/app/src/main/res/drawable/ic_baseline_manage_search_24.xml new file mode 100644 index 0000000..44aed15 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_manage_search_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_more_vert_24.xml b/app/src/main/res/drawable/ic_baseline_more_vert_24.xml new file mode 100644 index 0000000..34b93ec --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_more_vert_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_multiline_chart_24.xml b/app/src/main/res/drawable/ic_baseline_multiline_chart_24.xml new file mode 100644 index 0000000..96a6a38 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_multiline_chart_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_multiple_stop_24.xml b/app/src/main/res/drawable/ic_baseline_multiple_stop_24.xml new file mode 100644 index 0000000..f8d9aa2 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_multiple_stop_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_nat_24.xml b/app/src/main/res/drawable/ic_baseline_nat_24.xml new file mode 100644 index 0000000..bc3e7ef --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_nat_24.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_baseline_nfc_24.xml b/app/src/main/res/drawable/ic_baseline_nfc_24.xml new file mode 100644 index 0000000..63435db --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_nfc_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_no_encryption_gmailerrorred_24.xml b/app/src/main/res/drawable/ic_baseline_no_encryption_gmailerrorred_24.xml new file mode 100644 index 0000000..b49f243 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_no_encryption_gmailerrorred_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_person_24.xml b/app/src/main/res/drawable/ic_baseline_person_24.xml new file mode 100644 index 0000000..6bdced2 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_person_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_push_pin_24.xml b/app/src/main/res/drawable/ic_baseline_push_pin_24.xml new file mode 100644 index 0000000..d382d38 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_push_pin_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_refresh_24.xml b/app/src/main/res/drawable/ic_baseline_refresh_24.xml new file mode 100644 index 0000000..f2be45b --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_refresh_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_rule_folder_24.xml b/app/src/main/res/drawable/ic_baseline_rule_folder_24.xml new file mode 100644 index 0000000..318cda2 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_rule_folder_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_running_with_errors_24.xml b/app/src/main/res/drawable/ic_baseline_running_with_errors_24.xml new file mode 100644 index 0000000..b3eba04 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_running_with_errors_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_sanitizer_24.xml b/app/src/main/res/drawable/ic_baseline_sanitizer_24.xml new file mode 100644 index 0000000..72c4712 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_sanitizer_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_security_24.xml b/app/src/main/res/drawable/ic_baseline_security_24.xml new file mode 100644 index 0000000..c9e27cc --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_security_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_shuffle_24.xml b/app/src/main/res/drawable/ic_baseline_shuffle_24.xml new file mode 100644 index 0000000..2469a90 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_shuffle_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_shutter_speed_24.xml b/app/src/main/res/drawable/ic_baseline_shutter_speed_24.xml new file mode 100644 index 0000000..6c2598b --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_shutter_speed_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_speed_24.xml b/app/src/main/res/drawable/ic_baseline_speed_24.xml new file mode 100644 index 0000000..7acbed3 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_speed_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_stream_24.xml b/app/src/main/res/drawable/ic_baseline_stream_24.xml new file mode 100644 index 0000000..71d35c5 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_stream_24.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_baseline_texture_24.xml b/app/src/main/res/drawable/ic_baseline_texture_24.xml new file mode 100644 index 0000000..b2741ea --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_texture_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_timelapse_24.xml b/app/src/main/res/drawable/ic_baseline_timelapse_24.xml new file mode 100644 index 0000000..b03e05f --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_timelapse_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_transform_24.xml b/app/src/main/res/drawable/ic_baseline_transform_24.xml new file mode 100644 index 0000000..c452b9f --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_transform_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_transgender_24.xml b/app/src/main/res/drawable/ic_baseline_transgender_24.xml new file mode 100644 index 0000000..864edbf --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_transgender_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_update_24.xml b/app/src/main/res/drawable/ic_baseline_update_24.xml new file mode 100644 index 0000000..3b92302 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_update_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_view_list_24.xml b/app/src/main/res/drawable/ic_baseline_view_list_24.xml new file mode 100644 index 0000000..37ffb91 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_view_list_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_vpn_key_24.xml b/app/src/main/res/drawable/ic_baseline_vpn_key_24.xml new file mode 100644 index 0000000..93276f3 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_vpn_key_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_warning_24.xml b/app/src/main/res/drawable/ic_baseline_warning_24.xml new file mode 100644 index 0000000..a02bc13 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_warning_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_wb_sunny_24.xml b/app/src/main/res/drawable/ic_baseline_wb_sunny_24.xml new file mode 100644 index 0000000..fc8da89 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_wb_sunny_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_communication_phonelink_ring.xml b/app/src/main/res/drawable/ic_communication_phonelink_ring.xml new file mode 100644 index 0000000..beaf138 --- /dev/null +++ b/app/src/main/res/drawable/ic_communication_phonelink_ring.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/ic_device_data_usage.xml b/app/src/main/res/drawable/ic_device_data_usage.xml new file mode 100644 index 0000000..6431414 --- /dev/null +++ b/app/src/main/res/drawable/ic_device_data_usage.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/ic_device_developer_mode.xml b/app/src/main/res/drawable/ic_device_developer_mode.xml new file mode 100644 index 0000000..1a41f65 --- /dev/null +++ b/app/src/main/res/drawable/ic_device_developer_mode.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_file_cloud_queue.xml b/app/src/main/res/drawable/ic_file_cloud_queue.xml new file mode 100644 index 0000000..28d1f89 --- /dev/null +++ b/app/src/main/res/drawable/ic_file_cloud_queue.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_file_file_upload.xml b/app/src/main/res/drawable/ic_file_file_upload.xml new file mode 100644 index 0000000..11e906a --- /dev/null +++ b/app/src/main/res/drawable/ic_file_file_upload.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_hardware_router.xml b/app/src/main/res/drawable/ic_hardware_router.xml new file mode 100644 index 0000000..d6afc52 --- /dev/null +++ b/app/src/main/res/drawable/ic_hardware_router.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/ic_image_camera_alt.xml b/app/src/main/res/drawable/ic_image_camera_alt.xml new file mode 100644 index 0000000..5dc0cea --- /dev/null +++ b/app/src/main/res/drawable/ic_image_camera_alt.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_image_edit.xml b/app/src/main/res/drawable/ic_image_edit.xml new file mode 100644 index 0000000..7e3c1b6 --- /dev/null +++ b/app/src/main/res/drawable/ic_image_edit.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_image_looks_6.xml b/app/src/main/res/drawable/ic_image_looks_6.xml new file mode 100644 index 0000000..441f83e --- /dev/null +++ b/app/src/main/res/drawable/ic_image_looks_6.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_image_photo.xml b/app/src/main/res/drawable/ic_image_photo.xml new file mode 100644 index 0000000..722eadb --- /dev/null +++ b/app/src/main/res/drawable/ic_image_photo.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_maps_360.xml b/app/src/main/res/drawable/ic_maps_360.xml new file mode 100644 index 0000000..c507034 --- /dev/null +++ b/app/src/main/res/drawable/ic_maps_360.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_maps_directions.xml b/app/src/main/res/drawable/ic_maps_directions.xml new file mode 100644 index 0000000..5b549df --- /dev/null +++ b/app/src/main/res/drawable/ic_maps_directions.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/ic_maps_directions_boat.xml b/app/src/main/res/drawable/ic_maps_directions_boat.xml new file mode 100644 index 0000000..4b67183 --- /dev/null +++ b/app/src/main/res/drawable/ic_maps_directions_boat.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_navigation_apps.xml b/app/src/main/res/drawable/ic_navigation_apps.xml new file mode 100644 index 0000000..941ab03 --- /dev/null +++ b/app/src/main/res/drawable/ic_navigation_apps.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_navigation_close.xml b/app/src/main/res/drawable/ic_navigation_close.xml new file mode 100644 index 0000000..7caff12 --- /dev/null +++ b/app/src/main/res/drawable/ic_navigation_close.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_navigation_menu.xml b/app/src/main/res/drawable/ic_navigation_menu.xml new file mode 100644 index 0000000..f170e1f --- /dev/null +++ b/app/src/main/res/drawable/ic_navigation_menu.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_notification_enhanced_encryption.xml b/app/src/main/res/drawable/ic_notification_enhanced_encryption.xml new file mode 100644 index 0000000..b186b22 --- /dev/null +++ b/app/src/main/res/drawable/ic_notification_enhanced_encryption.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_qu_camera_launcher.xml b/app/src/main/res/drawable/ic_qu_camera_launcher.xml new file mode 100644 index 0000000..d0a9781 --- /dev/null +++ b/app/src/main/res/drawable/ic_qu_camera_launcher.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_qu_shadowsocks_foreground.xml b/app/src/main/res/drawable/ic_qu_shadowsocks_foreground.xml new file mode 100644 index 0000000..6bf8183 --- /dev/null +++ b/app/src/main/res/drawable/ic_qu_shadowsocks_foreground.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_qu_shadowsocks_launcher.xml b/app/src/main/res/drawable/ic_qu_shadowsocks_launcher.xml new file mode 100755 index 0000000..f8bdfcf --- /dev/null +++ b/app/src/main/res/drawable/ic_qu_shadowsocks_launcher.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_service_active.xml b/app/src/main/res/drawable/ic_service_active.xml new file mode 100644 index 0000000..dd91b8b --- /dev/null +++ b/app/src/main/res/drawable/ic_service_active.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_service_busy.xml b/app/src/main/res/drawable/ic_service_busy.xml new file mode 100755 index 0000000..3adac9f --- /dev/null +++ b/app/src/main/res/drawable/ic_service_busy.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/drawable/ic_service_connected.xml b/app/src/main/res/drawable/ic_service_connected.xml new file mode 100644 index 0000000..18c8960 --- /dev/null +++ b/app/src/main/res/drawable/ic_service_connected.xml @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_service_connecting.xml b/app/src/main/res/drawable/ic_service_connecting.xml new file mode 100644 index 0000000..45f8f15 --- /dev/null +++ b/app/src/main/res/drawable/ic_service_connecting.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_service_idle.xml b/app/src/main/res/drawable/ic_service_idle.xml new file mode 100755 index 0000000..205b81d --- /dev/null +++ b/app/src/main/res/drawable/ic_service_idle.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_service_stopped.xml b/app/src/main/res/drawable/ic_service_stopped.xml new file mode 100644 index 0000000..3d2e28b --- /dev/null +++ b/app/src/main/res/drawable/ic_service_stopped.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_service_stopping.xml b/app/src/main/res/drawable/ic_service_stopping.xml new file mode 100644 index 0000000..87f9233 --- /dev/null +++ b/app/src/main/res/drawable/ic_service_stopping.xml @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_settings_password.xml b/app/src/main/res/drawable/ic_settings_password.xml new file mode 100644 index 0000000..50b08a2 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_password.xml @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_social_emoji_symbols.xml b/app/src/main/res/drawable/ic_social_emoji_symbols.xml new file mode 100644 index 0000000..77549ef --- /dev/null +++ b/app/src/main/res/drawable/ic_social_emoji_symbols.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_social_share.xml b/app/src/main/res/drawable/ic_social_share.xml new file mode 100644 index 0000000..da0c4fb --- /dev/null +++ b/app/src/main/res/drawable/ic_social_share.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/terminal_scroll_shape.xml b/app/src/main/res/drawable/terminal_scroll_shape.xml new file mode 100644 index 0000000..57f644d --- /dev/null +++ b/app/src/main/res/drawable/terminal_scroll_shape.xml @@ -0,0 +1,22 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/font/jetbrains_mono.ttf b/app/src/main/res/font/jetbrains_mono.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7db854fd913a10f5c921bb7d9f2f4a891e009f78 GIT binary patch literal 136708 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/layout_about.xml b/app/src/main/res/layout/layout_about.xml new file mode 100644 index 0000000..2363c50 --- /dev/null +++ b/app/src/main/res/layout/layout_about.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_add_entity.xml b/app/src/main/res/layout/layout_add_entity.xml new file mode 100644 index 0000000..d6e856e --- /dev/null +++ b/app/src/main/res/layout/layout_add_entity.xml @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_app_list.xml b/app/src/main/res/layout/layout_app_list.xml new file mode 100644 index 0000000..f8c211b --- /dev/null +++ b/app/src/main/res/layout/layout_app_list.xml @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

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ⅈ$IG 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 z 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