From 0030c29b627ac0caabc6846c7bc1a259b2cc8380 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 12 Jan 2017 23:31:52 +0800 Subject: [PATCH 01/98] Move app to mobile subproject --- .gitignore | 2 - .gitmodules | 38 ++++----- .travis.yml | 2 +- README.md | 3 +- build.sbt | 74 ++++-------------- build.sh | 16 ---- kcptun/go | 1 - kcptun/kcptun | 1 - kcptun/make.bash | 69 ---------------- mobile/build.sbt | 44 +++++++++++ {gfwlist => mobile/gfwlist}/gen.pl | 0 {gfwlist => mobile/gfwlist}/gen.py | 0 lint.xml => mobile/lint.xml | 0 {src => mobile/src}/main/AndroidManifest.xml | 0 .../shadowsocks/aidl/IShadowsocksService.aidl | 0 .../aidl/IShadowsocksServiceCallback.aidl | 0 .../src}/main/assets/acl/bypass-china.acl | 0 .../src}/main/assets/acl/bypass-lan-china.acl | 0 .../src}/main/assets/acl/bypass-lan.acl | 0 .../src}/main/assets/acl/china-list.acl | 0 .../src}/main/assets/acl/gfwlist.acl | 0 .../src}/main/assets/fonts/Iceland.ttf | Bin .../src}/main/assets/pages/about.html | 0 {src => mobile/src}/main/ic_launcher-web.png | Bin .../github/shadowsocks/DialogFragment.java | 0 .../java/com/github/shadowsocks/System.java | 0 {src => mobile/src}/main/jni/Android.mk | 0 {src => mobile/src}/main/jni/Application.mk | 0 {src => mobile/src}/main/jni/badvpn | 0 .../src}/main/jni/build-shared-executable.mk | 0 .../src}/main/jni/include/libev/config.h | 0 .../src}/main/jni/include/pdnsd/config.h | 0 .../jni/include/shadowsocks-libev/config.h | 0 .../src}/main/jni/include/sodium/version.h | 0 {src => mobile/src}/main/jni/libancillary | 0 {src => mobile/src}/main/jni/libevent | 0 {src => mobile/src}/main/jni/mbedtls | 0 {src => mobile/src}/main/jni/pcre | 0 {src => mobile/src}/main/jni/pdnsd | 0 {src => mobile/src}/main/jni/redsocks | 0 .../src}/main/jni/shadowsocks-libev | 0 {src => mobile/src}/main/jni/system.cpp | 0 .../ic_navigation_close.xml | 0 .../res/drawable-hdpi/ic_navigation_close.png | Bin .../res/drawable-hdpi/ic_stat_shadowsocks.png | Bin .../res/drawable-mdpi/ic_navigation_close.png | Bin .../res/drawable-mdpi/ic_stat_shadowsocks.png | Bin .../drawable-v21/background_selectable.xml | 0 .../main/res/drawable-v21/background_stat.xml | 0 .../drawable-xhdpi/ic_navigation_close.png | Bin .../drawable-xhdpi/ic_stat_shadowsocks.png | Bin .../drawable-xxhdpi/ic_navigation_close.png | Bin .../drawable-xxhdpi/ic_stat_shadowsocks.png | Bin .../drawable-xxxhdpi/ic_navigation_close.png | Bin .../main/res/drawable/background_header.png | Bin .../main/res/drawable/background_profile.xml | 0 .../res/drawable/background_selectable.xml | 0 .../main/res/drawable/background_stat.xml | 0 .../res/drawable/ic_action_assignment.xml | 0 .../main/res/drawable/ic_action_copyright.xml | 0 .../main/res/drawable/ic_action_delete.xml | 0 .../res/drawable/ic_action_description.xml | 0 .../src}/main/res/drawable/ic_action_done.xml | 0 .../res/drawable/ic_action_help_outline.xml | 0 .../main/res/drawable/ic_action_note_add.xml | 0 .../main/res/drawable/ic_action_settings.xml | 0 .../main/res/drawable/ic_av_playlist_add.xml | 0 .../main/res/drawable/ic_content_copy.xml | 0 .../main/res/drawable/ic_content_paste.xml | 0 .../src}/main/res/drawable/ic_image_edit.xml | 0 .../res/drawable/ic_navigation_refresh.xml | 0 .../drawable/ic_qu_shadowsocks_launcher.xml | 0 .../main/res/drawable/ic_social_share.xml | 0 .../src}/main/res/drawable/ic_start_busy.xml | 0 .../main/res/drawable/ic_start_connected.xml | 0 .../src}/main/res/drawable/ic_start_idle.xml | 0 .../src}/main/res/layout/dialog_acl_rule.xml | 0 .../src}/main/res/layout/layout_about.xml | 0 .../src}/main/res/layout/layout_apps.xml | 0 .../src}/main/res/layout/layout_apps_item.xml | 0 .../main/res/layout/layout_custom_rules.xml | 0 .../res/layout/layout_global_settings.xml | 0 .../src}/main/res/layout/layout_header.xml | 0 .../src}/main/res/layout/layout_list.xml | 0 .../src}/main/res/layout/layout_main.xml | 0 .../src}/main/res/layout/layout_profile.xml | 0 .../main/res/layout/layout_profile_config.xml | 0 .../src}/main/res/layout/layout_tasker.xml | 0 .../main/res/layout/toolbar_light_dark.xml | 0 .../src}/main/res/menu/app_manager_menu.xml | 0 .../src}/main/res/menu/custom_rules_menu.xml | 0 .../main/res/menu/profile_config_menu.xml | 0 .../main/res/menu/profile_manager_menu.xml | 0 .../main/res/menu/profile_share_popup.xml | 0 .../src}/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src}/main/res/mipmap-mdpi/ic_launcher.png | Bin .../main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../src}/main/res/raw/gtm_default_container | Bin .../src}/main/res/values-ja/strings.xml | 0 .../src}/main/res/values-ru/strings.xml | 0 .../src}/main/res/values-v21/dimen.xml | 0 .../src}/main/res/values-v21/strings.xml | 0 .../src}/main/res/values-v21/styles.xml | 0 .../src}/main/res/values-zh-rCN/strings.xml | 0 .../src}/main/res/values-zh-rTW/strings.xml | 0 .../src}/main/res/values/arrays.xml | 0 {src => mobile/src}/main/res/values/attrs.xml | 0 .../src}/main/res/values/colors.xml | 0 .../src}/main/res/values/configs.xml | 0 {src => mobile/src}/main/res/values/dimen.xml | 0 .../src}/main/res/values/strings.xml | 0 .../src}/main/res/values/styles.xml | 0 .../src}/main/res/xml/pref_global.xml | 0 .../src}/main/res/xml/pref_profile.xml | 0 .../src}/main/res/xml/shortcuts.xml | 0 {src => mobile/src}/main/res/xml/tracker.xml | 0 .../preference/DialogPreferencePlus.scala | 0 .../mygod/preference/EditTextPreference.scala | 0 .../EditTextPreferenceDialogFragment.scala | 0 .../preference/NumberPickerPreference.scala | 0 ...NumberPickerPreferenceDialogFragment.scala | 0 .../mygod/preference/PreferenceCategory.scala | 0 .../mygod/preference/PreferenceFragment.scala | 0 .../preference/PreferenceGroupAdapter.scala | 0 .../mygod/preference/SummaryPreference.scala | 0 .../github/shadowsocks/AboutFragment.scala | 0 .../com/github/shadowsocks/AppManager.scala | 0 .../com/github/shadowsocks/BaseService.scala | 0 .../com/github/shadowsocks/BootReceiver.scala | 0 .../shadowsocks/GlobalConfigFragment.scala | 0 .../shadowsocks/GlobalSettingsFragment.scala | 0 .../github/shadowsocks/GuardedProcess.scala | 0 .../com/github/shadowsocks/MainActivity.scala | 0 .../shadowsocks/ProfileConfigActivity.scala | 0 .../shadowsocks/ProfileConfigFragment.scala | 0 .../github/shadowsocks/ProfilesFragment.scala | 0 .../com/github/shadowsocks/QRCodeDialog.scala | 0 .../shadowsocks/QuickToggleShortcut.scala | 0 .../shadowsocks/ServiceBoundContext.scala | 0 .../shadowsocks/ShadowsocksApplication.scala | 0 .../shadowsocks/ShadowsocksBackupAgent.scala | 0 .../shadowsocks/ShadowsocksNatService.scala | 0 .../shadowsocks/ShadowsocksNotification.scala | 0 .../ShadowsocksRunnerActivity.scala | 0 .../ShadowsocksRunnerService.scala | 0 .../shadowsocks/ShadowsocksTileService.scala | 0 .../shadowsocks/ShadowsocksVpnService.scala | 0 .../shadowsocks/ShadowsocksVpnThread.scala | 0 .../github/shadowsocks/TaskerActivity.scala | 0 .../github/shadowsocks/TaskerReceiver.scala | 0 .../github/shadowsocks/ToolbarFragment.scala | 0 .../com/github/shadowsocks/acl/Acl.scala | 0 .../github/shadowsocks/acl/AclSyncJob.scala | 0 .../shadowsocks/acl/CustomRulesFragment.scala | 0 .../github/shadowsocks/acl/DonaldTrump.scala | 0 .../com/github/shadowsocks/acl/Subnet.scala | 0 .../shadowsocks/database/DBHelper.scala | 0 .../github/shadowsocks/database/Profile.scala | 0 .../shadowsocks/database/ProfileManager.scala | 0 .../KcpCliPreferenceDialogFragment.scala | 0 .../github/shadowsocks/utils/CloseUtils.scala | 0 .../github/shadowsocks/utils/Constants.scala | 0 .../github/shadowsocks/utils/IOUtils.scala | 0 .../com/github/shadowsocks/utils/Parser.scala | 0 .../shadowsocks/utils/TaskerSettings.scala | 0 .../shadowsocks/utils/TcpFastOpen.scala | 0 .../shadowsocks/utils/TrafficMonitor.scala | 0 .../utils/TrafficMonitorThread.scala | 0 .../github/shadowsocks/utils/Typefaces.scala | 0 .../com/github/shadowsocks/utils/Utils.scala | 0 .../shadowsocks/widget/BoundedCardView.scala | 0 .../widget/BoundedGridLayout.scala | 0 .../shadowsocks/widget/BoundedView.scala | 0 .../widget/UndoSnackbarManager.scala | 0 .../scala/collection/mutable/SortedList.scala | 0 177 files changed, 77 insertions(+), 173 deletions(-) delete mode 100755 build.sh delete mode 160000 kcptun/go delete mode 160000 kcptun/kcptun delete mode 100755 kcptun/make.bash create mode 100644 mobile/build.sbt rename {gfwlist => mobile/gfwlist}/gen.pl (100%) rename {gfwlist => mobile/gfwlist}/gen.py (100%) rename lint.xml => mobile/lint.xml (100%) rename {src => mobile/src}/main/AndroidManifest.xml (100%) rename {src => mobile/src}/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl (100%) rename {src => mobile/src}/main/aidl/com/github/shadowsocks/aidl/IShadowsocksServiceCallback.aidl (100%) rename {src => mobile/src}/main/assets/acl/bypass-china.acl (100%) rename {src => mobile/src}/main/assets/acl/bypass-lan-china.acl (100%) rename {src => mobile/src}/main/assets/acl/bypass-lan.acl (100%) rename {src => mobile/src}/main/assets/acl/china-list.acl (100%) rename {src => mobile/src}/main/assets/acl/gfwlist.acl (100%) rename {src => mobile/src}/main/assets/fonts/Iceland.ttf (100%) rename {src => mobile/src}/main/assets/pages/about.html (100%) rename {src => mobile/src}/main/ic_launcher-web.png (100%) rename {src => mobile/src}/main/java/com/github/shadowsocks/DialogFragment.java (100%) rename {src => mobile/src}/main/java/com/github/shadowsocks/System.java (100%) rename {src => mobile/src}/main/jni/Android.mk (100%) rename {src => mobile/src}/main/jni/Application.mk (100%) rename {src => mobile/src}/main/jni/badvpn (100%) rename {src => mobile/src}/main/jni/build-shared-executable.mk (100%) rename {src => mobile/src}/main/jni/include/libev/config.h (100%) rename {src => mobile/src}/main/jni/include/pdnsd/config.h (100%) rename {src => mobile/src}/main/jni/include/shadowsocks-libev/config.h (100%) rename {src => mobile/src}/main/jni/include/sodium/version.h (100%) rename {src => mobile/src}/main/jni/libancillary (100%) rename {src => mobile/src}/main/jni/libevent (100%) rename {src => mobile/src}/main/jni/mbedtls (100%) rename {src => mobile/src}/main/jni/pcre (100%) rename {src => mobile/src}/main/jni/pdnsd (100%) rename {src => mobile/src}/main/jni/redsocks (100%) rename {src => mobile/src}/main/jni/shadowsocks-libev (100%) rename {src => mobile/src}/main/jni/system.cpp (100%) rename {src => mobile/src}/main/res/drawable-anydpi-v21/ic_navigation_close.xml (100%) rename {src => mobile/src}/main/res/drawable-hdpi/ic_navigation_close.png (100%) rename {src => mobile/src}/main/res/drawable-hdpi/ic_stat_shadowsocks.png (100%) rename {src => mobile/src}/main/res/drawable-mdpi/ic_navigation_close.png (100%) rename {src => mobile/src}/main/res/drawable-mdpi/ic_stat_shadowsocks.png (100%) rename {src => mobile/src}/main/res/drawable-v21/background_selectable.xml (100%) rename {src => mobile/src}/main/res/drawable-v21/background_stat.xml (100%) rename {src => mobile/src}/main/res/drawable-xhdpi/ic_navigation_close.png (100%) rename {src => mobile/src}/main/res/drawable-xhdpi/ic_stat_shadowsocks.png (100%) rename {src => mobile/src}/main/res/drawable-xxhdpi/ic_navigation_close.png (100%) rename {src => mobile/src}/main/res/drawable-xxhdpi/ic_stat_shadowsocks.png (100%) rename {src => mobile/src}/main/res/drawable-xxxhdpi/ic_navigation_close.png (100%) rename {src => mobile/src}/main/res/drawable/background_header.png (100%) rename {src => mobile/src}/main/res/drawable/background_profile.xml (100%) rename {src => mobile/src}/main/res/drawable/background_selectable.xml (100%) rename {src => mobile/src}/main/res/drawable/background_stat.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_action_assignment.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_action_copyright.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_action_delete.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_action_description.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_action_done.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_action_help_outline.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_action_note_add.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_action_settings.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_av_playlist_add.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_content_copy.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_content_paste.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_image_edit.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_navigation_refresh.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_qu_shadowsocks_launcher.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_social_share.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_start_busy.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_start_connected.xml (100%) rename {src => mobile/src}/main/res/drawable/ic_start_idle.xml (100%) rename {src => mobile/src}/main/res/layout/dialog_acl_rule.xml (100%) rename {src => mobile/src}/main/res/layout/layout_about.xml (100%) rename {src => mobile/src}/main/res/layout/layout_apps.xml (100%) rename {src => mobile/src}/main/res/layout/layout_apps_item.xml (100%) rename {src => mobile/src}/main/res/layout/layout_custom_rules.xml (100%) rename {src => mobile/src}/main/res/layout/layout_global_settings.xml (100%) rename {src => mobile/src}/main/res/layout/layout_header.xml (100%) rename {src => mobile/src}/main/res/layout/layout_list.xml (100%) rename {src => mobile/src}/main/res/layout/layout_main.xml (100%) rename {src => mobile/src}/main/res/layout/layout_profile.xml (100%) rename {src => mobile/src}/main/res/layout/layout_profile_config.xml (100%) rename {src => mobile/src}/main/res/layout/layout_tasker.xml (100%) rename {src => mobile/src}/main/res/layout/toolbar_light_dark.xml (100%) rename {src => mobile/src}/main/res/menu/app_manager_menu.xml (100%) rename {src => mobile/src}/main/res/menu/custom_rules_menu.xml (100%) rename {src => mobile/src}/main/res/menu/profile_config_menu.xml (100%) rename {src => mobile/src}/main/res/menu/profile_manager_menu.xml (100%) rename {src => mobile/src}/main/res/menu/profile_share_popup.xml (100%) rename {src => mobile/src}/main/res/mipmap-hdpi/ic_launcher.png (100%) rename {src => mobile/src}/main/res/mipmap-mdpi/ic_launcher.png (100%) rename {src => mobile/src}/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename {src => mobile/src}/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {src => mobile/src}/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {src => mobile/src}/main/res/raw/gtm_default_container (100%) rename {src => mobile/src}/main/res/values-ja/strings.xml (100%) rename {src => mobile/src}/main/res/values-ru/strings.xml (100%) rename {src => mobile/src}/main/res/values-v21/dimen.xml (100%) rename {src => mobile/src}/main/res/values-v21/strings.xml (100%) rename {src => mobile/src}/main/res/values-v21/styles.xml (100%) rename {src => mobile/src}/main/res/values-zh-rCN/strings.xml (100%) rename {src => mobile/src}/main/res/values-zh-rTW/strings.xml (100%) rename {src => mobile/src}/main/res/values/arrays.xml (100%) rename {src => mobile/src}/main/res/values/attrs.xml (100%) rename {src => mobile/src}/main/res/values/colors.xml (100%) rename {src => mobile/src}/main/res/values/configs.xml (100%) rename {src => mobile/src}/main/res/values/dimen.xml (100%) rename {src => mobile/src}/main/res/values/strings.xml (100%) rename {src => mobile/src}/main/res/values/styles.xml (100%) rename {src => mobile/src}/main/res/xml/pref_global.xml (100%) rename {src => mobile/src}/main/res/xml/pref_profile.xml (100%) rename {src => mobile/src}/main/res/xml/shortcuts.xml (100%) rename {src => mobile/src}/main/res/xml/tracker.xml (100%) rename {src => mobile/src}/main/scala/be/mygod/preference/DialogPreferencePlus.scala (100%) rename {src => mobile/src}/main/scala/be/mygod/preference/EditTextPreference.scala (100%) rename {src => mobile/src}/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala (100%) rename {src => mobile/src}/main/scala/be/mygod/preference/NumberPickerPreference.scala (100%) rename {src => mobile/src}/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala (100%) rename {src => mobile/src}/main/scala/be/mygod/preference/PreferenceCategory.scala (100%) rename {src => mobile/src}/main/scala/be/mygod/preference/PreferenceFragment.scala (100%) rename {src => mobile/src}/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala (100%) rename {src => mobile/src}/main/scala/be/mygod/preference/SummaryPreference.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/AboutFragment.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/AppManager.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/BaseService.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/BootReceiver.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/GlobalSettingsFragment.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/GuardedProcess.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/MainActivity.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/ProfileConfigActivity.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/ProfilesFragment.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/QRCodeDialog.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/QuickToggleShortcut.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/ServiceBoundContext.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/ShadowsocksBackupAgent.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/ShadowsocksTileService.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/TaskerActivity.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/TaskerReceiver.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/ToolbarFragment.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/acl/Acl.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/acl/AclSyncJob.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/acl/DonaldTrump.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/acl/Subnet.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/database/DBHelper.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/database/Profile.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/database/ProfileManager.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/preferences/KcpCliPreferenceDialogFragment.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/utils/CloseUtils.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/utils/Constants.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/utils/IOUtils.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/utils/Parser.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/utils/TaskerSettings.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/utils/TcpFastOpen.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/utils/Typefaces.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/utils/Utils.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/widget/BoundedCardView.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/widget/BoundedGridLayout.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/widget/BoundedView.scala (100%) rename {src => mobile/src}/main/scala/com/github/shadowsocks/widget/UndoSnackbarManager.scala (100%) rename {src => mobile/src}/main/scala/scala/collection/mutable/SortedList.scala (100%) diff --git a/.gitignore b/.gitignore index 5cdc29d1..2cd26960 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,4 @@ out/ #NDK src/main/obj src/main/libs -src/main/assets/armeabi-v7a/* -src/main/assets/x86/* jni/Application.mk diff --git a/.gitmodules b/.gitmodules index 713a88a5..f0e422f9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,35 +1,29 @@ -[submodule "src/main/jni/shadowsocks-libev"] - path = src/main/jni/shadowsocks-libev +[submodule "mobile/src/main/jni/shadowsocks-libev"] + path = mobile/src/main/jni/shadowsocks-libev url = https://github.com/shadowsocks/shadowsocks-libev -[submodule "src/main/jni/badvpn"] - path = src/main/jni/badvpn +[submodule "mobile/src/main/jni/badvpn"] + path = mobile/src/main/jni/badvpn url = https://github.com/shadowsocks/badvpn.git branch = shadowsocks-android -[submodule "src/main/jni/libancillary"] - path = src/main/jni/libancillary +[submodule "mobile/src/main/jni/libancillary"] + path = mobile/src/main/jni/libancillary url = https://github.com/shadowsocks/libancillary.git branch = shadowsocks-android -[submodule "src/main/jni/libevent"] - path = src/main/jni/libevent +[submodule "mobile/src/main/jni/libevent"] + path = mobile/src/main/jni/libevent url = https://github.com/shadowsocks/libevent.git branch = shadowsocks-android -[submodule "src/main/jni/redsocks"] - path = src/main/jni/redsocks +[submodule "mobile/src/main/jni/redsocks"] + path = mobile/src/main/jni/redsocks url = https://github.com/shadowsocks/redsocks.git branch = shadowsocks-android -[submodule "src/main/jni/pdnsd"] - path = src/main/jni/pdnsd +[submodule "mobile/src/main/jni/pdnsd"] + path = mobile/src/main/jni/pdnsd url = https://github.com/shadowsocks/pdnsd.git branch = shadowsocks-android -[submodule "kcptun/kcptun"] - path = kcptun/kcptun - url = https://github.com/shadowsocks/kcptun -[submodule "kcptun/go"] - path = kcptun/go - url = https://github.com/shadowsocks/go -[submodule "src/main/jni/mbedtls"] - path = src/main/jni/mbedtls +[submodule "mobile/src/main/jni/mbedtls"] + path = mobile/src/main/jni/mbedtls url = https://github.com/ARMmbed/mbedtls -[submodule "src/main/jni/pcre"] - path = src/main/jni/pcre +[submodule "mobile/src/main/jni/pcre"] + path = mobile/src/main/jni/pcre url = https://android.googlesource.com/platform/external/pcre diff --git a/.travis.yml b/.travis.yml index 22364c17..f217887e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,4 +50,4 @@ install: fi script: - - $SBTPATH/sbt native-build android:package-debug + - $SBTPATH/sbt android:package diff --git a/README.md b/README.md index 04564ceb..14688f34 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,6 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. * Set environment variable `ANDROID_HOME` to `/path/to/android-sdk` * Set environment variable `ANDROID_NDK_HOME` to `/path/to/android-ndk` -* Set environment variable `GOROOT_BOOTSTRAP` to `/path/to/go` * Create your key following the instructions at https://developer.android.com/studio/publish/app-signing.html * Create `local.properties` from `local.properties.example` with your own key information * Invoke the building like this @@ -31,7 +30,7 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. git submodule update --init # Build the App - sbt native-build clean android:package-release + sbt clean android:package-release ``` ### TRANSLATE diff --git a/build.sbt b/build.sbt index bf214363..87f0605b 100644 --- a/build.sbt +++ b/build.sbt @@ -1,66 +1,22 @@ -scalaVersion := "2.11.8" -dexMaxHeap := "4g" +import android.Keys.proguardCache -enablePlugins(AndroidApp) -android.useSupportVectors +lazy val commonSettings = Seq( + scalaVersion := "2.11.8", + dexMaxHeap := "4g", -name := "shadowsocks" -version := "3.4.2" -versionCode := Some(174) + organization := "com.github.shadowsocks", -platformTarget := "android-25" + platformTarget := "android-25", -compileOrder := CompileOrder.JavaThenScala -javacOptions ++= "-source" :: "1.7" :: "-target" :: "1.7" :: Nil -scalacOptions ++= "-target:jvm-1.7" :: "-Xexperimental" :: Nil + compileOrder := CompileOrder.JavaThenScala, + javacOptions ++= "-source" :: "1.7" :: "-target" :: "1.7" :: Nil, + scalacOptions ++= "-target:jvm-1.7" :: "-Xexperimental" :: Nil, -proguardVersion := "5.3.2" -proguardCache := Seq() -proguardOptions ++= - "-keep class com.github.shadowsocks.System { *; }" :: - "-dontwarn com.google.android.gms.internal.**" :: - "-dontwarn com.j256.ormlite.**" :: - "-dontwarn okio.**" :: - "-dontwarn org.xbill.**" :: - Nil + proguardVersion := "5.3.2", + proguardCache := Seq(), -shrinkResources := true -typedResources := false -resConfigs := Seq("ja", "ru", "zh-rCN", "zh-rTW") + shrinkResources := true, + typedResources := false +) -val supportLibsVersion = "25.1.0" -val playServicesVersion = "10.0.1" -resolvers += Resolver.jcenterRepo -libraryDependencies ++= - "com.android.support" % "cardview-v7" % supportLibsVersion :: - "com.android.support" % "customtabs" % supportLibsVersion :: - "com.android.support" % "design" % supportLibsVersion :: - "com.android.support" % "gridlayout-v7" % supportLibsVersion :: - "com.android.support" % "preference-v14" % supportLibsVersion :: - "com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" :: - "com.evernote" % "android-job" % "1.1.4" :: - "com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" :: - "com.google.android.gms" % "play-services-ads" % playServicesVersion :: - "com.google.android.gms" % "play-services-analytics" % playServicesVersion :: - "com.google.android.gms" % "play-services-gcm" % playServicesVersion :: - "com.j256.ormlite" % "ormlite-android" % "5.0" :: - "com.mikepenz" % "crossfader" % "1.5.0" :: - "com.mikepenz" % "fastadapter" % "2.1.5" :: - "com.mikepenz" % "iconics-core" % "2.8.2" :: - "com.mikepenz" % "materialdrawer" % "5.8.1" :: - "com.mikepenz" % "materialize" % "1.0.0" :: - "com.squareup.okhttp3" % "okhttp" % "3.5.0" :: - "com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2" :: - "dnsjava" % "dnsjava" % "2.1.7" :: - "eu.chainfire" % "libsuperuser" % "1.0.0.201608240809" :: - "net.glxn.qrgen" % "android" % "2.0" :: - Nil - -lazy val nativeBuild = TaskKey[Unit]("native-build", "Build native executables") -nativeBuild := { - val logger = streams.value.log - Process("./build.sh") ! logger match { - case 0 => // Success! - case n => sys.error(s"Native build script exit code: $n") - } -} +lazy val mobile = project.settings(commonSettings) diff --git a/build.sh b/build.sh deleted file mode 100755 index 5a646eab..00000000 --- a/build.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -function try () { -"$@" || exit -1 -} - -pushd src/main -# Clean up old binaries (no longer used) -rm -rf assets/armeabi-v7a assets/x86 libs/armeabi-v7a libs/x86 -mkdir -p libs/armeabi-v7a libs/x86 -popd - -# Build kcptun -pushd kcptun -try ./make.bash -popd diff --git a/kcptun/go b/kcptun/go deleted file mode 160000 index c3f75e65..00000000 --- a/kcptun/go +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c3f75e654ddb95bd53351294337649c08e9f9409 diff --git a/kcptun/kcptun b/kcptun/kcptun deleted file mode 160000 index c532a50e..00000000 --- a/kcptun/kcptun +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c532a50e1aeeaa9dee2428b374c3651a5c95258a diff --git a/kcptun/make.bash b/kcptun/make.bash deleted file mode 100755 index 2eb1454d..00000000 --- a/kcptun/make.bash +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash - -function try () { -"$@" || exit -1 -} - -[ -z "$ANDROID_NDK_HOME" ] && ANDROID_NDK_HOME=~/android-ndk-r12b - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -DEPS=$DIR/.deps -ANDROID_ARM_TOOLCHAIN=$DEPS/android-toolchain-16-arm -ANDROID_X86_TOOLCHAIN=$DEPS/android-toolchain-16-x86 - -ANDROID_ARM_CC=$ANDROID_ARM_TOOLCHAIN/bin/arm-linux-androideabi-gcc -ANDROID_ARM_STRIP=$ANDROID_ARM_TOOLCHAIN/bin/arm-linux-androideabi-strip - -ANDROID_X86_CC=$ANDROID_X86_TOOLCHAIN/bin/i686-linux-android-gcc -ANDROID_X86_STRIP=$ANDROID_X86_TOOLCHAIN/bin/i686-linux-android-strip - - -if [ ! -d "$DEPS" ]; then - mkdir -p $DEPS -fi - -if [ ! -d "$ANDROID_ARM_TOOLCHAIN" ]; then - echo "Make standalone toolchain for ARM arch" - $ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py --arch arm \ - --api 16 --install-dir $ANDROID_ARM_TOOLCHAIN -fi - -if [ ! -d "$ANDROID_X86_TOOLCHAIN" ]; then - echo "Make standalone toolchain for X86 arch" - $ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py --arch x86 \ - --api 16 --install-dir $ANDROID_X86_TOOLCHAIN -fi - -if [ ! -d "$DIR/go/bin" ]; then - echo "Build the custom go" - - pushd $DIR/go/src - try ./make.bash - popd -fi - -export GOROOT=$DIR/go -export GOPATH=$DEPS/gopath -export GOBIN=$GOPATH/bin -mkdir -p $GOBIN -export PATH=$GOROOT/bin:$PATH - -pushd kcptun/client - -echo "Get dependences for kcptun" -go get -u github.com/xtaci/kcp-go -go get -u github.com/xtaci/smux -go get - -echo "Cross compile kcptun for arm" -try env CGO_ENABLED=1 CC=$ANDROID_ARM_CC GOOS=android GOARCH=arm GOARM=7 go build -ldflags="-s -w" -try $ANDROID_ARM_STRIP client -try mv client $DIR/../src/main/libs/armeabi-v7a/libkcptun.so - -echo "Cross compile kcptun for x86" -try env CGO_ENABLED=1 CC=$ANDROID_X86_CC GOOS=android GOARCH=386 go build -ldflags="-s -w" -try $ANDROID_X86_STRIP client -try mv client $DIR/../src/main/libs/x86/libkcptun.so -popd - -echo "Successfully build kcptun" diff --git a/mobile/build.sbt b/mobile/build.sbt new file mode 100644 index 00000000..bebd4e1e --- /dev/null +++ b/mobile/build.sbt @@ -0,0 +1,44 @@ +enablePlugins(AndroidApp) +android.useSupportVectors + +name := "shadowsocks" +version := "4.0.0" +versionCode := Some(174) + +proguardOptions ++= + "-keep class com.github.shadowsocks.System { *; }" :: + "-dontwarn com.google.android.gms.internal.**" :: + "-dontwarn com.j256.ormlite.**" :: + "-dontwarn okio.**" :: + "-dontwarn org.xbill.**" :: + Nil + +resConfigs := Seq("ja", "ru", "zh-rCN", "zh-rTW") + +val supportLibsVersion = "25.1.0" +val playServicesVersion = "10.0.1" +resolvers += Resolver.jcenterRepo +libraryDependencies ++= + "com.android.support" % "cardview-v7" % supportLibsVersion :: + "com.android.support" % "customtabs" % supportLibsVersion :: + "com.android.support" % "design" % supportLibsVersion :: + "com.android.support" % "gridlayout-v7" % supportLibsVersion :: + "com.android.support" % "preference-v14" % supportLibsVersion :: + "com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" :: + "com.evernote" % "android-job" % "1.1.4" :: + "com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" :: + "com.google.android.gms" % "play-services-ads" % playServicesVersion :: + "com.google.android.gms" % "play-services-analytics" % playServicesVersion :: + "com.google.android.gms" % "play-services-gcm" % playServicesVersion :: + "com.j256.ormlite" % "ormlite-android" % "5.0" :: + "com.mikepenz" % "crossfader" % "1.5.0" :: + "com.mikepenz" % "fastadapter" % "2.1.5" :: + "com.mikepenz" % "iconics-core" % "2.8.2" :: + "com.mikepenz" % "materialdrawer" % "5.8.1" :: + "com.mikepenz" % "materialize" % "1.0.0" :: + "com.squareup.okhttp3" % "okhttp" % "3.5.0" :: + "com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2" :: + "dnsjava" % "dnsjava" % "2.1.7" :: + "eu.chainfire" % "libsuperuser" % "1.0.0.201608240809" :: + "net.glxn.qrgen" % "android" % "2.0" :: + Nil diff --git a/gfwlist/gen.pl b/mobile/gfwlist/gen.pl similarity index 100% rename from gfwlist/gen.pl rename to mobile/gfwlist/gen.pl diff --git a/gfwlist/gen.py b/mobile/gfwlist/gen.py similarity index 100% rename from gfwlist/gen.py rename to mobile/gfwlist/gen.py diff --git a/lint.xml b/mobile/lint.xml similarity index 100% rename from lint.xml rename to mobile/lint.xml diff --git a/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml similarity index 100% rename from src/main/AndroidManifest.xml rename to mobile/src/main/AndroidManifest.xml diff --git a/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl b/mobile/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl similarity index 100% rename from src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl rename to mobile/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl diff --git a/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksServiceCallback.aidl b/mobile/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksServiceCallback.aidl similarity index 100% rename from src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksServiceCallback.aidl rename to mobile/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksServiceCallback.aidl diff --git a/src/main/assets/acl/bypass-china.acl b/mobile/src/main/assets/acl/bypass-china.acl similarity index 100% rename from src/main/assets/acl/bypass-china.acl rename to mobile/src/main/assets/acl/bypass-china.acl diff --git a/src/main/assets/acl/bypass-lan-china.acl b/mobile/src/main/assets/acl/bypass-lan-china.acl similarity index 100% rename from src/main/assets/acl/bypass-lan-china.acl rename to mobile/src/main/assets/acl/bypass-lan-china.acl diff --git a/src/main/assets/acl/bypass-lan.acl b/mobile/src/main/assets/acl/bypass-lan.acl similarity index 100% rename from src/main/assets/acl/bypass-lan.acl rename to mobile/src/main/assets/acl/bypass-lan.acl diff --git a/src/main/assets/acl/china-list.acl b/mobile/src/main/assets/acl/china-list.acl similarity index 100% rename from src/main/assets/acl/china-list.acl rename to mobile/src/main/assets/acl/china-list.acl diff --git a/src/main/assets/acl/gfwlist.acl b/mobile/src/main/assets/acl/gfwlist.acl similarity index 100% rename from src/main/assets/acl/gfwlist.acl rename to mobile/src/main/assets/acl/gfwlist.acl diff --git a/src/main/assets/fonts/Iceland.ttf b/mobile/src/main/assets/fonts/Iceland.ttf similarity index 100% rename from src/main/assets/fonts/Iceland.ttf rename to mobile/src/main/assets/fonts/Iceland.ttf diff --git a/src/main/assets/pages/about.html b/mobile/src/main/assets/pages/about.html similarity index 100% rename from src/main/assets/pages/about.html rename to mobile/src/main/assets/pages/about.html diff --git a/src/main/ic_launcher-web.png b/mobile/src/main/ic_launcher-web.png similarity index 100% rename from src/main/ic_launcher-web.png rename to mobile/src/main/ic_launcher-web.png diff --git a/src/main/java/com/github/shadowsocks/DialogFragment.java b/mobile/src/main/java/com/github/shadowsocks/DialogFragment.java similarity index 100% rename from src/main/java/com/github/shadowsocks/DialogFragment.java rename to mobile/src/main/java/com/github/shadowsocks/DialogFragment.java diff --git a/src/main/java/com/github/shadowsocks/System.java b/mobile/src/main/java/com/github/shadowsocks/System.java similarity index 100% rename from src/main/java/com/github/shadowsocks/System.java rename to mobile/src/main/java/com/github/shadowsocks/System.java diff --git a/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk similarity index 100% rename from src/main/jni/Android.mk rename to mobile/src/main/jni/Android.mk diff --git a/src/main/jni/Application.mk b/mobile/src/main/jni/Application.mk similarity index 100% rename from src/main/jni/Application.mk rename to mobile/src/main/jni/Application.mk diff --git a/src/main/jni/badvpn b/mobile/src/main/jni/badvpn similarity index 100% rename from src/main/jni/badvpn rename to mobile/src/main/jni/badvpn diff --git a/src/main/jni/build-shared-executable.mk b/mobile/src/main/jni/build-shared-executable.mk similarity index 100% rename from src/main/jni/build-shared-executable.mk rename to mobile/src/main/jni/build-shared-executable.mk diff --git a/src/main/jni/include/libev/config.h b/mobile/src/main/jni/include/libev/config.h similarity index 100% rename from src/main/jni/include/libev/config.h rename to mobile/src/main/jni/include/libev/config.h diff --git a/src/main/jni/include/pdnsd/config.h b/mobile/src/main/jni/include/pdnsd/config.h similarity index 100% rename from src/main/jni/include/pdnsd/config.h rename to mobile/src/main/jni/include/pdnsd/config.h diff --git a/src/main/jni/include/shadowsocks-libev/config.h b/mobile/src/main/jni/include/shadowsocks-libev/config.h similarity index 100% rename from src/main/jni/include/shadowsocks-libev/config.h rename to mobile/src/main/jni/include/shadowsocks-libev/config.h diff --git a/src/main/jni/include/sodium/version.h b/mobile/src/main/jni/include/sodium/version.h similarity index 100% rename from src/main/jni/include/sodium/version.h rename to mobile/src/main/jni/include/sodium/version.h diff --git a/src/main/jni/libancillary b/mobile/src/main/jni/libancillary similarity index 100% rename from src/main/jni/libancillary rename to mobile/src/main/jni/libancillary diff --git a/src/main/jni/libevent b/mobile/src/main/jni/libevent similarity index 100% rename from src/main/jni/libevent rename to mobile/src/main/jni/libevent diff --git a/src/main/jni/mbedtls b/mobile/src/main/jni/mbedtls similarity index 100% rename from src/main/jni/mbedtls rename to mobile/src/main/jni/mbedtls diff --git a/src/main/jni/pcre b/mobile/src/main/jni/pcre similarity index 100% rename from src/main/jni/pcre rename to mobile/src/main/jni/pcre diff --git a/src/main/jni/pdnsd b/mobile/src/main/jni/pdnsd similarity index 100% rename from src/main/jni/pdnsd rename to mobile/src/main/jni/pdnsd diff --git a/src/main/jni/redsocks b/mobile/src/main/jni/redsocks similarity index 100% rename from src/main/jni/redsocks rename to mobile/src/main/jni/redsocks diff --git a/src/main/jni/shadowsocks-libev b/mobile/src/main/jni/shadowsocks-libev similarity index 100% rename from src/main/jni/shadowsocks-libev rename to mobile/src/main/jni/shadowsocks-libev diff --git a/src/main/jni/system.cpp b/mobile/src/main/jni/system.cpp similarity index 100% rename from src/main/jni/system.cpp rename to mobile/src/main/jni/system.cpp diff --git a/src/main/res/drawable-anydpi-v21/ic_navigation_close.xml b/mobile/src/main/res/drawable-anydpi-v21/ic_navigation_close.xml similarity index 100% rename from src/main/res/drawable-anydpi-v21/ic_navigation_close.xml rename to mobile/src/main/res/drawable-anydpi-v21/ic_navigation_close.xml diff --git a/src/main/res/drawable-hdpi/ic_navigation_close.png b/mobile/src/main/res/drawable-hdpi/ic_navigation_close.png similarity index 100% rename from src/main/res/drawable-hdpi/ic_navigation_close.png rename to mobile/src/main/res/drawable-hdpi/ic_navigation_close.png diff --git a/src/main/res/drawable-hdpi/ic_stat_shadowsocks.png b/mobile/src/main/res/drawable-hdpi/ic_stat_shadowsocks.png similarity index 100% rename from src/main/res/drawable-hdpi/ic_stat_shadowsocks.png rename to mobile/src/main/res/drawable-hdpi/ic_stat_shadowsocks.png diff --git a/src/main/res/drawable-mdpi/ic_navigation_close.png b/mobile/src/main/res/drawable-mdpi/ic_navigation_close.png similarity index 100% rename from src/main/res/drawable-mdpi/ic_navigation_close.png rename to mobile/src/main/res/drawable-mdpi/ic_navigation_close.png diff --git a/src/main/res/drawable-mdpi/ic_stat_shadowsocks.png b/mobile/src/main/res/drawable-mdpi/ic_stat_shadowsocks.png similarity index 100% rename from src/main/res/drawable-mdpi/ic_stat_shadowsocks.png rename to mobile/src/main/res/drawable-mdpi/ic_stat_shadowsocks.png diff --git a/src/main/res/drawable-v21/background_selectable.xml b/mobile/src/main/res/drawable-v21/background_selectable.xml similarity index 100% rename from src/main/res/drawable-v21/background_selectable.xml rename to mobile/src/main/res/drawable-v21/background_selectable.xml diff --git a/src/main/res/drawable-v21/background_stat.xml b/mobile/src/main/res/drawable-v21/background_stat.xml similarity index 100% rename from src/main/res/drawable-v21/background_stat.xml rename to mobile/src/main/res/drawable-v21/background_stat.xml diff --git a/src/main/res/drawable-xhdpi/ic_navigation_close.png b/mobile/src/main/res/drawable-xhdpi/ic_navigation_close.png similarity index 100% rename from src/main/res/drawable-xhdpi/ic_navigation_close.png rename to mobile/src/main/res/drawable-xhdpi/ic_navigation_close.png diff --git a/src/main/res/drawable-xhdpi/ic_stat_shadowsocks.png b/mobile/src/main/res/drawable-xhdpi/ic_stat_shadowsocks.png similarity index 100% rename from src/main/res/drawable-xhdpi/ic_stat_shadowsocks.png rename to mobile/src/main/res/drawable-xhdpi/ic_stat_shadowsocks.png diff --git a/src/main/res/drawable-xxhdpi/ic_navigation_close.png b/mobile/src/main/res/drawable-xxhdpi/ic_navigation_close.png similarity index 100% rename from src/main/res/drawable-xxhdpi/ic_navigation_close.png rename to mobile/src/main/res/drawable-xxhdpi/ic_navigation_close.png diff --git a/src/main/res/drawable-xxhdpi/ic_stat_shadowsocks.png b/mobile/src/main/res/drawable-xxhdpi/ic_stat_shadowsocks.png similarity index 100% rename from src/main/res/drawable-xxhdpi/ic_stat_shadowsocks.png rename to mobile/src/main/res/drawable-xxhdpi/ic_stat_shadowsocks.png diff --git a/src/main/res/drawable-xxxhdpi/ic_navigation_close.png b/mobile/src/main/res/drawable-xxxhdpi/ic_navigation_close.png similarity index 100% rename from src/main/res/drawable-xxxhdpi/ic_navigation_close.png rename to mobile/src/main/res/drawable-xxxhdpi/ic_navigation_close.png diff --git a/src/main/res/drawable/background_header.png b/mobile/src/main/res/drawable/background_header.png similarity index 100% rename from src/main/res/drawable/background_header.png rename to mobile/src/main/res/drawable/background_header.png diff --git a/src/main/res/drawable/background_profile.xml b/mobile/src/main/res/drawable/background_profile.xml similarity index 100% rename from src/main/res/drawable/background_profile.xml rename to mobile/src/main/res/drawable/background_profile.xml diff --git a/src/main/res/drawable/background_selectable.xml b/mobile/src/main/res/drawable/background_selectable.xml similarity index 100% rename from src/main/res/drawable/background_selectable.xml rename to mobile/src/main/res/drawable/background_selectable.xml diff --git a/src/main/res/drawable/background_stat.xml b/mobile/src/main/res/drawable/background_stat.xml similarity index 100% rename from src/main/res/drawable/background_stat.xml rename to mobile/src/main/res/drawable/background_stat.xml diff --git a/src/main/res/drawable/ic_action_assignment.xml b/mobile/src/main/res/drawable/ic_action_assignment.xml similarity index 100% rename from src/main/res/drawable/ic_action_assignment.xml rename to mobile/src/main/res/drawable/ic_action_assignment.xml diff --git a/src/main/res/drawable/ic_action_copyright.xml b/mobile/src/main/res/drawable/ic_action_copyright.xml similarity index 100% rename from src/main/res/drawable/ic_action_copyright.xml rename to mobile/src/main/res/drawable/ic_action_copyright.xml diff --git a/src/main/res/drawable/ic_action_delete.xml b/mobile/src/main/res/drawable/ic_action_delete.xml similarity index 100% rename from src/main/res/drawable/ic_action_delete.xml rename to mobile/src/main/res/drawable/ic_action_delete.xml diff --git a/src/main/res/drawable/ic_action_description.xml b/mobile/src/main/res/drawable/ic_action_description.xml similarity index 100% rename from src/main/res/drawable/ic_action_description.xml rename to mobile/src/main/res/drawable/ic_action_description.xml diff --git a/src/main/res/drawable/ic_action_done.xml b/mobile/src/main/res/drawable/ic_action_done.xml similarity index 100% rename from src/main/res/drawable/ic_action_done.xml rename to mobile/src/main/res/drawable/ic_action_done.xml diff --git a/src/main/res/drawable/ic_action_help_outline.xml b/mobile/src/main/res/drawable/ic_action_help_outline.xml similarity index 100% rename from src/main/res/drawable/ic_action_help_outline.xml rename to mobile/src/main/res/drawable/ic_action_help_outline.xml diff --git a/src/main/res/drawable/ic_action_note_add.xml b/mobile/src/main/res/drawable/ic_action_note_add.xml similarity index 100% rename from src/main/res/drawable/ic_action_note_add.xml rename to mobile/src/main/res/drawable/ic_action_note_add.xml diff --git a/src/main/res/drawable/ic_action_settings.xml b/mobile/src/main/res/drawable/ic_action_settings.xml similarity index 100% rename from src/main/res/drawable/ic_action_settings.xml rename to mobile/src/main/res/drawable/ic_action_settings.xml diff --git a/src/main/res/drawable/ic_av_playlist_add.xml b/mobile/src/main/res/drawable/ic_av_playlist_add.xml similarity index 100% rename from src/main/res/drawable/ic_av_playlist_add.xml rename to mobile/src/main/res/drawable/ic_av_playlist_add.xml diff --git a/src/main/res/drawable/ic_content_copy.xml b/mobile/src/main/res/drawable/ic_content_copy.xml similarity index 100% rename from src/main/res/drawable/ic_content_copy.xml rename to mobile/src/main/res/drawable/ic_content_copy.xml diff --git a/src/main/res/drawable/ic_content_paste.xml b/mobile/src/main/res/drawable/ic_content_paste.xml similarity index 100% rename from src/main/res/drawable/ic_content_paste.xml rename to mobile/src/main/res/drawable/ic_content_paste.xml diff --git a/src/main/res/drawable/ic_image_edit.xml b/mobile/src/main/res/drawable/ic_image_edit.xml similarity index 100% rename from src/main/res/drawable/ic_image_edit.xml rename to mobile/src/main/res/drawable/ic_image_edit.xml diff --git a/src/main/res/drawable/ic_navigation_refresh.xml b/mobile/src/main/res/drawable/ic_navigation_refresh.xml similarity index 100% rename from src/main/res/drawable/ic_navigation_refresh.xml rename to mobile/src/main/res/drawable/ic_navigation_refresh.xml diff --git a/src/main/res/drawable/ic_qu_shadowsocks_launcher.xml b/mobile/src/main/res/drawable/ic_qu_shadowsocks_launcher.xml similarity index 100% rename from src/main/res/drawable/ic_qu_shadowsocks_launcher.xml rename to mobile/src/main/res/drawable/ic_qu_shadowsocks_launcher.xml diff --git a/src/main/res/drawable/ic_social_share.xml b/mobile/src/main/res/drawable/ic_social_share.xml similarity index 100% rename from src/main/res/drawable/ic_social_share.xml rename to mobile/src/main/res/drawable/ic_social_share.xml diff --git a/src/main/res/drawable/ic_start_busy.xml b/mobile/src/main/res/drawable/ic_start_busy.xml similarity index 100% rename from src/main/res/drawable/ic_start_busy.xml rename to mobile/src/main/res/drawable/ic_start_busy.xml diff --git a/src/main/res/drawable/ic_start_connected.xml b/mobile/src/main/res/drawable/ic_start_connected.xml similarity index 100% rename from src/main/res/drawable/ic_start_connected.xml rename to mobile/src/main/res/drawable/ic_start_connected.xml diff --git a/src/main/res/drawable/ic_start_idle.xml b/mobile/src/main/res/drawable/ic_start_idle.xml similarity index 100% rename from src/main/res/drawable/ic_start_idle.xml rename to mobile/src/main/res/drawable/ic_start_idle.xml diff --git a/src/main/res/layout/dialog_acl_rule.xml b/mobile/src/main/res/layout/dialog_acl_rule.xml similarity index 100% rename from src/main/res/layout/dialog_acl_rule.xml rename to mobile/src/main/res/layout/dialog_acl_rule.xml diff --git a/src/main/res/layout/layout_about.xml b/mobile/src/main/res/layout/layout_about.xml similarity index 100% rename from src/main/res/layout/layout_about.xml rename to mobile/src/main/res/layout/layout_about.xml diff --git a/src/main/res/layout/layout_apps.xml b/mobile/src/main/res/layout/layout_apps.xml similarity index 100% rename from src/main/res/layout/layout_apps.xml rename to mobile/src/main/res/layout/layout_apps.xml diff --git a/src/main/res/layout/layout_apps_item.xml b/mobile/src/main/res/layout/layout_apps_item.xml similarity index 100% rename from src/main/res/layout/layout_apps_item.xml rename to mobile/src/main/res/layout/layout_apps_item.xml diff --git a/src/main/res/layout/layout_custom_rules.xml b/mobile/src/main/res/layout/layout_custom_rules.xml similarity index 100% rename from src/main/res/layout/layout_custom_rules.xml rename to mobile/src/main/res/layout/layout_custom_rules.xml diff --git a/src/main/res/layout/layout_global_settings.xml b/mobile/src/main/res/layout/layout_global_settings.xml similarity index 100% rename from src/main/res/layout/layout_global_settings.xml rename to mobile/src/main/res/layout/layout_global_settings.xml diff --git a/src/main/res/layout/layout_header.xml b/mobile/src/main/res/layout/layout_header.xml similarity index 100% rename from src/main/res/layout/layout_header.xml rename to mobile/src/main/res/layout/layout_header.xml diff --git a/src/main/res/layout/layout_list.xml b/mobile/src/main/res/layout/layout_list.xml similarity index 100% rename from src/main/res/layout/layout_list.xml rename to mobile/src/main/res/layout/layout_list.xml diff --git a/src/main/res/layout/layout_main.xml b/mobile/src/main/res/layout/layout_main.xml similarity index 100% rename from src/main/res/layout/layout_main.xml rename to mobile/src/main/res/layout/layout_main.xml diff --git a/src/main/res/layout/layout_profile.xml b/mobile/src/main/res/layout/layout_profile.xml similarity index 100% rename from src/main/res/layout/layout_profile.xml rename to mobile/src/main/res/layout/layout_profile.xml diff --git a/src/main/res/layout/layout_profile_config.xml b/mobile/src/main/res/layout/layout_profile_config.xml similarity index 100% rename from src/main/res/layout/layout_profile_config.xml rename to mobile/src/main/res/layout/layout_profile_config.xml diff --git a/src/main/res/layout/layout_tasker.xml b/mobile/src/main/res/layout/layout_tasker.xml similarity index 100% rename from src/main/res/layout/layout_tasker.xml rename to mobile/src/main/res/layout/layout_tasker.xml diff --git a/src/main/res/layout/toolbar_light_dark.xml b/mobile/src/main/res/layout/toolbar_light_dark.xml similarity index 100% rename from src/main/res/layout/toolbar_light_dark.xml rename to mobile/src/main/res/layout/toolbar_light_dark.xml diff --git a/src/main/res/menu/app_manager_menu.xml b/mobile/src/main/res/menu/app_manager_menu.xml similarity index 100% rename from src/main/res/menu/app_manager_menu.xml rename to mobile/src/main/res/menu/app_manager_menu.xml diff --git a/src/main/res/menu/custom_rules_menu.xml b/mobile/src/main/res/menu/custom_rules_menu.xml similarity index 100% rename from src/main/res/menu/custom_rules_menu.xml rename to mobile/src/main/res/menu/custom_rules_menu.xml diff --git a/src/main/res/menu/profile_config_menu.xml b/mobile/src/main/res/menu/profile_config_menu.xml similarity index 100% rename from src/main/res/menu/profile_config_menu.xml rename to mobile/src/main/res/menu/profile_config_menu.xml diff --git a/src/main/res/menu/profile_manager_menu.xml b/mobile/src/main/res/menu/profile_manager_menu.xml similarity index 100% rename from src/main/res/menu/profile_manager_menu.xml rename to mobile/src/main/res/menu/profile_manager_menu.xml diff --git a/src/main/res/menu/profile_share_popup.xml b/mobile/src/main/res/menu/profile_share_popup.xml similarity index 100% rename from src/main/res/menu/profile_share_popup.xml rename to mobile/src/main/res/menu/profile_share_popup.xml diff --git a/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from src/main/res/mipmap-hdpi/ic_launcher.png rename to mobile/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from src/main/res/mipmap-mdpi/ic_launcher.png rename to mobile/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/src/main/res/mipmap-xhdpi/ic_launcher.png b/mobile/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from src/main/res/mipmap-xhdpi/ic_launcher.png rename to mobile/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from src/main/res/mipmap-xxhdpi/ic_launcher.png rename to mobile/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to mobile/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/src/main/res/raw/gtm_default_container b/mobile/src/main/res/raw/gtm_default_container similarity index 100% rename from src/main/res/raw/gtm_default_container rename to mobile/src/main/res/raw/gtm_default_container diff --git a/src/main/res/values-ja/strings.xml b/mobile/src/main/res/values-ja/strings.xml similarity index 100% rename from src/main/res/values-ja/strings.xml rename to mobile/src/main/res/values-ja/strings.xml diff --git a/src/main/res/values-ru/strings.xml b/mobile/src/main/res/values-ru/strings.xml similarity index 100% rename from src/main/res/values-ru/strings.xml rename to mobile/src/main/res/values-ru/strings.xml diff --git a/src/main/res/values-v21/dimen.xml b/mobile/src/main/res/values-v21/dimen.xml similarity index 100% rename from src/main/res/values-v21/dimen.xml rename to mobile/src/main/res/values-v21/dimen.xml diff --git a/src/main/res/values-v21/strings.xml b/mobile/src/main/res/values-v21/strings.xml similarity index 100% rename from src/main/res/values-v21/strings.xml rename to mobile/src/main/res/values-v21/strings.xml diff --git a/src/main/res/values-v21/styles.xml b/mobile/src/main/res/values-v21/styles.xml similarity index 100% rename from src/main/res/values-v21/styles.xml rename to mobile/src/main/res/values-v21/styles.xml diff --git a/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml similarity index 100% rename from src/main/res/values-zh-rCN/strings.xml rename to mobile/src/main/res/values-zh-rCN/strings.xml diff --git a/src/main/res/values-zh-rTW/strings.xml b/mobile/src/main/res/values-zh-rTW/strings.xml similarity index 100% rename from src/main/res/values-zh-rTW/strings.xml rename to mobile/src/main/res/values-zh-rTW/strings.xml diff --git a/src/main/res/values/arrays.xml b/mobile/src/main/res/values/arrays.xml similarity index 100% rename from src/main/res/values/arrays.xml rename to mobile/src/main/res/values/arrays.xml diff --git a/src/main/res/values/attrs.xml b/mobile/src/main/res/values/attrs.xml similarity index 100% rename from src/main/res/values/attrs.xml rename to mobile/src/main/res/values/attrs.xml diff --git a/src/main/res/values/colors.xml b/mobile/src/main/res/values/colors.xml similarity index 100% rename from src/main/res/values/colors.xml rename to mobile/src/main/res/values/colors.xml diff --git a/src/main/res/values/configs.xml b/mobile/src/main/res/values/configs.xml similarity index 100% rename from src/main/res/values/configs.xml rename to mobile/src/main/res/values/configs.xml diff --git a/src/main/res/values/dimen.xml b/mobile/src/main/res/values/dimen.xml similarity index 100% rename from src/main/res/values/dimen.xml rename to mobile/src/main/res/values/dimen.xml diff --git a/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml similarity index 100% rename from src/main/res/values/strings.xml rename to mobile/src/main/res/values/strings.xml diff --git a/src/main/res/values/styles.xml b/mobile/src/main/res/values/styles.xml similarity index 100% rename from src/main/res/values/styles.xml rename to mobile/src/main/res/values/styles.xml diff --git a/src/main/res/xml/pref_global.xml b/mobile/src/main/res/xml/pref_global.xml similarity index 100% rename from src/main/res/xml/pref_global.xml rename to mobile/src/main/res/xml/pref_global.xml diff --git a/src/main/res/xml/pref_profile.xml b/mobile/src/main/res/xml/pref_profile.xml similarity index 100% rename from src/main/res/xml/pref_profile.xml rename to mobile/src/main/res/xml/pref_profile.xml diff --git a/src/main/res/xml/shortcuts.xml b/mobile/src/main/res/xml/shortcuts.xml similarity index 100% rename from src/main/res/xml/shortcuts.xml rename to mobile/src/main/res/xml/shortcuts.xml diff --git a/src/main/res/xml/tracker.xml b/mobile/src/main/res/xml/tracker.xml similarity index 100% rename from src/main/res/xml/tracker.xml rename to mobile/src/main/res/xml/tracker.xml diff --git a/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala b/mobile/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala similarity index 100% rename from src/main/scala/be/mygod/preference/DialogPreferencePlus.scala rename to mobile/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala diff --git a/src/main/scala/be/mygod/preference/EditTextPreference.scala b/mobile/src/main/scala/be/mygod/preference/EditTextPreference.scala similarity index 100% rename from src/main/scala/be/mygod/preference/EditTextPreference.scala rename to mobile/src/main/scala/be/mygod/preference/EditTextPreference.scala diff --git a/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala b/mobile/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala similarity index 100% rename from src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala rename to mobile/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala diff --git a/src/main/scala/be/mygod/preference/NumberPickerPreference.scala b/mobile/src/main/scala/be/mygod/preference/NumberPickerPreference.scala similarity index 100% rename from src/main/scala/be/mygod/preference/NumberPickerPreference.scala rename to mobile/src/main/scala/be/mygod/preference/NumberPickerPreference.scala diff --git a/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala b/mobile/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala similarity index 100% rename from src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala rename to mobile/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala diff --git a/src/main/scala/be/mygod/preference/PreferenceCategory.scala b/mobile/src/main/scala/be/mygod/preference/PreferenceCategory.scala similarity index 100% rename from src/main/scala/be/mygod/preference/PreferenceCategory.scala rename to mobile/src/main/scala/be/mygod/preference/PreferenceCategory.scala diff --git a/src/main/scala/be/mygod/preference/PreferenceFragment.scala b/mobile/src/main/scala/be/mygod/preference/PreferenceFragment.scala similarity index 100% rename from src/main/scala/be/mygod/preference/PreferenceFragment.scala rename to mobile/src/main/scala/be/mygod/preference/PreferenceFragment.scala diff --git a/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala b/mobile/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala similarity index 100% rename from src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala rename to mobile/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala diff --git a/src/main/scala/be/mygod/preference/SummaryPreference.scala b/mobile/src/main/scala/be/mygod/preference/SummaryPreference.scala similarity index 100% rename from src/main/scala/be/mygod/preference/SummaryPreference.scala rename to mobile/src/main/scala/be/mygod/preference/SummaryPreference.scala diff --git a/src/main/scala/com/github/shadowsocks/AboutFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/AboutFragment.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/AboutFragment.scala rename to mobile/src/main/scala/com/github/shadowsocks/AboutFragment.scala diff --git a/src/main/scala/com/github/shadowsocks/AppManager.scala b/mobile/src/main/scala/com/github/shadowsocks/AppManager.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/AppManager.scala rename to mobile/src/main/scala/com/github/shadowsocks/AppManager.scala diff --git a/src/main/scala/com/github/shadowsocks/BaseService.scala b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/BaseService.scala rename to mobile/src/main/scala/com/github/shadowsocks/BaseService.scala diff --git a/src/main/scala/com/github/shadowsocks/BootReceiver.scala b/mobile/src/main/scala/com/github/shadowsocks/BootReceiver.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/BootReceiver.scala rename to mobile/src/main/scala/com/github/shadowsocks/BootReceiver.scala diff --git a/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala rename to mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala diff --git a/src/main/scala/com/github/shadowsocks/GlobalSettingsFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/GlobalSettingsFragment.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/GlobalSettingsFragment.scala rename to mobile/src/main/scala/com/github/shadowsocks/GlobalSettingsFragment.scala diff --git a/src/main/scala/com/github/shadowsocks/GuardedProcess.scala b/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/GuardedProcess.scala rename to mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala diff --git a/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/MainActivity.scala rename to mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala diff --git a/src/main/scala/com/github/shadowsocks/ProfileConfigActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigActivity.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/ProfileConfigActivity.scala rename to mobile/src/main/scala/com/github/shadowsocks/ProfileConfigActivity.scala diff --git a/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala rename to mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala diff --git a/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/ProfilesFragment.scala rename to mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala diff --git a/src/main/scala/com/github/shadowsocks/QRCodeDialog.scala b/mobile/src/main/scala/com/github/shadowsocks/QRCodeDialog.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/QRCodeDialog.scala rename to mobile/src/main/scala/com/github/shadowsocks/QRCodeDialog.scala diff --git a/src/main/scala/com/github/shadowsocks/QuickToggleShortcut.scala b/mobile/src/main/scala/com/github/shadowsocks/QuickToggleShortcut.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/QuickToggleShortcut.scala rename to mobile/src/main/scala/com/github/shadowsocks/QuickToggleShortcut.scala diff --git a/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala b/mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala rename to mobile/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala rename to mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksBackupAgent.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksBackupAgent.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/ShadowsocksBackupAgent.scala rename to mobile/src/main/scala/com/github/shadowsocks/ShadowsocksBackupAgent.scala diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala rename to mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala rename to mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala rename to mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala rename to mobile/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksTileService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksTileService.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/ShadowsocksTileService.scala rename to mobile/src/main/scala/com/github/shadowsocks/ShadowsocksTileService.scala diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala rename to mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala rename to mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala diff --git a/src/main/scala/com/github/shadowsocks/TaskerActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/TaskerActivity.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/TaskerActivity.scala rename to mobile/src/main/scala/com/github/shadowsocks/TaskerActivity.scala diff --git a/src/main/scala/com/github/shadowsocks/TaskerReceiver.scala b/mobile/src/main/scala/com/github/shadowsocks/TaskerReceiver.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/TaskerReceiver.scala rename to mobile/src/main/scala/com/github/shadowsocks/TaskerReceiver.scala diff --git a/src/main/scala/com/github/shadowsocks/ToolbarFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ToolbarFragment.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/ToolbarFragment.scala rename to mobile/src/main/scala/com/github/shadowsocks/ToolbarFragment.scala diff --git a/src/main/scala/com/github/shadowsocks/acl/Acl.scala b/mobile/src/main/scala/com/github/shadowsocks/acl/Acl.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/acl/Acl.scala rename to mobile/src/main/scala/com/github/shadowsocks/acl/Acl.scala diff --git a/src/main/scala/com/github/shadowsocks/acl/AclSyncJob.scala b/mobile/src/main/scala/com/github/shadowsocks/acl/AclSyncJob.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/acl/AclSyncJob.scala rename to mobile/src/main/scala/com/github/shadowsocks/acl/AclSyncJob.scala diff --git a/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala rename to mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala diff --git a/src/main/scala/com/github/shadowsocks/acl/DonaldTrump.scala b/mobile/src/main/scala/com/github/shadowsocks/acl/DonaldTrump.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/acl/DonaldTrump.scala rename to mobile/src/main/scala/com/github/shadowsocks/acl/DonaldTrump.scala diff --git a/src/main/scala/com/github/shadowsocks/acl/Subnet.scala b/mobile/src/main/scala/com/github/shadowsocks/acl/Subnet.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/acl/Subnet.scala rename to mobile/src/main/scala/com/github/shadowsocks/acl/Subnet.scala diff --git a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala b/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/database/DBHelper.scala rename to mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala diff --git a/src/main/scala/com/github/shadowsocks/database/Profile.scala b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/database/Profile.scala rename to mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala diff --git a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala b/mobile/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/database/ProfileManager.scala rename to mobile/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala diff --git a/src/main/scala/com/github/shadowsocks/preferences/KcpCliPreferenceDialogFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/preferences/KcpCliPreferenceDialogFragment.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/preferences/KcpCliPreferenceDialogFragment.scala rename to mobile/src/main/scala/com/github/shadowsocks/preferences/KcpCliPreferenceDialogFragment.scala diff --git a/src/main/scala/com/github/shadowsocks/utils/CloseUtils.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/CloseUtils.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/utils/CloseUtils.scala rename to mobile/src/main/scala/com/github/shadowsocks/utils/CloseUtils.scala diff --git a/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/utils/Constants.scala rename to mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala diff --git a/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/utils/IOUtils.scala rename to mobile/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala diff --git a/src/main/scala/com/github/shadowsocks/utils/Parser.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/utils/Parser.scala rename to mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala diff --git a/src/main/scala/com/github/shadowsocks/utils/TaskerSettings.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/TaskerSettings.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/utils/TaskerSettings.scala rename to mobile/src/main/scala/com/github/shadowsocks/utils/TaskerSettings.scala diff --git a/src/main/scala/com/github/shadowsocks/utils/TcpFastOpen.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/TcpFastOpen.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/utils/TcpFastOpen.scala rename to mobile/src/main/scala/com/github/shadowsocks/utils/TcpFastOpen.scala diff --git a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala rename to mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala diff --git a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala rename to mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala diff --git a/src/main/scala/com/github/shadowsocks/utils/Typefaces.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Typefaces.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/utils/Typefaces.scala rename to mobile/src/main/scala/com/github/shadowsocks/utils/Typefaces.scala diff --git a/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/utils/Utils.scala rename to mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala diff --git a/src/main/scala/com/github/shadowsocks/widget/BoundedCardView.scala b/mobile/src/main/scala/com/github/shadowsocks/widget/BoundedCardView.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/widget/BoundedCardView.scala rename to mobile/src/main/scala/com/github/shadowsocks/widget/BoundedCardView.scala diff --git a/src/main/scala/com/github/shadowsocks/widget/BoundedGridLayout.scala b/mobile/src/main/scala/com/github/shadowsocks/widget/BoundedGridLayout.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/widget/BoundedGridLayout.scala rename to mobile/src/main/scala/com/github/shadowsocks/widget/BoundedGridLayout.scala diff --git a/src/main/scala/com/github/shadowsocks/widget/BoundedView.scala b/mobile/src/main/scala/com/github/shadowsocks/widget/BoundedView.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/widget/BoundedView.scala rename to mobile/src/main/scala/com/github/shadowsocks/widget/BoundedView.scala diff --git a/src/main/scala/com/github/shadowsocks/widget/UndoSnackbarManager.scala b/mobile/src/main/scala/com/github/shadowsocks/widget/UndoSnackbarManager.scala similarity index 100% rename from src/main/scala/com/github/shadowsocks/widget/UndoSnackbarManager.scala rename to mobile/src/main/scala/com/github/shadowsocks/widget/UndoSnackbarManager.scala diff --git a/src/main/scala/scala/collection/mutable/SortedList.scala b/mobile/src/main/scala/scala/collection/mutable/SortedList.scala similarity index 100% rename from src/main/scala/scala/collection/mutable/SortedList.scala rename to mobile/src/main/scala/scala/collection/mutable/SortedList.scala From 044f6f1fce58a3de63fec204cd77129c1e38550f Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 12 Jan 2017 23:52:10 +0800 Subject: [PATCH 02/98] Apply commonSettings to root --- build.sbt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 87f0605b..cfa9233b 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,3 @@ -import android.Keys.proguardCache - lazy val commonSettings = Seq( scalaVersion := "2.11.8", dexMaxHeap := "4g", @@ -19,4 +17,9 @@ lazy val commonSettings = Seq( typedResources := false ) -lazy val mobile = project.settings(commonSettings) +lazy val shadowsocks = project.in(file(".")) + .settings(commonSettings) + .aggregate(mobile) + +lazy val mobile = project + .settings(commonSettings) From 2798eddaa78fee94bdb87ef6da392ad05fe8d1d0 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 13 Jan 2017 00:31:58 +0800 Subject: [PATCH 03/98] Remove KCP from project --- .gitignore | 23 ------ README.md | 2 - mobile/src/main/assets/pages/about.html | 1 - mobile/src/main/res/values/strings.xml | 6 -- mobile/src/main/res/xml/pref_profile.xml | 22 ------ .../com/github/shadowsocks/BaseService.scala | 29 -------- .../shadowsocks/ProfileConfigFragment.scala | 7 -- .../shadowsocks/ShadowsocksNatService.scala | 40 +---------- .../shadowsocks/ShadowsocksVpnService.scala | 72 +------------------ .../shadowsocks/database/DBHelper.scala | 34 ++++----- .../github/shadowsocks/database/Profile.scala | 16 +---- .../KcpCliPreferenceDialogFragment.scala | 54 -------------- .../github/shadowsocks/utils/Constants.scala | 7 +- 13 files changed, 28 insertions(+), 285 deletions(-) delete mode 100644 mobile/src/main/scala/com/github/shadowsocks/preferences/KcpCliPreferenceDialogFragment.scala diff --git a/.gitignore b/.gitignore index 2cd26960..020b7ea5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,31 +1,8 @@ -classes -bin -gen target local.properties -.classpath -.project -.settings -tests/bin -tests/gen -tests/local.properties -NUL -gen_* -*.class -*.o local.sbt -.deps /proguard-sbt.txt #Intellij IDEA -*.iml -*.ipr -*.iws .idea/ -out/ .d - -#NDK -src/main/obj -src/main/libs -jni/Application.mk diff --git a/README.md b/README.md index 14688f34..bec1e2c9 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. * JDK 1.8 * SBT 0.13.0+ -* Go 1.4+ * Android SDK - Build Tools 25+ - Android Support Repository and Google Repository (see `build.sbt` for version) @@ -53,7 +52,6 @@ Translators can go to [POEditor](https://poeditor.com/join/project/u5VHO9vhSf) t
  • shadowsocks-libev: GPLv3
  • pdnsd: GPLv3
  • libev: GPLv2
  • -
  • kcptun: MIT
  • libsodium: ISC
  • diff --git a/mobile/src/main/assets/pages/about.html b/mobile/src/main/assets/pages/about.html index eb991850..e8d232db 100644 --- a/mobile/src/main/assets/pages/about.html +++ b/mobile/src/main/assets/pages/about.html @@ -298,7 +298,6 @@ along with this program. If not, see http
  • shadowsocks-libev: GPLv3
  • pdnsd: GPLv3
  • libev: GPLv2
  • -
  • kcptun: MIT
  • libsodium: ISC
  • diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index b14816a5..1aaf2586 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -135,12 +135,6 @@ Connected Not connected - - Enable Shadowsocks over KCP - KCP Port - %d (port number of the remote KCPTUN server) - KCP Parameters (CLI) - Custom rules Selection… diff --git a/mobile/src/main/res/xml/pref_profile.xml b/mobile/src/main/res/xml/pref_profile.xml index 80816c63..6f9af431 100644 --- a/mobile/src/main/res/xml/pref_profile.xml +++ b/mobile/src/main/res/xml/pref_profile.xml @@ -71,26 +71,4 @@ - - - - - - - - - - diff --git a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala index 3b532114..59af31ae 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -28,7 +28,6 @@ import java.util.{Timer, TimerTask} import android.app.Service import android.content.{BroadcastReceiver, Context, Intent, IntentFilter} -import android.net.ConnectivityManager import android.os.{Handler, IBinder, RemoteCallbackList} import android.text.TextUtils import android.util.Log @@ -49,7 +48,6 @@ trait BaseService extends Service { @volatile protected var profile: Profile = _ case class NameNotResolvedException() extends IOException - case class KcpcliParseException(cause: Throwable) extends Exception(cause) case class NullConnectionException() extends NullPointerException var timer: Timer = _ @@ -67,19 +65,6 @@ trait BaseService extends Service { } var closeReceiverRegistered: Boolean = _ - var kcptunProcess: GuardedProcess = _ - private val networkReceiver: BroadcastReceiver = (context: Context, _: Intent) => { - val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE).asInstanceOf[ConnectivityManager] - val activeNetwork = cm.getActiveNetworkInfo - val isConnected = activeNetwork != null && activeNetwork.isConnected - - if (isConnected && profile.kcp && kcptunProcess != null) { - restartHanlder.removeCallbacks(null) - restartHanlder.postDelayed(() => if (kcptunProcess != null) kcptunProcess.restart(), 2000) - } - } - var networkReceiverRegistered: Boolean = _ - val binder = new IShadowsocksService.Stub { override def getState: Int = { state @@ -185,14 +170,6 @@ trait BaseService extends Service { closeReceiverRegistered = true } - if (profile.kcp && !networkReceiverRegistered) { - // register network change receiver - val filter = new IntentFilter() - filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION) - registerReceiver(networkReceiver, filter) - networkReceiverRegistered = true - } - app.track(getClass.getSimpleName, "start") changeState(State.CONNECTING) @@ -202,8 +179,6 @@ trait BaseService extends Service { Utils.ThrowableFuture(try connect() catch { case _: NameNotResolvedException => stopRunner(stopService = true, getString(R.string.invalid_server)) - case exc: KcpcliParseException => - stopRunner(stopService = true, getString(R.string.service_failed) + ": " + exc.cause.getMessage) case _: NullConnectionException => stopRunner(stopService = true, getString(R.string.reboot_required)) case exc: Throwable => stopRunner(stopService = true, getString(R.string.service_failed) + ": " + exc.getMessage) @@ -218,10 +193,6 @@ trait BaseService extends Service { unregisterReceiver(closeReceiver) closeReceiverRegistered = false } - if (networkReceiverRegistered) { - unregisterReceiver(networkReceiver) - networkReceiverRegistered = false - } // Make sure update total traffic when stopping the runner updateTrafficTotal(TrafficMonitor.txTotal, TrafficMonitor.rxTotal) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala index f558b81f..60b51b98 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala @@ -25,13 +25,11 @@ import android.content.{DialogInterface, Intent, SharedPreferences} import android.os.{Build, Bundle, UserManager} import android.support.v14.preference.SwitchPreference import android.support.v7.app.AlertDialog -import android.support.v7.preference.Preference import android.support.v7.widget.Toolbar.OnMenuItemClickListener import android.view.MenuItem import be.mygod.preference.PreferenceFragment import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.database.Profile -import com.github.shadowsocks.preferences.KcpCliPreferenceDialogFragment import com.github.shadowsocks.utils.{Action, Key, Utils} class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListener @@ -75,11 +73,6 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe isProxyApps.setChecked(app.settings.getBoolean(Key.proxyApps, false)) // fetch proxyApps updated by AppManager } - override def onDisplayPreferenceDialog(preference: Preference): Unit = preference.getKey match { - case Key.kcpcli => displayPreferenceDialog(Key.kcpcli, new KcpCliPreferenceDialogFragment()) - case _ => super.onDisplayPreferenceDialog(preference) - } - override def onMenuItemClick(item: MenuItem): Boolean = item.getItemId match { case R.id.action_delete => new AlertDialog.Builder(getActivity) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index 58ad4139..48a4a366 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -53,15 +53,10 @@ class ShadowsocksNatService extends BaseService { var su: Shell.Interactive = _ def startShadowsocksDaemon() { - val conf = if (profile.kcp) { - ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, "127.0.0.1", profile.localPort + 90, profile.localPort, - profile.password, profile.method, 600) - } else { + val conf = ConfigUtils .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort, profile.password, profile.method, 600) - } Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-local-nat.conf"))(p => { p.println(conf) }) @@ -85,27 +80,6 @@ class ShadowsocksNatService extends BaseService { sslocalProcess = new GuardedProcess(cmd).start() } - def startKcptunDaemon() { - if (profile.kcpcli == null) profile.kcpcli = "" - - val host = if (profile.host.contains(":")) { - "[" + profile.host + "]" - } else { - profile.host - } - - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libkcptun.so" - , "-r", host + ":" + profile.kcpPort - , "-l", "127.0.0.1:" + (profile.localPort + 90)) - try cmd ++= Utils.translateCommandline(profile.kcpcli) catch { - case exc: Exception => throw KcpcliParseException(exc) - } - - if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) - - kcptunProcess = new GuardedProcess(cmd).start() - } - def startDNSTunnel() { if (profile.udpdns) { val conf = ConfigUtils @@ -130,15 +104,10 @@ class ShadowsocksNatService extends BaseService { sstunnelProcess = new GuardedProcess(cmd).start() } else { - val conf = if (profile.kcp) { - ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, "127.0.0.1", profile.localPort + 90, - profile.localPort + 63, profile.password, profile.method, 10) - } else { + val conf = ConfigUtils .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort + 63, profile.password, profile.method, 10) - } Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => { p.println(conf) }) @@ -205,7 +174,6 @@ class ShadowsocksNatService extends BaseService { startShadowsocksDaemon() if (!profile.udpdns) startDnsDaemon() - if (profile.kcp) startKcptunDaemon() setupIptables() @@ -221,10 +189,6 @@ class ShadowsocksNatService extends BaseService { } def killProcesses() { - if (kcptunProcess != null) { - kcptunProcess.destroy() - kcptunProcess = null - } if (sslocalProcess != null) { sslocalProcess.destroy() sslocalProcess = null diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 9218aee5..46b334e9 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -91,10 +91,6 @@ class ShadowsocksVpnService extends VpnService with BaseService { } def killProcesses() { - if (kcptunProcess != null) { - kcptunProcess.destroy() - kcptunProcess = null - } if (sslocalProcess != null) { sslocalProcess.destroy() sslocalProcess = null @@ -157,76 +153,19 @@ class ShadowsocksVpnService extends VpnService with BaseService { val fd = startVpn() if (!sendFd(fd)) throw new Exception("sendFd failed") - if (profile.kcp) { - startKcptunDaemon() - } - startShadowsocksDaemon() - if (profile.udpdns && profile.kcp) { - startShadowsocksUDPDaemon() - } - if (!profile.udpdns) { startDnsDaemon() startDnsTunnel() } } - def startKcptunDaemon() { - if (profile.kcpcli == null) profile.kcpcli = "" - - val host = if (profile.host.contains(":")) { - "[" + profile.host + "]" - } else { - profile.host - } - - val cmd = ArrayBuffer(getApplicationInfo.nativeLibraryDir + "/libkcptun.so" - , "-r", host + ":" + profile.kcpPort - , "-l", "127.0.0.1:" + (profile.localPort + 90) - , "--path", protectPath) - try cmd ++= Utils.translateCommandline(profile.kcpcli) catch { - case exc: Exception => throw KcpcliParseException(exc) - } - - if (BuildConfig.DEBUG) - Log.d(TAG, cmd.mkString(" ")) - - kcptunProcess = new GuardedProcess(cmd).start() - } - - def startShadowsocksUDPDaemon() { - val conf = ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort, - profile.password, profile.method, 600) - Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-local-udp-vpn.conf"))(p => { - p.println(conf) - }) - - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", "-V", "-U" - , "-b", "127.0.0.1" - , "-t", "600" - , "-P", getApplicationInfo.dataDir - , "-c", getApplicationInfo.dataDir + "/ss-local-udp-vpn.conf") - - if (profile.auth) cmd += "-A" - - if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) - - sstunnelProcess = new GuardedProcess(cmd).start() - } - def startShadowsocksDaemon() { - val conf = if (profile.kcp) { - ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, "127.0.0.1", profile.localPort + 90, profile.localPort, - profile.password, profile.method, 600) - } else { + val conf = ConfigUtils .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort, profile.password, profile.method, 600) - } Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-local-vpn.conf"))(p => { p.println(conf) }) @@ -239,7 +178,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { if (profile.auth) cmd += "-A" - if (profile.udpdns && !profile.kcp) cmd += "-u" + if (profile.udpdns) cmd += "-u" if (profile.route != Acl.ALL) { cmd += "--acl" @@ -254,15 +193,10 @@ class ShadowsocksVpnService extends VpnService with BaseService { } def startDnsTunnel() { - val conf = if (profile.kcp) { - ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, "127.0.0.1", profile.localPort + 90, profile.localPort + 63, - profile.password, profile.method, 10) - } else { + val conf = ConfigUtils .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort + 63, profile.password, profile.method, 10) - } Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf"))(p => { p.println(conf) }) diff --git a/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala b/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala index 9e4e55b5..ebbb3e2c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala @@ -46,7 +46,7 @@ object DBHelper { } class DBHelper(val context: Context) - extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 21) { + extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 22) { import DBHelper._ lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile]) @@ -115,24 +115,26 @@ class DBHelper(val context: Context) profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'bypass-lan-china' WHERE route = 'bypass-china'") } - if (oldVersion < 17) { - profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN kcp SMALLINT;") - profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN kcpcli VARCHAR DEFAULT " + - "'--crypt none --mode normal --mtu 1200 --nocomp --dscp 46 --parityshard 0';") - } else if (oldVersion < 20) { - profileDao.executeRawNoArgs("UPDATE `profile` SET kcpcli = '--crypt none --mode normal --mtu 1200 --nocomp " + - "--dscp 46 --parityshard 0' WHERE kcpcli IS NULL;") - } - - if (oldVersion < 18) { - profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN kcpPort INTEGER DEFAULT 8399;") - } else if (oldVersion < 19) { - profileDao.executeRawNoArgs("UPDATE `profile` SET kcpPort = 8399 WHERE kcpPort = 0;") - } - if (oldVersion < 21) { profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN remoteDns VARCHAR DEFAULT '8.8.8.8';") } + + if (oldVersion < 17) { + profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN plugin VARCHAR;") + } else if (oldVersion < 22) { + // upgrade kcptun to SIP003 plugin + profileDao.executeRawNoArgs("BEGIN TRANSACTION;") + profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;") + TableUtils.createTable(connectionSource, classOf[Profile]) + profileDao.executeRawNoArgs( + "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," + + " udpdns, auth, ipv6, individual, tx, rx, date, userOrder, plugin) " + + "SELECT id, name, host, localPort, CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, " + + "route, proxyApps, bypass, udpdns, auth, ipv6, individual, tx, rx, date, userOrder, " + + "CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;") + profileDao.executeRawNoArgs("DROP TABLE `tmp`;") + profileDao.executeRawNoArgs("COMMIT;") + } } catch { case ex: Exception => app.track(ex) diff --git a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala index dac6e203..19ffe4d5 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala @@ -90,13 +90,7 @@ class Profile { var userOrder: Long = _ @DatabaseField - var kcp: Boolean = _ - - @DatabaseField - var kcpPort: Int = 8399 - - @DatabaseField - var kcpcli: String = "--crypt none --mode normal --mtu 1200 --nocomp --dscp 46 --parityshard 0" + var plugin: String = _ def formattedAddress: String = (if (host.contains(":")) "[%s]:%d" else "%s:%d").format(host, remotePort) def nameIsEmpty: Boolean = name == null || name.isEmpty @@ -123,9 +117,7 @@ class Profile { .putBoolean(Key.auth, auth) .putBoolean(Key.ipv6, ipv6) .putString(Key.individual, individual) - .putBoolean(Key.kcp, kcp) - .putInt(Key.kcpPort, kcpPort) - .putString(Key.kcpcli, kcpcli) + .putString(Key.plugin, plugin) .remove(Key.dirty) def deserialize(pref: SharedPreferences) { // It's assumed that default values are never used, so 0/false/null is always used even if that isn't the case @@ -143,8 +135,6 @@ class Profile { auth = pref.getBoolean(Key.auth, false) ipv6 = pref.getBoolean(Key.ipv6, false) individual = pref.getString(Key.individual, null) - kcp = pref.getBoolean(Key.kcp, false) - kcpPort = pref.getInt(Key.kcpPort, 0) - kcpcli = pref.getString(Key.kcpcli, null) + plugin = pref.getString(Key.plugin, null) } } diff --git a/mobile/src/main/scala/com/github/shadowsocks/preferences/KcpCliPreferenceDialogFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/preferences/KcpCliPreferenceDialogFragment.scala deleted file mode 100644 index 05319d9c..00000000 --- a/mobile/src/main/scala/com/github/shadowsocks/preferences/KcpCliPreferenceDialogFragment.scala +++ /dev/null @@ -1,54 +0,0 @@ -/*******************************************************************************/ -/* */ -/* 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 com.github.shadowsocks.preferences - -import android.app.AlertDialog -import android.content.DialogInterface -import eu.chainfire.libsuperuser.Shell -import be.mygod.preference.EditTextPreferenceDialogFragment - -import scala.collection.JavaConverters._ - -/** - * @author Mygod - */ -class KcpCliPreferenceDialogFragment extends EditTextPreferenceDialogFragment { - override def onPrepareDialogBuilder(builder: AlertDialog.Builder) { - super.onPrepareDialogBuilder(builder) - builder.setNeutralButton("?", ((_, _) => new AlertDialog.Builder(builder.getContext) - .setTitle("?") - .setMessage(Shell.SH.run(builder.getContext.getApplicationInfo.nativeLibraryDir + "/libkcptun.so --help") - .asScala - .dropWhile(line => line != "GLOBAL OPTIONS:") - .drop(1) - .takeWhile(line => line.length() > 3) - .filter(line => - !line.startsWith(" --localaddr ") && - !line.startsWith(" --remoteaddr ") && - !line.startsWith(" --path ") && - !line.startsWith(" --help,") && - !line.startsWith(" --version,")) - .mkString("\n") - .replaceAll(" {2,}", "\n") - .substring(1)) // remove 1st \n - .show()): DialogInterface.OnClickListener) - } -} diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala index ea504bc0..2b2d1929 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala @@ -26,9 +26,8 @@ object Executable { val SS_LOCAL = "ss-local" val SS_TUNNEL = "ss-tunnel" val TUN2SOCKS = "tun2socks" - val KCPTUN = "kcptun" - val EXECUTABLES = Array(SS_LOCAL, SS_TUNNEL, PDNSD, REDSOCKS, TUN2SOCKS, KCPTUN) + val EXECUTABLES = Array(SS_LOCAL, SS_TUNNEL, PDNSD, REDSOCKS, TUN2SOCKS) } object ConfigUtils { @@ -153,9 +152,7 @@ object Key { val localPort = "localPortNum" val remoteDns = "remoteDns" - val kcp = "kcp" - val kcpPort = "kcpPort" - val kcpcli = "kcpcli" + val plugin = "plugin" val dirty = "profileDirty" From f6bbaa7251cfef45c9dca87d1612ba49f161e447 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 13 Jan 2017 00:33:57 +0800 Subject: [PATCH 04/98] Remove kcptun from travis --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f217887e..62338c27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ env: global: - NDK_VERSION=r12b - NDK_CCACHE=ccache - - GOROOT_BOOTSTRAP=$GOROOT - ANDROID_NDK_HOME=$HOME/.android/android-ndk-${NDK_VERSION} - SBTPATH=$HOME/.sbt - PATH=${ANDROID_NDK_HOME}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools:${PATH} From a03ef5dfe966edf028898f06a4449e840c2d6a68 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 13 Jan 2017 01:50:04 +0800 Subject: [PATCH 05/98] Initial commit for plugin-lib --- build.sbt | 25 +++++++++- mobile/build.sbt | 12 ++--- mobile/src/main/AndroidManifest.xml | 1 - mobile/src/main/res/values-v21/styles.xml | 4 -- mobile/src/main/res/values/attrs.xml | 4 -- mobile/src/main/res/values/styles.xml | 49 +------------------ .../shadowsocks/database/DBHelper.scala | 6 +-- plugin-lib/build.sbt | 5 ++ plugin-lib/src/main/AndroidManifest.xml | 11 +++++ plugin-lib/src/main/res/values-v21/styles.xml | 4 ++ plugin-lib/src/main/res/values/attrs.xml | 7 +++ .../src/main/res/values/colors.xml | 0 plugin-lib/src/main/res/values/styles.xml | 45 +++++++++++++++++ .../preference/DialogPreferencePlus.scala | 0 .../mygod/preference/EditTextPreference.scala | 0 .../EditTextPreferenceDialogFragment.scala | 0 .../preference/NumberPickerPreference.scala | 2 +- ...NumberPickerPreferenceDialogFragment.scala | 0 .../mygod/preference/PreferenceCategory.scala | 0 .../mygod/preference/PreferenceFragment.scala | 0 .../preference/PreferenceGroupAdapter.scala | 2 +- .../mygod/preference/SummaryPreference.scala | 0 22 files changed, 107 insertions(+), 70 deletions(-) delete mode 100644 mobile/src/main/res/values-v21/styles.xml create mode 100644 plugin-lib/build.sbt create mode 100644 plugin-lib/src/main/AndroidManifest.xml create mode 100644 plugin-lib/src/main/res/values-v21/styles.xml create mode 100644 plugin-lib/src/main/res/values/attrs.xml rename {mobile => plugin-lib}/src/main/res/values/colors.xml (100%) create mode 100644 plugin-lib/src/main/res/values/styles.xml rename {mobile => plugin-lib}/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala (100%) rename {mobile => plugin-lib}/src/main/scala/be/mygod/preference/EditTextPreference.scala (100%) rename {mobile => plugin-lib}/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala (100%) rename {mobile => plugin-lib}/src/main/scala/be/mygod/preference/NumberPickerPreference.scala (98%) rename {mobile => plugin-lib}/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala (100%) rename {mobile => plugin-lib}/src/main/scala/be/mygod/preference/PreferenceCategory.scala (100%) rename {mobile => plugin-lib}/src/main/scala/be/mygod/preference/PreferenceFragment.scala (100%) rename {mobile => plugin-lib}/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala (99%) rename {mobile => plugin-lib}/src/main/scala/be/mygod/preference/SummaryPreference.scala (100%) diff --git a/build.sbt b/build.sbt index cfa9233b..4851fe6f 100644 --- a/build.sbt +++ b/build.sbt @@ -17,9 +17,30 @@ lazy val commonSettings = Seq( typedResources := false ) -lazy val shadowsocks = project.in(file(".")) +val supportLibsVersion = "25.1.0" +lazy val root = Project(id = "shadowsocks-android", base = file(".")) .settings(commonSettings) - .aggregate(mobile) + .aggregate(pluginLib, mobile) + +install in Android := (install in (mobile, Android)).value +run in Android := (run in (mobile, Android)).evaluated + +lazy val pluginLib = Project(id = "plugin-lib", base = file("plugin-lib")) + .settings(commonSettings) + .settings( + libraryDependencies ++= + "com.android.support" % "preference-v14" % supportLibsVersion :: + Nil + ) lazy val mobile = project .settings(commonSettings) + .settings( + libraryDependencies ++= + "com.android.support" % "cardview-v7" % supportLibsVersion :: + "com.android.support" % "customtabs" % supportLibsVersion :: + "com.android.support" % "design" % supportLibsVersion :: + "com.android.support" % "gridlayout-v7" % supportLibsVersion :: + Nil + ) + .dependsOn(pluginLib) diff --git a/mobile/build.sbt b/mobile/build.sbt index bebd4e1e..2816e688 100644 --- a/mobile/build.sbt +++ b/mobile/build.sbt @@ -6,6 +6,12 @@ version := "4.0.0" versionCode := Some(174) proguardOptions ++= + "-keep class android.support.v14.preference.SwitchPreference { (...); }" :: + "-keep class android.support.v7.preference.DropDownPreference { (...); }" :: + "-keep class android.support.v7.preference.PreferenceScreen { (...); }" :: + "-keep class be.mygod.preference.EditTextPreference { (...); }" :: + "-keep class be.mygod.preference.NumberPickerPreference { (...); }" :: + "-keep class be.mygod.preference.PreferenceCategory { (...); }" :: "-keep class com.github.shadowsocks.System { *; }" :: "-dontwarn com.google.android.gms.internal.**" :: "-dontwarn com.j256.ormlite.**" :: @@ -15,15 +21,9 @@ proguardOptions ++= resConfigs := Seq("ja", "ru", "zh-rCN", "zh-rTW") -val supportLibsVersion = "25.1.0" val playServicesVersion = "10.0.1" resolvers += Resolver.jcenterRepo libraryDependencies ++= - "com.android.support" % "cardview-v7" % supportLibsVersion :: - "com.android.support" % "customtabs" % supportLibsVersion :: - "com.android.support" % "design" % supportLibsVersion :: - "com.android.support" % "gridlayout-v7" % supportLibsVersion :: - "com.android.support" % "preference-v14" % supportLibsVersion :: "com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" :: "com.evernote" % "android-job" % "1.1.4" :: "com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" :: diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 5594a06b..2c4f35dc 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -25,7 +25,6 @@ android:roundIcon="@mipmap/ic_launcher" android:backupAgent=".ShadowsocksBackupAgent" android:label="@string/app_name" - android:theme="@style/Theme.Material" android:banner="@drawable/ic_start_connected"> - - - - - - - - - - - - - - - diff --git a/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala b/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala index ebbb3e2c..90e6d4e0 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala @@ -127,10 +127,10 @@ class DBHelper(val context: Context) profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;") TableUtils.createTable(connectionSource, classOf[Profile]) profileDao.executeRawNoArgs( - "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," + - " udpdns, auth, ipv6, individual, tx, rx, date, userOrder, plugin) " + + "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, remoteDns, proxyApps," + + " bypass, udpdns, auth, ipv6, individual, tx, rx, date, userOrder, plugin) " + "SELECT id, name, host, localPort, CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, " + - "route, proxyApps, bypass, udpdns, auth, ipv6, individual, tx, rx, date, userOrder, " + + "route, remoteDns, proxyApps, bypass, udpdns, auth, ipv6, individual, tx, rx, date, userOrder, " + "CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;") profileDao.executeRawNoArgs("DROP TABLE `tmp`;") profileDao.executeRawNoArgs("COMMIT;") diff --git a/plugin-lib/build.sbt b/plugin-lib/build.sbt new file mode 100644 index 00000000..d3e6c0d5 --- /dev/null +++ b/plugin-lib/build.sbt @@ -0,0 +1,5 @@ +enablePlugins(AndroidLib) +android.useSupportVectors + +name := "plugin" +version := "0.0.1-SNAPSHOT" diff --git a/plugin-lib/src/main/AndroidManifest.xml b/plugin-lib/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a5e9871d --- /dev/null +++ b/plugin-lib/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/plugin-lib/src/main/res/values-v21/styles.xml b/plugin-lib/src/main/res/values-v21/styles.xml new file mode 100644 index 00000000..624881d2 --- /dev/null +++ b/plugin-lib/src/main/res/values-v21/styles.xml @@ -0,0 +1,4 @@ + + + + + + + + + + + + + + diff --git a/mobile/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala b/plugin-lib/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala similarity index 100% rename from mobile/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala rename to plugin-lib/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala diff --git a/mobile/src/main/scala/be/mygod/preference/EditTextPreference.scala b/plugin-lib/src/main/scala/be/mygod/preference/EditTextPreference.scala similarity index 100% rename from mobile/src/main/scala/be/mygod/preference/EditTextPreference.scala rename to plugin-lib/src/main/scala/be/mygod/preference/EditTextPreference.scala diff --git a/mobile/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala b/plugin-lib/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala similarity index 100% rename from mobile/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala rename to plugin-lib/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala diff --git a/mobile/src/main/scala/be/mygod/preference/NumberPickerPreference.scala b/plugin-lib/src/main/scala/be/mygod/preference/NumberPickerPreference.scala similarity index 98% rename from mobile/src/main/scala/be/mygod/preference/NumberPickerPreference.scala rename to plugin-lib/src/main/scala/be/mygod/preference/NumberPickerPreference.scala index b321db1d..9b303670 100644 --- a/mobile/src/main/scala/be/mygod/preference/NumberPickerPreference.scala +++ b/plugin-lib/src/main/scala/be/mygod/preference/NumberPickerPreference.scala @@ -26,7 +26,7 @@ import android.support.v7.preference.DialogPreference import android.util.AttributeSet import android.view.ContextThemeWrapper import android.widget.NumberPicker -import com.github.shadowsocks.R +import com.github.shadowsocks.plugin.R class NumberPickerPreference(private val context: Context, attrs: AttributeSet = null) extends DialogPreference(context, attrs) with DialogPreferencePlus with SummaryPreference { diff --git a/mobile/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala b/plugin-lib/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala similarity index 100% rename from mobile/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala rename to plugin-lib/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala diff --git a/mobile/src/main/scala/be/mygod/preference/PreferenceCategory.scala b/plugin-lib/src/main/scala/be/mygod/preference/PreferenceCategory.scala similarity index 100% rename from mobile/src/main/scala/be/mygod/preference/PreferenceCategory.scala rename to plugin-lib/src/main/scala/be/mygod/preference/PreferenceCategory.scala diff --git a/mobile/src/main/scala/be/mygod/preference/PreferenceFragment.scala b/plugin-lib/src/main/scala/be/mygod/preference/PreferenceFragment.scala similarity index 100% rename from mobile/src/main/scala/be/mygod/preference/PreferenceFragment.scala rename to plugin-lib/src/main/scala/be/mygod/preference/PreferenceFragment.scala diff --git a/mobile/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala b/plugin-lib/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala similarity index 99% rename from mobile/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala rename to plugin-lib/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala index 41934e17..8819ca37 100644 --- a/mobile/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala +++ b/plugin-lib/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala @@ -28,7 +28,7 @@ import android.support.v4.content.ContextCompat import android.support.v4.view.ViewCompat import android.support.v7.preference.{PreferenceGroup, PreferenceViewHolder, PreferenceGroupAdapter => Old} import android.view.{LayoutInflater, View, ViewGroup} -import com.github.shadowsocks.R +import com.github.shadowsocks.plugin.R /** * Fix by: https://github.com/Gericop/Android-Support-Preference-V7-Fix/commit/7de016b007e28a264001a8bb353f110a7f64bb69 diff --git a/mobile/src/main/scala/be/mygod/preference/SummaryPreference.scala b/plugin-lib/src/main/scala/be/mygod/preference/SummaryPreference.scala similarity index 100% rename from mobile/src/main/scala/be/mygod/preference/SummaryPreference.scala rename to plugin-lib/src/main/scala/be/mygod/preference/SummaryPreference.scala From 5ead55d67a0fbe11e8b380dd34b82513977fe49f Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 13 Jan 2017 02:35:02 +0800 Subject: [PATCH 06/98] Rename plugin-lib to plugin --- build.sbt | 6 +++--- {plugin-lib => plugin}/build.sbt | 0 {plugin-lib => plugin}/src/main/AndroidManifest.xml | 0 {plugin-lib => plugin}/src/main/res/values-v21/styles.xml | 0 {plugin-lib => plugin}/src/main/res/values/attrs.xml | 0 {plugin-lib => plugin}/src/main/res/values/colors.xml | 0 {plugin-lib => plugin}/src/main/res/values/styles.xml | 0 .../scala/be/mygod/preference/DialogPreferencePlus.scala | 0 .../main/scala/be/mygod/preference/EditTextPreference.scala | 0 .../mygod/preference/EditTextPreferenceDialogFragment.scala | 0 .../scala/be/mygod/preference/NumberPickerPreference.scala | 0 .../preference/NumberPickerPreferenceDialogFragment.scala | 0 .../main/scala/be/mygod/preference/PreferenceCategory.scala | 0 .../main/scala/be/mygod/preference/PreferenceFragment.scala | 0 .../scala/be/mygod/preference/PreferenceGroupAdapter.scala | 0 .../main/scala/be/mygod/preference/SummaryPreference.scala | 0 16 files changed, 3 insertions(+), 3 deletions(-) rename {plugin-lib => plugin}/build.sbt (100%) rename {plugin-lib => plugin}/src/main/AndroidManifest.xml (100%) rename {plugin-lib => plugin}/src/main/res/values-v21/styles.xml (100%) rename {plugin-lib => plugin}/src/main/res/values/attrs.xml (100%) rename {plugin-lib => plugin}/src/main/res/values/colors.xml (100%) rename {plugin-lib => plugin}/src/main/res/values/styles.xml (100%) rename {plugin-lib => plugin}/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala (100%) rename {plugin-lib => plugin}/src/main/scala/be/mygod/preference/EditTextPreference.scala (100%) rename {plugin-lib => plugin}/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala (100%) rename {plugin-lib => plugin}/src/main/scala/be/mygod/preference/NumberPickerPreference.scala (100%) rename {plugin-lib => plugin}/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala (100%) rename {plugin-lib => plugin}/src/main/scala/be/mygod/preference/PreferenceCategory.scala (100%) rename {plugin-lib => plugin}/src/main/scala/be/mygod/preference/PreferenceFragment.scala (100%) rename {plugin-lib => plugin}/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala (100%) rename {plugin-lib => plugin}/src/main/scala/be/mygod/preference/SummaryPreference.scala (100%) diff --git a/build.sbt b/build.sbt index 4851fe6f..3a2908b3 100644 --- a/build.sbt +++ b/build.sbt @@ -20,12 +20,12 @@ lazy val commonSettings = Seq( val supportLibsVersion = "25.1.0" lazy val root = Project(id = "shadowsocks-android", base = file(".")) .settings(commonSettings) - .aggregate(pluginLib, mobile) + .aggregate(plugin, mobile) install in Android := (install in (mobile, Android)).value run in Android := (run in (mobile, Android)).evaluated -lazy val pluginLib = Project(id = "plugin-lib", base = file("plugin-lib")) +lazy val plugin = project .settings(commonSettings) .settings( libraryDependencies ++= @@ -43,4 +43,4 @@ lazy val mobile = project "com.android.support" % "gridlayout-v7" % supportLibsVersion :: Nil ) - .dependsOn(pluginLib) + .dependsOn(plugin) diff --git a/plugin-lib/build.sbt b/plugin/build.sbt similarity index 100% rename from plugin-lib/build.sbt rename to plugin/build.sbt diff --git a/plugin-lib/src/main/AndroidManifest.xml b/plugin/src/main/AndroidManifest.xml similarity index 100% rename from plugin-lib/src/main/AndroidManifest.xml rename to plugin/src/main/AndroidManifest.xml diff --git a/plugin-lib/src/main/res/values-v21/styles.xml b/plugin/src/main/res/values-v21/styles.xml similarity index 100% rename from plugin-lib/src/main/res/values-v21/styles.xml rename to plugin/src/main/res/values-v21/styles.xml diff --git a/plugin-lib/src/main/res/values/attrs.xml b/plugin/src/main/res/values/attrs.xml similarity index 100% rename from plugin-lib/src/main/res/values/attrs.xml rename to plugin/src/main/res/values/attrs.xml diff --git a/plugin-lib/src/main/res/values/colors.xml b/plugin/src/main/res/values/colors.xml similarity index 100% rename from plugin-lib/src/main/res/values/colors.xml rename to plugin/src/main/res/values/colors.xml diff --git a/plugin-lib/src/main/res/values/styles.xml b/plugin/src/main/res/values/styles.xml similarity index 100% rename from plugin-lib/src/main/res/values/styles.xml rename to plugin/src/main/res/values/styles.xml diff --git a/plugin-lib/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala b/plugin/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala similarity index 100% rename from plugin-lib/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala rename to plugin/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala diff --git a/plugin-lib/src/main/scala/be/mygod/preference/EditTextPreference.scala b/plugin/src/main/scala/be/mygod/preference/EditTextPreference.scala similarity index 100% rename from plugin-lib/src/main/scala/be/mygod/preference/EditTextPreference.scala rename to plugin/src/main/scala/be/mygod/preference/EditTextPreference.scala diff --git a/plugin-lib/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala b/plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala similarity index 100% rename from plugin-lib/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala rename to plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala diff --git a/plugin-lib/src/main/scala/be/mygod/preference/NumberPickerPreference.scala b/plugin/src/main/scala/be/mygod/preference/NumberPickerPreference.scala similarity index 100% rename from plugin-lib/src/main/scala/be/mygod/preference/NumberPickerPreference.scala rename to plugin/src/main/scala/be/mygod/preference/NumberPickerPreference.scala diff --git a/plugin-lib/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala b/plugin/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala similarity index 100% rename from plugin-lib/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala rename to plugin/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala diff --git a/plugin-lib/src/main/scala/be/mygod/preference/PreferenceCategory.scala b/plugin/src/main/scala/be/mygod/preference/PreferenceCategory.scala similarity index 100% rename from plugin-lib/src/main/scala/be/mygod/preference/PreferenceCategory.scala rename to plugin/src/main/scala/be/mygod/preference/PreferenceCategory.scala diff --git a/plugin-lib/src/main/scala/be/mygod/preference/PreferenceFragment.scala b/plugin/src/main/scala/be/mygod/preference/PreferenceFragment.scala similarity index 100% rename from plugin-lib/src/main/scala/be/mygod/preference/PreferenceFragment.scala rename to plugin/src/main/scala/be/mygod/preference/PreferenceFragment.scala diff --git a/plugin-lib/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala b/plugin/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala similarity index 100% rename from plugin-lib/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala rename to plugin/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala diff --git a/plugin-lib/src/main/scala/be/mygod/preference/SummaryPreference.scala b/plugin/src/main/scala/be/mygod/preference/SummaryPreference.scala similarity index 100% rename from plugin-lib/src/main/scala/be/mygod/preference/SummaryPreference.scala rename to plugin/src/main/scala/be/mygod/preference/SummaryPreference.scala From 1887c6ffb0c1a3416bb01ad64d42aecab40207be Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 13 Jan 2017 11:17:19 +0800 Subject: [PATCH 07/98] Initial commit for plugin library See spec at #1073. --- .../github/shadowsocks/utils/Commandline.java | 146 ++++++++++++++++++ .../com/github/shadowsocks/utils/Utils.scala | 46 +----- .../shadowsocks/plugin/PluginInterface.java | 82 ++++++++++ .../shadowsocks/plugin/PluginOptions.java | 78 ++++++++++ .../main/res/layout/toolbar_light_dark.xml | 0 .../plugin/ConfigurationActivity.scala | 39 +++++ .../shadowsocks/plugin/HelpActivity.scala | 25 +++ .../shadowsocks/plugin/HelpCallback.scala | 18 +++ .../plugin/NativePluginProvider.scala | 65 ++++++++ .../plugin/OptionsCapableActivity.scala | 32 ++++ .../shadowsocks/plugin/PathProvider.scala | 36 +++++ 11 files changed, 522 insertions(+), 45 deletions(-) create mode 100644 mobile/src/main/java/com/github/shadowsocks/utils/Commandline.java create mode 100644 plugin/src/main/java/com/github/shadowsocks/plugin/PluginInterface.java create mode 100644 plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java rename {mobile => plugin}/src/main/res/layout/toolbar_light_dark.xml (100%) create mode 100644 plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala create mode 100644 plugin/src/main/scala/com/github/shadowsocks/plugin/HelpActivity.scala create mode 100644 plugin/src/main/scala/com/github/shadowsocks/plugin/HelpCallback.scala create mode 100644 plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala create mode 100644 plugin/src/main/scala/com/github/shadowsocks/plugin/OptionsCapableActivity.scala create mode 100644 plugin/src/main/scala/com/github/shadowsocks/plugin/PathProvider.scala diff --git a/mobile/src/main/java/com/github/shadowsocks/utils/Commandline.java b/mobile/src/main/java/com/github/shadowsocks/utils/Commandline.java new file mode 100644 index 00000000..112df2d5 --- /dev/null +++ b/mobile/src/main/java/com/github/shadowsocks/utils/Commandline.java @@ -0,0 +1,146 @@ +package com.github.shadowsocks.utils; + +import java.util.ArrayList; +import java.util.StringTokenizer; + +/** + * 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. + *

    + * + * <someelement>
    + *   <acommandline executable="/executable/to/run">
    + *     <argument value="argument 1" />
    + *     <argument line="argument_1 argument_2 argument_3" />
    + *     <argument value="argument 4" />
    + *   </acommandline>
    + * </someelement>
    + *
    + * + * Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java + * + * Adds support for escape character '\'. + * + * @author Mygod + */ +public final class Commandline { + private Commandline() { } + + /** + * Quote the parts of the given array in way that makes them + * usable as command line arguments. + * @param line 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. + */ + public static String toString(String[] line) { + // empty path return empty string + if (line == null || line.length == 0) { + return ""; + } + // path containing one or more elements + final StringBuilder result = new StringBuilder(); + for (int i = 0; i < line.length; i++) { + if (i > 0) { + result.append(' '); + } + for (int j = 0; j < line[i].length(); ++j) { + char ch = line[i].charAt(j); + switch (ch) { + case ' ': case '\\': case '"': case '\'': result.append('\\'); // intentionally no break + default: result.append(ch); + } + } + } + return result.toString(); + } + + /** + * 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. + */ + public static String[] translateCommandline(String toProcess) { + if (toProcess == null || toProcess.length() == 0) { + //no command? no string + return new String[0]; + } + // parse with a simple finite state machine + + final int normal = 0; + final int inQuote = 1; + final int inDoubleQuote = 2; + int state = normal; + final StringTokenizer tok = new StringTokenizer(toProcess, "\\\"\' ", true); + final ArrayList result = new ArrayList<>(); + final StringBuilder current = new StringBuilder(); + boolean lastTokenHasBeenQuoted = false; + boolean lastTokenIsSlash = false; + + while (tok.hasMoreTokens()) { + String nextTok = tok.nextToken(); + switch (state) { + case inQuote: + if ("\'".equals(nextTok)) { + lastTokenHasBeenQuoted = true; + state = normal; + } else { + current.append(nextTok); + } + break; + case inDoubleQuote: + if ("\"".equals(nextTok)) { + if (lastTokenIsSlash) { + current.append(nextTok); + lastTokenIsSlash = false; + } else { + lastTokenHasBeenQuoted = true; + state = normal; + } + } else if ("\\".equals(nextTok)) { + if (lastTokenIsSlash) { + current.append(nextTok); + lastTokenIsSlash = false; + } else lastTokenIsSlash = true; + } else { + if (lastTokenIsSlash) { + current.append("\\"); // unescaped + lastTokenIsSlash = false; + } + current.append(nextTok); + } + break; + default: + if (lastTokenIsSlash) { + current.append(nextTok); + lastTokenIsSlash = false; + } else if ("\\".equals(nextTok)) lastTokenIsSlash = true; else if ("\'".equals(nextTok)) { + state = inQuote; + } else if ("\"".equals(nextTok)) { + state = inDoubleQuote; + } else if (" ".equals(nextTok)) { + if (lastTokenHasBeenQuoted || current.length() != 0) { + result.add(current.toString()); + current.setLength(0); + } + } else { + current.append(nextTok); + } + lastTokenHasBeenQuoted = false; + break; + } + } + if (lastTokenHasBeenQuoted || current.length() != 0) { + result.add(current.toString()); + } + if (state == inQuote || state == inDoubleQuote) { + throw new IllegalArgumentException("unbalanced quotes in " + toProcess); + } + if (lastTokenIsSlash) throw new IllegalArgumentException("escape character following nothing in " + toProcess); + return result.toArray(new String[result.size()]); + } +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala index 348fc6a4..4937adfd 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala @@ -23,9 +23,8 @@ package com.github.shadowsocks.utils import java.io.File import java.net._ import java.security.MessageDigest -import java.util.{Scanner, StringTokenizer} +import java.util.Scanner -import android.animation.{Animator, AnimatorListenerAdapter} import android.content.pm.PackageManager import android.content.{Context, Intent} import android.graphics._ @@ -39,7 +38,6 @@ import com.github.shadowsocks.{BuildConfig, ShadowsocksRunnerService} import org.xbill.DNS._ import scala.collection.JavaConversions._ -import scala.collection.mutable.ArrayBuffer import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import scala.util.{Failure, Try} @@ -98,48 +96,6 @@ object Utils { } } - /** - * Crack a command line. - * Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java#L471 - * @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 ArrayBuffer. - */ - def translateCommandline(toProcess: String): ArrayBuffer[String] = { - if (toProcess == null || toProcess.length == 0) return ArrayBuffer[String]() - val tok = new StringTokenizer(toProcess, "\"' ", true) - val result = ArrayBuffer[String]() - val current = new StringBuilder() - var quote = ' ' - var last = " " - while (tok.hasMoreTokens) { - val nextTok = tok.nextToken - quote match { - case '\'' => nextTok match { - case "'" => quote = ' ' - case _ => current.append(nextTok) - } - case '"' => nextTok match { - case "\"" => quote = ' ' - case _ => current.append(nextTok) - } - case _ => nextTok match { - case "'" => quote = '\'' - case "\"" => quote = '"' - case " " => if (last != " ") { - result.append(current.toString) - current.setLength(0) - } - case _ => current.append(nextTok) - } - } - last = nextTok - } - if (current.nonEmpty) result.append(current.toString) - if (quote == '\'' || quote == '"') throw new Exception("Unbalanced quotes in " + toProcess) - result - } - def resolve(host: String, addrType: Int): Option[String] = { try { val lookup = new Lookup(host, addrType) diff --git a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginInterface.java b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginInterface.java new file mode 100644 index 00000000..480de520 --- /dev/null +++ b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginInterface.java @@ -0,0 +1,82 @@ +package com.github.shadowsocks.plugin; + +/** + * This class provides String constants that will be used in plugin interfaces. + * + * This class is written in Java to keep Java interoperability. + * + * @author Mygod + */ +public final class PluginInterface { + private PluginInterface() { } + + /** + * Should be a NativePluginProvider. + * + * Constant Value: "com.github.shadowsocks.plugin.CATEGORY_NATIVE_PLUGIN" + */ + public static final String CATEGORY_NATIVE_PLUGIN = "com.github.shadowsocks.plugin.CATEGORY_NATIVE_PLUGIN"; + + /** + * The lookup key for a string that provides the whole command as a string. + * + * Example: "obfs=http;obfs-host=www.baidu.com" + * + * Constant Value: "com.github.shadowsocks.plugin.EXTRA_OPTIONS" + */ + public static final String EXTRA_OPTIONS = "com.github.shadowsocks.plugin.EXTRA_OPTIONS"; + /** + * The lookup key for a CharSequence that provides user relevant help message. + * + * Example: "obfs= Enable obfuscating: HTTP or TLS (Experimental). + * obfs-host= Hostname for obfuscating (Experimental)." + * + * Constant Value: "com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE" + */ + public static final String EXTRA_HELP_MESSAGE = "com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE"; + + /** + * The metadata key to retrieve default configuration. Default value is empty. + * + * Constant Value: "com.github.shadowsocks.plugin.default_config" + */ + public static final String METADATA_KEY_DEFAULT_CONFIG = "com.github.shadowsocks.plugin.default_config"; + + /** + * Relative to the file to be copied. This column is required. + * + * Example: "kcptun", "doc/help.txt" + * + * Type: String + */ + public static final String COLUMN_PATH = "path"; + + /** + * Authority to use for native plugin ContentProvider. + * + * @param pluginId Plugin ID. + * @return com.github.shadowsocks.plugin.$PLUGIN_ID + */ + public static String getAuthority(String pluginId) { + return "com.github.shadowsocks.plugin." + pluginId; + } + + /** + * Activity Action: Used for ConfigurationActivity. + * + * @param pluginId Plugin ID. + * @return com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_CONFIGURE + */ + public static String ACTION_CONFIGURE(String pluginId) { + return getAuthority(pluginId) + ".ACTION_CONFIGURE"; + } + /** + * Activity Action: Used for HelpActivity or HelpCallback. + * + * @param pluginId Plugin ID. + * @return com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_HELP + */ + public static String ACTION_HELP(String pluginId) { + return getAuthority(pluginId) + ".ACTION_HELP"; + } +} diff --git a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java new file mode 100644 index 00000000..de76ac71 --- /dev/null +++ b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java @@ -0,0 +1,78 @@ +package com.github.shadowsocks.plugin; + +import android.text.TextUtils; + +import java.util.HashMap; +import java.util.StringTokenizer; + +/** + * Helper class for processing plugin options. + * + * Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java + * + * @author Mygod + */ +public final class PluginOptions extends HashMap { + public PluginOptions() { + super(); + } + public PluginOptions(int initialCapacity) { + super(initialCapacity); + } + public PluginOptions(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor); + } + + public PluginOptions(String options) throws IllegalArgumentException { + if (TextUtils.isEmpty(options)) return; + final StringTokenizer tokenizer = new StringTokenizer(options, "\\=;", true); + final StringBuilder current = new StringBuilder(); + String key = null; + boolean firstEntry = true; + while (tokenizer.hasMoreTokens()) { + String nextToken = tokenizer.nextToken(); + if ("\\".equals(nextToken)) current.append(tokenizer.nextToken()); + else if ("=".equals(nextToken)) { + if (key != null) throw new IllegalArgumentException("Duplicate keys in " + options); + key = current.toString(); + current.setLength(0); + } else if (";".equals(nextToken)) { + if (current.length() > 0) put(key, current.toString()); + else if (firstEntry) id = key; + else throw new IllegalArgumentException("Value missing in " + options); + firstEntry = false; + } + } + } + public PluginOptions(String id, String options) throws IllegalArgumentException { + this(options); + this.id = id; + } + + public String id; + + private static void append(StringBuilder result, String str) { + for (int i = 0; i < str.length(); ++i) { + char ch = str.charAt(i); + switch (ch) { + case '\\': case '=': case ';': result.append('\\'); // intentionally no break + default: result.append(ch); + } + } + } + public String toString(boolean trimId) { + final StringBuilder result = new StringBuilder(); + if (!trimId && !TextUtils.isEmpty(id)) append(result, id); + for (Entry entry : entrySet()) if (entry.getValue() != null) { + if (result.length() > 0) result.append(';'); + append(result, entry.getKey()); + result.append('='); + append(result, entry.getValue()); + } + return result.toString(); + } + @Override + public String toString() { + return toString(false); + } +} diff --git a/mobile/src/main/res/layout/toolbar_light_dark.xml b/plugin/src/main/res/layout/toolbar_light_dark.xml similarity index 100% rename from mobile/src/main/res/layout/toolbar_light_dark.xml rename to plugin/src/main/res/layout/toolbar_light_dark.xml diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala new file mode 100644 index 00000000..2200d79f --- /dev/null +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala @@ -0,0 +1,39 @@ +package com.github.shadowsocks.plugin + +import android.app.Activity +import android.content.Intent + +/** + * Base class for configuration activity. A configuration activity is started when user wishes to configure the + * selected plugin. To create a configuration activity, extend this class, implement abstract methods, invoke + * `saveChanges(options)` and `discardChanges()` when appropriate, and add it to your manifest like this: + * + *

    <manifest>
    +  *    ...
    +  *    <application>
    +  *        ...
    +  *        <activity android:name="com.github.shadowsocks.$PLUGIN_ID.ConfigureActivity">
    +  *            <intent-filter>
    +  *                <action android:name="com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_CONFIGURE" />
    +  *            </intent-filter>
    +  *        </activity>
    +  *        ...
    +  *    </application>
    +  *</manifest>
    + * + * @author Mygod + */ +trait ConfigurationActivity extends OptionsCapableActivity { + /** + * Equivalent to setResult(RESULT_CANCELED). + */ + final def discardChanges(): Unit = setResult(Activity.RESULT_CANCELED) + + /** + * Equivalent to setResult(RESULT_OK, args_with_correct_format). + * + * @param options PluginOptions to save. + */ + final def saveChanges(options: PluginOptions): Unit = + setResult(Activity.RESULT_OK, new Intent().putExtra(PluginInterface.EXTRA_OPTIONS, options.toString)) +} diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpActivity.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpActivity.scala new file mode 100644 index 00000000..40288b8c --- /dev/null +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpActivity.scala @@ -0,0 +1,25 @@ +package com.github.shadowsocks.plugin + +/** + * Base class for a help activity. A help activity is started when user taps help when configuring options for your + * plugin. To create a help activity, just extend this class, and add it to your manifest like this: + * + *
    <manifest>
    +  *    ...
    +  *    <application>
    +  *        ...
    +  *        <activity android:name="com.github.shadowsocks.$PLUGIN_ID.HelpActivity">
    +  *            <intent-filter>
    +  *                <action android:name="com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_HELP" />
    +  *            </intent-filter>
    +  *        </activity>
    +  *        ...
    +  *    </application>
    +  *</manifest>
    + * + * @author Mygod + */ +trait HelpActivity extends OptionsCapableActivity { + // HelpActivity can choose to ignore options + override protected def onInitializePluginOptions(options: PluginOptions): Unit = () +} diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpCallback.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpCallback.scala new file mode 100644 index 00000000..1babb814 --- /dev/null +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpCallback.scala @@ -0,0 +1,18 @@ +package com.github.shadowsocks.plugin + +import android.app.Activity +import android.content.Intent + +/** + * HelpCallback is an HelpActivity but you just need to produce a CharSequence help message instead of having to + * provide UI. To create a help callback, just extend this class, implement abstract methods, and add it to your + * manifest following the same procedure as adding a HelpActivity. + * + * @author Mygod + */ +trait HelpCallback extends HelpActivity { + def produceHelpMessage(options: PluginOptions): CharSequence + + override protected def onInitializePluginOptions(options: PluginOptions): Unit = setResult(Activity.RESULT_OK, + new Intent().putExtra(PluginInterface.EXTRA_HELP_MESSAGE, produceHelpMessage(options))) +} diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala new file mode 100644 index 00000000..1fdc7845 --- /dev/null +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala @@ -0,0 +1,65 @@ +package com.github.shadowsocks.plugin + +import android.content.{ContentProvider, ContentValues} +import android.database.{Cursor, MatrixCursor} +import android.net.Uri +import android.os.ParcelFileDescriptor + +/** + * Base class for a native plugin provider. A native plugin provider offers read-only access to files that are required + * to run a plugin, such as binary files and other configuration files. To create a native plugin provider, extend this + * class, implement the abstract methods, and add it to your manifest like this: + * + *
    <manifest>
    +  *    ...
    +  *    <application>
    +  *        ...
    +  *        <provider android:name="com.github.shadowsocks.$PLUGIN_ID.PluginProvider"
    +  *                     android:authorities="com.github.shadowsocks.$PLUGIN_ID">
    +  *            <intent-filter>
    +  *                <category android:name="com.github.shadowsocks.plugin.CATEGORY_NATIVE_PLUGIN" />
    +  *            </intent-filter>
    +  *        </provider>
    +  *        ...
    +  *    </application>
    +  *</manifest>
    + * + * @author Mygod + */ +abstract class NativePluginProvider extends ContentProvider { + /** + * @inheritdoc + * + * NativePluginProvider returns application/x-elf by default. It's implementer's responsibility to change this to + * correct type. + */ + override def getType(uri: Uri): String = "application/x-elf" + + override def onCreate(): Boolean = true + + /** + * Provide all files needed for native plugin. + * + * @param provider A helper object to use to add files. + */ + protected def populateFiles(provider: PathProvider) + + override def query(uri: Uri, projection: Array[String], selection: String, selectionArgs: Array[String], + sortOrder: String): Cursor = { + if (selection != null || selectionArgs != null || sortOrder != null) ??? + val result = new MatrixCursor(projection.filter(_ == PluginInterface.COLUMN_PATH)) + populateFiles(new PathProvider(uri, result)) + result + } + + def openFile(uri: Uri): ParcelFileDescriptor + override def openFile(uri: Uri, mode: String): ParcelFileDescriptor = { + if (mode != "r") ??? + openFile(uri) + } + + // Methods that should not be used + override def update(uri: Uri, values: ContentValues, selection: String, selectionArgs: Array[String]): Int = ??? + override def insert(uri: Uri, values: ContentValues): Uri = ??? + override def delete(uri: Uri, selection: String, selectionArgs: Array[String]): Int = ??? +} diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/OptionsCapableActivity.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/OptionsCapableActivity.scala new file mode 100644 index 00000000..40dbd96a --- /dev/null +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/OptionsCapableActivity.scala @@ -0,0 +1,32 @@ +package com.github.shadowsocks.plugin + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.widget.Toast + +/** + * Activity that's capable of getting EXTRA_OPTIONS input. + * + * @author Mygod + */ +trait OptionsCapableActivity extends Activity { + protected def pluginOptions(intent: Intent = getIntent): PluginOptions = + try new PluginOptions(intent.getStringExtra(PluginInterface.EXTRA_OPTIONS)) catch { + case exc: IllegalArgumentException => + Toast.makeText(this, exc.getMessage, Toast.LENGTH_SHORT).show() + null + } + + /** + * Populate args to your user interface. + * + * @param options PluginOptions parsed. + */ + protected def onInitializePluginOptions(options: PluginOptions = pluginOptions()): Unit + + override protected def onPostCreate(savedInstanceState: Bundle) { + super.onPostCreate(savedInstanceState) + if (savedInstanceState == null) onInitializePluginOptions() + } +} diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/PathProvider.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/PathProvider.scala new file mode 100644 index 00000000..4eddf6d6 --- /dev/null +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/PathProvider.scala @@ -0,0 +1,36 @@ +package com.github.shadowsocks.plugin + +import java.io.File + +import android.database.MatrixCursor +import android.net.Uri + +/** + * Helper class to provide relative paths of files to copy. + * + * @author Mygod + */ +final class PathProvider private[plugin](baseUri: Uri, cursor: MatrixCursor) { + private val basePath = baseUri.getPath match { + case null => "" + case p => p.stripPrefix("/").stripSuffix("/") + } + + def addPath(path: String): PathProvider = { + val stripped = path.stripPrefix("/").stripSuffix("/") + if (stripped.startsWith(basePath)) cursor.newRow().add(PluginInterface.COLUMN_PATH, stripped) + this + } + def addTo(file: File, to: String = ""): PathProvider = { + var sub = to + file.getName + if (basePath.startsWith(sub)) if (file.isDirectory) { + sub += '/' + file.listFiles().foreach(addTo(_, sub)) + } else addPath(sub) + this + } + def addAt(file: File, at: String = ""): PathProvider = { + if (basePath.startsWith(at)) if (file.isDirectory) file.listFiles().foreach(addTo(_, at)) else addPath(at) + this + } +} From a38c9920c3f22367b9e056b208c3c8c068132c81 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 14 Jan 2017 02:20:27 +0800 Subject: [PATCH 08/98] Add initial support for configuring plugins --- mobile/src/main/res/values/dimen.xml | 1 + mobile/src/main/res/values/strings.xml | 6 ++ mobile/src/main/res/xml/pref_profile.xml | 16 ++++ .../com/github/shadowsocks/AppManager.scala | 18 ++--- .../shadowsocks/ProfileConfigFragment.scala | 62 ++++++++++++++- .../shadowsocks/ShadowsocksApplication.scala | 13 +++- .../shadowsocks/plugin/NativePlugin.scala | 16 ++++ .../github/shadowsocks/plugin/NoPlugin.scala | 11 +++ .../github/shadowsocks/plugin/Plugin.scala | 13 ++++ .../plugin/PluginConfiguration.scala | 43 +++++++++++ .../shadowsocks/plugin/PluginManager.scala | 25 ++++++ .../shadowsocks/plugin/ResolvedPlugin.scala | 16 ++++ .../BottomSheetPreferenceDialogFragment.scala | 77 +++++++++++++++++++ .../preference/IconListPreference.scala | 65 ++++++++++++++++ .../PluginConfigurationDialogFragment.scala | 57 ++++++++++++++ plugin/build.sbt | 2 + .../shadowsocks/plugin/PluginInterface.java | 4 +- .../shadowsocks/plugin/PluginOptions.java | 12 +-- .../EditTextPreferenceDialogFragment.scala | 2 +- .../mygod/preference/PreferenceFragment.scala | 3 +- 20 files changed, 439 insertions(+), 23 deletions(-) create mode 100644 mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala create mode 100644 mobile/src/main/scala/com/github/shadowsocks/plugin/NoPlugin.scala create mode 100644 mobile/src/main/scala/com/github/shadowsocks/plugin/Plugin.scala create mode 100644 mobile/src/main/scala/com/github/shadowsocks/plugin/PluginConfiguration.scala create mode 100644 mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala create mode 100644 mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala create mode 100644 mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala create mode 100644 mobile/src/main/scala/com/github/shadowsocks/preference/IconListPreference.scala create mode 100644 mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala diff --git a/mobile/src/main/res/values/dimen.xml b/mobile/src/main/res/values/dimen.xml index ad252519..7fc6e0d3 100644 --- a/mobile/src/main/res/values/dimen.xml +++ b/mobile/src/main/res/values/dimen.xml @@ -1,6 +1,7 @@ 480dp + 8dp -44dp diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 1aaf2586..7e52d5dd 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -142,4 +142,10 @@ Subnet/Hostname PCRE pattern Domain name and all its subdomain names Edit rule + + + Plugin + Configure… + Disabled + Unknown plugin %s diff --git a/mobile/src/main/res/xml/pref_profile.xml b/mobile/src/main/res/xml/pref_profile.xml index 6f9af431..80b104e7 100644 --- a/mobile/src/main/res/xml/pref_profile.xml +++ b/mobile/src/main/res/xml/pref_profile.xml @@ -71,4 +71,20 @@ + + + + + + + diff --git a/mobile/src/main/scala/com/github/shadowsocks/AppManager.scala b/mobile/src/main/scala/com/github/shadowsocks/AppManager.scala index b61c2705..ceffb9d3 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/AppManager.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/AppManager.scala @@ -48,21 +48,13 @@ object AppManager { private var instance: AppManager = _ - private var receiverRegistered: Boolean = _ + private var receiver: BroadcastReceiver = _ private var cachedApps: Array[ProxiedApp] = _ private def getApps(pm: PackageManager) = { - if (!receiverRegistered) { - val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED) - filter.addAction(Intent.ACTION_PACKAGE_REMOVED) - filter.addDataScheme("package") - app.registerReceiver((_: Context, intent: Intent) => - if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED || - !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { - synchronized(cachedApps = null) - val instance = AppManager.instance - if (instance != null) instance.reloadApps() - }, filter) - receiverRegistered = true + if (receiver == null) receiver = app.listenForPackageChanges { + synchronized(cachedApps = null) + val instance = AppManager.instance + if (instance != null) instance.reloadApps() } synchronized { if (cachedApps == null) cachedApps = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala index 60b51b98..bb82e5c1 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala @@ -23,19 +23,33 @@ package com.github.shadowsocks import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.{DialogInterface, Intent, SharedPreferences} import android.os.{Build, Bundle, UserManager} +import android.support.design.widget.Snackbar import android.support.v14.preference.SwitchPreference import android.support.v7.app.AlertDialog +import android.support.v7.preference.Preference import android.support.v7.widget.Toolbar.OnMenuItemClickListener +import android.text.TextUtils import android.view.MenuItem -import be.mygod.preference.PreferenceFragment +import be.mygod.preference.{EditTextPreference, PreferenceFragment} import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.database.Profile +import com.github.shadowsocks.plugin.{PluginConfiguration, PluginInterface, PluginManager, PluginOptions} +import com.github.shadowsocks.preference.{IconListPreference, PluginConfigurationDialogFragment} import com.github.shadowsocks.utils.{Action, Key, Utils} +object ProfileConfigFragment { + private final val REQUEST_CODE_CONFIGURE = 1 +} + class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListener with OnSharedPreferenceChangeListener { + import ProfileConfigFragment._ + private var profile: Profile = _ private var isProxyApps: SwitchPreference = _ + private var plugin: IconListPreference = _ + private var pluginConfigure: EditTextPreference = _ + private var pluginConfiguration: PluginConfiguration = _ override def onCreatePreferences(bundle: Bundle, key: String) { app.profileManager.getProfile(getActivity.getIntent.getIntExtra(Action.EXTRA_PROFILE_ID, -1)) match { @@ -57,9 +71,43 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe isProxyApps.setChecked(true) false }) + plugin = findPreference(Key.plugin).asInstanceOf[IconListPreference] + pluginConfigure = findPreference("plugin.configure").asInstanceOf[EditTextPreference] + plugin.unknownValueSummary = getString(R.string.plugin_unknown) + plugin.setOnPreferenceChangeListener((_, value) => { + val selected = value.asInstanceOf[String] + pluginConfiguration = new PluginConfiguration(pluginConfiguration.pluginsOptions, selected) + app.editor.putString(Key.plugin, pluginConfiguration.toString).putBoolean(Key.dirty, true).apply() + pluginConfigure.setEnabled(!TextUtils.isEmpty(selected)) + true + }) + pluginConfigure.setOnPreferenceChangeListener((_, value) => try { + val selected = pluginConfiguration.selected + pluginConfiguration = new PluginConfiguration(pluginConfiguration.pluginsOptions + + (pluginConfiguration.selected -> new PluginOptions(selected, value.asInstanceOf[String])), selected) + app.editor.putString(Key.plugin, pluginConfiguration.toString).putBoolean(Key.dirty, true).apply() + true + } catch { + case exc: IllegalArgumentException => + Snackbar.make(getActivity.findViewById(R.id.snackbar), exc.getLocalizedMessage, Snackbar.LENGTH_LONG).show() + false + }) + initPlugins() + app.listenForPackageChanges(getView.post(initPlugins)) app.settings.registerOnSharedPreferenceChangeListener(this) } + def initPlugins() { + val plugins = PluginManager.fetchPlugins() + plugin.setEntries(plugins.map(_.label)) + plugin.setEntryValues(plugins.map(_.id.asInstanceOf[CharSequence])) + plugin.setEntryIcons(plugins.map(_.icon)) + pluginConfiguration = new PluginConfiguration(app.settings.getString(Key.plugin, null)) + plugin.setValue(pluginConfiguration.selected) + plugin.checkSummary() + pluginConfigure.setText(pluginConfiguration.selectedOptions.toString) + } + override def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String): Unit = if (key != Key.proxyApps && findPreference(key) != null) app.editor.putBoolean(Key.dirty, true).apply() @@ -73,6 +121,18 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe isProxyApps.setChecked(app.settings.getBoolean(Key.proxyApps, false)) // fetch proxyApps updated by AppManager } + override def onDisplayPreferenceDialog(preference: Preference): Unit = if (preference eq pluginConfigure) { + val selected = pluginConfiguration.selected + val intent = new Intent(PluginInterface.ACTION_CONFIGURE(selected)) + if (intent.resolveActivity(getActivity.getPackageManager) != null) + startActivityForResult(intent.putExtra(PluginInterface.EXTRA_OPTIONS, + pluginConfiguration.selectedOptions.toString), REQUEST_CODE_CONFIGURE) else { + val bundle = new Bundle() + bundle.putString(PluginConfigurationDialogFragment.PLUGIN_ID_FRAGMENT_TAG, selected) + displayPreferenceDialog(preference.getKey, new PluginConfigurationDialogFragment, bundle) + } + } else super.onDisplayPreferenceDialog(preference) + override def onMenuItemClick(item: MenuItem): Boolean = item.getItemId match { case R.id.action_delete => new AlertDialog.Builder(getActivity) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index 68ac4dac..f0d795cc 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -27,7 +27,7 @@ import java.util.concurrent.TimeUnit import android.annotation.SuppressLint import android.app.Application -import android.content.SharedPreferences +import android.content._ import android.content.res.Configuration import android.os.{Build, LocaleList} import android.preference.PreferenceManager @@ -210,4 +210,15 @@ class ShadowsocksApplication extends Application { } def updateAssets(): Unit = if (settings.getInt(Key.currentVersionCode, -1) != BuildConfig.VERSION_CODE) copyAssets() + + def listenForPackageChanges(callback: => Unit): BroadcastReceiver = { + val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED) + filter.addAction(Intent.ACTION_PACKAGE_REMOVED) + filter.addDataScheme("package") + val result: BroadcastReceiver = (_: Context, intent: Intent) => + if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED || !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) + callback + app.registerReceiver(result, filter) + result + } } diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala new file mode 100644 index 00000000..2efd9652 --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala @@ -0,0 +1,16 @@ +package com.github.shadowsocks.plugin + +import android.content.pm.{PackageManager, ResolveInfo} + +/** + * @author Mygod + */ +class NativePlugin(resolveInfo: ResolveInfo, packageManager: PackageManager) + extends ResolvedPlugin(resolveInfo, packageManager) { + assert(resolveInfo.providerInfo != null) + assert(resolveInfo.providerInfo.authority.startsWith(PluginInterface.AUTHORITY_BASE)) + override final lazy val id: String = + resolveInfo.providerInfo.authority.substring(PluginInterface.AUTHORITY_BASE.length) + override final lazy val defaultConfig: String = + resolveInfo.providerInfo.metaData.getString(PluginInterface.METADATA_KEY_DEFAULT_CONFIG) +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/NoPlugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/NoPlugin.scala new file mode 100644 index 00000000..7081b79c --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/NoPlugin.scala @@ -0,0 +1,11 @@ +package com.github.shadowsocks.plugin + +import com.github.shadowsocks.ShadowsocksApplication.app + +/** + * @author Mygod + */ +object NoPlugin extends Plugin { + override def id: String = "" + override def label: CharSequence = app.getText(com.github.shadowsocks.R.string.plugin_disabled) +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/Plugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/Plugin.scala new file mode 100644 index 00000000..f300c3aa --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/Plugin.scala @@ -0,0 +1,13 @@ +package com.github.shadowsocks.plugin + +import android.graphics.drawable.Drawable + +/** + * @author Mygod + */ +abstract class Plugin { + def id: String + def label: CharSequence + def icon: Drawable = null + def defaultConfig: String = null +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginConfiguration.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginConfiguration.scala new file mode 100644 index 00000000..23c873e3 --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginConfiguration.scala @@ -0,0 +1,43 @@ +package com.github.shadowsocks.plugin + +import android.util.Log +import com.github.shadowsocks.utils.Commandline + +import scala.collection.mutable + +/** + * @author Mygod + */ +class PluginConfiguration(val pluginsOptions: Map[String, PluginOptions], val selected: String) { + private def this(plugins: Array[PluginOptions]) = + this(plugins.map(opt => opt.id -> opt).toMap, if (plugins.isEmpty) "" else plugins(0).id) + def this(plugin: String) = this(if (plugin == null) Array[PluginOptions]() else plugin.split("\n").map { + case line if line.startsWith("kcptun ") => + val opt = new PluginOptions() + opt.id = "kcptun" + try { + val args = mutable.Queue(Commandline.translateCommandline(line): _*) + args.dequeue() + while (args.nonEmpty) args.dequeue() match { + case "--nocomp" => opt.put("nocomp", "1") + case option if option.startsWith("--") => opt.put(option.substring(2), args.dequeue()) + case option => throw new IllegalArgumentException("Unknown kcptun parameter: " + option) + } + } catch { + case exc: Exception => Log.w("PluginConfiguration", exc.getMessage) + } + opt + case line => new PluginOptions(line) + }) + + def selectedOptions: PluginOptions = pluginsOptions(selected) + + override def toString: String = { + val result = new mutable.ListBuffer[PluginOptions]() + for ((id, opt) <- pluginsOptions) id match { + case this.selected => result.prepend(opt) + case _ => result.append(opt) + } + result.map(_.toString(false)).mkString("\n") + } +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala new file mode 100644 index 00000000..e6c80095 --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala @@ -0,0 +1,25 @@ +package com.github.shadowsocks.plugin + +import android.content.pm.PackageManager +import android.content.{BroadcastReceiver, Intent} +import com.github.shadowsocks.ShadowsocksApplication.app + +import scala.collection.JavaConverters._ + +/** + * @author Mygod + */ +object PluginManager { + private var receiver: BroadcastReceiver = _ + private var cachedPlugins: Array[Plugin] = _ + def fetchPlugins(): Array[Plugin] = { + if (receiver == null) receiver = app.listenForPackageChanges(synchronized(cachedPlugins = null)) + synchronized(if (cachedPlugins == null) { + val pm = app.getPackageManager + cachedPlugins = (NoPlugin +: + pm.queryIntentContentProviders(new Intent().addCategory(PluginInterface.CATEGORY_NATIVE_PLUGIN), + PackageManager.GET_META_DATA).asScala.map(new NativePlugin(_, pm))).toArray + }) + cachedPlugins + } +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala new file mode 100644 index 00000000..13a6c600 --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala @@ -0,0 +1,16 @@ +package com.github.shadowsocks.plugin + +import android.content.pm.{PackageManager, ResolveInfo} +import android.graphics.drawable.Drawable +import com.github.shadowsocks.ShadowsocksApplication.app + +/** + * Base class for any kind of plugin that can be used. + * + * @author Mygod + */ +abstract class ResolvedPlugin(resolveInfo: ResolveInfo, packageManager: PackageManager = app.getPackageManager) + extends Plugin { + override final lazy val label: CharSequence = resolveInfo.loadLabel(packageManager) + override final lazy val icon: Drawable = resolveInfo.loadIcon(packageManager) +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala new file mode 100644 index 00000000..1caf4ff0 --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala @@ -0,0 +1,77 @@ +package com.github.shadowsocks.preference + +import android.graphics.Typeface +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.support.design.widget.BottomSheetDialog +import android.support.v14.preference.PreferenceDialogFragment +import android.support.v7.widget.RecyclerView.ViewHolder +import android.support.v7.widget.{LinearLayoutManager, RecyclerView} +import android.view.ViewGroup.LayoutParams +import android.view.{LayoutInflater, View, ViewGroup} +import android.widget.TextView +import com.github.shadowsocks.R + +/** + * @author Mygod + */ +final class BottomSheetPreferenceDialogFragment extends PreferenceDialogFragment { + private lazy val preference = getPreference.asInstanceOf[IconListPreference] + private lazy val index: Int = preference.selectedEntry + private lazy val entries: Array[CharSequence] = preference.getEntries + private lazy val entryIcons: Array[Drawable] = preference.getEntryIcons + private var clickedIndex = -1 + + override def onCreateDialog(savedInstanceState: Bundle): BottomSheetDialog = { + val activity = getActivity + val dialog = new BottomSheetDialog(activity, getTheme) + val recycler = new RecyclerView(activity) + recycler.setHasFixedSize(true) + recycler.setLayoutManager(new LinearLayoutManager(activity)) + recycler.setAdapter(new IconListAdapter(dialog)) + recycler.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)) + dialog.setContentView(recycler) + dialog + } + + def onDialogClosed(positiveResult: Boolean): Unit = if (clickedIndex >= 0) { + val value = preference.getEntryValues()(clickedIndex).toString + if (preference.callChangeListener(value)) preference.setValue(value) + } + + private final class IconListViewHolder(val dialog: BottomSheetDialog, val view: TextView) extends ViewHolder(view) + with View.OnClickListener { + private var index: Int = _ + + { + val padding = getResources.getDimension(R.dimen.icon_list_item_padding).toInt + view.setPadding(padding, padding, padding, padding) + view.setCompoundDrawablePadding(padding) + view.setOnClickListener(this) + val typedArray = dialog.getContext.obtainStyledAttributes(Array(android.R.attr.selectableItemBackground)) + view.setBackgroundResource(typedArray.getResourceId(0, 0)) + typedArray.recycle() + } + + def bind(i: Int, selected: Boolean = false) { + view.setText(entries(i)) + view.setCompoundDrawablesWithIntrinsicBounds(entryIcons(i), null, null, null) + view.setTypeface(null, if (selected) Typeface.BOLD else Typeface.NORMAL) + index = i + } + def onClick(v: View) { + clickedIndex = index + dialog.dismiss() + } + } + private final class IconListAdapter(dialog: BottomSheetDialog) extends RecyclerView.Adapter[IconListViewHolder] { + def getItemCount: Int = entries.length + def onBindViewHolder(vh: IconListViewHolder, i: Int): Unit = if (index < 0) vh.bind(i) else i match { + case 0 => vh.bind(index, selected = true) + case _ if i > index => vh.bind(i) + case _ => vh.bind(i - 1) + } + def onCreateViewHolder(vg: ViewGroup, i: Int): IconListViewHolder = new IconListViewHolder(dialog, + LayoutInflater.from(vg.getContext).inflate(android.R.layout.simple_list_item_1, vg, false).asInstanceOf[TextView]) + } +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/preference/IconListPreference.scala b/mobile/src/main/scala/com/github/shadowsocks/preference/IconListPreference.scala new file mode 100644 index 00000000..7be88c52 --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/preference/IconListPreference.scala @@ -0,0 +1,65 @@ +package com.github.shadowsocks.preference + +import android.content.Context +import android.graphics.drawable.Drawable +import android.support.annotation.ArrayRes +import android.support.v7.preference.ListPreference +import android.support.v7.preference.Preference.OnPreferenceChangeListener +import android.util.AttributeSet +import be.mygod.preference.DialogPreferencePlus + +/** + * @author Mygod + */ +class IconListPreference(context: Context, attrs: AttributeSet = null) extends ListPreference(context, attrs) + with DialogPreferencePlus { + override def createDialog() = new BottomSheetPreferenceDialogFragment() + + private var mEntryIcons: Array[Drawable] = _ + def selectedEntry: Int = getEntryValues.indexOf(getValue) + + var unknownValueSummary: String = _ + + private var listener: OnPreferenceChangeListener = _ + override def getOnPreferenceChangeListener: OnPreferenceChangeListener = listener + override def setOnPreferenceChangeListener(listener: OnPreferenceChangeListener): Unit = this.listener = listener + super.setOnPreferenceChangeListener((preference, newValue) => + if (listener == null || listener.onPreferenceChange(preference, newValue)) { + setValue(newValue.toString) + checkSummary() + if (mEntryIcons != null) setIcon(getEntryIcon) + true + } else false) + def checkSummary(): Unit = + if (unknownValueSummary != null) setSummary(if (selectedEntry < 0) unknownValueSummary.format(getValue) else "%s") + +// { +// val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.IconListPreference) +// val entryIconsResId: Int = a.getResourceId(R.styleable.IconListPreference_entryIcons, -1) +// if (entryIconsResId != -1) setEntryIcons(entryIconsResId) +// a.recycle() +// } + + def getEntryIcon: Drawable = mEntryIcons(selectedEntry) + def getEntryIcons: Array[Drawable] = mEntryIcons + def setEntryIcons(entryIcons: Array[Drawable]): Unit = mEntryIcons = entryIcons + + def setEntryIcons(@ArrayRes entryIconsResId: Int) { + val icons_array = getContext.getResources.obtainTypedArray(entryIconsResId) + val icon_ids_array = new Array[Drawable](icons_array.length) + for (i <- 0 until icons_array.length) icon_ids_array(i) = icons_array.getDrawable(i) + setEntryIcons(icon_ids_array) + icons_array.recycle() + } + + def init() { + val entryValues = getEntryValues + if (entryValues == null) return + if (mEntryIcons != null) setIcon(getEntryIcon) + } + + protected override def onSetInitialValue(restoreValue: Boolean, defaultValue: AnyRef) { + super.onSetInitialValue(restoreValue, defaultValue) + init() + } +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala new file mode 100644 index 00000000..adab7dca --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala @@ -0,0 +1,57 @@ +/*******************************************************************************/ +/* */ +/* 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 com.github.shadowsocks.preference + +import android.app.{Activity, AlertDialog} +import android.content.{DialogInterface, Intent} +import be.mygod.preference.EditTextPreferenceDialogFragment +import com.github.shadowsocks.plugin.PluginInterface + +/** + * @author Mygod + */ +object PluginConfigurationDialogFragment { + final val PLUGIN_ID_FRAGMENT_TAG = "com.github.shadowsocks.preference.PluginConfigurationDialogFragment.PLUGIN_ID" + private final val REQUEST_CODE_HELP = 1 +} + +class PluginConfigurationDialogFragment extends EditTextPreferenceDialogFragment { + import PluginConfigurationDialogFragment._ + + override def onPrepareDialogBuilder(builder: AlertDialog.Builder) { + super.onPrepareDialogBuilder(builder) + val intent = new Intent(PluginInterface.ACTION_HELP(getArguments.getString(PLUGIN_ID_FRAGMENT_TAG))) + if (intent.resolveActivity(getContext.getPackageManager) != null) builder.setNeutralButton("?", ((_, _) => + startActivityForResult(intent.putExtra(PluginInterface.EXTRA_OPTIONS, editText.getText.toString), + REQUEST_CODE_HELP)): DialogInterface.OnClickListener) + } + + override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = requestCode match { + case REQUEST_CODE_HELP => requestCode match { + case Activity.RESULT_OK => new AlertDialog.Builder(getContext) + .setTitle("?") + .setMessage(data.getCharSequenceExtra(PluginInterface.EXTRA_HELP_MESSAGE)) + .show() + case _ => + } + case _ => super.onActivityResult(requestCode, resultCode, data) + } +} diff --git a/plugin/build.sbt b/plugin/build.sbt index d3e6c0d5..b3e00983 100644 --- a/plugin/build.sbt +++ b/plugin/build.sbt @@ -3,3 +3,5 @@ android.useSupportVectors name := "plugin" version := "0.0.1-SNAPSHOT" + +publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath + "/.m2/repository"))) diff --git a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginInterface.java b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginInterface.java index 480de520..3d689ab8 100644 --- a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginInterface.java +++ b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginInterface.java @@ -51,6 +51,8 @@ public final class PluginInterface { */ public static final String COLUMN_PATH = "path"; + static final String AUTHORITY_BASE = "com.github.shadowsocks.plugin."; + /** * Authority to use for native plugin ContentProvider. * @@ -58,7 +60,7 @@ public final class PluginInterface { * @return com.github.shadowsocks.plugin.$PLUGIN_ID */ public static String getAuthority(String pluginId) { - return "com.github.shadowsocks.plugin." + pluginId; + return AUTHORITY_BASE + pluginId; } /** diff --git a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java index de76ac71..4c62b5e4 100644 --- a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java +++ b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java @@ -37,8 +37,10 @@ public final class PluginOptions extends HashMap { key = current.toString(); current.setLength(0); } else if (";".equals(nextToken)) { - if (current.length() > 0) put(key, current.toString()); - else if (firstEntry) id = key; + if (key != null) { + put(key, current.toString()); + key = null; + } else if (firstEntry) id = current.toString(); else throw new IllegalArgumentException("Value missing in " + options); firstEntry = false; } @@ -49,7 +51,7 @@ public final class PluginOptions extends HashMap { this.id = id; } - public String id; + public String id = ""; private static void append(StringBuilder result, String str) { for (int i = 0; i < str.length(); ++i) { @@ -62,7 +64,7 @@ public final class PluginOptions extends HashMap { } public String toString(boolean trimId) { final StringBuilder result = new StringBuilder(); - if (!trimId && !TextUtils.isEmpty(id)) append(result, id); + if (!trimId) if (TextUtils.isEmpty(id)) return ""; else append(result, id); for (Entry entry : entrySet()) if (entry.getValue() != null) { if (result.length() > 0) result.append(';'); append(result, entry.getKey()); @@ -73,6 +75,6 @@ public final class PluginOptions extends HashMap { } @Override public String toString() { - return toString(false); + return toString(true); } } diff --git a/plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala b/plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala index 7a03eea0..718b9287 100644 --- a/plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala +++ b/plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala @@ -27,7 +27,7 @@ import android.view.{View, ViewGroup} class EditTextPreferenceDialogFragment extends PreferenceDialogFragment { private lazy val preference = getPreference.asInstanceOf[EditTextPreference] - private lazy val editText = preference.editText + protected lazy val editText: AppCompatEditText = preference.editText override protected def onCreateDialogView(context: Context): AppCompatEditText = { val parent = editText.getParent.asInstanceOf[ViewGroup] diff --git a/plugin/src/main/scala/be/mygod/preference/PreferenceFragment.scala b/plugin/src/main/scala/be/mygod/preference/PreferenceFragment.scala index 821aeb5d..95139398 100644 --- a/plugin/src/main/scala/be/mygod/preference/PreferenceFragment.scala +++ b/plugin/src/main/scala/be/mygod/preference/PreferenceFragment.scala @@ -30,9 +30,10 @@ abstract class PreferenceFragment extends Base { override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View = super.onCreateView(inflater, container, savedInstanceState) - protected final def displayPreferenceDialog(key: String, fragment: DialogFragment) { + protected final def displayPreferenceDialog(key: String, fragment: DialogFragment, other: Bundle = null) { val bundle = new Bundle(1) bundle.putString("key", key) + if (other != null) bundle.putAll(other) fragment.setArguments(bundle) fragment.setTargetFragment(this, 0) getFragmentManager.beginTransaction() From d5be325ab5815dc3eb35f8beaf61a9e391b32a8d Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 14 Jan 2017 02:31:01 +0800 Subject: [PATCH 09/98] Some bugfixes for new UI --- .../scala/com/github/shadowsocks/ProfileConfigFragment.scala | 1 + .../com/github/shadowsocks/plugin/PluginConfiguration.scala | 5 +++-- .../preference/BottomSheetPreferenceDialogFragment.scala | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala index bb82e5c1..d30e8b66 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala @@ -105,6 +105,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe pluginConfiguration = new PluginConfiguration(app.settings.getString(Key.plugin, null)) plugin.setValue(pluginConfiguration.selected) plugin.checkSummary() + pluginConfigure.setEnabled(!TextUtils.isEmpty(pluginConfiguration.selected)) pluginConfigure.setText(pluginConfiguration.selectedOptions.toString) } diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginConfiguration.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginConfiguration.scala index 23c873e3..b94ebdce 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginConfiguration.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginConfiguration.scala @@ -10,7 +10,7 @@ import scala.collection.mutable */ class PluginConfiguration(val pluginsOptions: Map[String, PluginOptions], val selected: String) { private def this(plugins: Array[PluginOptions]) = - this(plugins.map(opt => opt.id -> opt).toMap, if (plugins.isEmpty) "" else plugins(0).id) + this(plugins.filter(_.id.nonEmpty).map(opt => opt.id -> opt).toMap, if (plugins.isEmpty) "" else plugins(0).id) def this(plugin: String) = this(if (plugin == null) Array[PluginOptions]() else plugin.split("\n").map { case line if line.startsWith("kcptun ") => val opt = new PluginOptions() @@ -30,7 +30,8 @@ class PluginConfiguration(val pluginsOptions: Map[String, PluginOptions], val se case line => new PluginOptions(line) }) - def selectedOptions: PluginOptions = pluginsOptions(selected) + def getOptions(id: String): PluginOptions = if (id.isEmpty) new PluginOptions() else pluginsOptions(id) + def selectedOptions: PluginOptions = getOptions(selected) override def toString: String = { val result = new mutable.ListBuffer[PluginOptions]() diff --git a/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala index 1caf4ff0..d0105e08 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala @@ -34,7 +34,7 @@ final class BottomSheetPreferenceDialogFragment extends PreferenceDialogFragment dialog } - def onDialogClosed(positiveResult: Boolean): Unit = if (clickedIndex >= 0) { + def onDialogClosed(positiveResult: Boolean): Unit = if (clickedIndex >= 0 && clickedIndex != index) { val value = preference.getEntryValues()(clickedIndex).toString if (preference.callChangeListener(value)) preference.setValue(value) } From e3cf54f7035c2cee8c521b61e4bb8940b71ee284 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 16 Jan 2017 09:22:31 +0800 Subject: [PATCH 10/98] Update shadowsocks-libev --- mobile/src/main/jni/Android.mk | 4 ++-- mobile/src/main/jni/Application.mk | 2 +- mobile/src/main/jni/shadowsocks-libev | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index ac13a962..9e3c51cc 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -387,7 +387,7 @@ include $(CLEAR_VARS) SHADOWSOCKS_SOURCES := local.c cache.c udprelay.c encrypt.c \ utils.c netutils.c json.c jconf.c acl.c http.c tls.c rule.c \ - obfs_http.c obfs_tls.c base64.c \ + plugin.c \ android.c LOCAL_MODULE := ss-local @@ -420,7 +420,7 @@ include $(BUILD_SHARED_EXECUTABLE) include $(CLEAR_VARS) -SHADOWSOCKS_SOURCES := tunnel.c cache.c udprelay.c encrypt.c utils.c netutils.c json.c jconf.c android.c obfs_http.c obfs_tls.c base64.c +SHADOWSOCKS_SOURCES := tunnel.c cache.c udprelay.c encrypt.c utils.c netutils.c json.c jconf.c android.c plugin.c LOCAL_MODULE := ss-tunnel LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES)) diff --git a/mobile/src/main/jni/Application.mk b/mobile/src/main/jni/Application.mk index 8ea8eb37..e32dd558 100644 --- a/mobile/src/main/jni/Application.mk +++ b/mobile/src/main/jni/Application.mk @@ -1,4 +1,4 @@ APP_ABI := armeabi-v7a x86 -APP_PLATFORM := android-16 +APP_PLATFORM := android-19 APP_STL := stlport_static NDK_TOOLCHAIN_VERSION := clang diff --git a/mobile/src/main/jni/shadowsocks-libev b/mobile/src/main/jni/shadowsocks-libev index 3b4af24f..45529cdf 160000 --- a/mobile/src/main/jni/shadowsocks-libev +++ b/mobile/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit 3b4af24fecae932abe02830321af6f6cb54f72e2 +Subproject commit 45529cdf7a3cd5384d58f97ec0e4c62de55cdae0 From c54745e24114fa09a553933d111aa3892d43d493 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 16 Jan 2017 09:31:37 +0800 Subject: [PATCH 11/98] Adapt to simple-obfs plugin options parser --- .../shadowsocks/plugin/PluginOptions.java | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java index 4c62b5e4..a010ff05 100644 --- a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java +++ b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java @@ -23,12 +23,11 @@ public final class PluginOptions extends HashMap { super(initialCapacity, loadFactor); } - public PluginOptions(String options) throws IllegalArgumentException { + private PluginOptions(String options, boolean parseId) throws IllegalArgumentException { if (TextUtils.isEmpty(options)) return; final StringTokenizer tokenizer = new StringTokenizer(options, "\\=;", true); final StringBuilder current = new StringBuilder(); String key = null; - boolean firstEntry = true; while (tokenizer.hasMoreTokens()) { String nextToken = tokenizer.nextToken(); if ("\\".equals(nextToken)) current.append(tokenizer.nextToken()); @@ -36,22 +35,28 @@ public final class PluginOptions extends HashMap { if (key != null) throw new IllegalArgumentException("Duplicate keys in " + options); key = current.toString(); current.setLength(0); - } else if (";".equals(nextToken)) { + } else if (";".equals(nextToken)) if (key != null) { put(key, current.toString()); key = null; - } else if (firstEntry) id = current.toString(); - else throw new IllegalArgumentException("Value missing in " + options); - firstEntry = false; - } + } else if (parseId) { + id = current.toString(); + parseId = false; + } else { + put(current.toString(), null); + current.setLength(0); + } } } + public PluginOptions(String options) throws IllegalArgumentException { + this(options, true); + } public PluginOptions(String id, String options) throws IllegalArgumentException { - this(options); + this(options, false); this.id = id; } - public String id = ""; + public String id; private static void append(StringBuilder result, String str) { for (int i = 0; i < str.length(); ++i) { @@ -65,11 +70,14 @@ public final class PluginOptions extends HashMap { public String toString(boolean trimId) { final StringBuilder result = new StringBuilder(); if (!trimId) if (TextUtils.isEmpty(id)) return ""; else append(result, id); - for (Entry entry : entrySet()) if (entry.getValue() != null) { + for (Entry entry : entrySet()) { if (result.length() > 0) result.append(';'); append(result, entry.getKey()); - result.append('='); - append(result, entry.getValue()); + String value = entry.getValue(); + if (value != null) { + result.append('='); + append(result, value); + } } return result.toString(); } From 0ffae32a4474d075f5af02e2b4a24be1ff54b80d Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 16 Jan 2017 22:27:41 +0800 Subject: [PATCH 12/98] Implement plugin interface in mobile app --- .../github/shadowsocks/utils/Commandline.java | 27 +++++--- .../com/github/shadowsocks/BaseService.scala | 27 +++++++- .../github/shadowsocks/GuardedProcess.scala | 9 +-- .../shadowsocks/ShadowsocksNatService.scala | 69 ++++--------------- .../shadowsocks/ShadowsocksVpnService.scala | 38 +++------- .../shadowsocks/plugin/PluginManager.scala | 56 ++++++++++++++- .../github/shadowsocks/utils/Constants.scala | 1 - .../github/shadowsocks/utils/IOUtils.scala | 8 ++- .../plugin/NativePluginProvider.scala | 4 +- 9 files changed, 136 insertions(+), 103 deletions(-) diff --git a/mobile/src/main/java/com/github/shadowsocks/utils/Commandline.java b/mobile/src/main/java/com/github/shadowsocks/utils/Commandline.java index 112df2d5..e364c567 100644 --- a/mobile/src/main/java/com/github/shadowsocks/utils/Commandline.java +++ b/mobile/src/main/java/com/github/shadowsocks/utils/Commandline.java @@ -1,6 +1,7 @@ package com.github.shadowsocks.utils; import java.util.ArrayList; +import java.util.Arrays; import java.util.StringTokenizer; /** @@ -32,23 +33,21 @@ public final class Commandline { /** * Quote the parts of the given array in way that makes them * usable as command line arguments. - * @param line the list of arguments to quote. + * @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. */ - public static String toString(String[] line) { + public static String toString(Iterable args) { // empty path return empty string - if (line == null || line.length == 0) { + if (args == null) { return ""; } // path containing one or more elements final StringBuilder result = new StringBuilder(); - for (int i = 0; i < line.length; i++) { - if (i > 0) { - result.append(' '); - } - for (int j = 0; j < line[i].length(); ++j) { - char ch = line[i].charAt(j); + for (String arg : args) { + if (result.length() > 0) result.append(' '); + for (int j = 0; j < arg.length(); ++j) { + char ch = arg.charAt(j); switch (ch) { case ' ': case '\\': case '"': case '\'': result.append('\\'); // intentionally no break default: result.append(ch); @@ -57,6 +56,16 @@ public final class Commandline { } 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. + */ + public static String toString(String[] args) { + return toString(Arrays.asList(args)); // thanks to Java, arrays aren't iterable + } /** * Crack a command line. diff --git a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala index 59af31ae..ccc13390 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -24,7 +24,7 @@ import java.io.IOException import java.net.InetAddress import java.util import java.util.concurrent.TimeUnit -import java.util.{Timer, TimerTask} +import java.util.{Locale, Timer, TimerTask} import android.app.Service import android.content.{BroadcastReceiver, Context, Intent, IntentFilter} @@ -36,6 +36,7 @@ import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.acl.Acl import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback} import com.github.shadowsocks.database.Profile +import com.github.shadowsocks.plugin.{PluginConfiguration, PluginManager, PluginOptions} import com.github.shadowsocks.utils._ import okhttp3.{Dns, FormBody, OkHttpClient, Request} @@ -46,6 +47,8 @@ trait BaseService extends Service { @volatile private var state = State.STOPPED @volatile protected var profile: Profile = _ + @volatile private var plugin: PluginOptions = _ + @volatile private var pluginPath: String = _ case class NameNotResolvedException() extends IOException case class NullConnectionException() extends NullPointerException @@ -149,8 +152,12 @@ trait BaseService extends Service { profile.password = proxy(2).trim profile.method = proxy(3).trim } + if (profile.route == Acl.CUSTOM_RULES) // rationalize custom rules Acl.save(Acl.CUSTOM_RULES, new Acl().fromId(Acl.CUSTOM_RULES)) + + plugin = new PluginConfiguration(profile.plugin).selectedOptions + pluginPath = PluginManager.initPlugin(plugin.id) } def startRunner(profile: Profile) { @@ -304,4 +311,22 @@ trait BaseService extends Service { case _: Exception => "exclude = " + default + ";" } } + + protected def buildShadowsocksConfig(file: String, localPortOffset: Int = 0): String = { + val builder = new StringBuilder( + """{"server": "%s", "server_port": %d, "local_port": %d, "password": "%s", "method": "%s"""" + .formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort + localPortOffset, + profile.password, profile.method)) + if (profile.auth) builder.append(""", "auth": true""") + if (pluginPath != null) { + builder.append(""", "plugin": """") + builder.append(pluginPath) + builder.append("""", "plugin_opts": """") + builder.append(plugin.toString) + builder.append('"') + } + builder.append('}') + IOUtils.writeString(file, builder.toString) + file + } } diff --git a/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala b/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala index bdbf77b6..ca117acb 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala @@ -26,6 +26,7 @@ import java.util.concurrent.Semaphore import android.util.Log import com.github.shadowsocks.utils.CloseUtils._ +import com.github.shadowsocks.utils.Commandline import scala.collection.JavaConversions._ import scala.collection.immutable.Stream @@ -57,7 +58,7 @@ class GuardedProcess(cmd: Seq[String]) { try { var callback: () => Unit = null while (!isDestroyed) { - Log.i(TAG, "start process: " + cmd) + Log.i(TAG, "start process: " + Commandline.toString(cmd)) val startTime = currentTimeMillis process = new ProcessBuilder(cmd).redirectErrorStream(true).start @@ -75,7 +76,7 @@ class GuardedProcess(cmd: Seq[String]) { isRestart = false } else { if (currentTimeMillis - startTime < 1000) { - Log.w(TAG, "process exit too fast, stop guard: " + cmd) + Log.w(TAG, "process exit too fast, stop guard: " + Commandline.toString(cmd)) isDestroyed = true } } @@ -83,11 +84,11 @@ class GuardedProcess(cmd: Seq[String]) { } } catch { case _: InterruptedException => - Log.i(TAG, "thread interrupt, destroy process: " + cmd) + Log.i(TAG, "thread interrupt, destroy process: " + Commandline.toString(cmd)) process.destroy() case e: IOException => ioException = e } finally semaphore.release() - }, "GuardThread-" + cmd) + }, "GuardThread-" + cmd.head) guardThread.start() semaphore.acquire() diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index 48a4a366..fa29c7dc 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -53,21 +53,11 @@ class ShadowsocksNatService extends BaseService { var su: Shell.Interactive = _ def startShadowsocksDaemon() { - val conf = - ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort, - profile.password, profile.method, 600) - Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-local-nat.conf"))(p => { - p.println(conf) - }) - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so" , "-b" , "127.0.0.1" , "-t" , "600" , "-P", getApplicationInfo.dataDir - , "-c" , getApplicationInfo.dataDir + "/ss-local-nat.conf") - - if (profile.auth) cmd += "-A" + , "-c" , buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-local-nat.conf")) if (TcpFastOpen.sendEnabled) cmd += "--fast-open" @@ -76,55 +66,24 @@ class ShadowsocksNatService extends BaseService { cmd += Acl.getPath(profile.route) } - if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) + if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) sslocalProcess = new GuardedProcess(cmd).start() } def startDNSTunnel() { - if (profile.udpdns) { - val conf = ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort + 53, - profile.password, profile.method, 10) - Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => { - p.println(conf) - }) - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so" - , "-u" - , "-t" , "10" - , "-b" , "127.0.0.1" - , "-l" , (profile.localPort + 53).toString - , "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53" - , "-P" , getApplicationInfo.dataDir - , "-c" , getApplicationInfo.dataDir + "/ss-tunnel-nat.conf") + val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so" + , "-t" , "10" + , "-b" , "127.0.0.1" + , "-l" , (profile.localPort + 53).toString + , "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53" + , "-P", getApplicationInfo.dataDir + , "-c" , buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf", 53)) - if (profile.auth) cmd += "-A" + if (profile.udpdns) cmd.append("-u") - if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) + if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) - sstunnelProcess = new GuardedProcess(cmd).start() - - } else { - val conf = - ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort + 63, - profile.password, profile.method, 10) - Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => { - p.println(conf) - }) - val cmdBuf = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so" - , "-t" , "10" - , "-b" , "127.0.0.1" - , "-l" , (profile.localPort + 63).toString - , "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53" - , "-P", getApplicationInfo.dataDir - , "-c" , getApplicationInfo.dataDir + "/ss-tunnel-nat.conf") - - if (profile.auth) cmdBuf += "-A" - - if (BuildConfig.DEBUG) Log.d(TAG, cmdBuf.mkString(" ")) - - sstunnelProcess = new GuardedProcess(cmdBuf).start() - } + sstunnelProcess = new GuardedProcess(cmd).start() } def startDnsDaemon() { @@ -150,7 +109,7 @@ class ShadowsocksNatService extends BaseService { }) val cmd = Array(getApplicationInfo.nativeLibraryDir + "/libpdnsd.so", "-c", getApplicationInfo.dataDir + "/pdnsd-nat.conf") - if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) + if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) pdnsdProcess = new GuardedProcess(cmd).start() } @@ -162,7 +121,7 @@ class ShadowsocksNatService extends BaseService { p.println(conf) }) - if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) + if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) redsocksProcess = new GuardedProcess(cmd).start() } diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 46b334e9..a9b8550a 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -30,10 +30,11 @@ import android.net.VpnService import android.os._ import android.util.Log import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.acl.{AclSyncJob, Acl} +import com.github.shadowsocks.acl.{Acl, AclSyncJob} import com.github.shadowsocks.database.Profile import com.github.shadowsocks.utils._ +import scala.collection.JavaConversions._ import scala.collection.mutable.ArrayBuffer class ShadowsocksVpnService extends VpnService with BaseService { @@ -162,21 +163,11 @@ class ShadowsocksVpnService extends VpnService with BaseService { } def startShadowsocksDaemon() { - val conf = - ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort, - profile.password, profile.method, 600) - Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-local-vpn.conf"))(p => { - p.println(conf) - }) - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", "-V" , "-b", "127.0.0.1" , "-t", "600" , "-P", getApplicationInfo.dataDir - , "-c", getApplicationInfo.dataDir + "/ss-local-vpn.conf") - - if (profile.auth) cmd += "-A" + , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-local-vpn.conf")) if (profile.udpdns) cmd += "-u" @@ -187,31 +178,20 @@ class ShadowsocksVpnService extends VpnService with BaseService { if (TcpFastOpen.sendEnabled) cmd += "--fast-open" - if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) + if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) sslocalProcess = new GuardedProcess(cmd).start() } def startDnsTunnel() { - val conf = - ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort + 63, - profile.password, profile.method, 10) - Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf"))(p => { - p.println(conf) - }) - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so" + val cmd = Array[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so" , "-V" , "-t", "10" , "-b", "127.0.0.1" , "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53" , "-P", getApplicationInfo.dataDir - , "-c", getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf") - - if (profile.auth) cmd += "-A" - - if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) - + , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf", 53)) + if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) sstunnelProcess = new GuardedProcess(cmd).start() } @@ -236,7 +216,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { }) val cmd = Array(getApplicationInfo.nativeLibraryDir + "/libpdnsd.so", "-c", getApplicationInfo.dataDir + "/pdnsd-vpn.conf") - if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) + if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) pdnsdProcess = new GuardedProcess(cmd).start() } @@ -310,7 +290,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), profile.localPort + 53)) - if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) + if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) tun2socksProcess = new GuardedProcess(cmd).start(() => sendFd(fd)) diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala index e6c80095..568e964a 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala @@ -1,8 +1,13 @@ package com.github.shadowsocks.plugin +import java.io.{File, FileNotFoundException, FileOutputStream, IOException} + import android.content.pm.PackageManager -import android.content.{BroadcastReceiver, Intent} +import android.content.{BroadcastReceiver, ContentResolver, Intent} +import android.net.Uri import com.github.shadowsocks.ShadowsocksApplication.app +import com.github.shadowsocks.utils.CloseUtils.autoClose +import com.github.shadowsocks.utils.IOUtils import scala.collection.JavaConverters._ @@ -22,4 +27,53 @@ object PluginManager { }) cachedPlugins } + + // the following parts are meant to be used by :bg + @throws[Throwable] + def initPlugin(id: String): String = { + if (id.isEmpty) return null + var throwable: Throwable = null + + try initNativePlugin(id) match { + case null => + case path => return path + } catch { + case t: Throwable => if (throwable == null) throwable = t + } + + // add other plugin types here + + throw if (throwable == null) + new FileNotFoundException(app.getString(com.github.shadowsocks.R.string.plugin_unknown, id)) else throwable + } + + @throws[IOException] + @throws[IndexOutOfBoundsException] + @throws[AssertionError] + private def initNativePlugin(id: String): String = { + val builder = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(PluginInterface.getAuthority(id)) + val cr = app.getContentResolver + val cursor = cr.query(builder.build(), Array(PluginInterface.COLUMN_PATH), null, null, null) + if (cursor != null) { + var initialized = false + val pluginDir = new File(app.getFilesDir, "plugin") + if (cursor.moveToFirst()) { + IOUtils.deleteRecursively(pluginDir) + if (!pluginDir.mkdirs()) throw new FileNotFoundException("Unable to create plugin directory") + val pluginDirPath = pluginDir.getAbsolutePath + '/' + do { + val path = cursor.getString(0) + val file = new File(pluginDir, path) + assert(file.getAbsolutePath.startsWith(pluginDirPath)) + autoClose(cr.openInputStream(builder.path(path).build()))(in => + autoClose(new FileOutputStream(file))(out => IOUtils.copy(in, out))) + if (path == id) initialized = true + } while (cursor.moveToNext()) + } + if (!initialized) throw new IndexOutOfBoundsException("Plugin entry binary not found") + new File(pluginDir, id).getAbsolutePath + } else null + } } diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala index 2b2d1929..7d7bea16 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala @@ -31,7 +31,6 @@ object Executable { } object ConfigUtils { - val SHADOWSOCKS = "{\"server\": \"%s\", \"server_port\": %d, \"local_port\": %d, \"password\": \"%s\", \"method\":\"%s\", \"timeout\": %d}" val REDSOCKS = "base {\n" + " log_debug = off;\n" + " log_info = off;\n" + diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala index ae04ef85..8ef7101c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala @@ -21,7 +21,7 @@ package com.github.shadowsocks.utils import com.github.shadowsocks.utils.CloseUtils._ -import java.io.{FileWriter, InputStream, OutputStream} +import java.io._ /** * @author Mygod @@ -39,4 +39,10 @@ object IOUtils { def writeString(file: String, content: String): Unit = autoClose(new FileWriter(file))(writer => writer.write(content)) + + @throws[FileNotFoundException] + def deleteRecursively(file: File) { + if (file.isDirectory) file.listFiles().foreach(deleteRecursively) + if (!file.delete()) throw new FileNotFoundException("Failed to delete: " + file.getAbsolutePath) + } } diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala index 1fdc7845..78d5b57d 100644 --- a/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala @@ -46,7 +46,7 @@ abstract class NativePluginProvider extends ContentProvider { override def query(uri: Uri, projection: Array[String], selection: String, selectionArgs: Array[String], sortOrder: String): Cursor = { - if (selection != null || selectionArgs != null || sortOrder != null) ??? + assert(selection == null && selectionArgs == null && sortOrder == null) val result = new MatrixCursor(projection.filter(_ == PluginInterface.COLUMN_PATH)) populateFiles(new PathProvider(uri, result)) result @@ -54,7 +54,7 @@ abstract class NativePluginProvider extends ContentProvider { def openFile(uri: Uri): ParcelFileDescriptor override def openFile(uri: Uri, mode: String): ParcelFileDescriptor = { - if (mode != "r") ??? + assert(mode == "r") openFile(uri) } From 70e0b3991d49e137a6b29cf783ab371f5e1fc5e0 Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 17 Jan 2017 21:14:10 +0800 Subject: [PATCH 13/98] Misc improvements for plugin design --- mobile/src/main/AndroidManifest.xml | 3 --- mobile/src/main/res/values/strings.xml | 6 ----- .../shadowsocks/plugin/PluginManager.scala | 4 ++- .../shadowsocks/plugin/PluginOptions.java | 25 +++++++++++++++---- plugin/src/main/res/values/strings.xml | 9 +++++++ .../plugin/NativePluginProvider.scala | 4 +-- 6 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 plugin/src/main/res/values/strings.xml diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 2c4f35dc..cb369460 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -18,11 +18,8 @@ android:targetSdkVersion="25"/> diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 7e52d5dd..1320f57c 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -26,7 +26,6 @@ Error code: #%d - Server Settings Profile Name Server Remote Port @@ -35,7 +34,6 @@ Encrypt Method - Feature Settings IPv6 Route Redirect IPv6 traffic to remote One-time Authentication @@ -75,8 +73,6 @@ No valid profile data found. - Yes - No Close Please select a profile Proxy/Password should not be empty @@ -105,8 +101,6 @@ Profile config - Changes not saved. Do you want to save? - Apply Remove Are you sure you want to remove this profile? QR code/NFC diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala index 568e964a..df1c048c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala @@ -38,7 +38,9 @@ object PluginManager { case null => case path => return path } catch { - case t: Throwable => if (throwable == null) throwable = t + case t: Throwable => + t.printStackTrace() + if (throwable == null) throwable = t } // add other plugin types here diff --git a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java index a010ff05..3d693d4b 100644 --- a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java +++ b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java @@ -3,6 +3,7 @@ package com.github.shadowsocks.plugin; import android.text.TextUtils; import java.util.HashMap; +import java.util.Objects; import java.util.StringTokenizer; /** @@ -23,7 +24,7 @@ public final class PluginOptions extends HashMap { super(initialCapacity, loadFactor); } - private PluginOptions(String options, boolean parseId) throws IllegalArgumentException { + private PluginOptions(String options, boolean parseId) { if (TextUtils.isEmpty(options)) return; final StringTokenizer tokenizer = new StringTokenizer(options, "\\=;", true); final StringBuilder current = new StringBuilder(); @@ -31,8 +32,7 @@ public final class PluginOptions extends HashMap { while (tokenizer.hasMoreTokens()) { String nextToken = tokenizer.nextToken(); if ("\\".equals(nextToken)) current.append(tokenizer.nextToken()); - else if ("=".equals(nextToken)) { - if (key != null) throw new IllegalArgumentException("Duplicate keys in " + options); + else if ("=".equals(nextToken) && key == null) { key = current.toString(); current.setLength(0); } else if (";".equals(nextToken)) @@ -46,12 +46,13 @@ public final class PluginOptions extends HashMap { put(current.toString(), null); current.setLength(0); } + else current.append(nextToken); } } - public PluginOptions(String options) throws IllegalArgumentException { + public PluginOptions(String options) { this(options, true); } - public PluginOptions(String id, String options) throws IllegalArgumentException { + public PluginOptions(String id, String options) { this(options, false); this.id = id; } @@ -85,4 +86,18 @@ public final class PluginOptions extends HashMap { public String toString() { return toString(true); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + PluginOptions that = (PluginOptions) o; + return Objects.equals(id, that.id) && super.equals(that); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), id); + } } diff --git a/plugin/src/main/res/values/strings.xml b/plugin/src/main/res/values/strings.xml new file mode 100644 index 00000000..84ed3818 --- /dev/null +++ b/plugin/src/main/res/values/strings.xml @@ -0,0 +1,9 @@ + + + Server Settings + Feature Settings + Changes not saved. Do you want to save? + Yes + No + Apply + diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala index 78d5b57d..9f555574 100644 --- a/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala @@ -14,8 +14,8 @@ import android.os.ParcelFileDescriptor * ... * <application> * ... - * <provider android:name="com.github.shadowsocks.$PLUGIN_ID.PluginProvider" - * android:authorities="com.github.shadowsocks.$PLUGIN_ID"> + * <provider android:name="com.github.shadowsocks.$PLUGIN_ID.BinaryProvider" + * android:authorities="com.github.shadowsocks.plugin.$PLUGIN_ID"> * <intent-filter> * <category android:name="com.github.shadowsocks.plugin.CATEGORY_NATIVE_PLUGIN" /> * </intent-filter> From 01c5e8bb6fd5024ee810d1aeea75631a087db42d Mon Sep 17 00:00:00 2001 From: Max Lv Date: Wed, 18 Jan 2017 16:22:55 +0800 Subject: [PATCH 14/98] Update submodules --- .gitignore | 3 + .gitmodules | 9 +++ mobile/src/main/jni/Android.mk | 108 +++++++------------------- mobile/src/main/jni/libev | 1 + mobile/src/main/jni/libsodium | 1 + mobile/src/main/jni/libudns | 1 + mobile/src/main/jni/shadowsocks-libev | 2 +- 7 files changed, 42 insertions(+), 83 deletions(-) create mode 160000 mobile/src/main/jni/libev create mode 160000 mobile/src/main/jni/libsodium create mode 160000 mobile/src/main/jni/libudns diff --git a/.gitignore b/.gitignore index 020b7ea5..ecec4bea 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ local.sbt #Intellij IDEA .idea/ .d + +mobile/src/main/libs +mobile/src/main/obj diff --git a/.gitmodules b/.gitmodules index f0e422f9..e8fc0ec8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -27,3 +27,12 @@ [submodule "mobile/src/main/jni/pcre"] path = mobile/src/main/jni/pcre url = https://android.googlesource.com/platform/external/pcre +[submodule "mobile/src/main/jni/libsodium"] + path = mobile/src/main/jni/libsodium + url = https://github.com/shadowsocks/libsodium.git +[submodule "mobile/src/main/jni/libev"] + path = mobile/src/main/jni/libev + url = https://github.com/shadowsocks/libev.git +[submodule "mobile/src/main/jni/libudns"] + path = mobile/src/main/jni/libudns + url = https://github.com/shadowsocks/libudns.git diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index 9e3c51cc..063dfbe5 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -84,7 +84,6 @@ SODIUM_SOURCE := \ crypto_secretbox/crypto_secretbox.c \ crypto_secretbox/crypto_secretbox_easy.c \ crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305_api.c \ - crypto_secretbox/xsalsa20poly1305/ref/box_xsalsa20poly1305.c \ crypto_shorthash/crypto_shorthash.c \ crypto_shorthash/siphash24/shorthash_siphash24_api.c \ crypto_shorthash/siphash24/ref/shorthash_siphash24.c \ @@ -92,58 +91,10 @@ SODIUM_SOURCE := \ crypto_sign/ed25519/ref10/base.h \ crypto_sign/ed25519/ref10/base2.h \ crypto_sign/ed25519/sign_ed25519_api.c \ - crypto_sign/ed25519/ref10/d.h \ - crypto_sign/ed25519/ref10/d2.h \ - crypto_sign/ed25519/ref10/fe.h \ - crypto_sign/ed25519/ref10/fe_0.c \ - crypto_sign/ed25519/ref10/fe_1.c \ - crypto_sign/ed25519/ref10/fe_add.c \ - crypto_sign/ed25519/ref10/fe_cmov.c \ - crypto_sign/ed25519/ref10/fe_copy.c \ - crypto_sign/ed25519/ref10/fe_frombytes.c \ - crypto_sign/ed25519/ref10/fe_invert.c \ - crypto_sign/ed25519/ref10/fe_isnegative.c \ - crypto_sign/ed25519/ref10/fe_isnonzero.c \ - crypto_sign/ed25519/ref10/fe_mul.c \ - crypto_sign/ed25519/ref10/fe_neg.c \ - crypto_sign/ed25519/ref10/fe_pow22523.c \ - crypto_sign/ed25519/ref10/fe_sq.c \ - crypto_sign/ed25519/ref10/fe_sq2.c \ - crypto_sign/ed25519/ref10/fe_sub.c \ - crypto_sign/ed25519/ref10/fe_tobytes.c \ - crypto_sign/ed25519/ref10/ge.h \ - crypto_sign/ed25519/ref10/ge_add.c \ - crypto_sign/ed25519/ref10/ge_add.h \ - crypto_sign/ed25519/ref10/ge_double_scalarmult.c \ - crypto_sign/ed25519/ref10/ge_frombytes.c \ - crypto_sign/ed25519/ref10/ge_madd.c \ - crypto_sign/ed25519/ref10/ge_madd.h \ - crypto_sign/ed25519/ref10/ge_msub.c \ - crypto_sign/ed25519/ref10/ge_msub.h \ - crypto_sign/ed25519/ref10/ge_p1p1_to_p2.c \ - crypto_sign/ed25519/ref10/ge_p1p1_to_p3.c \ - crypto_sign/ed25519/ref10/ge_p2_0.c \ - crypto_sign/ed25519/ref10/ge_p2_dbl.c \ - crypto_sign/ed25519/ref10/ge_p2_dbl.h \ - crypto_sign/ed25519/ref10/ge_p3_0.c \ - crypto_sign/ed25519/ref10/ge_p3_dbl.c \ - crypto_sign/ed25519/ref10/ge_p3_to_cached.c \ - crypto_sign/ed25519/ref10/ge_p3_to_p2.c \ - crypto_sign/ed25519/ref10/ge_p3_tobytes.c \ - crypto_sign/ed25519/ref10/ge_precomp_0.c \ - crypto_sign/ed25519/ref10/ge_scalarmult_base.c \ - crypto_sign/ed25519/ref10/ge_sub.c \ - crypto_sign/ed25519/ref10/ge_sub.h \ - crypto_sign/ed25519/ref10/ge_tobytes.c \ crypto_sign/ed25519/ref10/keypair.c \ crypto_sign/ed25519/ref10/open.c \ - crypto_sign/ed25519/ref10/pow22523.h \ - crypto_sign/ed25519/ref10/pow225521.h \ - crypto_sign/ed25519/ref10/sc.h \ - crypto_sign/ed25519/ref10/sc_muladd.c \ - crypto_sign/ed25519/ref10/sc_reduce.c \ + crypto_sign/ed25519/ref10/obsolete.c \ crypto_sign/ed25519/ref10/sign.c \ - crypto_sign/ed25519/ref10/sqrtm1.h \ crypto_stream/crypto_stream.c \ crypto_stream/chacha20/stream_chacha20.c \ crypto_stream/chacha20/stream_chacha20.h \ @@ -166,23 +117,16 @@ SODIUM_SOURCE := \ sodium/version.c SODIUM_SOURCE += \ - crypto_scalarmult/curve25519/ref10/curve25519_ref10.c \ - crypto_scalarmult/curve25519/ref10/curve25519_ref10.h \ - crypto_scalarmult/curve25519/ref10/fe.h \ - crypto_scalarmult/curve25519/ref10/fe_0_curve25519_ref10.c \ - crypto_scalarmult/curve25519/ref10/fe_1_curve25519_ref10.c \ - crypto_scalarmult/curve25519/ref10/fe_add_curve25519_ref10.c \ - crypto_scalarmult/curve25519/ref10/fe_copy_curve25519_ref10.c \ - crypto_scalarmult/curve25519/ref10/fe_cswap_curve25519_ref10.c \ - crypto_scalarmult/curve25519/ref10/fe_frombytes_curve25519_ref10.c \ - crypto_scalarmult/curve25519/ref10/fe_invert_curve25519_ref10.c \ - crypto_scalarmult/curve25519/ref10/fe_mul_curve25519_ref10.c \ - crypto_scalarmult/curve25519/ref10/fe_mul121666_curve25519_ref10.c \ - crypto_scalarmult/curve25519/ref10/fe_sq_curve25519_ref10.c \ - crypto_scalarmult/curve25519/ref10/fe_sub_curve25519_ref10.c \ - crypto_scalarmult/curve25519/ref10/fe_tobytes_curve25519_ref10.c \ - crypto_scalarmult/curve25519/ref10/montgomery.h \ - crypto_scalarmult/curve25519/ref10/pow225521.h + crypto_scalarmult/curve25519/ref10/x25519_ref10.c + +SODIUM_SOURCE += \ + crypto_pwhash/argon2/argon2-core.c \ + crypto_pwhash/argon2/argon2.c \ + crypto_pwhash/argon2/argon2-encoding.c \ + crypto_pwhash/argon2/blake2b-long.c \ + crypto_pwhash/argon2/argon2-fill-block-ref.c \ + crypto_pwhash/argon2/pwhash_argon2i.c \ + crypto_pwhash/argon2/argon2-fill-block-ssse3.c SODIUM_SOURCE += \ crypto_stream/salsa20/ref/stream_salsa20_ref.c \ @@ -192,10 +136,10 @@ SODIUM_SOURCE += \ randombytes/sysrandom/randombytes_sysrandom.c LOCAL_MODULE := sodium -LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/shadowsocks-libev/libsodium/src/libsodium/include \ +LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/libsodium/src/libsodium/include \ -I$(LOCAL_PATH)/include \ -I$(LOCAL_PATH)/include/sodium \ - -I$(LOCAL_PATH)/shadowsocks-libev/libsodium/src/libsodium/include/sodium \ + -I$(LOCAL_PATH)/libsodium/src/libsodium/include/sodium \ -DPACKAGE_NAME=\"libsodium\" -DPACKAGE_TARNAME=\"libsodium\" \ -DPACKAGE_VERSION=\"1.0.7\" -DPACKAGE_STRING=\"libsodium\ 1.0.7\" \ -DPACKAGE_BUGREPORT=\"https://github.com/jedisct1/libsodium/issues\" \ @@ -211,7 +155,7 @@ LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/shadowsocks-libev/libsodium/src/libsodium/in -DHAVE_WEAK_SYMBOLS=1 -DHAVE_ARC4RANDOM=1 -DHAVE_ARC4RANDOM_BUF=1 \ -DHAVE_MLOCK=1 -DHAVE_MPROTECT=1 -DHAVE_POSIX_MEMALIGN=1 -LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/libsodium/src/libsodium/,$(SODIUM_SOURCE)) +LOCAL_SRC_FILES := $(addprefix libsodium/src/libsodium/,$(SODIUM_SOURCE)) include $(BUILD_STATIC_LIBRARY) @@ -275,7 +219,7 @@ LOCAL_MODULE := libipset LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/shadowsocks-libev/libipset/include \ -I$(LOCAL_PATH)/shadowsocks-libev/libcork/include -LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/libipset/,$(IPSET_SOURCE)) +LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/libipset/src/libipset/,$(IPSET_SOURCE)) include $(BUILD_STATIC_LIBRARY) @@ -302,7 +246,7 @@ LOCAL_MODULE := libcork LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/shadowsocks-libev/libcork/include \ -DCORK_API=CORK_LOCAL -LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/libcork/,$(CORK_SOURCE)) +LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/libcork/src/libcork/,$(CORK_SOURCE)) include $(BUILD_STATIC_LIBRARY) @@ -318,10 +262,10 @@ UDNS_SOURCES := udns_dn.c udns_dntosp.c udns_parse.c udns_resolver.c udns_init.c udns_rr_srv.c udns_rr_naptr.c udns_codes.c udns_jran.c LOCAL_MODULE := libudns -LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/shadowsocks-libev/libudns \ +LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/libudns \ -DHAVE_DECL_INET_NTOP -LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/libudns/,$(UDNS_SOURCES)) +LOCAL_SRC_FILES := $(addprefix libudns/,$(UDNS_SOURCES)) include $(BUILD_STATIC_LIBRARY) @@ -335,8 +279,8 @@ LOCAL_MODULE := libev LOCAL_CFLAGS += -O2 -DNDEBUG -DHAVE_CONFIG_H \ -I$(LOCAL_PATH)/include/libev LOCAL_SRC_FILES := \ - shadowsocks-libev/libev/ev.c \ - shadowsocks-libev/libev/event.c + libev/ev.c \ + libev/event.c include $(BUILD_STATIC_LIBRARY) @@ -400,10 +344,10 @@ LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_LOCAL \ -I$(LOCAL_PATH)/libancillary \ -I$(LOCAL_PATH)/mbedtls/include \ -I$(LOCAL_PATH)/pcre \ - -I$(LOCAL_PATH)/shadowsocks-libev/libudns \ + -I$(LOCAL_PATH)/libudns \ -I$(LOCAL_PATH)/shadowsocks-libev/libcork/include \ - -I$(LOCAL_PATH)/shadowsocks-libev/libsodium/src/libsodium/include \ - -I$(LOCAL_PATH)/shadowsocks-libev/libsodium/src/libsodium/include/sodium \ + -I$(LOCAL_PATH)/libsodium/src/libsodium/include \ + -I$(LOCAL_PATH)/libsodium/src/libsodium/include/sodium \ -I$(LOCAL_PATH)/shadowsocks-libev/libipset/include \ -I$(LOCAL_PATH)/shadowsocks-libev/libev @@ -429,10 +373,10 @@ LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_TUNNEL \ -DCONNECT_IN_PROGRESS=EINPROGRESS \ -I$(LOCAL_PATH)/libancillary \ -I$(LOCAL_PATH)/include \ - -I$(LOCAL_PATH)/shadowsocks-libev/libudns \ + -I$(LOCAL_PATH)/libudns \ -I$(LOCAL_PATH)/shadowsocks-libev/libcork/include \ - -I$(LOCAL_PATH)/shadowsocks-libev/libsodium/src/libsodium/include \ - -I$(LOCAL_PATH)/shadowsocks-libev/libsodium/src/libsodium/include/sodium \ + -I$(LOCAL_PATH)/libsodium/include \ + -I$(LOCAL_PATH)/libsodium/src/libsodium/include/sodium \ -I$(LOCAL_PATH)/mbedtls/include \ -I$(LOCAL_PATH)/shadowsocks-libev/libev \ -I$(LOCAL_PATH)/include/shadowsocks-libev diff --git a/mobile/src/main/jni/libev b/mobile/src/main/jni/libev new file mode 160000 index 00000000..981fb847 --- /dev/null +++ b/mobile/src/main/jni/libev @@ -0,0 +1 @@ +Subproject commit 981fb847e1e306bc40460de4a810f63f9bbc7212 diff --git a/mobile/src/main/jni/libsodium b/mobile/src/main/jni/libsodium new file mode 160000 index 00000000..a81cea23 --- /dev/null +++ b/mobile/src/main/jni/libsodium @@ -0,0 +1 @@ +Subproject commit a81cea2366b9e814861a6f47e5bd460e06f19295 diff --git a/mobile/src/main/jni/libudns b/mobile/src/main/jni/libudns new file mode 160000 index 00000000..53a1abd2 --- /dev/null +++ b/mobile/src/main/jni/libudns @@ -0,0 +1 @@ +Subproject commit 53a1abd2b6fd7e3bf99ce6e2e267c636618d57ef diff --git a/mobile/src/main/jni/shadowsocks-libev b/mobile/src/main/jni/shadowsocks-libev index 45529cdf..19e222ba 160000 --- a/mobile/src/main/jni/shadowsocks-libev +++ b/mobile/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit 45529cdf7a3cd5384d58f97ec0e4c62de55cdae0 +Subproject commit 19e222bad8416f20b7694bff75f2cee72a70701e From 6eb6c571ac4c92f005d3932bb4a9d444efde0949 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Wed, 18 Jan 2017 17:29:21 +0800 Subject: [PATCH 15/98] Update makefile --- mobile/src/main/jni/Android.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index 063dfbe5..e8b34930 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -349,7 +349,7 @@ LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_LOCAL \ -I$(LOCAL_PATH)/libsodium/src/libsodium/include \ -I$(LOCAL_PATH)/libsodium/src/libsodium/include/sodium \ -I$(LOCAL_PATH)/shadowsocks-libev/libipset/include \ - -I$(LOCAL_PATH)/shadowsocks-libev/libev + -I$(LOCAL_PATH)/libev LOCAL_STATIC_LIBRARIES := libev libmbedtls libipset libcork libudns \ libsodium libancillary libpcre @@ -378,7 +378,7 @@ LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_TUNNEL \ -I$(LOCAL_PATH)/libsodium/include \ -I$(LOCAL_PATH)/libsodium/src/libsodium/include/sodium \ -I$(LOCAL_PATH)/mbedtls/include \ - -I$(LOCAL_PATH)/shadowsocks-libev/libev \ + -I$(LOCAL_PATH)/libev \ -I$(LOCAL_PATH)/include/shadowsocks-libev LOCAL_STATIC_LIBRARIES := libev libmbedtls libsodium libcork libudns libancillary From 77a06cb519e77ed9989fd967a77aa0330abbded3 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Wed, 18 Jan 2017 17:56:10 +0800 Subject: [PATCH 16/98] Fix CFLAGS --- mobile/src/main/jni/Android.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index e8b34930..6fbec5fc 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -375,7 +375,7 @@ LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_TUNNEL \ -I$(LOCAL_PATH)/include \ -I$(LOCAL_PATH)/libudns \ -I$(LOCAL_PATH)/shadowsocks-libev/libcork/include \ - -I$(LOCAL_PATH)/libsodium/include \ + -I$(LOCAL_PATH)/libsodium/src/libsodium/include \ -I$(LOCAL_PATH)/libsodium/src/libsodium/include/sodium \ -I$(LOCAL_PATH)/mbedtls/include \ -I$(LOCAL_PATH)/libev \ From 91be4146f9693f4616abf456ea34451acbe30e8c Mon Sep 17 00:00:00 2001 From: Max Lv Date: Thu, 19 Jan 2017 15:52:37 +0800 Subject: [PATCH 17/98] Update README --- README.md | 2 +- local.properties.example => mobile/local.properties.example | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename local.properties.example => mobile/local.properties.example (100%) diff --git a/README.md b/README.md index bec1e2c9..6cbbc7e6 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. * Set environment variable `ANDROID_HOME` to `/path/to/android-sdk` * Set environment variable `ANDROID_NDK_HOME` to `/path/to/android-ndk` * Create your key following the instructions at https://developer.android.com/studio/publish/app-signing.html -* Create `local.properties` from `local.properties.example` with your own key information +* Create `mobile/local.properties` from `mobile/local.properties.example` with your own key information * Invoke the building like this ```bash diff --git a/local.properties.example b/mobile/local.properties.example similarity index 100% rename from local.properties.example rename to mobile/local.properties.example From b78c10e8cc804f76c71e3fd4d9695d2d164d7c93 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Thu, 19 Jan 2017 20:43:02 +0800 Subject: [PATCH 18/98] Fix content provider --- .../shadowsocks/ProfileConfigFragment.scala | 2 +- .../github/shadowsocks/plugin/NativePlugin.scala | 14 +++++++------- .../shadowsocks/plugin/PluginManager.scala | 15 +++++++++++---- .../shadowsocks/plugin/ProviderPlugin.scala | 16 ++++++++++++++++ .../shadowsocks/plugin/ResolvedPlugin.scala | 16 ---------------- .../PluginConfigurationDialogFragment.scala | 2 +- 6 files changed, 36 insertions(+), 29 deletions(-) create mode 100644 mobile/src/main/scala/com/github/shadowsocks/plugin/ProviderPlugin.scala delete mode 100644 mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala index d30e8b66..e06bf9d9 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala @@ -93,7 +93,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe false }) initPlugins() - app.listenForPackageChanges(getView.post(initPlugins)) + app.listenForPackageChanges(if (getView != null) getView.post(initPlugins)) app.settings.registerOnSharedPreferenceChangeListener(this) } diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala index 2efd9652..e0dbddd8 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala @@ -1,16 +1,16 @@ package com.github.shadowsocks.plugin -import android.content.pm.{PackageManager, ResolveInfo} +import android.content.pm.{PackageManager, ProviderInfo} /** * @author Mygod */ -class NativePlugin(resolveInfo: ResolveInfo, packageManager: PackageManager) - extends ResolvedPlugin(resolveInfo, packageManager) { - assert(resolveInfo.providerInfo != null) - assert(resolveInfo.providerInfo.authority.startsWith(PluginInterface.AUTHORITY_BASE)) +class NativePlugin(providerInfo: ProviderInfo, packageManager: PackageManager) + extends ResolvedPlugin(providerInfo, packageManager) { + assert(providerInfo != null) + assert(providerInfo.authority.startsWith(PluginInterface.AUTHORITY_BASE)) override final lazy val id: String = - resolveInfo.providerInfo.authority.substring(PluginInterface.AUTHORITY_BASE.length) + providerInfo.authority.substring(PluginInterface.AUTHORITY_BASE.length) override final lazy val defaultConfig: String = - resolveInfo.providerInfo.metaData.getString(PluginInterface.METADATA_KEY_DEFAULT_CONFIG) + providerInfo.metaData.getString(PluginInterface.METADATA_KEY_DEFAULT_CONFIG) } diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala index df1c048c..35ae6f0c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala @@ -5,6 +5,7 @@ import java.io.{File, FileNotFoundException, FileOutputStream, IOException} import android.content.pm.PackageManager import android.content.{BroadcastReceiver, ContentResolver, Intent} import android.net.Uri +import android.text.TextUtils import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.utils.CloseUtils.autoClose import com.github.shadowsocks.utils.IOUtils @@ -22,8 +23,10 @@ object PluginManager { synchronized(if (cachedPlugins == null) { val pm = app.getPackageManager cachedPlugins = (NoPlugin +: - pm.queryIntentContentProviders(new Intent().addCategory(PluginInterface.CATEGORY_NATIVE_PLUGIN), - PackageManager.GET_META_DATA).asScala.map(new NativePlugin(_, pm))).toArray + pm.queryContentProviders(null, 0, PackageManager.GET_META_DATA).asScala + .filter(_ != null) + .filter(_.authority.startsWith(PluginInterface.AUTHORITY_BASE)) + .map(new NativePlugin(_, pm))).toArray }) cachedPlugins } @@ -31,7 +34,7 @@ object PluginManager { // the following parts are meant to be used by :bg @throws[Throwable] def initPlugin(id: String): String = { - if (id.isEmpty) return null + if (TextUtils.isEmpty(id)) return null var throwable: Throwable = null try initNativePlugin(id) match { @@ -62,7 +65,11 @@ object PluginManager { var initialized = false val pluginDir = new File(app.getFilesDir, "plugin") if (cursor.moveToFirst()) { - IOUtils.deleteRecursively(pluginDir) + try { + IOUtils.deleteRecursively(pluginDir) + } catch { + case ex: IOException => // Ignore + } if (!pluginDir.mkdirs()) throw new FileNotFoundException("Unable to create plugin directory") val pluginDirPath = pluginDir.getAbsolutePath + '/' do { diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/ProviderPlugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/ProviderPlugin.scala new file mode 100644 index 00000000..51c44108 --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/ProviderPlugin.scala @@ -0,0 +1,16 @@ +package com.github.shadowsocks.plugin + +import android.content.pm.{PackageManager, ProviderInfo} +import android.graphics.drawable.Drawable +import com.github.shadowsocks.ShadowsocksApplication.app + +/** + * Base class for any kind of plugin that can be used. + * + * @author Mygod + */ +abstract class ResolvedPlugin(providerInfo: ProviderInfo, packageManager: PackageManager = app.getPackageManager) + extends Plugin { + override final lazy val label: CharSequence = packageManager.getApplicationLabel(providerInfo.applicationInfo) + override final lazy val icon: Drawable = packageManager.getApplicationIcon(providerInfo.applicationInfo) +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala deleted file mode 100644 index 13a6c600..00000000 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.shadowsocks.plugin - -import android.content.pm.{PackageManager, ResolveInfo} -import android.graphics.drawable.Drawable -import com.github.shadowsocks.ShadowsocksApplication.app - -/** - * Base class for any kind of plugin that can be used. - * - * @author Mygod - */ -abstract class ResolvedPlugin(resolveInfo: ResolveInfo, packageManager: PackageManager = app.getPackageManager) - extends Plugin { - override final lazy val label: CharSequence = resolveInfo.loadLabel(packageManager) - override final lazy val icon: Drawable = resolveInfo.loadIcon(packageManager) -} diff --git a/mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala index adab7dca..bf88aa7b 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala @@ -39,7 +39,7 @@ class PluginConfigurationDialogFragment extends EditTextPreferenceDialogFragment override def onPrepareDialogBuilder(builder: AlertDialog.Builder) { super.onPrepareDialogBuilder(builder) val intent = new Intent(PluginInterface.ACTION_HELP(getArguments.getString(PLUGIN_ID_FRAGMENT_TAG))) - if (intent.resolveActivity(getContext.getPackageManager) != null) builder.setNeutralButton("?", ((_, _) => + if (intent.resolveActivity(getActivity.getPackageManager) != null) builder.setNeutralButton("?", ((_, _) => startActivityForResult(intent.putExtra(PluginInterface.EXTRA_OPTIONS, editText.getText.toString), REQUEST_CODE_HELP)): DialogInterface.OnClickListener) } From 99d97d1dbd4186737d391121294b1b71660d2a80 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Jan 2017 03:29:17 +0800 Subject: [PATCH 19/98] Fix launching plugin --- mobile/build.sbt | 6 -- mobile/src/main/res/values/dimen.xml | 2 +- .../shadowsocks/ProfileConfigFragment.scala | 52 +++++++++----- .../shadowsocks/plugin/NativePlugin.scala | 21 +++--- .../github/shadowsocks/plugin/Plugin.scala | 1 + .../plugin/PluginConfiguration.scala | 8 ++- .../shadowsocks/plugin/PluginManager.scala | 72 +++++++++++-------- .../shadowsocks/plugin/ProviderPlugin.scala | 16 ----- .../shadowsocks/plugin/ResolvedPlugin.scala | 22 ++++++ .../BottomSheetPreferenceDialogFragment.scala | 49 ++++++++----- .../preference/IconListPreference.scala | 1 + .../PluginConfigurationDialogFragment.scala | 8 +-- .../github/shadowsocks/utils/IOUtils.scala | 2 +- ...uginInterface.java => PluginContract.java} | 69 +++++++++--------- .../shadowsocks/plugin/PluginOptions.java | 20 +++--- .../plugin/ConfigurationActivity.scala | 2 +- .../shadowsocks/plugin/HelpCallback.scala | 2 +- .../plugin/NativePluginProvider.scala | 4 +- .../plugin/OptionsCapableActivity.scala | 2 +- .../shadowsocks/plugin/PathProvider.scala | 17 +++-- project/plugins.sbt | 2 +- 21 files changed, 216 insertions(+), 162 deletions(-) delete mode 100644 mobile/src/main/scala/com/github/shadowsocks/plugin/ProviderPlugin.scala create mode 100644 mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala rename plugin/src/main/java/com/github/shadowsocks/plugin/{PluginInterface.java => PluginContract.java} (54%) diff --git a/mobile/build.sbt b/mobile/build.sbt index 2816e688..be511b5d 100644 --- a/mobile/build.sbt +++ b/mobile/build.sbt @@ -6,12 +6,6 @@ version := "4.0.0" versionCode := Some(174) proguardOptions ++= - "-keep class android.support.v14.preference.SwitchPreference { (...); }" :: - "-keep class android.support.v7.preference.DropDownPreference { (...); }" :: - "-keep class android.support.v7.preference.PreferenceScreen { (...); }" :: - "-keep class be.mygod.preference.EditTextPreference { (...); }" :: - "-keep class be.mygod.preference.NumberPickerPreference { (...); }" :: - "-keep class be.mygod.preference.PreferenceCategory { (...); }" :: "-keep class com.github.shadowsocks.System { *; }" :: "-dontwarn com.google.android.gms.internal.**" :: "-dontwarn com.j256.ormlite.**" :: diff --git a/mobile/src/main/res/values/dimen.xml b/mobile/src/main/res/values/dimen.xml index 7fc6e0d3..e25dae51 100644 --- a/mobile/src/main/res/values/dimen.xml +++ b/mobile/src/main/res/values/dimen.xml @@ -1,7 +1,7 @@ 480dp - 8dp + 16dp -44dp diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala index e06bf9d9..53ce2fee 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala @@ -20,8 +20,9 @@ package com.github.shadowsocks +import android.app.Activity import android.content.SharedPreferences.OnSharedPreferenceChangeListener -import android.content.{DialogInterface, Intent, SharedPreferences} +import android.content._ import android.os.{Build, Bundle, UserManager} import android.support.design.widget.Snackbar import android.support.v14.preference.SwitchPreference @@ -33,7 +34,7 @@ import android.view.MenuItem import be.mygod.preference.{EditTextPreference, PreferenceFragment} import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.database.Profile -import com.github.shadowsocks.plugin.{PluginConfiguration, PluginInterface, PluginManager, PluginOptions} +import com.github.shadowsocks.plugin._ import com.github.shadowsocks.preference.{IconListPreference, PluginConfigurationDialogFragment} import com.github.shadowsocks.utils.{Action, Key, Utils} @@ -79,29 +80,21 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe pluginConfiguration = new PluginConfiguration(pluginConfiguration.pluginsOptions, selected) app.editor.putString(Key.plugin, pluginConfiguration.toString).putBoolean(Key.dirty, true).apply() pluginConfigure.setEnabled(!TextUtils.isEmpty(selected)) + pluginConfigure.setText(pluginConfiguration.selectedOptions.toString) true }) - pluginConfigure.setOnPreferenceChangeListener((_, value) => try { - val selected = pluginConfiguration.selected - pluginConfiguration = new PluginConfiguration(pluginConfiguration.pluginsOptions + - (pluginConfiguration.selected -> new PluginOptions(selected, value.asInstanceOf[String])), selected) - app.editor.putString(Key.plugin, pluginConfiguration.toString).putBoolean(Key.dirty, true).apply() - true - } catch { - case exc: IllegalArgumentException => - Snackbar.make(getActivity.findViewById(R.id.snackbar), exc.getLocalizedMessage, Snackbar.LENGTH_LONG).show() - false - }) + pluginConfigure.setOnPreferenceChangeListener(onPluginConfigureChanged) initPlugins() - app.listenForPackageChanges(if (getView != null) getView.post(initPlugins)) + app.listenForPackageChanges(initPlugins()) app.settings.registerOnSharedPreferenceChangeListener(this) } def initPlugins() { val plugins = PluginManager.fetchPlugins() - plugin.setEntries(plugins.map(_.label)) - plugin.setEntryValues(plugins.map(_.id.asInstanceOf[CharSequence])) - plugin.setEntryIcons(plugins.map(_.icon)) + plugin.setEntries(plugins.map(_._2.label).toArray) + plugin.setEntryValues(plugins.map(_._2.id.asInstanceOf[CharSequence]).toArray) + plugin.setEntryIcons(plugins.map(_._2.icon).toArray) + plugin.entryPackageNames = plugins.map(_._2.packageName).toArray pluginConfiguration = new PluginConfiguration(app.settings.getString(Key.plugin, null)) plugin.setValue(pluginConfiguration.selected) plugin.checkSummary() @@ -109,6 +102,18 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe pluginConfigure.setText(pluginConfiguration.selectedOptions.toString) } + private def onPluginConfigureChanged(p: Preference, value: Any) = try { + val selected = pluginConfiguration.selected + pluginConfiguration = new PluginConfiguration(pluginConfiguration.pluginsOptions + + (pluginConfiguration.selected -> new PluginOptions(selected, value.asInstanceOf[String])), selected) + app.editor.putString(Key.plugin, pluginConfiguration.toString).putBoolean(Key.dirty, true).apply() + true + } catch { + case exc: IllegalArgumentException => + Snackbar.make(getActivity.findViewById(R.id.snackbar), exc.getLocalizedMessage, Snackbar.LENGTH_LONG).show() + false + } + override def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String): Unit = if (key != Key.proxyApps && findPreference(key) != null) app.editor.putBoolean(Key.dirty, true).apply() @@ -124,9 +129,9 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe override def onDisplayPreferenceDialog(preference: Preference): Unit = if (preference eq pluginConfigure) { val selected = pluginConfiguration.selected - val intent = new Intent(PluginInterface.ACTION_CONFIGURE(selected)) + val intent = PluginManager.buildIntent(selected, PluginContract.ACTION_CONFIGURE) if (intent.resolveActivity(getActivity.getPackageManager) != null) - startActivityForResult(intent.putExtra(PluginInterface.EXTRA_OPTIONS, + startActivityForResult(intent.putExtra(PluginContract.EXTRA_OPTIONS, pluginConfiguration.selectedOptions.toString), REQUEST_CODE_CONFIGURE) else { val bundle = new Bundle() bundle.putString(PluginConfigurationDialogFragment.PLUGIN_ID_FRAGMENT_TAG, selected) @@ -134,6 +139,15 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe } } else super.onDisplayPreferenceDialog(preference) + override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = requestCode match { + case REQUEST_CODE_CONFIGURE => if (resultCode == Activity.RESULT_OK) { + val options = data.getStringExtra(PluginContract.EXTRA_OPTIONS) + pluginConfigure.setText(options) + onPluginConfigureChanged(null, options) + } + case _ => super.onActivityResult(requestCode, resultCode, data) + } + override def onMenuItemClick(item: MenuItem): Boolean = item.getItemId match { case R.id.action_delete => new AlertDialog.Builder(getActivity) diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala index e0dbddd8..7621f412 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala @@ -1,16 +1,19 @@ package com.github.shadowsocks.plugin -import android.content.pm.{PackageManager, ProviderInfo} +import android.content.ContentResolver +import android.content.pm.{PackageManager, ResolveInfo} +import android.net.Uri +import android.os.Bundle /** * @author Mygod */ -class NativePlugin(providerInfo: ProviderInfo, packageManager: PackageManager) - extends ResolvedPlugin(providerInfo, packageManager) { - assert(providerInfo != null) - assert(providerInfo.authority.startsWith(PluginInterface.AUTHORITY_BASE)) - override final lazy val id: String = - providerInfo.authority.substring(PluginInterface.AUTHORITY_BASE.length) - override final lazy val defaultConfig: String = - providerInfo.metaData.getString(PluginInterface.METADATA_KEY_DEFAULT_CONFIG) +final class NativePlugin(resolveInfo: ResolveInfo, packageManager: PackageManager) + extends ResolvedPlugin(resolveInfo, packageManager) { + assert(resolveInfo.providerInfo != null) + override protected def metaData: Bundle = resolveInfo.providerInfo.metaData + + private lazy val uriBuilder: Uri.Builder = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(resolveInfo.providerInfo.authority) } diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/Plugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/Plugin.scala index f300c3aa..2017eb5c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/Plugin.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/Plugin.scala @@ -10,4 +10,5 @@ abstract class Plugin { def label: CharSequence def icon: Drawable = null def defaultConfig: String = null + def packageName: String = null } diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginConfiguration.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginConfiguration.scala index b94ebdce..cbb37f0a 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginConfiguration.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginConfiguration.scala @@ -19,7 +19,7 @@ class PluginConfiguration(val pluginsOptions: Map[String, PluginOptions], val se val args = mutable.Queue(Commandline.translateCommandline(line): _*) args.dequeue() while (args.nonEmpty) args.dequeue() match { - case "--nocomp" => opt.put("nocomp", "1") + case "--nocomp" => opt.put("nocomp", null) case option if option.startsWith("--") => opt.put(option.substring(2), args.dequeue()) case option => throw new IllegalArgumentException("Unknown kcptun parameter: " + option) } @@ -30,7 +30,10 @@ class PluginConfiguration(val pluginsOptions: Map[String, PluginOptions], val se case line => new PluginOptions(line) }) - def getOptions(id: String): PluginOptions = if (id.isEmpty) new PluginOptions() else pluginsOptions(id) + def getOptions(id: String): PluginOptions = if (id.isEmpty) new PluginOptions() else pluginsOptions.get(id) match { + case Some(options) => options + case None => new PluginOptions(id, PluginManager.fetchPlugins()(id).defaultConfig) + } def selectedOptions: PluginOptions = getOptions(selected) override def toString: String = { @@ -39,6 +42,7 @@ class PluginConfiguration(val pluginsOptions: Map[String, PluginOptions], val se case this.selected => result.prepend(opt) case _ => result.append(opt) } + if (!pluginsOptions.contains(selected)) result.prepend(selectedOptions) result.map(_.toString(false)).mkString("\n") } } diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala index 35ae6f0c..84178df9 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala @@ -5,36 +5,43 @@ import java.io.{File, FileNotFoundException, FileOutputStream, IOException} import android.content.pm.PackageManager import android.content.{BroadcastReceiver, ContentResolver, Intent} import android.net.Uri -import android.text.TextUtils import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.utils.CloseUtils.autoClose -import com.github.shadowsocks.utils.IOUtils +import com.github.shadowsocks.utils.{Commandline, IOUtils} +import eu.chainfire.libsuperuser.Shell -import scala.collection.JavaConverters._ +import scala.collection.JavaConversions._ /** * @author Mygod */ object PluginManager { private var receiver: BroadcastReceiver = _ - private var cachedPlugins: Array[Plugin] = _ - def fetchPlugins(): Array[Plugin] = { + private var cachedPlugins: Map[String, Plugin] = _ + def fetchPlugins(): Map[String, Plugin] = { if (receiver == null) receiver = app.listenForPackageChanges(synchronized(cachedPlugins = null)) synchronized(if (cachedPlugins == null) { val pm = app.getPackageManager - cachedPlugins = (NoPlugin +: - pm.queryContentProviders(null, 0, PackageManager.GET_META_DATA).asScala - .filter(_ != null) - .filter(_.authority.startsWith(PluginInterface.AUTHORITY_BASE)) - .map(new NativePlugin(_, pm))).toArray + cachedPlugins = ( + pm.queryIntentContentProviders(new Intent(PluginContract.ACTION_NATIVE_PLUGIN), PackageManager.GET_META_DATA) + .map(new NativePlugin(_, pm)) :+ + NoPlugin + ).map(plugin => plugin.id -> plugin).toMap }) cachedPlugins } + private def buildUri(id: String) = new Uri.Builder() + .scheme(PluginContract.SCHEME) + .authority(PluginContract.AUTHORITY) + .path('/' + id) + .build() + def buildIntent(id: String, action: String): Intent = new Intent(action, buildUri(id)) + // the following parts are meant to be used by :bg @throws[Throwable] def initPlugin(id: String): String = { - if (TextUtils.isEmpty(id)) return null + if (id.isEmpty) return null var throwable: Throwable = null try initNativePlugin(id) match { @@ -56,32 +63,35 @@ object PluginManager { @throws[IndexOutOfBoundsException] @throws[AssertionError] private def initNativePlugin(id: String): String = { + val providers = app.getPackageManager.queryIntentContentProviders( + new Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(id)), 0) + assert(providers.length == 1) val builder = new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) - .authority(PluginInterface.getAuthority(id)) + .authority(providers(0).providerInfo.authority) val cr = app.getContentResolver - val cursor = cr.query(builder.build(), Array(PluginInterface.COLUMN_PATH), null, null, null) + val cursor = cr.query(builder.build(), Array(PluginContract.COLUMN_PATH, PluginContract.COLUMN_MODE), + null, null, null) if (cursor != null) { var initialized = false val pluginDir = new File(app.getFilesDir, "plugin") - if (cursor.moveToFirst()) { - try { - IOUtils.deleteRecursively(pluginDir) - } catch { - case ex: IOException => // Ignore - } - if (!pluginDir.mkdirs()) throw new FileNotFoundException("Unable to create plugin directory") - val pluginDirPath = pluginDir.getAbsolutePath + '/' - do { - val path = cursor.getString(0) - val file = new File(pluginDir, path) - assert(file.getAbsolutePath.startsWith(pluginDirPath)) - autoClose(cr.openInputStream(builder.path(path).build()))(in => - autoClose(new FileOutputStream(file))(out => IOUtils.copy(in, out))) - if (path == id) initialized = true - } while (cursor.moveToNext()) - } - if (!initialized) throw new IndexOutOfBoundsException("Plugin entry binary not found") + def entryNotFound() = throw new IndexOutOfBoundsException("Plugin entry binary not found") + if (!cursor.moveToFirst()) entryNotFound() + IOUtils.deleteRecursively(pluginDir) + if (!pluginDir.mkdirs()) throw new FileNotFoundException("Unable to create plugin directory") + val pluginDirPath = pluginDir.getAbsolutePath + '/' + var list: List[String] = Nil + do { + val path = cursor.getString(0) + val file = new File(pluginDir, path) + assert(file.getAbsolutePath.startsWith(pluginDirPath)) + autoClose(cr.openInputStream(builder.path(path).build()))(in => + autoClose(new FileOutputStream(file))(out => IOUtils.copy(in, out))) + list +:= Commandline.toString(Array("chmod", cursor.getString(1), file.getAbsolutePath)) + if (path == id) initialized = true + } while (cursor.moveToNext()) + if (!initialized) entryNotFound() + Shell.SH.run(list) new File(pluginDir, id).getAbsolutePath } else null } diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/ProviderPlugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/ProviderPlugin.scala deleted file mode 100644 index 51c44108..00000000 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/ProviderPlugin.scala +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.shadowsocks.plugin - -import android.content.pm.{PackageManager, ProviderInfo} -import android.graphics.drawable.Drawable -import com.github.shadowsocks.ShadowsocksApplication.app - -/** - * Base class for any kind of plugin that can be used. - * - * @author Mygod - */ -abstract class ResolvedPlugin(providerInfo: ProviderInfo, packageManager: PackageManager = app.getPackageManager) - extends Plugin { - override final lazy val label: CharSequence = packageManager.getApplicationLabel(providerInfo.applicationInfo) - override final lazy val icon: Drawable = packageManager.getApplicationIcon(providerInfo.applicationInfo) -} diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala new file mode 100644 index 00000000..3cc2e915 --- /dev/null +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala @@ -0,0 +1,22 @@ +package com.github.shadowsocks.plugin + +import android.content.pm.{PackageManager, ResolveInfo} +import android.graphics.drawable.Drawable +import android.os.Bundle +import com.github.shadowsocks.ShadowsocksApplication.app + +/** + * Base class for any kind of plugin that can be used. + * + * @author Mygod + */ +abstract class ResolvedPlugin(resolveInfo: ResolveInfo, packageManager: PackageManager = app.getPackageManager) + extends Plugin { + protected def metaData: Bundle + + override final lazy val id: String = metaData.getString(PluginContract.METADATA_KEY_ID) + override final lazy val label: CharSequence = resolveInfo.loadLabel(packageManager) + override final lazy val icon: Drawable = resolveInfo.loadIcon(packageManager) + override final lazy val defaultConfig: String = metaData.getString(PluginContract.METADATA_KEY_DEFAULT_CONFIG) + override final def packageName: String = resolveInfo.resolvePackageName +} diff --git a/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala index d0105e08..978ccdd9 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala @@ -1,15 +1,18 @@ package com.github.shadowsocks.preference +import android.content.{ActivityNotFoundException, Intent} import android.graphics.Typeface import android.graphics.drawable.Drawable +import android.net.Uri import android.os.Bundle +import android.provider.Settings import android.support.design.widget.BottomSheetDialog import android.support.v14.preference.PreferenceDialogFragment import android.support.v7.widget.RecyclerView.ViewHolder import android.support.v7.widget.{LinearLayoutManager, RecyclerView} import android.view.ViewGroup.LayoutParams import android.view.{LayoutInflater, View, ViewGroup} -import android.widget.TextView +import android.widget.{ImageView, TextView} import com.github.shadowsocks.R /** @@ -19,6 +22,7 @@ final class BottomSheetPreferenceDialogFragment extends PreferenceDialogFragment private lazy val preference = getPreference.asInstanceOf[IconListPreference] private lazy val index: Int = preference.selectedEntry private lazy val entries: Array[CharSequence] = preference.getEntries + private lazy val entryValues: Array[CharSequence] = preference.getEntryValues private lazy val entryIcons: Array[Drawable] = preference.getEntryIcons private var clickedIndex = -1 @@ -39,30 +43,41 @@ final class BottomSheetPreferenceDialogFragment extends PreferenceDialogFragment if (preference.callChangeListener(value)) preference.setValue(value) } - private final class IconListViewHolder(val dialog: BottomSheetDialog, val view: TextView) extends ViewHolder(view) - with View.OnClickListener { + private final class IconListViewHolder(val dialog: BottomSheetDialog, view: View) extends ViewHolder(view) + with View.OnClickListener with View.OnLongClickListener { private var index: Int = _ - - { - val padding = getResources.getDimension(R.dimen.icon_list_item_padding).toInt - view.setPadding(padding, padding, padding, padding) - view.setCompoundDrawablePadding(padding) - view.setOnClickListener(this) - val typedArray = dialog.getContext.obtainStyledAttributes(Array(android.R.attr.selectableItemBackground)) - view.setBackgroundResource(typedArray.getResourceId(0, 0)) - typedArray.recycle() - } + private val text1 = view.findViewById(android.R.id.text1).asInstanceOf[TextView] + private val text2 = view.findViewById(android.R.id.text2).asInstanceOf[TextView] + private val icon = view.findViewById(android.R.id.icon).asInstanceOf[ImageView] + view.setOnClickListener(this) + view.setOnLongClickListener(this) def bind(i: Int, selected: Boolean = false) { - view.setText(entries(i)) - view.setCompoundDrawablesWithIntrinsicBounds(entryIcons(i), null, null, null) - view.setTypeface(null, if (selected) Typeface.BOLD else Typeface.NORMAL) + text1.setText(entries(i)) + text2.setText(entryValues(i)) + val typeface = if (selected) Typeface.BOLD else Typeface.NORMAL + text1.setTypeface(null, typeface) + text2.setTypeface(null, typeface) + text2.setVisibility(if (entryValues(i).length > 0) View.VISIBLE else View.GONE) + icon.setImageDrawable(entryIcons(i)) index = i } def onClick(v: View) { clickedIndex = index dialog.dismiss() } + override def onLongClick(v: View): Boolean = preference.entryPackageNames(index) match { + case null => false + case pn => try { + startActivity(new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, new Uri.Builder() + .authority(Settings.AUTHORITY) + .opaquePart(pn) + .build())) + true + } catch { + case _: ActivityNotFoundException => false + } + } } private final class IconListAdapter(dialog: BottomSheetDialog) extends RecyclerView.Adapter[IconListViewHolder] { def getItemCount: Int = entries.length @@ -72,6 +87,6 @@ final class BottomSheetPreferenceDialogFragment extends PreferenceDialogFragment case _ => vh.bind(i - 1) } def onCreateViewHolder(vg: ViewGroup, i: Int): IconListViewHolder = new IconListViewHolder(dialog, - LayoutInflater.from(vg.getContext).inflate(android.R.layout.simple_list_item_1, vg, false).asInstanceOf[TextView]) + LayoutInflater.from(vg.getContext).inflate(R.layout.icon_list_item_2, vg, false)) } } diff --git a/mobile/src/main/scala/com/github/shadowsocks/preference/IconListPreference.scala b/mobile/src/main/scala/com/github/shadowsocks/preference/IconListPreference.scala index 7be88c52..23301b2f 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/preference/IconListPreference.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/preference/IconListPreference.scala @@ -19,6 +19,7 @@ class IconListPreference(context: Context, attrs: AttributeSet = null) extends L def selectedEntry: Int = getEntryValues.indexOf(getValue) var unknownValueSummary: String = _ + var entryPackageNames: Array[String] = _ private var listener: OnPreferenceChangeListener = _ override def getOnPreferenceChangeListener: OnPreferenceChangeListener = listener diff --git a/mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala index bf88aa7b..ff414d70 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala @@ -23,7 +23,7 @@ package com.github.shadowsocks.preference import android.app.{Activity, AlertDialog} import android.content.{DialogInterface, Intent} import be.mygod.preference.EditTextPreferenceDialogFragment -import com.github.shadowsocks.plugin.PluginInterface +import com.github.shadowsocks.plugin.{PluginContract, PluginManager} /** * @author Mygod @@ -38,9 +38,9 @@ class PluginConfigurationDialogFragment extends EditTextPreferenceDialogFragment override def onPrepareDialogBuilder(builder: AlertDialog.Builder) { super.onPrepareDialogBuilder(builder) - val intent = new Intent(PluginInterface.ACTION_HELP(getArguments.getString(PLUGIN_ID_FRAGMENT_TAG))) + val intent = PluginManager.buildIntent(getArguments.getString(PLUGIN_ID_FRAGMENT_TAG), PluginContract.ACTION_HELP) if (intent.resolveActivity(getActivity.getPackageManager) != null) builder.setNeutralButton("?", ((_, _) => - startActivityForResult(intent.putExtra(PluginInterface.EXTRA_OPTIONS, editText.getText.toString), + startActivityForResult(intent.putExtra(PluginContract.EXTRA_OPTIONS, editText.getText.toString), REQUEST_CODE_HELP)): DialogInterface.OnClickListener) } @@ -48,7 +48,7 @@ class PluginConfigurationDialogFragment extends EditTextPreferenceDialogFragment case REQUEST_CODE_HELP => requestCode match { case Activity.RESULT_OK => new AlertDialog.Builder(getContext) .setTitle("?") - .setMessage(data.getCharSequenceExtra(PluginInterface.EXTRA_HELP_MESSAGE)) + .setMessage(data.getCharSequenceExtra(PluginContract.EXTRA_HELP_MESSAGE)) .show() case _ => } diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala index 8ef7101c..3c4b4be1 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala @@ -41,7 +41,7 @@ object IOUtils { autoClose(new FileWriter(file))(writer => writer.write(content)) @throws[FileNotFoundException] - def deleteRecursively(file: File) { + def deleteRecursively(file: File): Unit = if (file.exists()) { if (file.isDirectory) file.listFiles().foreach(deleteRecursively) if (!file.delete()) throw new FileNotFoundException("Failed to delete: " + file.getAbsolutePath) } diff --git a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginInterface.java b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java similarity index 54% rename from plugin/src/main/java/com/github/shadowsocks/plugin/PluginInterface.java rename to plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java index 3d689ab8..90e274dc 100644 --- a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginInterface.java +++ b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java @@ -1,21 +1,34 @@ package com.github.shadowsocks.plugin; /** - * This class provides String constants that will be used in plugin interfaces. + * The contract between the plugin provider and host. Contains definitions for the supported actions, extras, etc. * * This class is written in Java to keep Java interoperability. * * @author Mygod */ -public final class PluginInterface { - private PluginInterface() { } +public final class PluginContract { + private PluginContract() { } /** - * Should be a NativePluginProvider. + * ContentProvider Action: Used for NativePluginProvider. * - * Constant Value: "com.github.shadowsocks.plugin.CATEGORY_NATIVE_PLUGIN" + * Constant Value: "com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" */ - public static final String CATEGORY_NATIVE_PLUGIN = "com.github.shadowsocks.plugin.CATEGORY_NATIVE_PLUGIN"; + public static final String ACTION_NATIVE_PLUGIN = "com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"; + + /** + * Activity Action: Used for ConfigurationActivity. + * + * Constant Value: "com.github.shadowsocks.plugin.ACTION_CONFIGURE" + */ + public static final String ACTION_CONFIGURE = "com.github.shadowsocks.plugin.ACTION_CONFIGURE"; + /** + * Activity Action: Used for HelpActivity or HelpCallback. + * + * Constant Value: "com.github.shadowsocks.plugin.ACTION_CONFIGURE" + */ + public static final String ACTION_HELP = "com.github.shadowsocks.plugin.ACTION_HELP"; /** * The lookup key for a string that provides the whole command as a string. @@ -35,6 +48,12 @@ public final class PluginInterface { */ public static final String EXTRA_HELP_MESSAGE = "com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE"; + /** + * The metadata key to retrieve plugin id. Required for plugins. + * + * Constant Value: "com.github.shadowsocks.plugin.id" + */ + public static final String METADATA_KEY_ID = "com.github.shadowsocks.plugin.id"; /** * The metadata key to retrieve default configuration. Default value is empty. * @@ -50,35 +69,21 @@ public final class PluginInterface { * Type: String */ public static final String COLUMN_PATH = "path"; - - static final String AUTHORITY_BASE = "com.github.shadowsocks.plugin."; + /** + * File mode bits. Default value is "644". + * + * Example: "755" + * + * Type: String + */ + public static final String COLUMN_MODE = "mode"; /** - * Authority to use for native plugin ContentProvider. - * - * @param pluginId Plugin ID. - * @return com.github.shadowsocks.plugin.$PLUGIN_ID + * The scheme for general plugin actions. */ - public static String getAuthority(String pluginId) { - return AUTHORITY_BASE + pluginId; - } - + public static final String SCHEME = "plugin"; /** - * Activity Action: Used for ConfigurationActivity. - * - * @param pluginId Plugin ID. - * @return com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_CONFIGURE + * The authority for general plugin actions. */ - public static String ACTION_CONFIGURE(String pluginId) { - return getAuthority(pluginId) + ".ACTION_CONFIGURE"; - } - /** - * Activity Action: Used for HelpActivity or HelpCallback. - * - * @param pluginId Plugin ID. - * @return com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_HELP - */ - public static String ACTION_HELP(String pluginId) { - return getAuthority(pluginId) + ".ACTION_HELP"; - } + public static final String AUTHORITY = "com.github.shadowsocks"; } diff --git a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java index 3d693d4b..7794b5d4 100644 --- a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java +++ b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java @@ -25,8 +25,9 @@ public final class PluginOptions extends HashMap { } private PluginOptions(String options, boolean parseId) { + this(); if (TextUtils.isEmpty(options)) return; - final StringTokenizer tokenizer = new StringTokenizer(options, "\\=;", true); + final StringTokenizer tokenizer = new StringTokenizer(options + ';', "\\=;", true); final StringBuilder current = new StringBuilder(); String key = null; while (tokenizer.hasMoreTokens()) { @@ -35,18 +36,15 @@ public final class PluginOptions extends HashMap { else if ("=".equals(nextToken) && key == null) { key = current.toString(); current.setLength(0); - } else if (";".equals(nextToken)) + } else if (";".equals(nextToken)) { if (key != null) { put(key, current.toString()); key = null; - } else if (parseId) { - id = current.toString(); - parseId = false; - } else { - put(current.toString(), null); - current.setLength(0); - } - else current.append(nextToken); + } else if (current.length() > 0) + if (parseId) id = current.toString(); else put(current.toString(), null); + current.setLength(0); + parseId = false; + } else current.append(nextToken); } } public PluginOptions(String options) { @@ -57,7 +55,7 @@ public final class PluginOptions extends HashMap { this.id = id; } - public String id; + public String id = ""; private static void append(StringBuilder result, String str) { for (int i = 0; i < str.length(); ++i) { diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala index 2200d79f..f35e4dc3 100644 --- a/plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala @@ -35,5 +35,5 @@ trait ConfigurationActivity extends OptionsCapableActivity { * @param options PluginOptions to save. */ final def saveChanges(options: PluginOptions): Unit = - setResult(Activity.RESULT_OK, new Intent().putExtra(PluginInterface.EXTRA_OPTIONS, options.toString)) + setResult(Activity.RESULT_OK, new Intent().putExtra(PluginContract.EXTRA_OPTIONS, options.toString)) } diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpCallback.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpCallback.scala index 1babb814..236f9a29 100644 --- a/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpCallback.scala +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpCallback.scala @@ -14,5 +14,5 @@ trait HelpCallback extends HelpActivity { def produceHelpMessage(options: PluginOptions): CharSequence override protected def onInitializePluginOptions(options: PluginOptions): Unit = setResult(Activity.RESULT_OK, - new Intent().putExtra(PluginInterface.EXTRA_HELP_MESSAGE, produceHelpMessage(options))) + new Intent().putExtra(PluginContract.EXTRA_HELP_MESSAGE, produceHelpMessage(options))) } diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala index 9f555574..5c1357e1 100644 --- a/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala @@ -17,7 +17,7 @@ import android.os.ParcelFileDescriptor * <provider android:name="com.github.shadowsocks.$PLUGIN_ID.BinaryProvider" * android:authorities="com.github.shadowsocks.plugin.$PLUGIN_ID"> * <intent-filter> - * <category android:name="com.github.shadowsocks.plugin.CATEGORY_NATIVE_PLUGIN" /> + * <category android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" /> * </intent-filter> * </provider> * ... @@ -47,7 +47,7 @@ abstract class NativePluginProvider extends ContentProvider { override def query(uri: Uri, projection: Array[String], selection: String, selectionArgs: Array[String], sortOrder: String): Cursor = { assert(selection == null && selectionArgs == null && sortOrder == null) - val result = new MatrixCursor(projection.filter(_ == PluginInterface.COLUMN_PATH)) + val result = new MatrixCursor(projection) populateFiles(new PathProvider(uri, result)) result } diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/OptionsCapableActivity.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/OptionsCapableActivity.scala index 40dbd96a..c2cb0f39 100644 --- a/plugin/src/main/scala/com/github/shadowsocks/plugin/OptionsCapableActivity.scala +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/OptionsCapableActivity.scala @@ -12,7 +12,7 @@ import android.widget.Toast */ trait OptionsCapableActivity extends Activity { protected def pluginOptions(intent: Intent = getIntent): PluginOptions = - try new PluginOptions(intent.getStringExtra(PluginInterface.EXTRA_OPTIONS)) catch { + try new PluginOptions(intent.getStringExtra(PluginContract.EXTRA_OPTIONS)) catch { case exc: IllegalArgumentException => Toast.makeText(this, exc.getMessage, Toast.LENGTH_SHORT).show() null diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/PathProvider.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/PathProvider.scala index 4eddf6d6..536e94e8 100644 --- a/plugin/src/main/scala/com/github/shadowsocks/plugin/PathProvider.scala +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/PathProvider.scala @@ -16,21 +16,24 @@ final class PathProvider private[plugin](baseUri: Uri, cursor: MatrixCursor) { case p => p.stripPrefix("/").stripSuffix("/") } - def addPath(path: String): PathProvider = { + def addPath(path: String, mode: String = "644"): PathProvider = { val stripped = path.stripPrefix("/").stripSuffix("/") - if (stripped.startsWith(basePath)) cursor.newRow().add(PluginInterface.COLUMN_PATH, stripped) + if (stripped.startsWith(basePath)) cursor.newRow() + .add(PluginContract.COLUMN_PATH, stripped) + .add(PluginContract.COLUMN_MODE, mode) this } - def addTo(file: File, to: String = ""): PathProvider = { + def addTo(file: File, to: String = "", mode: String = "644"): PathProvider = { var sub = to + file.getName if (basePath.startsWith(sub)) if (file.isDirectory) { sub += '/' - file.listFiles().foreach(addTo(_, sub)) - } else addPath(sub) + file.listFiles().foreach(addTo(_, sub, mode)) + } else addPath(sub, mode) this } - def addAt(file: File, at: String = ""): PathProvider = { - if (basePath.startsWith(at)) if (file.isDirectory) file.listFiles().foreach(addTo(_, at)) else addPath(at) + def addAt(file: File, at: String = "", mode: String = "644"): PathProvider = { + if (basePath.startsWith(at)) + if (file.isDirectory) file.listFiles().foreach(addTo(_, at, mode)) else addPath(at, mode) this } } diff --git a/project/plugins.sbt b/project/plugins.sbt index dd47db9f..9199c685 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ -addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.2") +addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.3") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10") From b95bee968a158ce9ddb062f4fdad57d1b8051466 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Jan 2017 03:46:43 +0800 Subject: [PATCH 20/98] Use dimen instead of Utils.dpToPx --- mobile/src/main/res/values/dimen.xml | 3 ++- .../src/main/scala/com/github/shadowsocks/MainActivity.scala | 2 +- .../main/scala/com/github/shadowsocks/ProfilesFragment.scala | 4 ++-- .../src/main/scala/com/github/shadowsocks/QRCodeDialog.scala | 4 ++-- .../src/main/scala/com/github/shadowsocks/utils/Utils.scala | 2 -- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/mobile/src/main/res/values/dimen.xml b/mobile/src/main/res/values/dimen.xml index e25dae51..415496c2 100644 --- a/mobile/src/main/res/values/dimen.xml +++ b/mobile/src/main/res/values/dimen.xml @@ -1,7 +1,8 @@ 480dp - 16dp + 250dp + 8dp -44dp diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index 7bab1a25..aad23528 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -309,7 +309,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe }) fab.setOnLongClickListener(_ => { Utils.positionToast(Toast.makeText(this, if (state == State.CONNECTED) R.string.stop else R.string.connect, - Toast.LENGTH_SHORT), fab, getWindow, 0, Utils.dpToPx(this, 8)).show() + Toast.LENGTH_SHORT), fab, getWindow, 0, getResources.getDimensionPixelOffset(R.dimen.margin_small)).show() true }) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala index bc5b4fbe..517950e6 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala @@ -55,7 +55,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic private val cardButtonLongClickListener: OnLongClickListener = view => { Utils.positionToast(Toast.makeText(getActivity, view.getContentDescription, Toast.LENGTH_SHORT), view, - getActivity.getWindow, 0, Utils.dpToPx(getActivity, 8)).show() + getActivity.getWindow, 0, getResources.getDimensionPixelOffset(R.dimen.margin_small)).show() true } @@ -129,7 +129,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic val params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) params.gravity = Gravity.CENTER_HORIZONTAL - params.setMargins(0, Utils.dpToPx(getActivity, 8), 0, 0) + params.setMargins(0, getResources.getDimensionPixelOffset(R.dimen.margin_small), 0, 0) adView = new NativeExpressAdView(getActivity) adView.setLayoutParams(params) adView.setAdUnitId("ca-app-pub-9097031975646651/5224027521") diff --git a/mobile/src/main/scala/com/github/shadowsocks/QRCodeDialog.scala b/mobile/src/main/scala/com/github/shadowsocks/QRCodeDialog.scala index 255444dc..66317aa8 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/QRCodeDialog.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/QRCodeDialog.scala @@ -27,7 +27,6 @@ import android.nfc.{NdefMessage, NdefRecord, NfcAdapter} import android.os.Bundle import android.view.{LayoutInflater, View, ViewGroup} import android.widget.{ImageView, LinearLayout} -import com.github.shadowsocks.utils.Utils import net.glxn.qrgen.android.QRCode object QRCodeDialog { @@ -50,8 +49,9 @@ final class QRCodeDialog extends DialogFragment { override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View = { val image = new ImageView(getActivity) image.setLayoutParams(new LinearLayout.LayoutParams(-1, -1)) + val size = getResources.getDimensionPixelSize(R.dimen.qr_code_size) val qrcode = QRCode.from(url) - .withSize(Utils.dpToPx(getActivity, 250), Utils.dpToPx(getActivity, 250)) + .withSize(size, size) .asInstanceOf[QRCode].bitmap() image.setImageBitmap(qrcode) image diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala index 4937adfd..20a9e14c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala @@ -56,8 +56,6 @@ object Utils { new String(Base64.encode(mdg.digest, 0)) } - def dpToPx(context: Context, dp: Int): Int = Math.round(dp * context.getResources.getDisplayMetrics.density) - /* * round or floor depending on whether you are using offsets(floor) or * widths(round) From c7c72c6543547a9e4203055eae5b1447b2db48f7 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Jan 2017 03:49:08 +0800 Subject: [PATCH 21/98] Add missing layout --- .../src/main/res/layout/icon_list_item_2.xml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 mobile/src/main/res/layout/icon_list_item_2.xml diff --git a/mobile/src/main/res/layout/icon_list_item_2.xml b/mobile/src/main/res/layout/icon_list_item_2.xml new file mode 100644 index 00000000..521f7842 --- /dev/null +++ b/mobile/src/main/res/layout/icon_list_item_2.xml @@ -0,0 +1,32 @@ + + + + + + + + + From 7d3f6096b76760242883c7747776e88695a6e75a Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Jan 2017 04:13:59 +0800 Subject: [PATCH 22/98] Add plugin to card Fix #1054. --- mobile/src/main/res/layout/layout_profile.xml | 28 ++++++++++--------- mobile/src/main/res/values/strings.xml | 1 + .../github/shadowsocks/ProfilesFragment.scala | 27 +++++++++++++----- .../shadowsocks/plugin/PluginManager.scala | 5 ++-- 4 files changed, 39 insertions(+), 22 deletions(-) diff --git a/mobile/src/main/res/layout/layout_profile.xml b/mobile/src/main/res/layout/layout_profile.xml index 35cd1447..aacb3bd8 100644 --- a/mobile/src/main/res/layout/layout_profile.xml +++ b/mobile/src/main/res/layout/layout_profile.xml @@ -24,7 +24,7 @@ android:layout_height="wrap_content" android:layout_marginStart="8dp" android:id="@+id/container"> - - - + android:layout_marginTop="4dp"> + - + diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 1320f57c..8e78ddf6 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -142,4 +142,5 @@ Configure… Disabled Unknown plugin %s + Plugin: %s diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala index 517950e6..7c0981ad 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfilesFragment.scala @@ -37,11 +37,12 @@ import android.view._ import android.widget.{LinearLayout, PopupMenu, TextView, Toast} import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.database.Profile +import com.github.shadowsocks.plugin.PluginConfiguration import com.github.shadowsocks.utils._ import com.github.shadowsocks.widget.UndoSnackbarManager import com.google.android.gms.ads.{AdRequest, AdSize, NativeExpressAdView} -import scala.collection.mutable.ArrayBuffer +import scala.collection.mutable.{ArrayBuffer, ListBuffer} import scala.util.Random object ProfilesFragment { @@ -77,8 +78,8 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic var item: Profile = _ - private val title = itemView.findViewById(R.id.title).asInstanceOf[TextView] - private val address = itemView.findViewById(R.id.address).asInstanceOf[TextView] + private val text1 = itemView.findViewById(android.R.id.text1).asInstanceOf[TextView] + private val text2 = itemView.findViewById(android.R.id.text2).asInstanceOf[TextView] private val traffic = itemView.findViewById(R.id.traffic).asInstanceOf[TextView] private val edit = itemView.findViewById(R.id.edit) edit.setOnClickListener(_ => startConfig(item.id)) @@ -111,10 +112,22 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic tx += txTotal rx += rxTotal } - title.setText(item.getName) - address.setText(if (item.nameIsEmpty) "" else item.formattedAddress) - traffic.setText(if (tx <= 0 && rx <= 0) null else getString(R.string.stat_profiles, - TrafficMonitor.formatTraffic(tx), TrafficMonitor.formatTraffic(rx))) + text1.setText(item.getName) + val t2 = new ListBuffer[String] + if (!item.nameIsEmpty) t2 += item.formattedAddress + new PluginConfiguration(item.plugin).selected match { + case "" => + case id => t2 += app.getString(R.string.profile_plugin, id) + } + if (t2.isEmpty) text2.setVisibility(View.GONE) else { + text2.setVisibility(View.VISIBLE) + text2.setText(t2.mkString("\n")) + } + if (tx <= 0 && rx <= 0) traffic.setVisibility(View.GONE) else { + traffic.setVisibility(View.VISIBLE) + traffic.setText(getString(R.string.stat_profiles, + TrafficMonitor.formatTraffic(tx), TrafficMonitor.formatTraffic(rx))) + } if (item.id == app.profileId) { itemView.setSelected(true) diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala index 84178df9..5be8ccfc 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala @@ -11,6 +11,7 @@ import com.github.shadowsocks.utils.{Commandline, IOUtils} import eu.chainfire.libsuperuser.Shell import scala.collection.JavaConversions._ +import scala.collection.mutable.ListBuffer /** * @author Mygod @@ -80,14 +81,14 @@ object PluginManager { IOUtils.deleteRecursively(pluginDir) if (!pluginDir.mkdirs()) throw new FileNotFoundException("Unable to create plugin directory") val pluginDirPath = pluginDir.getAbsolutePath + '/' - var list: List[String] = Nil + val list = new ListBuffer[String] do { val path = cursor.getString(0) val file = new File(pluginDir, path) assert(file.getAbsolutePath.startsWith(pluginDirPath)) autoClose(cr.openInputStream(builder.path(path).build()))(in => autoClose(new FileOutputStream(file))(out => IOUtils.copy(in, out))) - list +:= Commandline.toString(Array("chmod", cursor.getString(1), file.getAbsolutePath)) + list += Commandline.toString(Array("chmod", cursor.getString(1), file.getAbsolutePath)) if (path == id) initialized = true } while (cursor.moveToNext()) if (!initialized) entryNotFound() From 923374a28b9ebb81e9a14e0732117808bcc472f2 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Jan 2017 04:30:59 +0800 Subject: [PATCH 23/98] Add back handler for ACL fragment --- .../scala/com/github/shadowsocks/MainActivity.scala | 9 +++++---- .../com/github/shadowsocks/ToolbarFragment.scala | 2 ++ .../github/shadowsocks/acl/CustomRulesFragment.scala | 12 +++++++++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index aad23528..f4a84f1a 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -406,10 +406,11 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe } override def onBackPressed(): Unit = - if (drawer.isDrawerOpen) drawer.closeDrawer() else if (currentFragment != profilesFragment) { - displayFragment(profilesFragment) - drawer.setSelection(DRAWER_PROFILES) - } else super.onBackPressed() + if (drawer.isDrawerOpen) drawer.closeDrawer() else if (!currentFragment.onBackPressed()) + if (currentFragment != profilesFragment) { + displayFragment(profilesFragment) + drawer.setSelection(DRAWER_PROFILES) + } else super.onBackPressed() override def onStop() { setListeningForBandwidth(false) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ToolbarFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ToolbarFragment.scala index 3afe18f3..e21a6b80 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ToolbarFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ToolbarFragment.scala @@ -36,4 +36,6 @@ class ToolbarFragment extends Fragment { } def onTrafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long): Unit = () + + def onBackPressed(): Boolean = false } diff --git a/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala index 220dc804..5426c3ab 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala @@ -63,9 +63,8 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi } private val selectedItems = new mutable.HashSet[AnyRef] - private def onSelectedItemsUpdated(): Unit = if (selectionItem != null) { - selectionItem.setVisible(selectedItems.nonEmpty) - } + private def onSelectedItemsUpdated(): Unit = + if (selectionItem != null) selectionItem.setVisible(selectedItems.nonEmpty) private final class AclRuleViewHolder(view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener with View.OnLongClickListener { @@ -263,6 +262,13 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi }).attachToRecyclerView(list) } + override def onBackPressed(): Boolean = if (selectedItems.nonEmpty) { + selectedItems.clear() + onSelectedItemsUpdated() + adapter.notifyDataSetChanged() + true + } else super.onBackPressed() + override def onSaveInstanceState(outState: Bundle): Unit = { super.onSaveInstanceState(outState) outState.putStringArray(SELECTED_SUBNETS, selectedItems.filter(_.isInstanceOf[Subnet]).map(_.toString).toArray) From 4f3c7dd8fd85ea18853ea2b7ce44bf07cb6aeb3c Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Jan 2017 04:37:22 +0800 Subject: [PATCH 24/98] Use proper JSON support for config files --- .../com/github/shadowsocks/BaseService.scala | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala index ccc13390..7b1ed966 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -24,7 +24,7 @@ import java.io.IOException import java.net.InetAddress import java.util import java.util.concurrent.TimeUnit -import java.util.{Locale, Timer, TimerTask} +import java.util.{Timer, TimerTask} import android.app.Service import android.content.{BroadcastReceiver, Context, Intent, IntentFilter} @@ -39,6 +39,7 @@ import com.github.shadowsocks.database.Profile import com.github.shadowsocks.plugin.{PluginConfiguration, PluginManager, PluginOptions} import com.github.shadowsocks.utils._ import okhttp3.{Dns, FormBody, OkHttpClient, Request} +import org.json.JSONObject import scala.collection.mutable import scala.util.Random @@ -313,20 +314,18 @@ trait BaseService extends Service { } protected def buildShadowsocksConfig(file: String, localPortOffset: Int = 0): String = { - val builder = new StringBuilder( - """{"server": "%s", "server_port": %d, "local_port": %d, "password": "%s", "method": "%s"""" - .formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort + localPortOffset, - profile.password, profile.method)) - if (profile.auth) builder.append(""", "auth": true""") + val config = new JSONObject() + config.put("server", profile.host) + config.put("server_port", profile.remotePort) + config.put("local_port", profile.localPort + localPortOffset) + config.put("password", profile.password) + config.put("method", profile.method) + if (profile.auth) config.put("auth", true) if (pluginPath != null) { - builder.append(""", "plugin": """") - builder.append(pluginPath) - builder.append("""", "plugin_opts": """") - builder.append(plugin.toString) - builder.append('"') + config.put("plugin", pluginPath) + config.put("plugin_opts", plugin.toString) } - builder.append('}') - IOUtils.writeString(file, builder.toString) + IOUtils.writeString(file, config.toString) file } } From 7e6cc4e84662285b42c6efa13a4bf9a2c9ec1d7a Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Jan 2017 06:10:50 +0800 Subject: [PATCH 25/98] Refine EditTextPreference layout --- .../res/layout/preference_dialog_edittext.xml | 40 +++++++++++++++++++ .../mygod/preference/EditTextPreference.scala | 12 ++++++ .../EditTextPreferenceDialogFragment.scala | 16 ++++---- 3 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 plugin/src/main/res/layout/preference_dialog_edittext.xml diff --git a/plugin/src/main/res/layout/preference_dialog_edittext.xml b/plugin/src/main/res/layout/preference_dialog_edittext.xml new file mode 100644 index 00000000..34c8f873 --- /dev/null +++ b/plugin/src/main/res/layout/preference_dialog_edittext.xml @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/plugin/src/main/scala/be/mygod/preference/EditTextPreference.scala b/plugin/src/main/scala/be/mygod/preference/EditTextPreference.scala index 88da9663..e23d261a 100644 --- a/plugin/src/main/scala/be/mygod/preference/EditTextPreference.scala +++ b/plugin/src/main/scala/be/mygod/preference/EditTextPreference.scala @@ -25,6 +25,9 @@ import android.support.v7.preference.{EditTextPreference => Parent} import android.support.v7.widget.AppCompatEditText import android.text.InputType import android.util.AttributeSet +import android.view.ViewGroup +import android.widget.FrameLayout +import com.github.shadowsocks.plugin.R /** * Fixed EditTextPreference + SummaryPreference with password support! @@ -35,6 +38,15 @@ class EditTextPreference(context: Context, attrs: AttributeSet = null) extends P val editText = new AppCompatEditText(context, attrs) editText.setId(android.R.id.edit) + { + val arr = context.obtainStyledAttributes(Array(R.attr.dialogPreferredPadding)) + val margin = arr.getDimensionPixelOffset(0, 0) + arr.recycle() + val params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + params.setMargins(margin, 0, margin, 0) + editText.setLayoutParams(params) + } + override def createDialog() = new EditTextPreferenceDialogFragment() override protected def getSummaryValue: String = { diff --git a/plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala b/plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala index 718b9287..dadf7893 100644 --- a/plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala +++ b/plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala @@ -20,7 +20,6 @@ package be.mygod.preference -import android.content.Context import android.support.v14.preference.PreferenceDialogFragment import android.support.v7.widget.AppCompatEditText import android.view.{View, ViewGroup} @@ -29,17 +28,20 @@ class EditTextPreferenceDialogFragment extends PreferenceDialogFragment { private lazy val preference = getPreference.asInstanceOf[EditTextPreference] protected lazy val editText: AppCompatEditText = preference.editText - override protected def onCreateDialogView(context: Context): AppCompatEditText = { - val parent = editText.getParent.asInstanceOf[ViewGroup] - if (parent != null) parent.removeView(editText) - editText - } - override protected def onBindDialogView(view: View) { super.onBindDialogView(view) editText.setText(preference.getText) val text = editText.getText if (text != null) editText.setSelection(0, text.length) + val oldParent = editText.getParent.asInstanceOf[ViewGroup] + if (oldParent eq view) return + if (oldParent != null) oldParent.removeView(editText) + val oldEdit = view.findViewById(android.R.id.edit) + if (oldEdit == null) return + val container = oldEdit.getParent.asInstanceOf[ViewGroup] + if (container == null) return + container.removeView(oldEdit) + container.addView(editText, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) } override protected def needInputMethod = true From 0e85514c76e7b3d417125eef1698d457417da213 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Jan 2017 06:13:33 +0800 Subject: [PATCH 26/98] Connection test should connect to China LAN when using Chinalist Fix #1080. --- .../main/scala/com/github/shadowsocks/MainActivity.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index f4a84f1a..0ec162b4 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -43,7 +43,7 @@ import android.view.View import android.widget.{TextView, Toast} import com.github.jorgecastilloprz.FABProgressCircle import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.acl.CustomRulesFragment +import com.github.shadowsocks.acl.{Acl, CustomRulesFragment} import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.utils.CloseUtils.autoDisconnect import com.github.shadowsocks.utils._ @@ -267,7 +267,10 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe val id = testCount // it would change by other code Utils.ThrowableFuture { // Based on: https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/connectivity/NetworkMonitor.java#640 - autoDisconnect(new URL("https", "www.google.com", "/generate_204").openConnection() + autoDisconnect(new URL("https", app.currentProfile.get.route match { + case Acl.CHINALIST => "www.qualcomm.cn" + case _ => "www.google.com" + }, "/generate_204").openConnection() .asInstanceOf[HttpURLConnection]) { conn => conn.setConnectTimeout(5 * 1000) conn.setReadTimeout(5 * 1000) From d734e9fd4e9fc7e1d8abb2868e3024f2c4b11ae8 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Fri, 20 Jan 2017 09:57:26 +0800 Subject: [PATCH 27/98] Fix socket protect in simple-obfs --- .../scala/com/github/shadowsocks/BaseService.scala | 10 ++++++++-- .../com/github/shadowsocks/ShadowsocksVpnService.scala | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala index 7b1ed966..941e27d4 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -313,7 +313,7 @@ trait BaseService extends Service { } } - protected def buildShadowsocksConfig(file: String, localPortOffset: Int = 0): String = { + protected def buildShadowsocksConfig(file: String, localPortOffset: Int = 0, vpn: Boolean = false): String = { val config = new JSONObject() config.put("server", profile.host) config.put("server_port", profile.remotePort) @@ -323,7 +323,13 @@ trait BaseService extends Service { if (profile.auth) config.put("auth", true) if (pluginPath != null) { config.put("plugin", pluginPath) - config.put("plugin_opts", plugin.toString) + val plugin_opts = if (vpn) { + "V;P=" + getApplicationInfo.dataDir + ";" + plugin.toString + } else { + plugin.toString + } + Log.d("shadowsocks", plugin_opts) + config.put("plugin_opts", plugin_opts) } IOUtils.writeString(file, config.toString) file diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index a9b8550a..194c8b25 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -167,7 +167,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { , "-b", "127.0.0.1" , "-t", "600" , "-P", getApplicationInfo.dataDir - , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-local-vpn.conf")) + , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-local-vpn.conf", 0, true)) if (profile.udpdns) cmd += "-u" @@ -190,7 +190,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { , "-b", "127.0.0.1" , "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53" , "-P", getApplicationInfo.dataDir - , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf", 53)) + , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf", 53, true)) if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) sstunnelProcess = new GuardedProcess(cmd).start() } From 1041dc370a598e90202f928ade9709ecbfd6047b Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Jan 2017 15:35:57 +0800 Subject: [PATCH 28/98] Move non-options to command line options --- .../com/github/shadowsocks/BaseService.scala | 34 +++++++++---------- .../shadowsocks/ShadowsocksVpnService.scala | 7 ++-- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala index 941e27d4..709ede02 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -42,6 +42,8 @@ import okhttp3.{Dns, FormBody, OkHttpClient, Request} import org.json.JSONObject import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer +import scala.collection.JavaConversions._ import scala.util.Random trait BaseService extends Service { @@ -49,7 +51,7 @@ trait BaseService extends Service { @volatile private var state = State.STOPPED @volatile protected var profile: Profile = _ @volatile private var plugin: PluginOptions = _ - @volatile private var pluginPath: String = _ + @volatile protected var pluginPath: String = _ case class NameNotResolvedException() extends IOException case class NullConnectionException() extends NullPointerException @@ -313,24 +315,22 @@ trait BaseService extends Service { } } - protected def buildShadowsocksConfig(file: String, localPortOffset: Int = 0, vpn: Boolean = false): String = { + protected def buildPluginCommandLine(): ArrayBuffer[String] = { + val result = ArrayBuffer(pluginPath) + if (TcpFastOpen.sendEnabled) result += "--fast-open" + result + } + protected def buildShadowsocksConfig(file: String, localPortOffset: Int = 0): String = { val config = new JSONObject() - config.put("server", profile.host) - config.put("server_port", profile.remotePort) - config.put("local_port", profile.localPort + localPortOffset) - config.put("password", profile.password) - config.put("method", profile.method) + .put("server", profile.host) + .put("server_port", profile.remotePort) + .put("local_port", profile.localPort + localPortOffset) + .put("password", profile.password) + .put("method", profile.method) if (profile.auth) config.put("auth", true) - if (pluginPath != null) { - config.put("plugin", pluginPath) - val plugin_opts = if (vpn) { - "V;P=" + getApplicationInfo.dataDir + ";" + plugin.toString - } else { - plugin.toString - } - Log.d("shadowsocks", plugin_opts) - config.put("plugin_opts", plugin_opts) - } + if (pluginPath != null) config + .put("plugin", Commandline.toString(buildPluginCommandLine())) + .put("plugin_opts", plugin.toString) IOUtils.writeString(file, config.toString) file } diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 194c8b25..0747715a 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -162,12 +162,15 @@ class ShadowsocksVpnService extends VpnService with BaseService { } } + override protected def buildPluginCommandLine(): ArrayBuffer[String] = + super.buildPluginCommandLine() ++= Seq("-V", "-P", getApplicationInfo.dataDir) + def startShadowsocksDaemon() { val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", "-V" , "-b", "127.0.0.1" , "-t", "600" , "-P", getApplicationInfo.dataDir - , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-local-vpn.conf", 0, true)) + , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-local-vpn.conf", 0)) if (profile.udpdns) cmd += "-u" @@ -190,7 +193,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { , "-b", "127.0.0.1" , "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53" , "-P", getApplicationInfo.dataDir - , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf", 53, true)) + , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf", 53)) if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) sstunnelProcess = new GuardedProcess(cmd).start() } From 7491a667a5ad3cdd587dad7c2dc91f4ad93ed72d Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Jan 2017 17:27:42 +0800 Subject: [PATCH 29/98] Fix ss-tunnel local port --- .../scala/com/github/shadowsocks/ShadowsocksNatService.scala | 2 +- .../scala/com/github/shadowsocks/ShadowsocksVpnService.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index fa29c7dc..754c7a28 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -77,7 +77,7 @@ class ShadowsocksNatService extends BaseService { , "-l" , (profile.localPort + 53).toString , "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53" , "-P", getApplicationInfo.dataDir - , "-c" , buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf", 53)) + , "-c" , buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf", 63)) if (profile.udpdns) cmd.append("-u") diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 0747715a..bf569cbe 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -193,7 +193,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { , "-b", "127.0.0.1" , "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53" , "-P", getApplicationInfo.dataDir - , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf", 53)) + , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf", 63)) if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) sstunnelProcess = new GuardedProcess(cmd).start() } From 2e3baa727d6ecdba0f50ad755b9ee601c0e006b9 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Jan 2017 17:33:10 +0800 Subject: [PATCH 30/98] Move config files to files directory It looks nicer. Other changes btw: * Removes duplicated logcats from GuardedProcess; * local_port is moved to command line options since that's not sensitive; * pdnsd uses cache dir; * Refinements to Utils. --- mobile/src/main/jni/shadowsocks-libev | 2 +- .../com/github/shadowsocks/BaseService.scala | 8 +-- .../github/shadowsocks/GuardedProcess.scala | 12 ++-- .../shadowsocks/ShadowsocksApplication.scala | 6 +- .../shadowsocks/ShadowsocksNatService.scala | 65 +++++++------------ .../shadowsocks/ShadowsocksVpnService.scala | 65 ++++++++----------- .../shadowsocks/ShadowsocksVpnThread.scala | 10 +-- .../com/github/shadowsocks/acl/Acl.scala | 8 +-- .../github/shadowsocks/acl/AclSyncJob.scala | 6 +- .../github/shadowsocks/utils/IOUtils.scala | 2 + .../shadowsocks/utils/TcpFastOpen.scala | 3 +- .../utils/TrafficMonitorThread.scala | 6 +- .../com/github/shadowsocks/utils/Utils.scala | 42 +++--------- 13 files changed, 93 insertions(+), 142 deletions(-) diff --git a/mobile/src/main/jni/shadowsocks-libev b/mobile/src/main/jni/shadowsocks-libev index 19e222ba..4c3c0ce9 160000 --- a/mobile/src/main/jni/shadowsocks-libev +++ b/mobile/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit 19e222bad8416f20b7694bff75f2cee72a70701e +Subproject commit 4c3c0ce9c33825949e289aa36f217d2de5af48c6 diff --git a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala index 709ede02..8dfca684 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -20,7 +20,7 @@ package com.github.shadowsocks -import java.io.IOException +import java.io.{File, IOException} import java.net.InetAddress import java.util import java.util.concurrent.TimeUnit @@ -63,7 +63,6 @@ trait BaseService extends Service { private final val bandwidthListeners = new mutable.HashSet[IBinder]() // the binder is the real identifier lazy val handler = new Handler(getMainLooper) lazy val restartHanlder = new Handler(getMainLooper) - lazy val protectPath: String = getApplicationInfo.dataDir + "/protect_path" private val closeReceiver: BroadcastReceiver = (context: Context, _: Intent) => { Toast.makeText(context, R.string.stopping, Toast.LENGTH_SHORT).show() @@ -320,18 +319,17 @@ trait BaseService extends Service { if (TcpFastOpen.sendEnabled) result += "--fast-open" result } - protected def buildShadowsocksConfig(file: String, localPortOffset: Int = 0): String = { + protected final def buildShadowsocksConfig(file: String): String = { val config = new JSONObject() .put("server", profile.host) .put("server_port", profile.remotePort) - .put("local_port", profile.localPort + localPortOffset) .put("password", profile.password) .put("method", profile.method) if (profile.auth) config.put("auth", true) if (pluginPath != null) config .put("plugin", Commandline.toString(buildPluginCommandLine())) .put("plugin_opts", plugin.toString) - IOUtils.writeString(file, config.toString) + IOUtils.writeString(new File(getFilesDir, file), config.toString) file } } diff --git a/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala b/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala index ca117acb..b995c8a7 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala @@ -27,6 +27,7 @@ import java.util.concurrent.Semaphore import android.util.Log import com.github.shadowsocks.utils.CloseUtils._ import com.github.shadowsocks.utils.Commandline +import com.github.shadowsocks.ShadowsocksApplication.app import scala.collection.JavaConversions._ import scala.collection.immutable.Stream @@ -41,7 +42,7 @@ class StreamLogger(is: InputStream, tag: String) extends Thread { /** * @author ayanamist@gmail.com */ -class GuardedProcess(cmd: Seq[String]) { +class GuardedProcess(cmd: String*) { private val TAG = classOf[GuardedProcess].getSimpleName @volatile private var guardThread: Thread = _ @@ -58,10 +59,13 @@ class GuardedProcess(cmd: Seq[String]) { try { var callback: () => Unit = null while (!isDestroyed) { - Log.i(TAG, "start process: " + Commandline.toString(cmd)) + if (BuildConfig.DEBUG) Log.d(TAG, "start process: " + Commandline.toString(cmd)) val startTime = currentTimeMillis - process = new ProcessBuilder(cmd).redirectErrorStream(true).start + process = new ProcessBuilder(cmd) + .redirectErrorStream(true) + .directory(app.getFilesDir) + .start() val is = process.getInputStream new StreamLogger(is, TAG).start() @@ -84,7 +88,7 @@ class GuardedProcess(cmd: Seq[String]) { } } catch { case _: InterruptedException => - Log.i(TAG, "thread interrupt, destroy process: " + Commandline.toString(cmd)) + if (BuildConfig.DEBUG) Log.d(TAG, "thread interrupt, destroy process: " + Commandline.toString(cmd)) process.destroy() case e: IOException => ioException = e } finally semaphore.release() diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index f0d795cc..5d79a7c6 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -20,7 +20,7 @@ package com.github.shadowsocks -import java.io.{FileOutputStream, IOException} +import java.io.{File, FileOutputStream, IOException} import java.util import java.util.Locale import java.util.concurrent.TimeUnit @@ -184,7 +184,7 @@ class ShadowsocksApplication extends Application { for (task <- Executable.EXECUTABLES) { cmd.append("killall lib%s.so".formatLocal(Locale.ENGLISH, task)) cmd.append("rm -f %1$s/%2$s-nat.conf %1$s/%2$s-vpn.conf" - .formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task)) + .formatLocal(Locale.ENGLISH, getFilesDir.getAbsolutePath, task)) } if (app.isNatEnabled) { cmd.append("iptables -t nat -F OUTPUT") @@ -204,7 +204,7 @@ class ShadowsocksApplication extends Application { app.track(e) } if (files != null) for (file <- files) autoClose(assetManager.open("acl/" + file))(in => - autoClose(new FileOutputStream(getApplicationInfo.dataDir + '/' + file))(out => + autoClose(new FileOutputStream(new File(getFilesDir, file)))(out => IOUtils.copy(in, out))) editor.putInt(Key.currentVersionCode, BuildConfig.VERSION_CODE).apply() } diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index 754c7a28..07279289 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -53,76 +53,59 @@ class ShadowsocksNatService extends BaseService { var su: Shell.Interactive = _ def startShadowsocksDaemon() { - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so" - , "-b" , "127.0.0.1" - , "-t" , "600" - , "-P", getApplicationInfo.dataDir - , "-c" , buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-local-nat.conf")) + val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", + "-b", "127.0.0.1", + "-l", profile.localPort.toString, + "-t", "600", + "-c", buildShadowsocksConfig("ss-local-nat.conf")) if (TcpFastOpen.sendEnabled) cmd += "--fast-open" if (profile.route != Acl.ALL) { cmd += "--acl" - cmd += Acl.getPath(profile.route) + cmd += Acl.getFile(profile.route).getAbsolutePath } - if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) - sslocalProcess = new GuardedProcess(cmd).start() + sslocalProcess = new GuardedProcess(cmd: _*).start() } def startDNSTunnel() { - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so" - , "-t" , "10" - , "-b" , "127.0.0.1" - , "-l" , (profile.localPort + 53).toString - , "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53" - , "-P", getApplicationInfo.dataDir - , "-c" , buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf", 63)) + val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so", + "-t", "10", + "-b", "127.0.0.1", + "-l", (profile.localPort + 63).toString, + "-L", if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53", + "-c", buildShadowsocksConfig("ss-tunnel-nat.conf")) if (profile.udpdns) cmd.append("-u") - if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) - - sstunnelProcess = new GuardedProcess(cmd).start() + sstunnelProcess = new GuardedProcess(cmd: _*).start() } def startDnsDaemon() { - val reject = if (profile.ipv6) "224.0.0.0/3" else "224.0.0.0/3, ::/0" - - val conf = profile.route match { + IOUtils.writeString(new File(getFilesDir, "pdnsd-nat.conf"), profile.route match { case Acl.BYPASS_CHN | Acl.BYPASS_LAN_CHN | Acl.GFWLIST | Acl.CUSTOM_RULES => - ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "", getApplicationInfo.dataDir, + ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "", getCacheDir.getAbsolutePath, "127.0.0.1", profile.localPort + 53, "114.114.114.114, 223.5.5.5, 1.2.4.8", getBlackList, reject, profile.localPort + 63, reject) case Acl.CHINALIST => - ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "", getApplicationInfo.dataDir, + ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "", getCacheDir.getAbsolutePath, "127.0.0.1", profile.localPort + 53, "8.8.8.8, 8.8.4.4, 208.67.222.222", "", reject, profile.localPort + 63, reject) case _ => - ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, "", getApplicationInfo.dataDir, + ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, "", getCacheDir.getAbsolutePath, "127.0.0.1", profile.localPort + 53, profile.localPort + 63, reject) - } - - Utils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-nat.conf"))(p => { - p.println(conf) }) - val cmd = Array(getApplicationInfo.nativeLibraryDir + "/libpdnsd.so", "-c", getApplicationInfo.dataDir + "/pdnsd-nat.conf") - - if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) - - pdnsdProcess = new GuardedProcess(cmd).start() + pdnsdProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/libpdnsd.so", "-c", "pdnsd-nat.conf") + .start() } def startRedsocksDaemon() { - val conf = ConfigUtils.REDSOCKS.formatLocal(Locale.ENGLISH, profile.localPort) - val cmd = Array(getApplicationInfo.nativeLibraryDir + "/libredsocks.so", "-c", getApplicationInfo.dataDir + "/redsocks-nat.conf") - Utils.printToFile(new File(getApplicationInfo.dataDir + "/redsocks-nat.conf"))(p => { - p.println(conf) - }) - - if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) - redsocksProcess = new GuardedProcess(cmd).start() + IOUtils.writeString(new File(getFilesDir, "redsocks-nat.conf"), + ConfigUtils.REDSOCKS.formatLocal(Locale.ENGLISH, profile.localPort)) + redsocksProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/libredsocks.so", + "-c", "redsocks-nat.conf").start() } /** Called when the activity is first created. */ diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index bf569cbe..e00458fc 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -34,7 +34,6 @@ import com.github.shadowsocks.acl.{Acl, AclSyncJob} import com.github.shadowsocks.database.Profile import com.github.shadowsocks.utils._ -import scala.collection.JavaConversions._ import scala.collection.mutable.ArrayBuffer class ShadowsocksVpnService extends VpnService with BaseService { @@ -163,65 +162,55 @@ class ShadowsocksVpnService extends VpnService with BaseService { } override protected def buildPluginCommandLine(): ArrayBuffer[String] = - super.buildPluginCommandLine() ++= Seq("-V", "-P", getApplicationInfo.dataDir) + super.buildPluginCommandLine() ++= Seq("-V", "-P", getFilesDir.getAbsolutePath) def startShadowsocksDaemon() { - val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", "-V" - , "-b", "127.0.0.1" - , "-t", "600" - , "-P", getApplicationInfo.dataDir - , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-local-vpn.conf", 0)) + val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", + "-V", + "-b", "127.0.0.1", + "-l", profile.localPort.toString, + "-t", "600", + "-c", buildShadowsocksConfig("ss-local-vpn.conf")) if (profile.udpdns) cmd += "-u" if (profile.route != Acl.ALL) { cmd += "--acl" - cmd += Acl.getPath(profile.route) + cmd += Acl.getFile(profile.route).getAbsolutePath } if (TcpFastOpen.sendEnabled) cmd += "--fast-open" - if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) - - sslocalProcess = new GuardedProcess(cmd).start() + sslocalProcess = new GuardedProcess(cmd: _*).start() } - def startDnsTunnel() { - val cmd = Array[String](getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so" - , "-V" - , "-t", "10" - , "-b", "127.0.0.1" - , "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53" - , "-P", getApplicationInfo.dataDir - , "-c", buildShadowsocksConfig(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf", 63)) - if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) - sstunnelProcess = new GuardedProcess(cmd).start() - } + def startDnsTunnel(): Unit = + sstunnelProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so", + "-V", + "-t", "10", + "-b", "127.0.0.1", + "-l", (profile.localPort + 63).toString, + "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53", + "-c", buildShadowsocksConfig("ss-tunnel-vpn.conf")).start() def startDnsDaemon() { val reject = if (profile.ipv6) "224.0.0.0/3" else "224.0.0.0/3, ::/0" - val protect = "protect = \"" + protectPath +"\";" - val conf = profile.route match { + IOUtils.writeString(new File(getFilesDir, "pdnsd-vpn.conf"), profile.route match { case Acl.BYPASS_CHN | Acl.BYPASS_LAN_CHN | Acl.GFWLIST | Acl.CUSTOM_RULES => - ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, protect, getApplicationInfo.dataDir, + ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "protect = \"protect_path\";", getCacheDir.getAbsolutePath, "0.0.0.0", profile.localPort + 53, "114.114.114.114, 223.5.5.5, 1.2.4.8", getBlackList, reject, profile.localPort + 63, reject) case Acl.CHINALIST => - ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, protect, getApplicationInfo.dataDir, + ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "protect = \"protect_path\";", getCacheDir.getAbsolutePath, "0.0.0.0", profile.localPort + 53, "8.8.8.8, 8.8.4.4, 208.67.222.222", "", reject, profile.localPort + 63, reject) case _ => - ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, protect, getApplicationInfo.dataDir, + ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, "protect = \"protect_path\";", getCacheDir.getAbsolutePath, "0.0.0.0", profile.localPort + 53, profile.localPort + 63, reject) - } - Utils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-vpn.conf"))(p => { - p.println(conf) }) - val cmd = Array(getApplicationInfo.nativeLibraryDir + "/libpdnsd.so", "-c", getApplicationInfo.dataDir + "/pdnsd-vpn.conf") + val cmd = Array(getApplicationInfo.nativeLibraryDir + "/libpdnsd.so", "-c", "pdnsd-vpn.conf") - if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) - - pdnsdProcess = new GuardedProcess(cmd).start() + pdnsdProcess = new GuardedProcess(cmd: _*).start() } @SuppressLint(Array("NewApi")) @@ -281,7 +270,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { "--socks-server-addr", "127.0.0.1:" + profile.localPort, "--tunfd", fd.toString, "--tunmtu", VPN_MTU.toString, - "--sock-path", getApplicationInfo.dataDir + "/sock_path", + "--sock-path", "sock_path", "--loglevel", "3") if (profile.ipv6) @@ -293,9 +282,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), profile.localPort + 53)) - if (BuildConfig.DEBUG) Log.d(TAG, Commandline.toString(cmd)) - - tun2socksProcess = new GuardedProcess(cmd).start(() => sendFd(fd)) + tun2socksProcess = new GuardedProcess(cmd: _*).start(() => sendFd(fd)) fd } @@ -305,7 +292,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { var tries = 1 while (tries < 5) { Thread.sleep(1000 * tries) - if (System.sendfd(fd, getApplicationInfo.dataDir + "/sock_path") != -1) { + if (System.sendfd(fd, new File(getFilesDir, "sock_path").getAbsolutePath) != -1) { return true } tries += 1 diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala index 63d7d25d..d34a60c0 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala @@ -32,11 +32,11 @@ object ShadowsocksVpnThread { val getInt: Method = classOf[FileDescriptor].getDeclaredMethod("getInt$") } -class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread { +class ShadowsocksVpnThread(service: ShadowsocksVpnService) extends Thread { import ShadowsocksVpnThread._ val TAG = "ShadowsocksVpnService" - lazy val PATH: String = vpnService.getApplicationInfo.dataDir + "/protect_path" + val protect = new File(service.getFilesDir, "protect_path") @volatile var isRunning: Boolean = true @volatile var serverSocket: LocalServerSocket = _ @@ -59,11 +59,11 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread { override def run() { - new File(PATH).delete() + protect.delete() try { val localSocket = new LocalSocket - localSocket.bind(new LocalSocketAddress(PATH, LocalSocketAddress.Namespace.FILESYSTEM)) + localSocket.bind(new LocalSocketAddress(protect.getAbsolutePath, LocalSocketAddress.Namespace.FILESYSTEM)) serverSocket = new LocalServerSocket(localSocket.getFileDescriptor) } catch { case e: IOException => @@ -89,7 +89,7 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread { if (fds.nonEmpty) { val fd = getInt.invoke(fds(0)).asInstanceOf[Int] - val ret = vpnService.protect(fd) + val ret = service.protect(fd) // Trick to close file decriptor System.jniclose(fd) diff --git a/mobile/src/main/scala/com/github/shadowsocks/acl/Acl.scala b/mobile/src/main/scala/com/github/shadowsocks/acl/Acl.scala index 715d11cb..676f09e4 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/acl/Acl.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/acl/Acl.scala @@ -1,6 +1,6 @@ package com.github.shadowsocks.acl -import java.io.FileNotFoundException +import java.io.{File, FileNotFoundException} import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.utils.IOUtils @@ -83,7 +83,7 @@ class Acl { this.subnets ++= (if (bypass) proxySubnets else bypassSubnets) this } - final def fromId(id: String): Acl = fromSource(Source.fromFile(Acl.getPath(id))) + final def fromId(id: String): Acl = fromSource(Source.fromFile(Acl.getFile(id))) override def toString: String = { val result = new StringBuilder() @@ -128,7 +128,7 @@ object Acl { final val CHINALIST = "china-list" final val CUSTOM_RULES = "custom-rules" - def getPath(id: String): String = app.getApplicationInfo.dataDir + '/' + id + ".acl" + def getFile(id: String) = new File(app.getFilesDir, id + ".acl") def customRules: Acl = { val acl = new Acl() try acl.fromId(CUSTOM_RULES) catch { @@ -138,5 +138,5 @@ object Acl { acl.bypassHostnames.clear() // everything is bypassed acl } - def save(id: String, acl: Acl): Unit = IOUtils.writeString(getPath(id), acl.toString) + def save(id: String, acl: Acl): Unit = IOUtils.writeString(getFile(id), acl.toString) } diff --git a/mobile/src/main/scala/com/github/shadowsocks/acl/AclSyncJob.scala b/mobile/src/main/scala/com/github/shadowsocks/acl/AclSyncJob.scala index c5082f30..450ec903 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/acl/AclSyncJob.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/acl/AclSyncJob.scala @@ -25,7 +25,6 @@ import java.util.concurrent.TimeUnit import com.evernote.android.job.Job.{Params, Result} import com.evernote.android.job.{Job, JobRequest} -import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.utils.IOUtils import scala.io.Source @@ -47,11 +46,10 @@ object AclSyncJob { class AclSyncJob(route: String) extends Job { override def onRunJob(params: Params): Result = { - val filename = route + ".acl" try { //noinspection JavaAccessorMethodCalledAsEmptyParen - IOUtils.writeString(app.getApplicationInfo.dataDir + '/' + filename, - Source.fromURL("https://shadowsocks.org/acl/android/v1/" + filename).mkString) + IOUtils.writeString(Acl.getFile(route), + Source.fromURL("https://shadowsocks.org/acl/android/v1/" + route + ".acl").mkString) Result.SUCCESS } catch { case e: IOException => diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala index 3c4b4be1..993a4cf6 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala @@ -37,6 +37,8 @@ object IOUtils { } } + def writeString(file: File, content: String): Unit = + autoClose(new FileWriter(file))(writer => writer.write(content)) def writeString(file: String, content: String): Unit = autoClose(new FileWriter(file))(writer => writer.write(content)) diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/TcpFastOpen.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/TcpFastOpen.scala index ffb7606c..80108f26 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/TcpFastOpen.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/TcpFastOpen.scala @@ -25,6 +25,7 @@ import java.io.File import eu.chainfire.libsuperuser.Shell import scala.collection.JavaConverters._ +import scala.io.Source /** * @author Mygod @@ -45,7 +46,7 @@ object TcpFastOpen { def sendEnabled: Boolean = { val file = new File("/proc/sys/net/ipv4/tcp_fastopen") - file.canRead && (Utils.readAllLines(file).toInt & 1) > 0 + file.canRead && (Source.fromFile(file).mkString.toInt & 1) > 0 } def enabled(value: Boolean): String = if (sendEnabled != value) { diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala index 6efa39cf..0edb656f 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala @@ -32,7 +32,7 @@ import com.github.shadowsocks.ShadowsocksApplication.app class TrafficMonitorThread(context: Context) extends Thread { val TAG = "TrafficMonitorThread" - lazy val PATH: String = context.getApplicationInfo.dataDir + "/stat_path" + val stat = new File(context.getFilesDir, "/stat_path") @volatile var serverSocket: LocalServerSocket = _ @volatile var isRunning: Boolean = true @@ -55,11 +55,11 @@ class TrafficMonitorThread(context: Context) extends Thread { override def run() { - new File(PATH).delete() + stat.delete() try { val localSocket = new LocalSocket - localSocket.bind(new LocalSocketAddress(PATH, LocalSocketAddress.Namespace.FILESYSTEM)) + localSocket.bind(new LocalSocketAddress(stat.getAbsolutePath, LocalSocketAddress.Namespace.FILESYSTEM)) serverSocket = new LocalServerSocket(localSocket.getFileDescriptor) } catch { case e: IOException => diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala index 20a9e14c..03e55b85 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala @@ -20,10 +20,8 @@ package com.github.shadowsocks.utils -import java.io.File import java.net._ import java.security.MessageDigest -import java.util.Scanner import android.content.pm.PackageManager import android.content.{Context, Intent} @@ -78,22 +76,6 @@ object Utils { toast } - def readAllLines(f: File): String = { - val scanner = new Scanner(f) - try { - scanner.useDelimiter("\\Z") - scanner.next() - } finally scanner.close() - } - def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) { - val p = new java.io.PrintWriter(f) - try { - op(p) - } finally { - p.close() - } - } - def resolve(host: String, addrType: Int): Option[String] = { try { val lookup = new Lookup(host, addrType) @@ -131,20 +113,16 @@ object Utils { /** * If there exists a valid IPv6 interface */ - def isIPv6Support: Boolean = { - try { - for (intf <- enumerationAsScalaIterator(NetworkInterface.getNetworkInterfaces)) - for (addr <- enumerationAsScalaIterator(intf.getInetAddresses)) - if (addr.isInstanceOf[Inet6Address] && !addr.isLoopbackAddress && !addr.isLinkLocalAddress) { - if (BuildConfig.DEBUG) Log.d(TAG, "IPv6 address detected") - return true - } - } catch { - case ex: Exception => - Log.e(TAG, "Failed to get interfaces' addresses.", ex) - app.track(ex) - } - false + def isIPv6Support: Boolean = try { + val result = NetworkInterface.getNetworkInterfaces.flatMap(_.getInetAddresses) + .count(addr => addr.isInstanceOf[Inet6Address] && !addr.isLoopbackAddress && !addr.isLinkLocalAddress) > 0 + if (result && BuildConfig.DEBUG) Log.d(TAG, "IPv6 address detected") + result + } catch { + case ex: Exception => + Log.e(TAG, "Failed to get interfaces' addresses.", ex) + app.track(ex) + false } def startSsService(context: Context) { From 56e4f153ed2c316c53deeece825d74943f76b6a7 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Jan 2017 17:46:52 +0800 Subject: [PATCH 31/98] Update backup helper --- .../scala/com/github/shadowsocks/ShadowsocksBackupAgent.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksBackupAgent.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksBackupAgent.scala index 8c26810a..37e8657a 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksBackupAgent.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksBackupAgent.scala @@ -40,6 +40,6 @@ class ShadowsocksBackupAgent extends BackupAgentHelper { val helper = new SharedPreferencesBackupHelper(this, PREFS_DISPLAY) addHelper(MY_PREFS_BACKUP_KEY, helper) addHelper(DATABASE, new FileBackupHelper(this, "../databases/" + DBHelper.PROFILE, - "../" + Acl.CUSTOM_RULES + ".acl")) + Acl.CUSTOM_RULES + ".acl")) } } From 2181e664ef411743a043184aafb69817d5d9d3a9 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Sat, 21 Jan 2017 16:22:55 +0800 Subject: [PATCH 32/98] Add gvt2 --- mobile/src/main/assets/acl/gfwlist.acl | 1 + mobile/src/main/jni/shadowsocks-libev | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/assets/acl/gfwlist.acl b/mobile/src/main/assets/acl/gfwlist.acl index b2fe61ec..d732ae4f 100644 --- a/mobile/src/main/assets/acl/gfwlist.acl +++ b/mobile/src/main/assets/acl/gfwlist.acl @@ -363,6 +363,7 @@ (^|\.)googlezip\.net$ (^|\.)gvt0\.com$ (^|\.)gvt1\.com$ +(^|\.)gvt2\.com$ (^|\.)gvt3\.com$ (^|\.)html5rocks\.com$ (^|\.)iam\.soy$ diff --git a/mobile/src/main/jni/shadowsocks-libev b/mobile/src/main/jni/shadowsocks-libev index 4c3c0ce9..19e222ba 160000 --- a/mobile/src/main/jni/shadowsocks-libev +++ b/mobile/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit 4c3c0ce9c33825949e289aa36f217d2de5af48c6 +Subproject commit 19e222bad8416f20b7694bff75f2cee72a70701e From 4c45c923bee0bad3b772423be697a8ea48d1e5d2 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Sat, 21 Jan 2017 16:35:45 +0800 Subject: [PATCH 33/98] Update shadowsocks-libev --- mobile/src/main/jni/shadowsocks-libev | 2 +- .../main/scala/com/github/shadowsocks/utils/TcpFastOpen.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/jni/shadowsocks-libev b/mobile/src/main/jni/shadowsocks-libev index 19e222ba..1c2b6170 160000 --- a/mobile/src/main/jni/shadowsocks-libev +++ b/mobile/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit 19e222bad8416f20b7694bff75f2cee72a70701e +Subproject commit 1c2b6170b9ec5443d4a3e211425bc0d5f8e18119 diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/TcpFastOpen.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/TcpFastOpen.scala index 80108f26..033fc03d 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/TcpFastOpen.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/TcpFastOpen.scala @@ -46,7 +46,7 @@ object TcpFastOpen { def sendEnabled: Boolean = { val file = new File("/proc/sys/net/ipv4/tcp_fastopen") - file.canRead && (Source.fromFile(file).mkString.toInt & 1) > 0 + file.canRead && (Source.fromFile(file).mkString.trim.toInt & 1) > 0 } def enabled(value: Boolean): String = if (sendEnabled != value) { From 36faaad44d21f0e6ccda9ea4cd5f449c6c08ab07 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Sat, 21 Jan 2017 16:46:54 +0800 Subject: [PATCH 34/98] Update simple-obfs --- .../scala/com/github/shadowsocks/ShadowsocksVpnService.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index e00458fc..82019164 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -162,7 +162,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { } override protected def buildPluginCommandLine(): ArrayBuffer[String] = - super.buildPluginCommandLine() ++= Seq("-V", "-P", getFilesDir.getAbsolutePath) + super.buildPluginCommandLine() ++= Seq("-V") def startShadowsocksDaemon() { val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", From 340e9cfffab0ec0195313feb7cddd9354b6d8023 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Sat, 21 Jan 2017 16:51:10 +0800 Subject: [PATCH 35/98] Update GFWList --- mobile/src/main/assets/acl/bypass-china.acl | 1 + mobile/src/main/assets/acl/bypass-lan-china.acl | 1 + 2 files changed, 2 insertions(+) diff --git a/mobile/src/main/assets/acl/bypass-china.acl b/mobile/src/main/assets/acl/bypass-china.acl index 66436872..197fb763 100644 --- a/mobile/src/main/assets/acl/bypass-china.acl +++ b/mobile/src/main/assets/acl/bypass-china.acl @@ -4780,6 +4780,7 @@ ^(.*\.)?googlezip\.net$ ^(.*\.)?gvt0\.com$ ^(.*\.)?gvt1\.com$ +^(.*\.)?gvt2\.com$ ^(.*\.)?gvt3\.com$ ^(.*\.)?html5rocks\.com$ ^(.*\.)?iam\.soy$ diff --git a/mobile/src/main/assets/acl/bypass-lan-china.acl b/mobile/src/main/assets/acl/bypass-lan-china.acl index ac3e14b9..743885d4 100644 --- a/mobile/src/main/assets/acl/bypass-lan-china.acl +++ b/mobile/src/main/assets/acl/bypass-lan-china.acl @@ -4794,6 +4794,7 @@ ^(.*\.)?googlezip\.net$ ^(.*\.)?gvt0\.com$ ^(.*\.)?gvt1\.com$ +^(.*\.)?gvt2\.com$ ^(.*\.)?gvt3\.com$ ^(.*\.)?html5rocks\.com$ ^(.*\.)?iam\.soy$ From 18302e1c4045838768bbaf68d0ec74ae434f5eab Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 22 Jan 2017 16:06:36 +0800 Subject: [PATCH 36/98] Refine code style --- build.sbt | 4 +--- .../scala/com/github/shadowsocks/ShadowsocksVpnService.scala | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index 3a2908b3..2f2ccd57 100644 --- a/build.sbt +++ b/build.sbt @@ -28,9 +28,7 @@ run in Android := (run in (mobile, Android)).evaluated lazy val plugin = project .settings(commonSettings) .settings( - libraryDependencies ++= - "com.android.support" % "preference-v14" % supportLibsVersion :: - Nil + libraryDependencies += "com.android.support" % "preference-v14" % supportLibsVersion ) lazy val mobile = project diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 82019164..d8d37808 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -161,8 +161,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { } } - override protected def buildPluginCommandLine(): ArrayBuffer[String] = - super.buildPluginCommandLine() ++= Seq("-V") + override protected def buildPluginCommandLine(): ArrayBuffer[String] = super.buildPluginCommandLine() += "-V" def startShadowsocksDaemon() { val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", From 9e6948caf638fffe75609985d622a7505745a357 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 22 Jan 2017 16:16:48 +0800 Subject: [PATCH 37/98] Fix switch to VPN button in Snackbar --- .../shadowsocks/GlobalConfigFragment.scala | 16 +++++++++++++++- .../com/github/shadowsocks/MainActivity.scala | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala index c6470bf2..a3de92be 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala @@ -20,13 +20,16 @@ package com.github.shadowsocks +import android.content.SharedPreferences +import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.os.Bundle import android.support.design.widget.Snackbar import android.support.v14.preference.SwitchPreference import be.mygod.preference.PreferenceFragment import com.github.shadowsocks.utils.{Key, TcpFastOpen} +import com.github.shadowsocks.ShadowsocksApplication.app -class GlobalConfigFragment extends PreferenceFragment { +class GlobalConfigFragment extends PreferenceFragment with OnSharedPreferenceChangeListener { override def onCreatePreferences(bundle: Bundle, key: String) { addPreferencesFromResource(R.xml.pref_global) val switch = findPreference(Key.isAutoConnect).asInstanceOf[SwitchPreference] @@ -53,5 +56,16 @@ class GlobalConfigFragment extends PreferenceFragment { tfo.setEnabled(false) tfo.setSummary(getString(R.string.tcp_fastopen_summary_unsupported, java.lang.System.getProperty("os.version"))) } + app.settings.registerOnSharedPreferenceChangeListener(this) + } + + override def onDestroy() { + app.settings.unregisterOnSharedPreferenceChangeListener(this) + super.onDestroy() + } + + def onSharedPreferenceChanged(pref: SharedPreferences, key: String): Unit = key match { + case Key.isNAT => findPreference(key).asInstanceOf[SwitchPreference].setChecked(pref.getBoolean(key, false)) + case _ => } } diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index 0ec162b4..51ea18ba 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -170,7 +170,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe override def onServiceDisconnected(): Unit = changeState(State.IDLE) private def addDisableNatToSnackbar(snackbar: Snackbar) = snackbar.setAction(R.string.switch_to_vpn, (_ => - if (state == State.STOPPED) app.editor.putBoolean(Key.isNAT, false)): View.OnClickListener) + if (state == State.STOPPED) app.editor.putBoolean(Key.isNAT, false).apply()): View.OnClickListener) override def binderDied() { detachService() From 14745317d05e13286d1d524a791526634dc795e5 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 22 Jan 2017 20:51:16 +0800 Subject: [PATCH 38/98] Implement native mode without binary copying Plugins that support this mode only need to implement `NativePluginProvider.getExecutable`. --- .../com/github/shadowsocks/BaseService.scala | 2 +- .../shadowsocks/plugin/PluginManager.scala | 83 ++++++++++++------- .../shadowsocks/plugin/PluginContract.java | 10 +++ .../plugin/NativePluginProvider.scala | 20 ++++- 4 files changed, 81 insertions(+), 34 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala index 8dfca684..3b3d2ccb 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -159,7 +159,7 @@ trait BaseService extends Service { Acl.save(Acl.CUSTOM_RULES, new Acl().fromId(Acl.CUSTOM_RULES)) plugin = new PluginConfiguration(profile.plugin).selectedOptions - pluginPath = PluginManager.initPlugin(plugin.id) + pluginPath = PluginManager.init(plugin) } def startRunner(profile: Profile) { diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala index 5be8ccfc..00a66e7d 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala @@ -5,6 +5,8 @@ import java.io.{File, FileNotFoundException, FileOutputStream, IOException} import android.content.pm.PackageManager import android.content.{BroadcastReceiver, ContentResolver, Intent} import android.net.Uri +import android.os.Bundle +import android.util.Log import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.utils.CloseUtils.autoClose import com.github.shadowsocks.utils.{Commandline, IOUtils} @@ -41,11 +43,11 @@ object PluginManager { // the following parts are meant to be used by :bg @throws[Throwable] - def initPlugin(id: String): String = { - if (id.isEmpty) return null + def init(options: PluginOptions): String = { + if (options.id.isEmpty) return null var throwable: Throwable = null - try initNativePlugin(id) match { + try initNative(options) match { case null => case path => return path } catch { @@ -56,44 +58,61 @@ object PluginManager { // add other plugin types here - throw if (throwable == null) - new FileNotFoundException(app.getString(com.github.shadowsocks.R.string.plugin_unknown, id)) else throwable + throw if (throwable == null) new FileNotFoundException( + app.getString(com.github.shadowsocks.R.string.plugin_unknown, options.id)) else throwable } @throws[IOException] @throws[IndexOutOfBoundsException] @throws[AssertionError] - private def initNativePlugin(id: String): String = { + private def initNative(options: PluginOptions): String = { val providers = app.getPackageManager.queryIntentContentProviders( - new Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(id)), 0) + new Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(options.id)), 0) assert(providers.length == 1) - val builder = new Uri.Builder() + val uri = new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(providers(0).providerInfo.authority) + .build() val cr = app.getContentResolver - val cursor = cr.query(builder.build(), Array(PluginContract.COLUMN_PATH, PluginContract.COLUMN_MODE), - null, null, null) - if (cursor != null) { - var initialized = false - val pluginDir = new File(app.getFilesDir, "plugin") - def entryNotFound() = throw new IndexOutOfBoundsException("Plugin entry binary not found") - if (!cursor.moveToFirst()) entryNotFound() - IOUtils.deleteRecursively(pluginDir) - if (!pluginDir.mkdirs()) throw new FileNotFoundException("Unable to create plugin directory") - val pluginDirPath = pluginDir.getAbsolutePath + '/' - val list = new ListBuffer[String] - do { - val path = cursor.getString(0) - val file = new File(pluginDir, path) - assert(file.getAbsolutePath.startsWith(pluginDirPath)) - autoClose(cr.openInputStream(builder.path(path).build()))(in => - autoClose(new FileOutputStream(file))(out => IOUtils.copy(in, out))) - list += Commandline.toString(Array("chmod", cursor.getString(1), file.getAbsolutePath)) - if (path == id) initialized = true - } while (cursor.moveToNext()) - if (!initialized) entryNotFound() - Shell.SH.run(list) - new File(pluginDir, id).getAbsolutePath - } else null + try initNativeFast(cr, options, uri) catch { + case t: Throwable => + t.printStackTrace() + Log.w("PluginManager", "Initializing native plugin fast mode failed. Falling back to slow mode.") + initNativeSlow(cr, options, uri) + } } + + private def initNativeFast(cr: ContentResolver, options: PluginOptions, uri: Uri): String = { + val out = new Bundle() + out.putString(PluginContract.EXTRA_OPTIONS, options.id) + val result = cr.call(uri, PluginContract.METHOD_GET_EXECUTABLE, null, out).getString(PluginContract.EXTRA_ENTRY) + assert(new File(result).canExecute) + result + } + + private def initNativeSlow(cr: ContentResolver, options: PluginOptions, uri: Uri): String = + cr.query(uri, Array(PluginContract.COLUMN_PATH, PluginContract.COLUMN_MODE), null, null, null) match { + case null => null + case cursor => + var initialized = false + val pluginDir = new File(app.getFilesDir, "plugin") + def entryNotFound() = throw new IndexOutOfBoundsException("Plugin entry binary not found") + if (!cursor.moveToFirst()) entryNotFound() + IOUtils.deleteRecursively(pluginDir) + if (!pluginDir.mkdirs()) throw new FileNotFoundException("Unable to create plugin directory") + val pluginDirPath = pluginDir.getAbsolutePath + '/' + val list = new ListBuffer[String] + do { + val path = cursor.getString(0) + val file = new File(pluginDir, path) + assert(file.getAbsolutePath.startsWith(pluginDirPath)) + autoClose(cr.openInputStream(uri.buildUpon().path(path).build()))(in => + autoClose(new FileOutputStream(file))(out => IOUtils.copy(in, out))) + list += Commandline.toString(Array("chmod", cursor.getString(1), file.getAbsolutePath)) + if (path == options.id) initialized = true + } while (cursor.moveToNext()) + if (!initialized) entryNotFound() + Shell.SH.run(list) + new File(pluginDir, options.id).getAbsolutePath + } } diff --git a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java index 90e274dc..1bd3732f 100644 --- a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java +++ b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java @@ -30,6 +30,14 @@ public final class PluginContract { */ public static final String ACTION_HELP = "com.github.shadowsocks.plugin.ACTION_HELP"; + /** + * The lookup key for a string that provides the plugin entry binary. + * + * Example: "/data/data/com.github.shadowsocks.plugin.obfs_local/lib/libobfs-local.so" + * + * Constant Value: "com.github.shadowsocks.plugin.EXTRA_ENTRY" + */ + public static final String EXTRA_ENTRY = "com.github.shadowsocks.plugin.EXTRA_ENTRY"; /** * The lookup key for a string that provides the whole command as a string. * @@ -61,6 +69,8 @@ public final class PluginContract { */ public static final String METADATA_KEY_DEFAULT_CONFIG = "com.github.shadowsocks.plugin.default_config"; + static final String METHOD_GET_EXECUTABLE = "shadowsocks:getExecutable"; + /** * Relative to the file to be copied. This column is required. * diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala index 5c1357e1..982c7f37 100644 --- a/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala @@ -3,7 +3,7 @@ package com.github.shadowsocks.plugin import android.content.{ContentProvider, ContentValues} import android.database.{Cursor, MatrixCursor} import android.net.Uri -import android.os.ParcelFileDescriptor +import android.os.{Bundle, ParcelFileDescriptor} /** * Base class for a native plugin provider. A native plugin provider offers read-only access to files that are required @@ -52,12 +52,30 @@ abstract class NativePluginProvider extends ContentProvider { result } + /** + * Returns executable entry absolute path. This is used if plugin is sharing UID with the host. + * + * Default behavior is throwing UnsupportedOperationException. If you don't wish to use this feature, use the default + * behavior. + * + * @return Absolute path for executable entry. + */ + def getExecutable: String = throw new UnsupportedOperationException + def openFile(uri: Uri): ParcelFileDescriptor override def openFile(uri: Uri, mode: String): ParcelFileDescriptor = { assert(mode == "r") openFile(uri) } + override def call(method: String, arg: String, extras: Bundle): Bundle = method match { + case PluginContract.METHOD_GET_EXECUTABLE => + val out = new Bundle() + out.putString(PluginContract.EXTRA_ENTRY, getExecutable) + out + case _ => super.call(method, arg, extras) + } + // Methods that should not be used override def update(uri: Uri, values: ContentValues, selection: String, selectionArgs: Array[String]): Int = ??? override def insert(uri: Uri, values: ContentValues): Uri = ??? From 77296cbd3e220cdf82d5fafee6a628efaef139b7 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 22 Jan 2017 20:51:59 +0800 Subject: [PATCH 39/98] Remove obsolete permission --- mobile/src/main/AndroidManifest.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index cb369460..0f77f4df 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -2,7 +2,6 @@ xmlns:tools="http://schemas.android.com/tools" package="com.github.shadowsocks"> - From e3ad38318d960f7e297e5e56d873015c672c8db9 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 22 Jan 2017 21:10:48 +0800 Subject: [PATCH 40/98] Update README and licenses --- README.md | 3 ++- mobile/src/main/assets/pages/about.html | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6cbbc7e6..4799fdf4 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. * Invoke the building like this ```bash - git submodule update --init + git submodule update --init --recursive # Build the App sbt clean android:package-release @@ -53,6 +53,7 @@ Translators can go to [POEditor](https://poeditor.com/join/project/u5VHO9vhSf) t
  • pdnsd: GPLv3
  • libev: GPLv2
  • libsodium: ISC
  • +
  • libudns: LGPL
  • ### LICENSE diff --git a/mobile/src/main/assets/pages/about.html b/mobile/src/main/assets/pages/about.html index e8d232db..3e45a17d 100644 --- a/mobile/src/main/assets/pages/about.html +++ b/mobile/src/main/assets/pages/about.html @@ -299,6 +299,7 @@ along with this program. If not, see http
  • pdnsd: GPLv3
  • libev: GPLv2
  • libsodium: ISC
  • +
  • libudns: LGPL
  • From 128d47aba1fde69784135c8f2a7a5c3b9b22ca74 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 23 Jan 2017 01:03:03 +0800 Subject: [PATCH 41/98] Properly speed up ndk-build Original idea from @wongsyrone. --- build.sbt | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sbt b/build.sbt index 2f2ccd57..bc8d1f61 100644 --- a/build.sbt +++ b/build.sbt @@ -9,6 +9,7 @@ lazy val commonSettings = Seq( compileOrder := CompileOrder.JavaThenScala, javacOptions ++= "-source" :: "1.7" :: "-target" :: "1.7" :: Nil, scalacOptions ++= "-target:jvm-1.7" :: "-Xexperimental" :: Nil, + ndkArgs := "-j" :: java.lang.Runtime.getRuntime.availableProcessors.toString :: Nil, proguardVersion := "5.3.2", proguardCache := Seq(), From cbb0345e5b1f0c78443b66005baf7dda1dccfd8b Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 23 Jan 2017 01:39:55 +0800 Subject: [PATCH 42/98] Fix obtaining packageName in NativePlugin --- .../scala/com/github/shadowsocks/plugin/NativePlugin.scala | 7 +------ .../com/github/shadowsocks/plugin/ResolvedPlugin.scala | 2 +- .../preference/BottomSheetPreferenceDialogFragment.scala | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala index 7621f412..34c65a79 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala @@ -1,8 +1,6 @@ package com.github.shadowsocks.plugin -import android.content.ContentResolver import android.content.pm.{PackageManager, ResolveInfo} -import android.net.Uri import android.os.Bundle /** @@ -12,8 +10,5 @@ final class NativePlugin(resolveInfo: ResolveInfo, packageManager: PackageManage extends ResolvedPlugin(resolveInfo, packageManager) { assert(resolveInfo.providerInfo != null) override protected def metaData: Bundle = resolveInfo.providerInfo.metaData - - private lazy val uriBuilder: Uri.Builder = new Uri.Builder() - .scheme(ContentResolver.SCHEME_CONTENT) - .authority(resolveInfo.providerInfo.authority) + override def packageName: String = resolveInfo.providerInfo.packageName } diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala index 3cc2e915..21a3d0a5 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala @@ -18,5 +18,5 @@ abstract class ResolvedPlugin(resolveInfo: ResolveInfo, packageManager: PackageM override final lazy val label: CharSequence = resolveInfo.loadLabel(packageManager) override final lazy val icon: Drawable = resolveInfo.loadIcon(packageManager) override final lazy val defaultConfig: String = metaData.getString(PluginContract.METADATA_KEY_DEFAULT_CONFIG) - override final def packageName: String = resolveInfo.resolvePackageName + override def packageName: String = resolveInfo.resolvePackageName } diff --git a/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala index 978ccdd9..9057d872 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala @@ -70,7 +70,7 @@ final class BottomSheetPreferenceDialogFragment extends PreferenceDialogFragment case null => false case pn => try { startActivity(new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, new Uri.Builder() - .authority(Settings.AUTHORITY) + .scheme("package") .opaquePart(pn) .build())) true From f6253d2eba4c5784416bc2218173834fc7cecdbb Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 23 Jan 2017 02:33:58 +0800 Subject: [PATCH 43/98] Fix crash when creating Snackbar in ProfileConfigFragment --- .../scala/com/github/shadowsocks/ProfileConfigFragment.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala index 53ce2fee..d338ddbb 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala @@ -110,7 +110,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe true } catch { case exc: IllegalArgumentException => - Snackbar.make(getActivity.findViewById(R.id.snackbar), exc.getLocalizedMessage, Snackbar.LENGTH_LONG).show() + Snackbar.make(getView, exc.getLocalizedMessage, Snackbar.LENGTH_LONG).show() false } From c0aff7dda13bf6056db914c3f451c8fcb7cb4e46 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 23 Jan 2017 02:38:56 +0800 Subject: [PATCH 44/98] Add signature checking to plugin platform --- mobile/src/main/res/values/strings.xml | 1 + .../shadowsocks/ProfileConfigFragment.scala | 2 + .../github/shadowsocks/plugin/Plugin.scala | 1 + .../shadowsocks/plugin/PluginManager.scala | 37 ++++++++++++++++++- .../shadowsocks/plugin/ResolvedPlugin.scala | 2 + 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 8e78ddf6..a8e902f9 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -142,5 +142,6 @@ Configure… Disabled Unknown plugin %s + Warning: This plugin does not seem to come from a known trusted source. Plugin: %s diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala index d338ddbb..4e8f1aa6 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala @@ -81,6 +81,8 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe app.editor.putString(Key.plugin, pluginConfiguration.toString).putBoolean(Key.dirty, true).apply() pluginConfigure.setEnabled(!TextUtils.isEmpty(selected)) pluginConfigure.setText(pluginConfiguration.selectedOptions.toString) + if (!PluginManager.fetchPlugins()(selected).trusted) + Snackbar.make(getView, R.string.plugin_untrusted, Snackbar.LENGTH_LONG).show() true }) pluginConfigure.setOnPreferenceChangeListener(onPluginConfigureChanged) diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/Plugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/Plugin.scala index 2017eb5c..f206034e 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/Plugin.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/Plugin.scala @@ -11,4 +11,5 @@ abstract class Plugin { def icon: Drawable = null def defaultConfig: String = null def packageName: String = null + def trusted: Boolean = true } diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala index 00a66e7d..935a0006 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala @@ -2,11 +2,11 @@ package com.github.shadowsocks.plugin import java.io.{File, FileNotFoundException, FileOutputStream, IOException} -import android.content.pm.PackageManager +import android.content.pm.{PackageManager, Signature} import android.content.{BroadcastReceiver, ContentResolver, Intent} import android.net.Uri import android.os.Bundle -import android.util.Log +import android.util.{Base64, Log} import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.utils.CloseUtils.autoClose import com.github.shadowsocks.utils.{Commandline, IOUtils} @@ -19,6 +19,39 @@ import scala.collection.mutable.ListBuffer * @author Mygod */ object PluginManager { + /** + * Trusted signatures by the app. Third-party fork should add their public key to their fork if the developer wishes + * to publish or has published plugins for this app. You can obtain your public key by executing: + * + * $ keytool -export -alias key-alias -keystore /path/to/keystore.jks -rfc + * + * If you don't plan to publish any plugin but is developing/has developed some, it's not necessary to add your + * public key yet since it will also automatically trust packages signed by the same signatures, e.g. debug keys. + */ + lazy val trustedSignatures: Set[Signature] = + app.getPackageManager.getPackageInfo(app.getPackageName, PackageManager.GET_SIGNATURES).signatures.toSet + + new Signature(Base64.decode( // @Mygod + """ + |MIIDWzCCAkOgAwIBAgIEUzfv8DANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJD + |TjEOMAwGA1UECBMFTXlnb2QxDjAMBgNVBAcTBU15Z29kMQ4wDAYDVQQKEwVNeWdv + |ZDEOMAwGA1UECxMFTXlnb2QxDjAMBgNVBAMTBU15Z29kMCAXDTE0MDUwMjA5MjQx + |OVoYDzMwMTMwOTAyMDkyNDE5WjBdMQswCQYDVQQGEwJDTjEOMAwGA1UECBMFTXln + |b2QxDjAMBgNVBAcTBU15Z29kMQ4wDAYDVQQKEwVNeWdvZDEOMAwGA1UECxMFTXln + |b2QxDjAMBgNVBAMTBU15Z29kMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC + |AQEAjm5ikHoP3w6zavvZU5bRo6Birz41JL/nZidpdww21q/G9APA+IiJMUeeocy0 + |L7/QY8MQZABVwNq79LXYWJBcmmFXM9xBPgDqQP4uh9JsvazCI9bvDiMn92mz9HiS + |Sg9V4KGg0AcY0r230KIFo7hz+2QBp1gwAAE97myBfA3pi3IzJM2kWsh4LWkKQMfL + |M6KDhpb4mdDQnHlgi4JWe3SYbLtpB6whnTqjHaOzvyiLspx1tmrb0KVxssry9KoX + |YQzl56scfE/QJX0jJ5qYmNAYRCb4PibMuNSGB2NObDabSOMAdT4JLueOcHZ/x9tw + |agGQ9UdymVZYzf8uqc+29ppKdQIDAQABoyEwHzAdBgNVHQ4EFgQUBK4uJ0cqmnho + |6I72VmOVQMvVCXowDQYJKoZIhvcNAQELBQADggEBABZQ3yNESQdgNJg+NRIcpF9l + |YSKZvrBZ51gyrC7/2ZKMpRIyXruUOIrjuTR5eaONs1E4HI/uA3xG1eeW2pjPxDnO + |zgM4t7EPH6QbzibihoHw1MAB/mzECzY8r11PBhDQlst0a2hp+zUNR8CLbpmPPqTY + |RSo6EooQ7+NBejOXysqIF1q0BJs8Y5s/CaTOmgbL7uPCkzArB6SS/hzXgDk5gw6v + |wkGeOtzcj1DlbUTvt1s5GlnwBTGUmkbLx+YUje+n+IBgMbohLUDYBtUHylRVgMsc + |1WS67kDqeJiiQZvrxvyW6CZZ/MIGI+uAkkj3DqJpaZirkwPgvpcOIrjZy0uFvQM= + """, Base64.DEFAULT)) + private var receiver: BroadcastReceiver = _ private var cachedPlugins: Map[String, Plugin] = _ def fetchPlugins(): Map[String, Plugin] = { diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala index 21a3d0a5..ac23b390 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala @@ -19,4 +19,6 @@ abstract class ResolvedPlugin(resolveInfo: ResolveInfo, packageManager: PackageM override final lazy val icon: Drawable = resolveInfo.loadIcon(packageManager) override final lazy val defaultConfig: String = metaData.getString(PluginContract.METADATA_KEY_DEFAULT_CONFIG) override def packageName: String = resolveInfo.resolvePackageName + override final lazy val trusted: Boolean = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) + .signatures.exists(PluginManager.trustedSignatures.contains) } From 974c2544608131f0b48055d0e67d38ada283df09 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 23 Jan 2017 02:48:36 +0800 Subject: [PATCH 45/98] Fix plugin preference icon --- .../scala/com/github/shadowsocks/ProfileConfigFragment.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala index 4e8f1aa6..5dfbb616 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala @@ -99,6 +99,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe plugin.entryPackageNames = plugins.map(_._2.packageName).toArray pluginConfiguration = new PluginConfiguration(app.settings.getString(Key.plugin, null)) plugin.setValue(pluginConfiguration.selected) + plugin.init() plugin.checkSummary() pluginConfigure.setEnabled(!TextUtils.isEmpty(pluginConfiguration.selected)) pluginConfigure.setText(pluginConfiguration.selectedOptions.toString) From 6fb7330e368dd399bac2dc3646a4068dc9e4ab78 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 23 Jan 2017 03:42:59 +0800 Subject: [PATCH 46/98] Fix crash on unknown plugin --- .../github/shadowsocks/preference/IconListPreference.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/preference/IconListPreference.scala b/mobile/src/main/scala/com/github/shadowsocks/preference/IconListPreference.scala index 23301b2f..86591147 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/preference/IconListPreference.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/preference/IconListPreference.scala @@ -41,7 +41,9 @@ class IconListPreference(context: Context, attrs: AttributeSet = null) extends L // a.recycle() // } - def getEntryIcon: Drawable = mEntryIcons(selectedEntry) + def getEntryIcon: Drawable = try mEntryIcons(selectedEntry) catch { + case _: ArrayIndexOutOfBoundsException => null + } def getEntryIcons: Array[Drawable] = mEntryIcons def setEntryIcons(entryIcons: Array[Drawable]): Unit = mEntryIcons = entryIcons From b3bab982a87af87db1102d95d7194db94cc83f1b Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 23 Jan 2017 03:43:23 +0800 Subject: [PATCH 47/98] Fix HelpCallback --- .../shadowsocks/ProfileConfigActivity.scala | 17 ++++++++++++++- .../shadowsocks/ProfileConfigFragment.scala | 6 +++--- .../PluginConfigurationDialogFragment.scala | 21 +++++-------------- .../shadowsocks/plugin/HelpCallback.scala | 6 ++++-- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigActivity.scala index e8344b87..fd22a282 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigActivity.scala @@ -21,14 +21,21 @@ package com.github.shadowsocks import android.app.Activity -import android.content.DialogInterface +import android.content.{DialogInterface, Intent} import android.os.Bundle import android.support.v7.app.AlertDialog import android.support.v7.widget.Toolbar import com.github.shadowsocks.ShadowsocksApplication.app +import com.github.shadowsocks.plugin.PluginContract import com.github.shadowsocks.utils.Key +object ProfileConfigActivity { + final val REQUEST_CODE_PLUGIN_HELP = 1 +} + class ProfileConfigActivity extends Activity { + import ProfileConfigActivity._ + private lazy val child = getFragmentManager.findFragmentById(R.id.content).asInstanceOf[ProfileConfigFragment] override def onCreate(savedInstanceState: Bundle) { @@ -49,4 +56,12 @@ class ProfileConfigActivity extends Activity { .setNeutralButton(android.R.string.cancel, null) .create() .show() else super.onBackPressed() + + override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = requestCode match { + case REQUEST_CODE_PLUGIN_HELP => if (resultCode == Activity.RESULT_OK) new AlertDialog.Builder(this) + .setTitle("?") + .setMessage(data.getCharSequenceExtra(PluginContract.EXTRA_HELP_MESSAGE)) + .show() + case _ => super.onActivityResult(requestCode, resultCode, data) + } } diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala index 5dfbb616..d04ce749 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala @@ -39,7 +39,7 @@ import com.github.shadowsocks.preference.{IconListPreference, PluginConfiguratio import com.github.shadowsocks.utils.{Action, Key, Utils} object ProfileConfigFragment { - private final val REQUEST_CODE_CONFIGURE = 1 + private final val REQUEST_CODE_PLUGIN_CONFIGURE = 1 } class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListener @@ -135,7 +135,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe val intent = PluginManager.buildIntent(selected, PluginContract.ACTION_CONFIGURE) if (intent.resolveActivity(getActivity.getPackageManager) != null) startActivityForResult(intent.putExtra(PluginContract.EXTRA_OPTIONS, - pluginConfiguration.selectedOptions.toString), REQUEST_CODE_CONFIGURE) else { + pluginConfiguration.selectedOptions.toString), REQUEST_CODE_PLUGIN_CONFIGURE) else { val bundle = new Bundle() bundle.putString(PluginConfigurationDialogFragment.PLUGIN_ID_FRAGMENT_TAG, selected) displayPreferenceDialog(preference.getKey, new PluginConfigurationDialogFragment, bundle) @@ -143,7 +143,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe } else super.onDisplayPreferenceDialog(preference) override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = requestCode match { - case REQUEST_CODE_CONFIGURE => if (resultCode == Activity.RESULT_OK) { + case REQUEST_CODE_PLUGIN_CONFIGURE => if (resultCode == Activity.RESULT_OK) { val options = data.getStringExtra(PluginContract.EXTRA_OPTIONS) pluginConfigure.setText(options) onPluginConfigureChanged(null, options) diff --git a/mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala index ff414d70..bc6046f0 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala @@ -20,9 +20,10 @@ package com.github.shadowsocks.preference -import android.app.{Activity, AlertDialog} -import android.content.{DialogInterface, Intent} +import android.app.AlertDialog +import android.content.DialogInterface import be.mygod.preference.EditTextPreferenceDialogFragment +import com.github.shadowsocks.ProfileConfigActivity import com.github.shadowsocks.plugin.{PluginContract, PluginManager} /** @@ -30,7 +31,6 @@ import com.github.shadowsocks.plugin.{PluginContract, PluginManager} */ object PluginConfigurationDialogFragment { final val PLUGIN_ID_FRAGMENT_TAG = "com.github.shadowsocks.preference.PluginConfigurationDialogFragment.PLUGIN_ID" - private final val REQUEST_CODE_HELP = 1 } class PluginConfigurationDialogFragment extends EditTextPreferenceDialogFragment { @@ -40,18 +40,7 @@ class PluginConfigurationDialogFragment extends EditTextPreferenceDialogFragment super.onPrepareDialogBuilder(builder) val intent = PluginManager.buildIntent(getArguments.getString(PLUGIN_ID_FRAGMENT_TAG), PluginContract.ACTION_HELP) if (intent.resolveActivity(getActivity.getPackageManager) != null) builder.setNeutralButton("?", ((_, _) => - startActivityForResult(intent.putExtra(PluginContract.EXTRA_OPTIONS, editText.getText.toString), - REQUEST_CODE_HELP)): DialogInterface.OnClickListener) - } - - override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = requestCode match { - case REQUEST_CODE_HELP => requestCode match { - case Activity.RESULT_OK => new AlertDialog.Builder(getContext) - .setTitle("?") - .setMessage(data.getCharSequenceExtra(PluginContract.EXTRA_HELP_MESSAGE)) - .show() - case _ => - } - case _ => super.onActivityResult(requestCode, resultCode, data) + getActivity.startActivityForResult(intent.putExtra(PluginContract.EXTRA_OPTIONS, editText.getText.toString), + ProfileConfigActivity.REQUEST_CODE_PLUGIN_HELP)): DialogInterface.OnClickListener) } } diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpCallback.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpCallback.scala index 236f9a29..76c11b09 100644 --- a/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpCallback.scala +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpCallback.scala @@ -13,6 +13,8 @@ import android.content.Intent trait HelpCallback extends HelpActivity { def produceHelpMessage(options: PluginOptions): CharSequence - override protected def onInitializePluginOptions(options: PluginOptions): Unit = setResult(Activity.RESULT_OK, - new Intent().putExtra(PluginContract.EXTRA_HELP_MESSAGE, produceHelpMessage(options))) + override protected def onInitializePluginOptions(options: PluginOptions) { + setResult(Activity.RESULT_OK, new Intent().putExtra(PluginContract.EXTRA_HELP_MESSAGE, produceHelpMessage(options))) + finish() + } } From a8222317d766b18818e8c937874c45185325e029 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 23 Jan 2017 14:19:08 +0800 Subject: [PATCH 48/98] Fix layout issues on Android 4.4 --- mobile/src/main/res/drawable/background_profile.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mobile/src/main/res/drawable/background_profile.xml b/mobile/src/main/res/drawable/background_profile.xml index 50d83e66..d1fba8c5 100644 --- a/mobile/src/main/res/drawable/background_profile.xml +++ b/mobile/src/main/res/drawable/background_profile.xml @@ -1,5 +1,11 @@ + + + + + + @@ -9,12 +15,6 @@ - - - - - - From 4d4d6ed1a043e1cb095f8d1ec713fe782724634f Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 23 Jan 2017 14:31:42 +0800 Subject: [PATCH 49/98] Add RESULT_FALLBACK to ConfigurationActivity --- .../shadowsocks/ProfileConfigFragment.scala | 28 +++++++++++-------- .../github/shadowsocks/utils/Constants.scala | 1 + .../shadowsocks/plugin/PluginContract.java | 3 ++ .../plugin/ConfigurationActivity.scala | 8 ++++++ 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala index d04ce749..506f459f 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala @@ -73,7 +73,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe false }) plugin = findPreference(Key.plugin).asInstanceOf[IconListPreference] - pluginConfigure = findPreference("plugin.configure").asInstanceOf[EditTextPreference] + pluginConfigure = findPreference(Key.pluginConfigure).asInstanceOf[EditTextPreference] plugin.unknownValueSummary = getString(R.string.plugin_unknown) plugin.setOnPreferenceChangeListener((_, value) => { val selected = value.asInstanceOf[String] @@ -130,23 +130,29 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe isProxyApps.setChecked(app.settings.getBoolean(Key.proxyApps, false)) // fetch proxyApps updated by AppManager } - override def onDisplayPreferenceDialog(preference: Preference): Unit = if (preference eq pluginConfigure) { - val selected = pluginConfiguration.selected - val intent = PluginManager.buildIntent(selected, PluginContract.ACTION_CONFIGURE) + private def showPluginEditor() { + val bundle = new Bundle() + bundle.putString(PluginConfigurationDialogFragment.PLUGIN_ID_FRAGMENT_TAG, pluginConfiguration.selected) + displayPreferenceDialog(Key.pluginConfigure, new PluginConfigurationDialogFragment, bundle) + } + + override def onDisplayPreferenceDialog(preference: Preference): Unit = if (preference.getKey == Key.pluginConfigure) { + val intent = PluginManager.buildIntent(pluginConfiguration.selected, PluginContract.ACTION_CONFIGURE) if (intent.resolveActivity(getActivity.getPackageManager) != null) startActivityForResult(intent.putExtra(PluginContract.EXTRA_OPTIONS, pluginConfiguration.selectedOptions.toString), REQUEST_CODE_PLUGIN_CONFIGURE) else { - val bundle = new Bundle() - bundle.putString(PluginConfigurationDialogFragment.PLUGIN_ID_FRAGMENT_TAG, selected) - displayPreferenceDialog(preference.getKey, new PluginConfigurationDialogFragment, bundle) + showPluginEditor() } } else super.onDisplayPreferenceDialog(preference) override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = requestCode match { - case REQUEST_CODE_PLUGIN_CONFIGURE => if (resultCode == Activity.RESULT_OK) { - val options = data.getStringExtra(PluginContract.EXTRA_OPTIONS) - pluginConfigure.setText(options) - onPluginConfigureChanged(null, options) + case REQUEST_CODE_PLUGIN_CONFIGURE => resultCode match { + case Activity.RESULT_OK => + val options = data.getStringExtra(PluginContract.EXTRA_OPTIONS) + pluginConfigure.setText(options) + onPluginConfigureChanged(null, options) + case PluginContract.RESULT_FALLBACK => showPluginEditor() + case _ => } case _ => super.onActivityResult(requestCode, resultCode, data) } diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala index 7d7bea16..69034aba 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala @@ -152,6 +152,7 @@ object Key { val remoteDns = "remoteDns" val plugin = "plugin" + val pluginConfigure = "plugin.configure" val dirty = "profileDirty" diff --git a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java index 1bd3732f..ec73be4b 100644 --- a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java +++ b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java @@ -71,6 +71,9 @@ public final class PluginContract { static final String METHOD_GET_EXECUTABLE = "shadowsocks:getExecutable"; + /** ConfigurationActivity result: fallback to manual edit mode. */ + public static final int RESULT_FALLBACK = 1; + /** * Relative to the file to be copied. This column is required. * diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala index f35e4dc3..ff618ade 100644 --- a/plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala @@ -36,4 +36,12 @@ trait ConfigurationActivity extends OptionsCapableActivity { */ final def saveChanges(options: PluginOptions): Unit = setResult(Activity.RESULT_OK, new Intent().putExtra(PluginContract.EXTRA_OPTIONS, options.toString)) + + /** + * Finish this activity and request manual editor to pop up instead. + */ + final def fallbackToManualEditor() { + setResult(PluginContract.RESULT_FALLBACK) + finish() + } } From a31befef7fc5a66d4cada760ddf90500380f1c6b Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 23 Jan 2017 16:28:01 +0800 Subject: [PATCH 50/98] Remove duplicated entryValues from BottomSheetPreferenceDialogFragment --- .../preference/BottomSheetPreferenceDialogFragment.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala index 9057d872..1cb28118 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala @@ -58,7 +58,7 @@ final class BottomSheetPreferenceDialogFragment extends PreferenceDialogFragment val typeface = if (selected) Typeface.BOLD else Typeface.NORMAL text1.setTypeface(null, typeface) text2.setTypeface(null, typeface) - text2.setVisibility(if (entryValues(i).length > 0) View.VISIBLE else View.GONE) + text2.setVisibility(if (entryValues(i).length > 0 && entries(i) != entryValues(i)) View.VISIBLE else View.GONE) icon.setImageDrawable(entryIcons(i)) index = i } From 911eb3dab3932de3cabd7bfdd6c6f810ad0fa817 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 23 Jan 2017 17:27:04 +0800 Subject: [PATCH 51/98] Add documentations to plugin --- .github/issue_template.md | 2 +- plugin/README.md | 169 +++++++++++++ plugin/doc.md | 230 ++++++++++++++++++ .../shadowsocks/plugin/PluginContract.java | 6 +- .../plugin/ConfigurationActivity.scala | 8 +- .../shadowsocks/plugin/HelpActivity.scala | 8 +- 6 files changed, 415 insertions(+), 8 deletions(-) create mode 100644 plugin/README.md create mode 100644 plugin/doc.md diff --git a/.github/issue_template.md b/.github/issue_template.md index d5edcba1..bc91020f 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -30,7 +30,7 @@ _Put an `x` inside the [ ] that applies._ * [ ] Per-App Proxy * [ ] Bypass mode * [ ] UDP Forwarding -* KCP Parameters: (leave blank if not enabled) +* Plugin configuration (if applicable): * [ ] Auto Connect * [ ] TCP Fast Open * [ ] NAT mode diff --git a/plugin/README.md b/plugin/README.md new file mode 100644 index 00000000..05cbe427 --- /dev/null +++ b/plugin/README.md @@ -0,0 +1,169 @@ +# shadowsocks-android plugin framework + +Support library for easier development on [shadowsocks + plugin](https://github.com/shadowsocks/shadowsocks-org/issues/28) for Android. Also includes some + useful resources to easily get consistent styling with the main app. + +## Official plugins + +These are some plugins ready to use on shadowsocks-android. + +* [simple-obfs](https://github.com/shadowsocks/simple-obfs-android/releases) +* [kcptun](https://github.com/shadowsocks/kcptun-android/releases) + +## Developer's guide + +WARNING: This library is still in beta (0.x) and its content is subject to massive changes. + +This library is designed with Java interoperability in mind so theoretically you can use this + library with other languages and/or build tools but there isn't documentation for that yet. This + guide is written for Scala + SBT. Contributions are welcome. + +The following is just a simple guide to get you started, for more technical details, see + [doc.md](https://github.com/shadowsocks/shadowsocks-android/blob/develop/plugin/doc.md). + +### Package name + +There are no arbitrary restrictions/requirements on package name, component name and content + provider authority, but you're suggested to follow the format in this documentations. For package + name, use `com.github.shadowsocks.plugin.$PLUGIN_ID` if it only contains a single plugin to + prevent duplicated plugins. In some places hyphens are not accepted, for example package name. In + that case, hyphens `-` should be changed into underscores `_`. For example, the package name for + `obfs-local` would probably be `com.github.shadowsocks.plugin.obfs_local`. + +### Add dependency + +First you need to add this library to your dependencies. This library is written mostly in Scala + and it's most convenient to use it with SBT: + +```scala +libraryDependencies += "com.github.shadowsocks" %% "plugin" % "0.0.1-SNAPSHOT" +``` + +### Native binary configuration + +First you need to get your native binary compiling on Android platform. + +* [Sample project for C](https://github.com/shadowsocks/simple-obfs-android/tree/4f82c4a4e415d666e70a7e2e60955cb0d85c1615); +* [Sample project for Go](https://github.com/shadowsocks/kcptun-android/tree/41f42077e177618553417c16559784a51e9d8c4c). + +In addition to functionalities of a normal plugin, it has to support these additional flags that + may get passed through arguments: + +* `-V`: VPN mode. In this case, the plugin should pass all file descriptors that needs protecting + from VPN connections (i.e. its traffic will not be forwarded through the VPN) through an + ancillary message to `./protect_path`; +* `--fast-open`: TCP fast open enabled. + +### Implement a binary provider + +It's super easy. You just need to implement two or three methods. For example for `obfs-local`: + +```scala +final class BinaryProvider extends NativePluginProvider { + override protected def populateFiles(provider: PathProvider) { + provider.addPath("obfs-local", "755") + // add additional files here + } + + // remove this method to disable fast mode, read more in the documentation + override def getExecutable: String = + getContext.getApplicationInfo.nativeLibraryDir + "/libobfs-local.so" + + override def openFile(uri: Uri): ParcelFileDescriptor = uri.getPath match { + case "/obfs-local" => + ParcelFileDescriptor.open(new File(getExecutable), ParcelFileDescriptor.MODE_READ_ONLY) + // handle additional files here + case _ => throw new FileNotFoundException() + } +} +``` + +Then add it to your manifest: + +```xml + + ... + + ... + + + + + + + + + + + + ... + + +``` + +### Add user interfaces + +You should add to your plugin app a configuration activity or a help activity or both if you're + going to use `ConfigurationActivity.fallbackToManualEditor`. + +#### Configuration activity + +This is used if found instead of a manual input dialog when user clicks "Configure..." in the main + app. This gives you maximum freedom of the user interface. To implement this, you need to extend + `ConfigurationActivity` and you will get current options via + `onInitializePluginOptions(PluginOptions)` and you can invoke `saveChanges(PluginOptions)` or + `discardChanges()` before `finish()` or `fallbackToManualEditor()`. Then add it to your manifest: + +```xml + + ... + + ... + + + + + + + + ... + + +``` + +#### Help activity/callback + +This is started when user taps "?" in manual editor. To implement this, you need to extend + `HelpCallback` if you want a simple dialog with help message as `CharSequence` or `HelpActivity` + if you want to provide custom user interface, implement the required methods, then add it to your + manifest: + +```xml + + ... + + ... + + + + + + + + ... + + +``` + +Great. Now your plugin is ready to use. diff --git a/plugin/doc.md b/plugin/doc.md new file mode 100644 index 00000000..4caebaac --- /dev/null +++ b/plugin/doc.md @@ -0,0 +1,230 @@ +# Overview + +Plugin should be bundled as an apk. `$PLUGIN_ID` in this documentation corresponds to the + executable name for the plugin in order to be cross-platform, e.g. `obfs-local`. An apk can have + more than one plugins bundled. We don't care as long as they have different `$PLUGIN_ID`. For + duplicated plugin ID, host should refuse to start. + +There are no arbitrary restrictions/requirements on package name, component name and content + provider authority, but you're suggested to follow the format in this documentations. For package + name, use `com.github.shadowsocks.plugin.$PLUGIN_ID` if it only contains a single plugin to prevent + duplicated plugins. In some places hyphens are not accepted, for example package name. In that + case, hyphens `-` should be changed into underscores `_`. For example, the package name for + `obfs-local` would probably be `com.github.shadowsocks.plugin.obfs_local`. + +It's advised to use this library for easier development, but you're free to start from scratch following this + documentation. + +# Plugin configuration + +Plugins get their args configured via one of the following two options: + +* A configuration activity; + ([example](https://github.com/shadowsocks/simple-obfs-android/tree/4f82c4a4e415d666e70a7e2e60955cb0d85c1615)) +* If no configuration activity is found or the activity requests the fallback mode, the fallback + mode will be used: user manual input and optional help message. + ([example](https://github.com/shadowsocks/kcptun-android/tree/41f42077e177618553417c16559784a51e9d8c4c)) + +Your user interface need not be consistent with shadowsocks-android styling - you don't need to use + preferences UI at all if you don't feel like it - however it's recommended to use Material Design + at minimum. + +## Configuration activity + +If the plugin provides a configuration activity, it will be started when user picks your plugin and + taps configure. It: + +* MUST have action: `com.github.shadowsocks.plugin.ACTION_CONFIGURE`; +* MUST have category: `android.intent.category.DEFAULT`; +* MUST be able to receive data URI `plugin://com.github.shadowsocks/$PLUGIN_ID`; +* SHOULD parse string extra `com.github.shadowsocks.plugin.EXTRA_OPTIONS` (all options as a single + string) and display the current options; +* SHOULD distinguish between server settings and feature settings in some way, e.g. for + `obfs-local`, `obfs` is a server setting and `obfs_host` is a feature setting; +* On finish, it SHOULD return one of the following results: + - `RESULT_OK = 0`: In this case it MUST return the data Intent with the new + `com.github.shadowsocks.plugin.EXTRA_OPTIONS`; + - `RESULT_CANCELED = -1`: Nothing will be changed; + - `RESULT_FALLBACK = 1`: Fallback mode is requested and the host should display the fallback + editor. + +This corresponds to `com.github.shadowsocks.plugin.ConfigurationActivity` in the plugin library. + Here's what a proper configuration activity usually should look like in `AndroidManifest.xml`: + +```xml + + ... + + ... + + + + + + + + ... + + +``` + +## Help activity/callback + +If the plugin doesn't provide a configuration activity, it's highly recommended to provide a help + message in the form of an Activity. It: + +* MUST have action: `com.github.shadowsocks.plugin.ACTION_HELP`; +* MUST have category: `android.intent.category.DEFAULT`; +* MUST be able to receive data URI `plugin://com.github.shadowsocks/$PLUGIN_ID`; +* CAN parse string extra `com.github.shadowsocks.plugin.EXTRA_OPTIONS` and display some more + relevant information; +* SHOULD either: + - Be invisible and return help message with CharSequence extra + `com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE` in the data intent with `RESULT_OK`; (in this + case, a simple dialog will be shown containing the message) + - Be visible and return `RESULT_CANCELED`. +* SHOULD distinguish between server settings and feature settings in some way, e.g. for + `simple_obfs`, `obfs` is a server setting and `obfs_host` is a feature setting. + +This corresponds to `com.github.shadowsocks.plugin.HelpActivity` or + `com.github.shadowsocks.plugin.HelpCallback` in the plugin library. Here's what a proper help + activity/callback usually should look like in `AndroidManifest.xml`: + +```xml + + ... + + ... + + + + + + + + ... + + +``` + +# Plugin implementation + +Every plugin can be either in native mode or JVM mode. + +## Native mode + +In native mode, plugins are provided as native executables and `shadowsocks-libev`'s plugin mode + will be used. + +Every native mode plugin MUST have a content provider to provide the native executables (since they + can exceed 1M which is the limit of Intent size) that: + +* MUST have `android:label` and `android:icon`; (may be configured by its parent `application`) +* MUST have an intent filter with action `com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN`; + (used for discovering plugins) +* MUST have meta-data `com.github.shadowsocks.plugin.id` with string value `$PLUGIN_ID`; +* MUST have an intent filter with action `com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN` and + data `plugin://com.github.shadowsocks/$PLUGIN_ID`; (used for configuring plugin) +* CAN have meta-data `com.github.shadowsocks.plugin.default_config` with string value, default is + empty; +* MUST implement `query` that returns the file list which MUST include `$PLUGIN_ID` when having + these as arguments: + - `uri = "content://$authority_of_your_provider`; + - `projection = ["path", "mode"]`; (relative path, for example `obfs-local`; file mode, for + example `755`) + - `selection = null`; + - `selectionArgs = null`; + - `sortOrder = null`; +* MUST implement `openFile` that for files returned in `query`, `openFile` with `mode = "r"` returns + a valid `ParcelFileDescriptor` for reading. For example, `uri` can be + `content://com.github.shadowsocks.plugin.kcptun/kcptun`. + +This corresponds to `com.github.shadowsocks.plugin.NativePluginProvider` in the plugin library. + Here's what a proper native plugin provider usually should look like in `AndroidManifest.xml`: + +```xml + + ... + + ... + + + + + + + + + + + + ... + + +``` + +## Native mode without binary copying + +If your plugin binary executable can run in place, you can support native mode without binary + copying. To support this mode, your `ContentProvider` must first support native mode with binary + copying (this will be used if the fast routine fails) and: + +* MUST implement `call` that returns absolute path to the entry executable as + `com.github.shadowsocks.plugin.EXTRA_ENTRY` when having `method = "shadowsocks:getExecutable"`; + (`com.github.shadowsocks.plugin.EXTRA_OPTIONS` is provided in extras as well just in case you + need them) + +If you don't plan to support this mode, you can just throw `UnsupportedOperationException` when + being invoked. It will fallback to the slow routine automatically. + +## JVM mode + +This feature hasn't been implemented yet. + +# Plugin security + +Plugins are certified using package signatures and shadowsocks-android will consider these + signatures as trusted: + +* Signatures by [trusted sources](https://github.com/shadowsocks/shadowsocks-android/blob/develop/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala#L22) + which includes: + - @madeye, i.e. the signer of the main repo; + - The main repo doesn't contain any other trusted signatures. Third-party forks should add their + signatures to this trusted sources if they have plugins signed by them before publishing their + source code. +* Current package signature, which means: + - If you get apk from shadowsocks-android releases or Google Play, this means only apk signed by + @madeye will be recognized as trusted. + - If you get apk from a third-party fork, all plugins from that developer will get recognized as + trusted automatically even if its source code isn't available anywhere online. + +A warning will be shown for untrusted plugins. No arbitrary restrictions will be applied. + +# Plugin platform versioning + +In order to be able to identify compatible and incompatible plugins, [Semantic + Versioning](http://semver.org/) will be used. + +>Given a version number MAJOR.MINOR.PATCH, increment the: +> +>1. MAJOR version when you make incompatible API changes, +>2. MINOR version when you add functionality in a backwards-compatible manner, and +>3. PATCH version when you make backwards-compatible bug fixes. + +Plugin app must include this in their application tag: (which should be automatically included if + you are using our library) + +``` + +``` diff --git a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java index ec73be4b..e716d2cb 100644 --- a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java +++ b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java @@ -26,7 +26,7 @@ public final class PluginContract { /** * Activity Action: Used for HelpActivity or HelpCallback. * - * Constant Value: "com.github.shadowsocks.plugin.ACTION_CONFIGURE" + * Constant Value: "com.github.shadowsocks.plugin.ACTION_HELP" */ public static final String ACTION_HELP = "com.github.shadowsocks.plugin.ACTION_HELP"; @@ -39,7 +39,7 @@ public final class PluginContract { */ public static final String EXTRA_ENTRY = "com.github.shadowsocks.plugin.EXTRA_ENTRY"; /** - * The lookup key for a string that provides the whole command as a string. + * The lookup key for a string that provides the options as a string. * * Example: "obfs=http;obfs-host=www.baidu.com" * @@ -69,7 +69,7 @@ public final class PluginContract { */ public static final String METADATA_KEY_DEFAULT_CONFIG = "com.github.shadowsocks.plugin.default_config"; - static final String METHOD_GET_EXECUTABLE = "shadowsocks:getExecutable"; + public static final String METHOD_GET_EXECUTABLE = "shadowsocks:getExecutable"; /** ConfigurationActivity result: fallback to manual edit mode. */ public static final int RESULT_FALLBACK = 1; diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala index ff618ade..3759c868 100644 --- a/plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/ConfigurationActivity.scala @@ -12,9 +12,13 @@ import android.content.Intent * ... * <application> * ... - * <activity android:name="com.github.shadowsocks.$PLUGIN_ID.ConfigureActivity"> + * <activity android:name=".ConfigureActivity"> * <intent-filter> - * <action android:name="com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_CONFIGURE" /> + * <action android:name="com.github.shadowsocks.plugin.ACTION_CONFIGURE"/> + * <category android:name="android.intent.category.DEFAULT"/> + * <data android:scheme="plugin" + * android:host="com.github.shadowsocks" + * android:path="/$PLUGIN_ID"/> * </intent-filter> * </activity> * ... diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpActivity.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpActivity.scala index 40288b8c..591833d9 100644 --- a/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpActivity.scala +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/HelpActivity.scala @@ -8,9 +8,13 @@ package com.github.shadowsocks.plugin * ... * <application> * ... - * <activity android:name="com.github.shadowsocks.$PLUGIN_ID.HelpActivity"> + * <activity android:name=".HelpActivity"> * <intent-filter> - * <action android:name="com.github.shadowsocks.plugin.$PLUGIN_ID.ACTION_HELP" /> + * <action android:name="com.github.shadowsocks.plugin.ACTION_HELP"/> + * <category android:name="android.intent.category.DEFAULT"/> + * <data android:scheme="plugin" + * android:host="com.github.shadowsocks" + * android:path="/$PLUGIN_ID"/> * </intent-filter> * </activity> * ... From 41da705fc22a93910b58c525438fb74d1bfba26e Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 23 Jan 2017 17:42:21 +0800 Subject: [PATCH 52/98] Plugin library 0.0.1 --- plugin/build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/build.sbt b/plugin/build.sbt index b3e00983..a18108c6 100644 --- a/plugin/build.sbt +++ b/plugin/build.sbt @@ -2,6 +2,6 @@ enablePlugins(AndroidLib) android.useSupportVectors name := "plugin" -version := "0.0.1-SNAPSHOT" +version := "0.0.1" publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath + "/.m2/repository"))) From d8775d4420b3ce202ae1009e98d01ac419968b74 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 23 Jan 2017 18:17:37 +0800 Subject: [PATCH 53/98] Fine tuning icon_list_item_2 padding --- mobile/src/main/res/layout/icon_list_item_2.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/res/layout/icon_list_item_2.xml b/mobile/src/main/res/layout/icon_list_item_2.xml index 521f7842..4b49fa6c 100644 --- a/mobile/src/main/res/layout/icon_list_item_2.xml +++ b/mobile/src/main/res/layout/icon_list_item_2.xml @@ -3,8 +3,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?android:attr/selectableItemBackground" - android:paddingStart="16dp" - android:paddingEnd="16dp"> + android:paddingStart="12dp" + android:paddingEnd="12dp"> From e634083820101edf27a6476b1d5bf8aa8d895b2a Mon Sep 17 00:00:00 2001 From: Max Lv Date: Mon, 23 Jan 2017 18:18:04 +0800 Subject: [PATCH 54/98] Add plugins for sonatype --- plugin/build.sbt | 4 ++-- project/plugins.sbt | 4 ++++ sonatype.sbt | 23 +++++++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 sonatype.sbt diff --git a/plugin/build.sbt b/plugin/build.sbt index a18108c6..15385f69 100644 --- a/plugin/build.sbt +++ b/plugin/build.sbt @@ -1,7 +1,7 @@ -enablePlugins(AndroidLib) +enablePlugins(AndroidLib, Sonatype) android.useSupportVectors name := "plugin" version := "0.0.1" -publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath + "/.m2/repository"))) +// publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath + "/.m2/repository"))) diff --git a/project/plugins.sbt b/project/plugins.sbt index 9199c685..adb19d7c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,3 +3,7 @@ addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.3") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10") addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.8.2") + +addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "1.1") + +addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") // fot sbt-0.13.5 or higher diff --git a/sonatype.sbt b/sonatype.sbt new file mode 100644 index 00000000..70e4a823 --- /dev/null +++ b/sonatype.sbt @@ -0,0 +1,23 @@ +sonatypeProfileName := "com.github.shadowsocks" + +pomExtra in Global := { + https://github.com/shadowsocks/shadowsocks-android + + + GPLv3 + https://www.gnu.org/licenses/gpl-3.0.html + + + + scm:git:git://github.com/shadowsocks/shadowsocks-android.git + scm:git:git@github.com:shadowsocks/shadowsocks-android.git + github.com/shadowsocks/shadowsocks-android + + + + madeye + Max Lv + https://github.com/madeye/ + + +} From 1cec7299e8eee65221709b2c454f7aeb06f633eb Mon Sep 17 00:00:00 2001 From: Max Lv Date: Mon, 23 Jan 2017 18:28:52 +0800 Subject: [PATCH 55/98] Clean up --- plugin/build.sbt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugin/build.sbt b/plugin/build.sbt index 15385f69..42df4257 100644 --- a/plugin/build.sbt +++ b/plugin/build.sbt @@ -1,7 +1,5 @@ -enablePlugins(AndroidLib, Sonatype) +enablePlugins(AndroidLib) android.useSupportVectors name := "plugin" version := "0.0.1" - -// publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath + "/.m2/repository"))) From 04e0cb02b54efdc589254ca8ce6b46bb6bc9bf7f Mon Sep 17 00:00:00 2001 From: Max Lv Date: Mon, 23 Jan 2017 19:17:56 +0800 Subject: [PATCH 56/98] Bump version --- mobile/build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/build.sbt b/mobile/build.sbt index be511b5d..9ee9f4a8 100644 --- a/mobile/build.sbt +++ b/mobile/build.sbt @@ -3,7 +3,7 @@ android.useSupportVectors name := "shadowsocks" version := "4.0.0" -versionCode := Some(174) +versionCode := Some(175) proguardOptions ++= "-keep class com.github.shadowsocks.System { *; }" :: From dadeffa9039468b4a0d47c81e97feff2e6be3173 Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 24 Jan 2017 16:58:20 +0800 Subject: [PATCH 57/98] Rename System to JniHelper to rename naming conflicts with `java.lang.System` --- mobile/build.sbt | 2 +- .../{System.java => JniHelper.java} | 8 ++++---- mobile/src/main/jni/Android.mk | 6 +++--- .../main/jni/{system.cpp => jni-helper.cpp} | 18 +++++++++--------- .../shadowsocks/ShadowsocksVpnService.scala | 2 +- .../shadowsocks/ShadowsocksVpnThread.scala | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) rename mobile/src/main/java/com/github/shadowsocks/{System.java => JniHelper.java} (91%) rename mobile/src/main/jni/{system.cpp => jni-helper.cpp} (85%) diff --git a/mobile/build.sbt b/mobile/build.sbt index 9ee9f4a8..ce4f4c03 100644 --- a/mobile/build.sbt +++ b/mobile/build.sbt @@ -6,7 +6,7 @@ version := "4.0.0" versionCode := Some(175) proguardOptions ++= - "-keep class com.github.shadowsocks.System { *; }" :: + "-keep class com.github.shadowsocks.JniHelper { *; }" :: "-dontwarn com.google.android.gms.internal.**" :: "-dontwarn com.j256.ormlite.**" :: "-dontwarn okio.**" :: diff --git a/mobile/src/main/java/com/github/shadowsocks/System.java b/mobile/src/main/java/com/github/shadowsocks/JniHelper.java similarity index 91% rename from mobile/src/main/java/com/github/shadowsocks/System.java rename to mobile/src/main/java/com/github/shadowsocks/JniHelper.java index ab167c27..a042a9f2 100755 --- a/mobile/src/main/java/com/github/shadowsocks/System.java +++ b/mobile/src/main/java/com/github/shadowsocks/JniHelper.java @@ -38,12 +38,12 @@ package com.github.shadowsocks; -public class System { +public class JniHelper { static { - java.lang.System.loadLibrary("system"); + System.loadLibrary("jni-helper"); } public static native int exec(String cmd); - public static native int sendfd(int fd, String path); - public static native void jniclose(int fd); + public static native int sendFd(int fd, String path); + public static native void close(int fd); } diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index 6fbec5fc..45d3b96d 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -388,16 +388,16 @@ LOCAL_LDLIBS := -llog include $(BUILD_SHARED_EXECUTABLE) ######################################################## -## system +## jni-helper ######################################################## include $(CLEAR_VARS) -LOCAL_MODULE:= system +LOCAL_MODULE:= jni-helper LOCAL_C_INCLUDES:= $(LOCAL_PATH)/libancillary -LOCAL_SRC_FILES:= system.cpp +LOCAL_SRC_FILES:= jni-helper.cpp LOCAL_LDLIBS := -ldl -llog diff --git a/mobile/src/main/jni/system.cpp b/mobile/src/main/jni/jni-helper.cpp similarity index 85% rename from mobile/src/main/jni/system.cpp rename to mobile/src/main/jni/jni-helper.cpp index c5e2bc8c..30b118f5 100644 --- a/mobile/src/main/jni/system.cpp +++ b/mobile/src/main/jni/jni-helper.cpp @@ -17,7 +17,7 @@ #define LOGW(...) do { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); } while(0) #define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0) -jint Java_com_github_shadowsocks_system_exec(JNIEnv *env, jobject thiz, jstring cmd) { +jint Java_com_github_shadowsocks_jnihelper_exec(JNIEnv *env, jobject thiz, jstring cmd) { const char *cmd_str = env->GetStringUTFChars(cmd, 0); pid_t pid; @@ -40,11 +40,11 @@ jint Java_com_github_shadowsocks_system_exec(JNIEnv *env, jobject thiz, jstring return 1; } -void Java_com_github_shadowsocks_system_jniclose(JNIEnv *env, jobject thiz, jint fd) { +void Java_com_github_shadowsocks_jnihelper_close(JNIEnv *env, jobject thiz, jint fd) { close(fd); } -jint Java_com_github_shadowsocks_system_sendfd(JNIEnv *env, jobject thiz, jint tun_fd, jstring path) { +jint Java_com_github_shadowsocks_jnihelper_sendfd(JNIEnv *env, jobject thiz, jint tun_fd, jstring path) { int fd; struct sockaddr_un addr; const char *sock_str = env->GetStringUTFChars(path, 0); @@ -75,15 +75,15 @@ jint Java_com_github_shadowsocks_system_sendfd(JNIEnv *env, jobject thiz, jint t return 0; } -static const char *classPathName = "com/github/shadowsocks/System"; +static const char *classPathName = "com/github/shadowsocks/JniHelper"; static JNINativeMethod method_table[] = { - { "jniclose", "(I)V", - (void*) Java_com_github_shadowsocks_system_jniclose }, - { "sendfd", "(ILjava/lang/String;)I", - (void*) Java_com_github_shadowsocks_system_sendfd }, + { "close", "(I)V", + (void*) Java_com_github_shadowsocks_jnihelper_close }, + { "sendFd", "(ILjava/lang/String;)I", + (void*) Java_com_github_shadowsocks_jnihelper_sendfd }, { "exec", "(Ljava/lang/String;)I", - (void*) Java_com_github_shadowsocks_system_exec } + (void*) Java_com_github_shadowsocks_jnihelper_exec } }; diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index d8d37808..dcdf80be 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -291,7 +291,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { var tries = 1 while (tries < 5) { Thread.sleep(1000 * tries) - if (System.sendfd(fd, new File(getFilesDir, "sock_path").getAbsolutePath) != -1) { + if (JniHelper.sendFd(fd, new File(getFilesDir, "sock_path").getAbsolutePath) != -1) { return true } tries += 1 diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala index d34a60c0..42eddfd7 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala @@ -92,7 +92,7 @@ class ShadowsocksVpnThread(service: ShadowsocksVpnService) extends Thread { val ret = service.protect(fd) // Trick to close file decriptor - System.jniclose(fd) + JniHelper.close(fd) if (ret) { output.write(0) From 30c8c7e6eb0d8e0529898517fe923e13ecd8d902 Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 24 Jan 2017 16:58:55 +0800 Subject: [PATCH 58/98] Fix wrong thread error in binderDied --- .../src/main/scala/com/github/shadowsocks/MainActivity.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index 51ea18ba..d55e1a0c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -172,11 +172,11 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe private def addDisableNatToSnackbar(snackbar: Snackbar) = snackbar.setAction(R.string.switch_to_vpn, (_ => if (state == State.STOPPED) app.editor.putBoolean(Key.isNAT, false).apply()): View.OnClickListener) - override def binderDied() { + override def binderDied(): Unit = handler.post(() => { detachService() app.crashRecovery() attachService(callback) - } + }) override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = resultCode match { case Activity.RESULT_OK => bgService.use(app.profileId) From 8d1f2da1f075957a1d87d409a8345d29c590b648 Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 24 Jan 2017 18:44:31 +0800 Subject: [PATCH 59/98] Implement SIGTERM in jni-helper --- .../com/github/shadowsocks/JniHelper.java | 25 +++++--- mobile/src/main/jni/jni-helper.cpp | 63 +++++++++++-------- .../github/shadowsocks/GuardedProcess.scala | 15 ++++- 3 files changed, 67 insertions(+), 36 deletions(-) diff --git a/mobile/src/main/java/com/github/shadowsocks/JniHelper.java b/mobile/src/main/java/com/github/shadowsocks/JniHelper.java index a042a9f2..dfce9080 100755 --- a/mobile/src/main/java/com/github/shadowsocks/JniHelper.java +++ b/mobile/src/main/java/com/github/shadowsocks/JniHelper.java @@ -38,12 +38,23 @@ package com.github.shadowsocks; -public class JniHelper { - static { - System.loadLibrary("jni-helper"); - } +import android.os.Build; +import android.system.ErrnoException; - public static native int exec(String cmd); - public static native int sendFd(int fd, String path); - public static native void close(int fd); +public class JniHelper { + static { + System.loadLibrary("jni-helper"); + } + + @Deprecated // Use Process.destroy() since API 24 + public static void sigtermCompat(Process process) throws Exception { + if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!"); + int errno = sigterm(process); + if (errno != 0) throw Build.VERSION.SDK_INT >= 21 + ? new ErrnoException("kill", errno) : new Exception("kill failed: " + errno); + } + + private static native int sigterm(Process process); + public static native int sendFd(int fd, String path); + public static native void close(int fd); } diff --git a/mobile/src/main/jni/jni-helper.cpp b/mobile/src/main/jni/jni-helper.cpp index 30b118f5..cbba6dbc 100644 --- a/mobile/src/main/jni/jni-helper.cpp +++ b/mobile/src/main/jni/jni-helper.cpp @@ -1,11 +1,14 @@ -#define LOG_TAG "Shadowsocks" +#define LOG_TAG "JniHelper" #include "jni.h" #include +#include + #include #include #include #include +#include #include #include @@ -16,28 +19,26 @@ #define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0) #define LOGW(...) do { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); } while(0) #define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0) +#define THROW(env, clazz, msg) do { env->ThrowNew(env->FindClass(clazz), msg); } while (0) -jint Java_com_github_shadowsocks_jnihelper_exec(JNIEnv *env, jobject thiz, jstring cmd) { - const char *cmd_str = env->GetStringUTFChars(cmd, 0); +static jclass ProcessImpl; +static jfieldID ProcessImpl_pid; - pid_t pid; +static int sdk_version() { + char version[PROP_VALUE_MAX + 1]; + __system_property_get("ro.build.version.sdk", version); + return atoi(version); +} - /* Fork off the parent process */ - pid = fork(); - if (pid < 0) { - env->ReleaseStringUTFChars(cmd, cmd_str); +jint Java_com_github_shadowsocks_jnihelper_sigterm(JNIEnv *env, jobject thiz, jobject process) { + if (!env->IsInstanceOf(process, ProcessImpl)) { + THROW(env, "java/lang/ClassCastException", + "Unsupported process object. Only java.lang.ProcessManager$ProcessImpl is accepted."); return -1; } - - if (pid > 0) { - env->ReleaseStringUTFChars(cmd, cmd_str); - return pid; - } - - execl("/system/bin/sh", "sh", "-c", cmd_str, NULL); - env->ReleaseStringUTFChars(cmd, cmd_str); - - return 1; + jint pid = env->GetIntField(process, ProcessImpl_pid); + // Suppress "No such process" errors. We just want the process killed. It's fine if it's already killed. + return kill(pid, SIGTERM) == -1 && errno != ESRCH ? errno : 0; } void Java_com_github_shadowsocks_jnihelper_close(JNIEnv *env, jobject thiz, jint fd) { @@ -82,8 +83,8 @@ static JNINativeMethod method_table[] = { (void*) Java_com_github_shadowsocks_jnihelper_close }, { "sendFd", "(ILjava/lang/String;)I", (void*) Java_com_github_shadowsocks_jnihelper_sendfd }, - { "exec", "(Ljava/lang/String;)I", - (void*) Java_com_github_shadowsocks_jnihelper_exec } + { "sigterm", "(Ljava/lang/Process;)I", + (void*) Java_com_github_shadowsocks_jnihelper_sigterm } }; @@ -139,20 +140,30 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { jint result = -1; JNIEnv* env = NULL; - LOGI("JNI_OnLoad"); - - if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) { - LOGE("ERROR: GetEnv failed"); + if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK) { + THROW(env, "java/lang/RuntimeException", "GetEnv failed"); goto bail; } env = uenv.env; if (registerNatives(env) != JNI_TRUE) { - LOGE("ERROR: registerNatives failed"); + THROW(env, "java/lang/RuntimeException", "registerNativeMethods failed"); goto bail; } - result = JNI_VERSION_1_4; + if (sdk_version() < 24) { + if (!(ProcessImpl = env->FindClass("java/lang/ProcessManager$ProcessImpl"))) { + THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl not found"); + goto bail; + } + ProcessImpl = (jclass) env->NewGlobalRef((jobject) ProcessImpl); + if (!(ProcessImpl_pid = env->GetFieldID(ProcessImpl, "pid", "I"))) { + THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.pid not found"); + goto bail; + } + } + + result = JNI_VERSION_1_6; bail: return result; diff --git a/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala b/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala index b995c8a7..7ff41694 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala @@ -24,6 +24,7 @@ import java.io._ import java.lang.System.currentTimeMillis import java.util.concurrent.Semaphore +import android.os.Build import android.util.Log import com.github.shadowsocks.utils.CloseUtils._ import com.github.shadowsocks.utils.Commandline @@ -89,7 +90,7 @@ class GuardedProcess(cmd: String*) { } catch { case _: InterruptedException => if (BuildConfig.DEBUG) Log.d(TAG, "thread interrupt, destroy process: " + Commandline.toString(cmd)) - process.destroy() + destroyProcess() case e: IOException => ioException = e } finally semaphore.release() }, "GuardThread-" + cmd.head) @@ -105,16 +106,24 @@ class GuardedProcess(cmd: String*) { def destroy() { isDestroyed = true guardThread.interrupt() - process.destroy() + destroyProcess() try guardThread.join() catch { case _: InterruptedException => } } + private def destroyProcess() { + if (Build.VERSION.SDK_INT < 24) { + JniHelper.sigtermCompat(process) + process.waitFor() + } + process.destroy() + } + def restart() { this.synchronized { isRestart = true - process.destroy() + destroyProcess() } } From 61a926ff5ec2426031865326f4f28ea2571f4ce5 Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 24 Jan 2017 22:42:24 +0800 Subject: [PATCH 60/98] Update plugin doc --- plugin/README.md | 2 +- plugin/build.sbt | 27 +++++++++++++++++++++++++++ sonatype.sbt | 23 ----------------------- 3 files changed, 28 insertions(+), 24 deletions(-) delete mode 100644 sonatype.sbt diff --git a/plugin/README.md b/plugin/README.md index 05cbe427..601744f4 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -37,7 +37,7 @@ First you need to add this library to your dependencies. This library is written and it's most convenient to use it with SBT: ```scala -libraryDependencies += "com.github.shadowsocks" %% "plugin" % "0.0.1-SNAPSHOT" +libraryDependencies += "com.github.shadowsocks" %% "plugin" % "0.0.1" ``` ### Native binary configuration diff --git a/plugin/build.sbt b/plugin/build.sbt index 42df4257..4e648f36 100644 --- a/plugin/build.sbt +++ b/plugin/build.sbt @@ -3,3 +3,30 @@ android.useSupportVectors name := "plugin" version := "0.0.1" + +pomExtra in Global := { + https://github.com/shadowsocks/shadowsocks-android + + + GPLv3 + https://www.gnu.org/licenses/gpl-3.0.html + + + + scm:git:git://github.com/shadowsocks/shadowsocks-android.git + scm:git:git@github.com:shadowsocks/shadowsocks-android.git + github.com/shadowsocks/shadowsocks-android + + + + madeye + Max Lv + https://github.com/madeye + + + Mygod + Mygod Studio + https://github.com/Mygod + + +} diff --git a/sonatype.sbt b/sonatype.sbt deleted file mode 100644 index 70e4a823..00000000 --- a/sonatype.sbt +++ /dev/null @@ -1,23 +0,0 @@ -sonatypeProfileName := "com.github.shadowsocks" - -pomExtra in Global := { - https://github.com/shadowsocks/shadowsocks-android - - - GPLv3 - https://www.gnu.org/licenses/gpl-3.0.html - - - - scm:git:git://github.com/shadowsocks/shadowsocks-android.git - scm:git:git@github.com:shadowsocks/shadowsocks-android.git - github.com/shadowsocks/shadowsocks-android - - - - madeye - Max Lv - https://github.com/madeye/ - - -} From e81c4f257e2cfaf0016ceb66c571a591448b7260 Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 24 Jan 2017 22:52:54 +0800 Subject: [PATCH 61/98] Bump plugin lib version --- plugin/build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/build.sbt b/plugin/build.sbt index 4e648f36..c902a8b9 100644 --- a/plugin/build.sbt +++ b/plugin/build.sbt @@ -2,7 +2,7 @@ enablePlugins(AndroidLib) android.useSupportVectors name := "plugin" -version := "0.0.1" +version := "0.0.2-SNAPSHOT" pomExtra in Global := { https://github.com/shadowsocks/shadowsocks-android From 55667c9e763eea35b078e187183d4d1e597319b9 Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 24 Jan 2017 22:53:24 +0800 Subject: [PATCH 62/98] Add getOrDefault method for API 23- in plugin lib --- .../java/com/github/shadowsocks/plugin/PluginOptions.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java index 7794b5d4..7aa436db 100644 --- a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java +++ b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java @@ -24,6 +24,14 @@ public final class PluginOptions extends HashMap { super(initialCapacity, loadFactor); } + // TODO: this method is not needed since API 24 + public String getOrDefault(Object key, String defaultValue) { + String v; + return (((v = get(key)) != null) || containsKey(key)) + ? v + : defaultValue; + } + private PluginOptions(String options, boolean parseId) { this(); if (TextUtils.isEmpty(options)) return; From 8de1dc1f7afa6ca4d4c04ba2b493f6afc7bf573f Mon Sep 17 00:00:00 2001 From: Max Lv Date: Tue, 24 Jan 2017 22:59:18 +0800 Subject: [PATCH 63/98] Add signature --- .../github/shadowsocks/plugin/PluginManager.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala index 935a0006..7f8f536c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala @@ -50,6 +50,20 @@ object PluginManager { |RSo6EooQ7+NBejOXysqIF1q0BJs8Y5s/CaTOmgbL7uPCkzArB6SS/hzXgDk5gw6v |wkGeOtzcj1DlbUTvt1s5GlnwBTGUmkbLx+YUje+n+IBgMbohLUDYBtUHylRVgMsc |1WS67kDqeJiiQZvrxvyW6CZZ/MIGI+uAkkj3DqJpaZirkwPgvpcOIrjZy0uFvQM= + """, Base64.DEFAULT)) + + new Signature(Base64.decode( // @madeye + """ + |MIICQzCCAaygAwIBAgIETV9OhjANBgkqhkiG9w0BAQUFADBmMQswCQYDVQQGEwJjbjERMA8GA1UE + |CBMIU2hhbmdoYWkxDzANBgNVBAcTBlB1ZG9uZzEUMBIGA1UEChMLRnVkYW4gVW5pdi4xDDAKBgNV + |BAsTA1BQSTEPMA0GA1UEAxMGTWF4IEx2MB4XDTExMDIxOTA1MDA1NFoXDTM2MDIxMzA1MDA1NFow + |ZjELMAkGA1UEBhMCY24xETAPBgNVBAgTCFNoYW5naGFpMQ8wDQYDVQQHEwZQdWRvbmcxFDASBgNV + |BAoTC0Z1ZGFuIFVuaXYuMQwwCgYDVQQLEwNQUEkxDzANBgNVBAMTBk1heCBMdjCBnzANBgkqhkiG + |9w0BAQEFAAOBjQAwgYkCgYEAq6lA8LqdeEI+es9SDX85aIcx8LoL3cc//iRRi+2mFIWvzvZ+bLKr + |4Wd0rhu/iU7OeMm2GvySFyw/GdMh1bqh5nNPLiRxAlZxpaZxLOdRcxuvh5Nc5yzjM+QBv8ECmuvu + |AOvvT3UDmA0AMQjZqSCmxWIxc/cClZ/0DubreBo2st0CAwEAATANBgkqhkiG9w0BAQUFAAOBgQAQ + |Iqonxpwk2ay+Dm5RhFfZyG9SatM/JNFx2OdErU16WzuK1ItotXGVJaxCZv3u/tTwM5aaMACGED5n + |AvHaDGCWynY74oDAopM4liF/yLe1wmZDu6Zo/7fXrH+T03LBgj2fcIkUfN1AA4dvnBo8XWAm9VrI + |1iNuLIssdhDz3IL9Yg== """, Base64.DEFAULT)) private var receiver: BroadcastReceiver = _ From 3b93302539859582f3a6aa0b57b73392725e5717 Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 31 Jan 2017 01:28:16 +0800 Subject: [PATCH 64/98] Update dependencies --- mobile/build.sbt | 8 ++++---- project/plugins.sbt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mobile/build.sbt b/mobile/build.sbt index ce4f4c03..d3cad7c8 100644 --- a/mobile/build.sbt +++ b/mobile/build.sbt @@ -19,20 +19,20 @@ val playServicesVersion = "10.0.1" resolvers += Resolver.jcenterRepo libraryDependencies ++= "com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" :: - "com.evernote" % "android-job" % "1.1.4" :: + "com.evernote" % "android-job" % "1.1.5" :: "com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" :: "com.google.android.gms" % "play-services-ads" % playServicesVersion :: "com.google.android.gms" % "play-services-analytics" % playServicesVersion :: "com.google.android.gms" % "play-services-gcm" % playServicesVersion :: "com.j256.ormlite" % "ormlite-android" % "5.0" :: "com.mikepenz" % "crossfader" % "1.5.0" :: - "com.mikepenz" % "fastadapter" % "2.1.5" :: + "com.mikepenz" % "fastadapter" % "2.1.6" :: "com.mikepenz" % "iconics-core" % "2.8.2" :: "com.mikepenz" % "materialdrawer" % "5.8.1" :: "com.mikepenz" % "materialize" % "1.0.0" :: - "com.squareup.okhttp3" % "okhttp" % "3.5.0" :: + "com.squareup.okhttp3" % "okhttp" % "3.6.0" :: "com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2" :: - "dnsjava" % "dnsjava" % "2.1.7" :: + "dnsjava" % "dnsjava" % "2.1.8" :: "eu.chainfire" % "libsuperuser" % "1.0.0.201608240809" :: "net.glxn.qrgen" % "android" % "2.0" :: Nil diff --git a/project/plugins.sbt b/project/plugins.sbt index adb19d7c..b135a886 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ -addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.3") +addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.4") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10") From 14fd86c716807d7af59a593f2780b47ee7212cac Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 31 Jan 2017 20:15:25 +0800 Subject: [PATCH 65/98] Update android support library --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index bc8d1f61..bd13b300 100644 --- a/build.sbt +++ b/build.sbt @@ -18,7 +18,7 @@ lazy val commonSettings = Seq( typedResources := false ) -val supportLibsVersion = "25.1.0" +val supportLibsVersion = "25.1.1" lazy val root = Project(id = "shadowsocks-android", base = file(".")) .settings(commonSettings) .aggregate(plugin, mobile) From 4996d4fc5273439af10092922f52a64ee26ea6cb Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 2 Feb 2017 17:22:27 +0800 Subject: [PATCH 66/98] Add a known issue for Huawei's buggy ROM --- .github/faq.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/faq.md b/.github/faq.md index 75726121..c6a9e03d 100644 --- a/.github/faq.md +++ b/.github/faq.md @@ -29,7 +29,8 @@ The exclamation mark in the Wi-Fi/cellular icon appears because the system fails 1. Broken VPNService implementation, especially for IPv6; 2. Aggressive (or called broken) background service killing policy. -Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772) +* Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772) +* Known issue for Huawei: [#1106](https://github.com/shadowsocks/shadowsocks-android/issues/1106) ### How to pause Shadowsocks service? From 33c087f13fde860944c17c6f3bb539d68a392d32 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 2 Feb 2017 20:58:30 +0800 Subject: [PATCH 67/98] Refine FAQ --- .github/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/faq.md b/.github/faq.md index c6a9e03d..848e607b 100644 --- a/.github/faq.md +++ b/.github/faq.md @@ -30,7 +30,7 @@ The exclamation mark in the Wi-Fi/cellular icon appears because the system fails 2. Aggressive (or called broken) background service killing policy. * Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772) -* Known issue for Huawei: [#1106](https://github.com/shadowsocks/shadowsocks-android/issues/1106) +* Fixes for Huawei: [#1091 (comment)](https://github.com/shadowsocks/shadowsocks-android/issues/1091#issuecomment-276949836) ### How to pause Shadowsocks service? From b397ab2bbdfdedf3489c54e996bb8d9c50398ed7 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 2 Feb 2017 21:26:16 +0800 Subject: [PATCH 68/98] Support pasting multiple lines --- mobile/src/main/res/layout/dialog_acl_rule.xml | 3 +-- .../shadowsocks/acl/CustomRulesFragment.scala | 17 ++++++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/mobile/src/main/res/layout/dialog_acl_rule.xml b/mobile/src/main/res/layout/dialog_acl_rule.xml index 6750be86..dfb27e8d 100644 --- a/mobile/src/main/res/layout/dialog_acl_rule.xml +++ b/mobile/src/main/res/layout/dialog_acl_rule.xml @@ -10,6 +10,5 @@ + android:inputType="textNoSuggestions|textMultiLine"/> diff --git a/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala b/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala index 5426c3ab..0bf6a523 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala @@ -20,6 +20,7 @@ import com.github.shadowsocks.widget.UndoSnackbarManager import com.github.shadowsocks.{MainActivity, R, ToolbarFragment} import scala.collection.mutable +import scala.io.Source /** * @author Mygod @@ -144,8 +145,12 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi apply() index } else -1 - def addToProxy(input: String): Int = try addSubnet(Subnet.fromString(input)) catch { - case _: IllegalArgumentException => addHostname(input) + def addToProxy(input: String): Int = { + val acl = new Acl().fromSource(Source.fromString(input), defaultBypass = true) + var result = -1 + for (hostname <- acl.proxyHostnames) result = addHostname(hostname) + if (acl.bypass) for (subnet <- acl.subnets) result = addSubnet(subnet) + result } def addFromTemplate(template: Int, text: CharSequence): Int = template match { case GENERIC => addToProxy(text.toString) @@ -298,13 +303,7 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi .create().show() true case R.id.action_import => - try { - val items = clipboard.getPrimaryClip.getItemAt(0).getText.toString.split("\n") - if (items.nonEmpty) { - items.foreach(adapter.addToProxy) - return true - } - } catch { + try if (adapter.addToProxy(clipboard.getPrimaryClip.getItemAt(0).getText.toString) != -1) return true catch { case _: Exception => } Snackbar.make(getActivity.findViewById(R.id.snackbar), R.string.action_import_err, Snackbar.LENGTH_LONG).show() From 88319ecac73c30f748a7f43058fc059a930e1175 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 2 Feb 2017 21:43:01 +0800 Subject: [PATCH 69/98] Suggest to use fully qualified name for authorities in doc --- plugin/README.md | 2 +- plugin/doc.md | 2 +- .../com/github/shadowsocks/plugin/NativePluginProvider.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/README.md b/plugin/README.md index 601744f4..f3a8df6c 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -88,7 +88,7 @@ Then add it to your manifest: ... + android:authorities="$FULLY_QUALIFIED_NAME_OF_YOUR_CONTENTPROVIDER"> diff --git a/plugin/doc.md b/plugin/doc.md index 4caebaac..b652eea3 100644 --- a/plugin/doc.md +++ b/plugin/doc.md @@ -153,7 +153,7 @@ This corresponds to `com.github.shadowsocks.plugin.NativePluginProvider` in the ... + android:authorities="$FULLY_QUALIFIED_NAME_OF_YOUR_CONTENTPROVIDER"> diff --git a/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala b/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala index 982c7f37..412ab0c1 100644 --- a/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala +++ b/plugin/src/main/scala/com/github/shadowsocks/plugin/NativePluginProvider.scala @@ -15,7 +15,7 @@ import android.os.{Bundle, ParcelFileDescriptor} * <application> * ... * <provider android:name="com.github.shadowsocks.$PLUGIN_ID.BinaryProvider" - * android:authorities="com.github.shadowsocks.plugin.$PLUGIN_ID"> + * android:authorities="com.github.shadowsocks.plugin.$PLUGIN_ID.BinaryProvider"> * <intent-filter> * <category android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" /> * </intent-filter> From 7af26d1a4bc0054b278d300ff1452bdae11ba79c Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Jan 2017 18:07:50 +0800 Subject: [PATCH 70/98] Increase minimum sync time to let shadowsocks set up --- .../src/main/scala/com/github/shadowsocks/acl/AclSyncJob.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/acl/AclSyncJob.scala b/mobile/src/main/scala/com/github/shadowsocks/acl/AclSyncJob.scala index 450ec903..54129aef 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/acl/AclSyncJob.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/acl/AclSyncJob.scala @@ -36,7 +36,7 @@ object AclSyncJob { final val TAG = "AclSyncJob" def schedule(route: String): Int = new JobRequest.Builder(AclSyncJob.TAG + ':' + route) - .setExecutionWindow(1, TimeUnit.DAYS.toMillis(28)) + .setExecutionWindow(TimeUnit.SECONDS.toMillis(10), TimeUnit.DAYS.toMillis(28)) .setRequirementsEnforced(true) .setRequiredNetworkType(JobRequest.NetworkType.UNMETERED) .setRequiresCharging(true) From ee08f02660cc1061f7b3e419cff4a59eecef1b4f Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Jan 2017 18:14:34 +0800 Subject: [PATCH 71/98] Use remote DNS for VPN --- .../scala/com/github/shadowsocks/ShadowsocksNatService.scala | 2 +- .../scala/com/github/shadowsocks/ShadowsocksVpnService.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index 07279289..f5cb3b36 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -74,7 +74,7 @@ class ShadowsocksNatService extends BaseService { "-t", "10", "-b", "127.0.0.1", "-l", (profile.localPort + 63).toString, - "-L", if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53", + "-L", profile.remoteDns + ":53", "-c", buildShadowsocksConfig("ss-tunnel-nat.conf")) if (profile.udpdns) cmd.append("-u") diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index dcdf80be..8b06aee4 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -189,7 +189,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { "-t", "10", "-b", "127.0.0.1", "-l", (profile.localPort + 63).toString, - "-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53", + "-L" , profile.remoteDns + ":53", "-c", buildShadowsocksConfig("ss-tunnel-vpn.conf")).start() def startDnsDaemon() { @@ -221,7 +221,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { .setMtu(VPN_MTU) .addAddress(PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), 24) - builder.addDnsServer("8.8.8.8") + builder.addDnsServer(profile.remoteDns) if (profile.ipv6) { builder.addAddress(PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "1"), 126) From df1e40d60b54cc6240e7e228b0715269d65b8861 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 2 Feb 2017 21:51:13 +0800 Subject: [PATCH 72/98] Refine Subnet --- .../shadowsocks/ShadowsocksVpnService.scala | 13 ++++------- .../com/github/shadowsocks/acl/Subnet.scala | 23 ++++++------------- .../com/github/shadowsocks/utils/Utils.scala | 9 ++++++++ 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 8b06aee4..48626a84 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -23,14 +23,13 @@ package com.github.shadowsocks import java.io.File import java.util.Locale -import android.annotation.SuppressLint import android.content._ import android.content.pm.PackageManager.NameNotFoundException import android.net.VpnService import android.os._ import android.util.Log import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.acl.{Acl, AclSyncJob} +import com.github.shadowsocks.acl.{Acl, AclSyncJob, Subnet} import com.github.shadowsocks.database.Profile import com.github.shadowsocks.utils._ @@ -212,7 +211,6 @@ class ShadowsocksVpnService extends VpnService with BaseService { pdnsdProcess = new GuardedProcess(cmd: _*).start() } - @SuppressLint(Array("NewApi")) def startVpn(): Int = { val builder = new Builder() @@ -249,15 +247,12 @@ class ShadowsocksVpnService extends VpnService with BaseService { if (profile.route == Acl.ALL || profile.route == Acl.BYPASS_CHN) { builder.addRoute("0.0.0.0", 0) } else { - val privateList = getResources.getStringArray(R.array.bypass_private_route) - privateList.foreach(cidr => { - val addr = cidr.split('/') - builder.addRoute(addr(0), addr(1).toInt) + getResources.getStringArray(R.array.bypass_private_route).foreach(cidr => { + val subnet = Subnet.fromString(cidr) + builder.addRoute(subnet.address.getHostAddress, subnet.prefixSize) }) } - builder.addRoute("8.8.0.0", 16) - conn = builder.establish() if (conn == null) throw new NullConnectionException diff --git a/mobile/src/main/scala/com/github/shadowsocks/acl/Subnet.scala b/mobile/src/main/scala/com/github/shadowsocks/acl/Subnet.scala index d7d4cc0b..8d7c140c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/acl/Subnet.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/acl/Subnet.scala @@ -1,6 +1,6 @@ package com.github.shadowsocks.acl -import java.net.{Inet4Address, Inet6Address, InetAddress} +import java.net.InetAddress import com.github.shadowsocks.utils.Utils @@ -9,16 +9,11 @@ import com.github.shadowsocks.utils.Utils */ @throws[IllegalArgumentException] class Subnet(val address: InetAddress, val prefixSize: Int) extends Comparable[Subnet] { - if (prefixSize < 0) throw new IllegalArgumentException - address match { - case _: Inet4Address => if (prefixSize > 32) throw new IllegalArgumentException - case _: Inet6Address => if (prefixSize > 128) throw new IllegalArgumentException - } + private def addressLength = address.getAddress.length << 3 + if (prefixSize < 0 || prefixSize > addressLength) throw new IllegalArgumentException - override def toString: String = if (address match { - case _: Inet4Address => prefixSize == 32 - case _: Inet6Address => prefixSize == 128 - }) address.getHostAddress else address.getHostAddress + '/' + prefixSize + override def toString: String = + if (prefixSize == addressLength) address.getHostAddress else address.getHostAddress + '/' + prefixSize override def compareTo(that: Subnet): Int = { val addrThis = address.getAddress @@ -37,13 +32,9 @@ object Subnet { @throws[IllegalArgumentException] def fromString(value: String): Subnet = { val parts = value.split("/") - if (!Utils.isNumeric(parts(0))) throw new IllegalArgumentException() - val addr = InetAddress.getByName(parts(0)) + val addr = Utils.parseNumericAddress(parts(0)) parts.length match { - case 1 => new Subnet(addr, addr match { - case _: Inet4Address => 32 - case _: Inet6Address => 128 - }) + case 1 => new Subnet(addr, addr.getAddress.length << 3) case 2 => new Subnet(addr, parts(1).toInt) case _ => throw new IllegalArgumentException() } diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala index 03e55b85..a97f6f25 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Utils.scala @@ -20,6 +20,7 @@ package com.github.shadowsocks.utils +import java.lang.reflect.InvocationTargetException import java.net._ import java.security.MessageDigest @@ -108,7 +109,15 @@ object Utils { .orElse(resolve(host)) private lazy val isNumericMethod = classOf[InetAddress].getMethod("isNumeric", classOf[String]) + private lazy val parseNumericAddressMethod = classOf[InetAddress].getMethod("parseNumericAddress", classOf[String]) def isNumeric(address: String): Boolean = isNumericMethod.invoke(null, address).asInstanceOf[Boolean] + def parseNumericAddress(address: String): InetAddress = + try parseNumericAddressMethod.invoke(null, address).asInstanceOf[InetAddress] catch { + case exc: InvocationTargetException => throw exc.getCause match { + case null => exc + case cause => cause + } + } /** * If there exists a valid IPv6 interface From f121ea2549edecf769ef15faca6d4b8ee88c7377 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 20 Jan 2017 18:49:14 +0800 Subject: [PATCH 73/98] Prevent crashing when no market is installed --- mobile/src/main/res/xml/pref_profile.xml | 9 +++++---- .../scala/com/github/shadowsocks/ProfilesFragment.scala | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/mobile/src/main/res/xml/pref_profile.xml b/mobile/src/main/res/xml/pref_profile.xml index 80b104e7..e2784a69 100644 --- a/mobile/src/main/res/xml/pref_profile.xml +++ b/mobile/src/main/res/xml/pref_profile.xml @@ -2,13 +2,14 @@ + + - Toast.makeText(getActivity, R.string.add_profile_scanner_not_installed, Toast.LENGTH_LONG).show() - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=tw.com.quickmark"))) + try startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=tw.com.quickmark"))) catch { + case exc: ActivityNotFoundException => exc.printStackTrace() + } } true case R.id.action_import => From 3fa8a0c90c86d4fb08089262ba651a4d0859da00 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 4 Feb 2017 11:46:13 +0800 Subject: [PATCH 74/98] Refine roundings for TrafficMonitor --- .../scala/com/github/shadowsocks/utils/TrafficMonitor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala index d407356c..6e4d7927 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala @@ -45,7 +45,7 @@ object TrafficMonitor { def formatTraffic(size: Long): String = { var n: Double = size var i = -1 - while (n >= 1000) { + while (n >= 999.5) { n /= 1024 i = i + 1 } From b71b27fbdfae7233ba1a094f0495c40199a3445d Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 31 Jan 2017 21:47:56 +0800 Subject: [PATCH 75/98] Use official repo of libsodium --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index e8fc0ec8..69ee8b25 100644 --- a/.gitmodules +++ b/.gitmodules @@ -29,7 +29,7 @@ url = https://android.googlesource.com/platform/external/pcre [submodule "mobile/src/main/jni/libsodium"] path = mobile/src/main/jni/libsodium - url = https://github.com/shadowsocks/libsodium.git + url = https://github.com/jedisct1/libsodium.git [submodule "mobile/src/main/jni/libev"] path = mobile/src/main/jni/libev url = https://github.com/shadowsocks/libev.git From 1e96a13ab1ef85c6880d616eec51f5e6ed5faaea Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 31 Jan 2017 21:51:59 +0800 Subject: [PATCH 76/98] Implement SIP004 https://github.com/shadowsocks/shadowsocks-org/issues/30 --- mobile/src/main/jni/Android.mk | 15 ++++++++++++--- mobile/src/main/jni/shadowsocks-libev | 2 +- mobile/src/main/res/values/arrays.xml | 6 ++++++ mobile/src/main/res/values/strings.xml | 2 -- mobile/src/main/res/xml/pref_profile.xml | 4 ---- .../com/github/shadowsocks/BaseService.scala | 1 - .../github/shadowsocks/database/DBHelper.scala | 15 ++++++--------- .../com/github/shadowsocks/database/Profile.scala | 9 ++------- .../com/github/shadowsocks/utils/Constants.scala | 1 - .../com/github/shadowsocks/utils/Parser.scala | 1 - 10 files changed, 27 insertions(+), 29 deletions(-) diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index 45d3b96d..ed6698e3 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -26,6 +26,7 @@ include $(CLEAR_VARS) SODIUM_SOURCE := \ crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c \ + crypto_aead/xchacha20poly1305/sodium/aead_xchacha20poly1305.c \ crypto_auth/crypto_auth.c \ crypto_auth/hmacsha256/auth_hmacsha256_api.c \ crypto_auth/hmacsha256/cp/hmac_hmacsha256.c \ @@ -48,6 +49,7 @@ SODIUM_SOURCE := \ crypto_core/hsalsa20/core_hsalsa20_api.c \ crypto_core/salsa20/ref/core_salsa20.c \ crypto_core/salsa20/core_salsa20_api.c \ + crypto_core/hchacha20/core_hchacha20.c \ crypto_generichash/crypto_generichash.c \ crypto_generichash/blake2/generichash_blake2_api.c \ crypto_generichash/blake2/ref/blake2-impl.h \ @@ -120,6 +122,7 @@ SODIUM_SOURCE += \ crypto_scalarmult/curve25519/ref10/x25519_ref10.c SODIUM_SOURCE += \ + crypto_pwhash/crypto_pwhash.c \ crypto_pwhash/argon2/argon2-core.c \ crypto_pwhash/argon2/argon2.c \ crypto_pwhash/argon2/argon2-encoding.c \ @@ -329,8 +332,10 @@ include $(BUILD_SHARED_EXECUTABLE) include $(CLEAR_VARS) -SHADOWSOCKS_SOURCES := local.c cache.c udprelay.c encrypt.c \ - utils.c netutils.c json.c jconf.c acl.c http.c tls.c rule.c \ +SHADOWSOCKS_SOURCES := local.c \ + cache.c udprelay.c utils.c netutils.c json.c jconf.c \ + acl.c http.c tls.c rule.c \ + crypto.c aead.c stream.c \ plugin.c \ android.c @@ -364,7 +369,11 @@ include $(BUILD_SHARED_EXECUTABLE) include $(CLEAR_VARS) -SHADOWSOCKS_SOURCES := tunnel.c cache.c udprelay.c encrypt.c utils.c netutils.c json.c jconf.c android.c plugin.c +SHADOWSOCKS_SOURCES := tunnel.c \ + cache.c udprelay.c utils.c netutils.c json.c jconf.c \ + crypto.c aead.c stream.c \ + plugin.c \ + android.c LOCAL_MODULE := ss-tunnel LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES)) diff --git a/mobile/src/main/jni/shadowsocks-libev b/mobile/src/main/jni/shadowsocks-libev index 1c2b6170..c0030f0d 160000 --- a/mobile/src/main/jni/shadowsocks-libev +++ b/mobile/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit 1c2b6170b9ec5443d4a3e211425bc0d5f8e18119 +Subproject commit c0030f0d7d74d1197877e093366bbd2713296aaa diff --git a/mobile/src/main/res/values/arrays.xml b/mobile/src/main/res/values/arrays.xml index 58e8ae88..1961833f 100644 --- a/mobile/src/main/res/values/arrays.xml +++ b/mobile/src/main/res/values/arrays.xml @@ -17,6 +17,9 @@ SALSA20 CHACHA20 CHACHA20-IETF + AES-128-GCM + AES-192-GCM + AES-256-GCM @@ -36,6 +39,9 @@ salsa20 chacha20 chacha20-ietf + aes-128-gcm + aes-192-gcm + aes-256-gcm diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index a8e902f9..38a689a9 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -36,8 +36,6 @@ IPv6 Route Redirect IPv6 traffic to remote - One-time Authentication - Enable one-time authentication Route All Bypass LAN diff --git a/mobile/src/main/res/xml/pref_profile.xml b/mobile/src/main/res/xml/pref_profile.xml index 80b104e7..2ad83064 100644 --- a/mobile/src/main/res/xml/pref_profile.xml +++ b/mobile/src/main/res/xml/pref_profile.xml @@ -36,10 +36,6 @@ android:entryValues="@array/enc_method_value" android:summary="%s" android:title="@string/enc_method"/> - diff --git a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala index 3b3d2ccb..2d418e0d 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -325,7 +325,6 @@ trait BaseService extends Service { .put("server_port", profile.remotePort) .put("password", profile.password) .put("method", profile.method) - if (profile.auth) config.put("auth", true) if (pluginPath != null) config .put("plugin", Commandline.toString(buildPluginCommandLine())) .put("plugin_opts", plugin.toString) diff --git a/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala b/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala index 90e6d4e0..155ddf28 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/database/DBHelper.scala @@ -77,9 +77,6 @@ class DBHelper(val context: Context) } else if (oldVersion < 19) { profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'all' WHERE route IS NULL;") } - if (oldVersion < 10) { - profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN auth SMALLINT;") - } if (oldVersion < 11) { profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;") } @@ -89,9 +86,9 @@ class DBHelper(val context: Context) TableUtils.createTable(connectionSource, classOf[Profile]) profileDao.executeRawNoArgs( "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," + - " udpdns, auth, ipv6, individual) " + - "SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, auth," + - " ipv6, individual FROM `tmp`;") + " udpdns, ipv6, individual) " + + "SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, ipv6," + + " individual FROM `tmp`;") profileDao.executeRawNoArgs("DROP TABLE `tmp`;") profileDao.executeRawNoArgs("COMMIT;") } else if (oldVersion < 13) { @@ -127,10 +124,10 @@ class DBHelper(val context: Context) profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;") TableUtils.createTable(connectionSource, classOf[Profile]) profileDao.executeRawNoArgs( - "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, remoteDns, proxyApps," + - " bypass, udpdns, auth, ipv6, individual, tx, rx, date, userOrder, plugin) " + + "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, remoteDns, " + + "proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, plugin) " + "SELECT id, name, host, localPort, CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, " + - "route, remoteDns, proxyApps, bypass, udpdns, auth, ipv6, individual, tx, rx, date, userOrder, " + + "route, remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " + "CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;") profileDao.executeRawNoArgs("DROP TABLE `tmp`;") profileDao.executeRawNoArgs("COMMIT;") diff --git a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala index 19ffe4d5..b7ad44d7 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala @@ -68,9 +68,6 @@ class Profile { @DatabaseField var udpdns: Boolean = _ - @DatabaseField - var auth: Boolean = _ - @DatabaseField var ipv6: Boolean = _ @@ -96,8 +93,8 @@ class Profile { def nameIsEmpty: Boolean = name == null || name.isEmpty def getName: String = if (nameIsEmpty) formattedAddress else name - override def toString: String = "ss://" + Base64.encodeToString("%s%s:%s@%s:%d".formatLocal(Locale.ENGLISH, - method, if (auth) "-auth" else "", password, host, remotePort).getBytes, Base64.NO_PADDING | Base64.NO_WRAP) + + override def toString: String = "ss://" + Base64.encodeToString("%s:%s@%s:%d".formatLocal(Locale.ENGLISH, + method, password, host, remotePort).getBytes, Base64.NO_PADDING | Base64.NO_WRAP) + (if (TextUtils.isEmpty(name)) "" else '#' + URLEncoder.encode(name, "utf-8")) def isMethodUnsafe: Boolean = "table".equalsIgnoreCase(method) || "rc4".equalsIgnoreCase(method) @@ -114,7 +111,6 @@ class Profile { .putBoolean(Key.proxyApps, proxyApps) .putBoolean(Key.bypass, bypass) .putBoolean(Key.udpdns, udpdns) - .putBoolean(Key.auth, auth) .putBoolean(Key.ipv6, ipv6) .putString(Key.individual, individual) .putString(Key.plugin, plugin) @@ -132,7 +128,6 @@ class Profile { proxyApps = pref.getBoolean(Key.proxyApps, false) bypass = pref.getBoolean(Key.bypass, false) udpdns = pref.getBoolean(Key.udpdns, false) - auth = pref.getBoolean(Key.auth, false) ipv6 = pref.getBoolean(Key.ipv6, false) individual = pref.getString(Key.individual, null) plugin = pref.getString(Key.plugin, null) diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala index 69034aba..d1d9fe21 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala @@ -141,7 +141,6 @@ object Key { val proxyApps = "isProxyApps" val bypass = "isBypassApps" val udpdns = "isUdpDns" - val auth = "isAuth" val ipv6 = "isIpv6" val host = "proxy" diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala index e9666fda..8597da9a 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala @@ -36,7 +36,6 @@ object Parser { case Some(ss) => val profile = new Profile profile.method = ss.group(2).toLowerCase - if (ss.group(3) != null) profile.auth = true profile.password = ss.group(4) profile.host = ss.group(5) profile.remotePort = ss.group(6).toInt From d579660682fe47af4d05b4d614e4a5d779f1723f Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 31 Jan 2017 22:15:59 +0800 Subject: [PATCH 77/98] Remove obsolete ciphers --- mobile/src/main/res/values/arrays.xml | 4 ---- mobile/src/main/res/values/strings.xml | 1 - .../src/main/scala/com/github/shadowsocks/BaseService.scala | 3 --- 3 files changed, 8 deletions(-) diff --git a/mobile/src/main/res/values/arrays.xml b/mobile/src/main/res/values/arrays.xml index 1961833f..6b7c1722 100644 --- a/mobile/src/main/res/values/arrays.xml +++ b/mobile/src/main/res/values/arrays.xml @@ -1,8 +1,6 @@ - TABLE - RC4 RC4-MD5 AES-128-CFB AES-192-CFB @@ -23,8 +21,6 @@ - table - rc4 rc4-md5 aes-128-cfb aes-192-cfb diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 38a689a9..17d2f7fe 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -60,7 +60,6 @@ Shadowsocks started. Invalid server name Failed to connect the remote server - WARNING: The current encryption method is unsafe WARNING: NAT mode has been deprecated since Android 5.0 NAT mode requires ROOT permission Switch to VPN mode diff --git a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala index 2d418e0d..5d5ec16d 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -183,9 +183,6 @@ trait BaseService extends Service { changeState(State.CONNECTING) - if (profile.isMethodUnsafe) - handler.post(() => Toast.makeText(this, R.string.method_unsafe, Toast.LENGTH_LONG).show()) - Utils.ThrowableFuture(try connect() catch { case _: NameNotResolvedException => stopRunner(stopService = true, getString(R.string.invalid_server)) case _: NullConnectionException => stopRunner(stopService = true, getString(R.string.reboot_required)) From dc13249a1ef1bb7de6b5b8fcb29a4bc35a06d20b Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 31 Jan 2017 22:16:15 +0800 Subject: [PATCH 78/98] Implement SIP002 https://github.com/shadowsocks/shadowsocks-org/issues/27 --- .../github/shadowsocks/database/Profile.scala | 22 +++++--- .../com/github/shadowsocks/utils/Parser.scala | 51 ++++++++++++------- 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala index b7ad44d7..9fb9782d 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala @@ -20,13 +20,13 @@ package com.github.shadowsocks.database -import java.net.URLEncoder import java.util.Locale import android.content.SharedPreferences +import android.net.Uri import android.os.Binder -import android.text.TextUtils import android.util.Base64 +import com.github.shadowsocks.plugin.PluginConfiguration import com.github.shadowsocks.utils.Key import com.j256.ormlite.field.{DataType, DatabaseField} @@ -93,11 +93,19 @@ class Profile { def nameIsEmpty: Boolean = name == null || name.isEmpty def getName: String = if (nameIsEmpty) formattedAddress else name - override def toString: String = "ss://" + Base64.encodeToString("%s:%s@%s:%d".formatLocal(Locale.ENGLISH, - method, password, host, remotePort).getBytes, Base64.NO_PADDING | Base64.NO_WRAP) + - (if (TextUtils.isEmpty(name)) "" else '#' + URLEncoder.encode(name, "utf-8")) - - def isMethodUnsafe: Boolean = "table".equalsIgnoreCase(method) || "rc4".equalsIgnoreCase(method) + def toUri: Uri = { + val builder = new Uri.Builder() + .scheme("ss") + .authority("%s@%s:%d".formatLocal(Locale.ENGLISH, + Base64.encodeToString("%s:%s".formatLocal(Locale.ENGLISH, method, password).getBytes, + Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE), host, remotePort)) + val configuration = new PluginConfiguration(plugin) + if (configuration.selected.nonEmpty) + builder.appendQueryParameter(Key.plugin, configuration.selectedOptions.toString) + if (!nameIsEmpty) builder.fragment(name) + builder.build() + } + override def toString: String = toUri.toString def serialize(editor: SharedPreferences.Editor): SharedPreferences.Editor = editor .putString(Key.name, name) diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala index 8597da9a..a58efc49 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala @@ -20,31 +20,46 @@ package com.github.shadowsocks.utils -import java.net.URLDecoder - -import android.util.{Base64, Log} +import android.net.Uri +import android.util.Log import com.github.shadowsocks.database.Profile object Parser { val TAG = "ShadowParser" - private val pattern = "(?i)ss://([A-Za-z0-9+-/=_]+)(#(.+))?".r - private val decodedPattern = "(?i)^((.+?)(-auth)??:(.*)@(.+?):(\\d+?))$".r + private val pattern = "(?i)^ss://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]$".r + private val userInfoPattern = "^(.+?):(.*)$".r + private val legacyPattern = "^((.+?):(.*)@(.+?):(\\d+?))$".r def findAll(data: CharSequence): Iterator[Profile] = - pattern.findAllMatchIn(if (data == null) "" else data).map(m => try - decodedPattern.findFirstMatchIn(new String(Base64.decode(m.group(1), Base64.NO_PADDING), "UTF-8")) match { - case Some(ss) => + pattern.findAllMatchIn(if (data == null) "" else data).map(m => { + val uri = Uri.parse(m.matched) + uri.getUserInfo match { + case null => uri.getHost match { + case legacyPattern(_, method, password, host, port) => // legacy uri + val profile = new Profile + profile.method = method.toLowerCase + profile.password = password + profile.host = host + profile.remotePort = port.toInt + profile.plugin = uri.getQueryParameter(Key.plugin) + profile.name = uri.getFragment + profile + case _ => + Log.e(TAG, "Unrecognized URI: " + m.matched) + null + } + case userInfoPattern(method, password) => val profile = new Profile - profile.method = ss.group(2).toLowerCase - profile.password = ss.group(4) - profile.host = ss.group(5) - profile.remotePort = ss.group(6).toInt - if (m.group(2) != null) profile.name = URLDecoder.decode(m.group(3), "utf-8") + profile.method = method + profile.password = password + profile.host = uri.getHost + profile.remotePort = uri.getPort + profile.plugin = uri.getQueryParameter(Key.plugin) + profile.name = uri.getFragment profile - case _ => null - } catch { - case ex: Exception => - Log.e(TAG, "parser error: " + m.source, ex)// Ignore + case _ => + Log.e(TAG, "Unknown user info: " + m.matched) null - }).filter(_ != null) + } + }).filter(_ != null) } From 56709385b1c52d9e10e8b2e8ce83017767bebe18 Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 31 Jan 2017 22:29:24 +0800 Subject: [PATCH 79/98] Add missing AEAD ciphers --- mobile/src/main/res/values/arrays.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mobile/src/main/res/values/arrays.xml b/mobile/src/main/res/values/arrays.xml index 6b7c1722..c7c50a7d 100644 --- a/mobile/src/main/res/values/arrays.xml +++ b/mobile/src/main/res/values/arrays.xml @@ -18,6 +18,9 @@ AES-128-GCM AES-192-GCM AES-256-GCM + CHACHA20-POLY1305 + CHACHA20-IETF-POLY1305 + XCHACHA20-IETF-POLY1305 @@ -38,6 +41,9 @@ aes-128-gcm aes-192-gcm aes-256-gcm + chacha20-poly1305 + chacha20-ietf-poly1305 + xchacha20-ietf-poly1305 From 41eec3e351efa502279ca6846e0dc1a95c161180 Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 31 Jan 2017 22:38:54 +0800 Subject: [PATCH 80/98] Use stable branch of libsodium --- .gitmodules | 1 + mobile/src/main/jni/Android.mk | 1 - mobile/src/main/jni/libsodium | 2 +- mobile/src/main/res/values/arrays.xml | 2 -- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitmodules b/.gitmodules index 69ee8b25..c14024db 100644 --- a/.gitmodules +++ b/.gitmodules @@ -30,6 +30,7 @@ [submodule "mobile/src/main/jni/libsodium"] path = mobile/src/main/jni/libsodium url = https://github.com/jedisct1/libsodium.git + branch = stable [submodule "mobile/src/main/jni/libev"] path = mobile/src/main/jni/libev url = https://github.com/shadowsocks/libev.git diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index ed6698e3..97d3a622 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -26,7 +26,6 @@ include $(CLEAR_VARS) SODIUM_SOURCE := \ crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c \ - crypto_aead/xchacha20poly1305/sodium/aead_xchacha20poly1305.c \ crypto_auth/crypto_auth.c \ crypto_auth/hmacsha256/auth_hmacsha256_api.c \ crypto_auth/hmacsha256/cp/hmac_hmacsha256.c \ diff --git a/mobile/src/main/jni/libsodium b/mobile/src/main/jni/libsodium index a81cea23..e160de24 160000 --- a/mobile/src/main/jni/libsodium +++ b/mobile/src/main/jni/libsodium @@ -1 +1 @@ -Subproject commit a81cea2366b9e814861a6f47e5bd460e06f19295 +Subproject commit e160de24fd146ab84b4eb397c7cffbf34775aa02 diff --git a/mobile/src/main/res/values/arrays.xml b/mobile/src/main/res/values/arrays.xml index c7c50a7d..b1bcba81 100644 --- a/mobile/src/main/res/values/arrays.xml +++ b/mobile/src/main/res/values/arrays.xml @@ -20,7 +20,6 @@ AES-256-GCM CHACHA20-POLY1305 CHACHA20-IETF-POLY1305 - XCHACHA20-IETF-POLY1305 @@ -43,7 +42,6 @@ aes-256-gcm chacha20-poly1305 chacha20-ietf-poly1305 - xchacha20-ietf-poly1305 From 5559295beb44f1f4e70e1702109f8b103b3a11ae Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 31 Jan 2017 22:59:04 +0800 Subject: [PATCH 81/98] Remove useless files in libsodium makefile --- mobile/src/main/jni/Android.mk | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index 97d3a622..d5bad8f0 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -26,16 +26,6 @@ include $(CLEAR_VARS) SODIUM_SOURCE := \ crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c \ - crypto_auth/crypto_auth.c \ - crypto_auth/hmacsha256/auth_hmacsha256_api.c \ - crypto_auth/hmacsha256/cp/hmac_hmacsha256.c \ - crypto_auth/hmacsha256/cp/verify_hmacsha256.c \ - crypto_auth/hmacsha512/auth_hmacsha512_api.c \ - crypto_auth/hmacsha512/cp/hmac_hmacsha512.c \ - crypto_auth/hmacsha512/cp/verify_hmacsha512.c \ - crypto_auth/hmacsha512256/auth_hmacsha512256_api.c \ - crypto_auth/hmacsha512256/cp/hmac_hmacsha512256.c \ - crypto_auth/hmacsha512256/cp/verify_hmacsha512256.c \ crypto_box/crypto_box.c \ crypto_box/crypto_box_easy.c \ crypto_box/crypto_box_seal.c \ @@ -51,13 +41,8 @@ SODIUM_SOURCE := \ crypto_core/hchacha20/core_hchacha20.c \ crypto_generichash/crypto_generichash.c \ crypto_generichash/blake2/generichash_blake2_api.c \ - crypto_generichash/blake2/ref/blake2-impl.h \ - crypto_generichash/blake2/ref/blake2.h \ crypto_generichash/blake2/ref/blake2b-compress-ref.c \ - crypto_generichash/blake2/ref/blake2b-load-sse2.h \ - crypto_generichash/blake2/ref/blake2b-load-sse41.h \ crypto_generichash/blake2/ref/blake2b-ref.c \ - crypto_generichash/blake2/ref/blake2b-round.h \ crypto_generichash/blake2/ref/generichash_blake2b.c \ crypto_hash/crypto_hash.c \ crypto_hash/sha256/hash_sha256_api.c \ @@ -66,22 +51,14 @@ SODIUM_SOURCE := \ crypto_hash/sha512/cp/hash_sha512.c \ crypto_onetimeauth/crypto_onetimeauth.c \ crypto_onetimeauth/poly1305/onetimeauth_poly1305.c \ - crypto_onetimeauth/poly1305/onetimeauth_poly1305.h \ - crypto_onetimeauth/poly1305/donna/poly1305_donna.h \ - crypto_onetimeauth/poly1305/donna/poly1305_donna32.h \ - crypto_onetimeauth/poly1305/donna/poly1305_donna64.h \ crypto_onetimeauth/poly1305/donna/poly1305_donna.c \ crypto_pwhash/scryptsalsa208sha256/crypto_scrypt-common.c \ - crypto_pwhash/scryptsalsa208sha256/crypto_scrypt.h \ crypto_pwhash/scryptsalsa208sha256/scrypt_platform.c \ crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.c \ - crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.h \ crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c \ - crypto_pwhash/scryptsalsa208sha256/sysendian.h \ crypto_pwhash/scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c \ crypto_scalarmult/crypto_scalarmult.c \ crypto_scalarmult/curve25519/scalarmult_curve25519.c \ - crypto_scalarmult/curve25519/scalarmult_curve25519.h \ crypto_secretbox/crypto_secretbox.c \ crypto_secretbox/crypto_secretbox_easy.c \ crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305_api.c \ @@ -89,8 +66,6 @@ SODIUM_SOURCE := \ crypto_shorthash/siphash24/shorthash_siphash24_api.c \ crypto_shorthash/siphash24/ref/shorthash_siphash24.c \ crypto_sign/crypto_sign.c \ - crypto_sign/ed25519/ref10/base.h \ - crypto_sign/ed25519/ref10/base2.h \ crypto_sign/ed25519/sign_ed25519_api.c \ crypto_sign/ed25519/ref10/keypair.c \ crypto_sign/ed25519/ref10/open.c \ @@ -98,8 +73,6 @@ SODIUM_SOURCE := \ crypto_sign/ed25519/ref10/sign.c \ crypto_stream/crypto_stream.c \ crypto_stream/chacha20/stream_chacha20.c \ - crypto_stream/chacha20/stream_chacha20.h \ - crypto_stream/chacha20/ref/stream_chacha20_ref.h \ crypto_stream/chacha20/ref/stream_chacha20_ref.c \ crypto_stream/salsa20/stream_salsa20_api.c \ crypto_stream/xsalsa20/stream_xsalsa20_api.c \ From deba1028c0435a6ff4585d1997fc4ddd8e86362b Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 31 Jan 2017 23:45:49 +0800 Subject: [PATCH 82/98] Remove unused sources in libsodium --- mobile/src/main/jni/Android.mk | 82 +++++----------------------------- 1 file changed, 12 insertions(+), 70 deletions(-) diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index d5bad8f0..7c7b1b17 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -26,89 +26,31 @@ include $(CLEAR_VARS) SODIUM_SOURCE := \ crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c \ - crypto_box/crypto_box.c \ - crypto_box/crypto_box_easy.c \ - crypto_box/crypto_box_seal.c \ - crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305_api.c \ - crypto_box/curve25519xsalsa20poly1305/ref/after_curve25519xsalsa20poly1305.c \ - crypto_box/curve25519xsalsa20poly1305/ref/before_curve25519xsalsa20poly1305.c \ - crypto_box/curve25519xsalsa20poly1305/ref/box_curve25519xsalsa20poly1305.c \ - crypto_box/curve25519xsalsa20poly1305/ref/keypair_curve25519xsalsa20poly1305.c \ - crypto_core/hsalsa20/ref2/core_hsalsa20.c \ - crypto_core/hsalsa20/core_hsalsa20_api.c \ crypto_core/salsa20/ref/core_salsa20.c \ - crypto_core/salsa20/core_salsa20_api.c \ - crypto_core/hchacha20/core_hchacha20.c \ - crypto_generichash/crypto_generichash.c \ - crypto_generichash/blake2/generichash_blake2_api.c \ crypto_generichash/blake2/ref/blake2b-compress-ref.c \ crypto_generichash/blake2/ref/blake2b-ref.c \ crypto_generichash/blake2/ref/generichash_blake2b.c \ - crypto_hash/crypto_hash.c \ - crypto_hash/sha256/hash_sha256_api.c \ - crypto_hash/sha256/cp/hash_sha256.c \ - crypto_hash/sha512/hash_sha512_api.c \ - crypto_hash/sha512/cp/hash_sha512.c \ - crypto_onetimeauth/crypto_onetimeauth.c \ crypto_onetimeauth/poly1305/onetimeauth_poly1305.c \ crypto_onetimeauth/poly1305/donna/poly1305_donna.c \ - crypto_pwhash/scryptsalsa208sha256/crypto_scrypt-common.c \ - crypto_pwhash/scryptsalsa208sha256/scrypt_platform.c \ - crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.c \ - crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c \ - crypto_pwhash/scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c \ - crypto_scalarmult/crypto_scalarmult.c \ - crypto_scalarmult/curve25519/scalarmult_curve25519.c \ - crypto_secretbox/crypto_secretbox.c \ - crypto_secretbox/crypto_secretbox_easy.c \ - crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305_api.c \ - crypto_shorthash/crypto_shorthash.c \ - crypto_shorthash/siphash24/shorthash_siphash24_api.c \ - crypto_shorthash/siphash24/ref/shorthash_siphash24.c \ - crypto_sign/crypto_sign.c \ - crypto_sign/ed25519/sign_ed25519_api.c \ - crypto_sign/ed25519/ref10/keypair.c \ - crypto_sign/ed25519/ref10/open.c \ - crypto_sign/ed25519/ref10/obsolete.c \ - crypto_sign/ed25519/ref10/sign.c \ - crypto_stream/crypto_stream.c \ - crypto_stream/chacha20/stream_chacha20.c \ - crypto_stream/chacha20/ref/stream_chacha20_ref.c \ - crypto_stream/salsa20/stream_salsa20_api.c \ - crypto_stream/xsalsa20/stream_xsalsa20_api.c \ - crypto_stream/xsalsa20/ref/stream_xsalsa20.c \ - crypto_stream/xsalsa20/ref/xor_xsalsa20.c \ - crypto_verify/16/verify_16_api.c \ - crypto_verify/16/ref/verify_16.c \ - crypto_verify/32/verify_32_api.c \ - crypto_verify/32/ref/verify_32.c \ - crypto_verify/64/verify_64_api.c \ - crypto_verify/64/ref/verify_64.c \ - randombytes/randombytes.c \ - sodium/core.c \ - sodium/runtime.c \ - sodium/utils.c \ - sodium/version.c - -SODIUM_SOURCE += \ - crypto_scalarmult/curve25519/ref10/x25519_ref10.c - -SODIUM_SOURCE += \ crypto_pwhash/crypto_pwhash.c \ crypto_pwhash/argon2/argon2-core.c \ crypto_pwhash/argon2/argon2.c \ crypto_pwhash/argon2/argon2-encoding.c \ - crypto_pwhash/argon2/blake2b-long.c \ crypto_pwhash/argon2/argon2-fill-block-ref.c \ + crypto_pwhash/argon2/blake2b-long.c \ crypto_pwhash/argon2/pwhash_argon2i.c \ - crypto_pwhash/argon2/argon2-fill-block-ssse3.c - -SODIUM_SOURCE += \ + crypto_scalarmult/curve25519/scalarmult_curve25519.c \ + crypto_stream/chacha20/stream_chacha20.c \ + crypto_stream/chacha20/ref/stream_chacha20_ref.c \ crypto_stream/salsa20/ref/stream_salsa20_ref.c \ - crypto_stream/salsa20/ref/xor_salsa20_ref.c - -SODIUM_SOURCE += \ - randombytes/sysrandom/randombytes_sysrandom.c + crypto_stream/salsa20/ref/xor_salsa20_ref.c \ + crypto_verify/16/ref/verify_16.c \ + randombytes/randombytes.c \ + randombytes/sysrandom/randombytes_sysrandom.c \ + sodium/core.c \ + sodium/runtime.c \ + sodium/utils.c \ + sodium/version.c LOCAL_MODULE := sodium LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/libsodium/src/libsodium/include \ From 212c3f666bf61deeb231efd90da40f04f66fa8da Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 2 Feb 2017 17:38:06 +0800 Subject: [PATCH 83/98] Update shadowsocks-libev --- mobile/src/main/jni/shadowsocks-libev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/jni/shadowsocks-libev b/mobile/src/main/jni/shadowsocks-libev index c0030f0d..c8bcf594 160000 --- a/mobile/src/main/jni/shadowsocks-libev +++ b/mobile/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit c0030f0d7d74d1197877e093366bbd2713296aaa +Subproject commit c8bcf594e2df8ee5d2fa1661ddd2fd058552c18f From be5036183d13ad1750b6e175d644089511958415 Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 2 Feb 2017 18:41:14 +0800 Subject: [PATCH 84/98] Fix issues about SIP002 --- .../github/shadowsocks/database/Profile.scala | 4 +-- .../com/github/shadowsocks/utils/Parser.scala | 29 ++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala index 9fb9782d..53581e85 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala @@ -96,12 +96,12 @@ class Profile { def toUri: Uri = { val builder = new Uri.Builder() .scheme("ss") - .authority("%s@%s:%d".formatLocal(Locale.ENGLISH, + .encodedAuthority("%s@%s:%d".formatLocal(Locale.ENGLISH, Base64.encodeToString("%s:%s".formatLocal(Locale.ENGLISH, method, password).getBytes, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE), host, remotePort)) val configuration = new PluginConfiguration(plugin) if (configuration.selected.nonEmpty) - builder.appendQueryParameter(Key.plugin, configuration.selectedOptions.toString) + builder.appendQueryParameter(Key.plugin, configuration.selectedOptions.toString(false)) if (!nameIsEmpty) builder.fragment(name) builder.build() } diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala index a58efc49..a571bad6 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala @@ -21,7 +21,7 @@ package com.github.shadowsocks.utils import android.net.Uri -import android.util.Log +import android.util.{Base64, Log} import com.github.shadowsocks.database.Profile object Parser { @@ -48,18 +48,21 @@ object Parser { Log.e(TAG, "Unrecognized URI: " + m.matched) null } - case userInfoPattern(method, password) => - val profile = new Profile - profile.method = method - profile.password = password - profile.host = uri.getHost - profile.remotePort = uri.getPort - profile.plugin = uri.getQueryParameter(Key.plugin) - profile.name = uri.getFragment - profile - case _ => - Log.e(TAG, "Unknown user info: " + m.matched) - null + case userInfo => + new String(Base64.decode(userInfo, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE)) match { + case userInfoPattern(method, password) => + val profile = new Profile + profile.method = method + profile.password = password + profile.host = uri.getHost + profile.remotePort = uri.getPort + profile.plugin = uri.getQueryParameter(Key.plugin) + profile.name = uri.getFragment + profile + case _ => + Log.e(TAG, "Unknown user info: " + m.matched) + null + } } }).filter(_ != null) } From b671bc2c503a77f991498253225227a1c882a0f7 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Fri, 3 Feb 2017 09:06:19 +0800 Subject: [PATCH 85/98] Update shadowsocks-libev --- mobile/src/main/jni/shadowsocks-libev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/jni/shadowsocks-libev b/mobile/src/main/jni/shadowsocks-libev index c8bcf594..7ef88b17 160000 --- a/mobile/src/main/jni/shadowsocks-libev +++ b/mobile/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit c8bcf594e2df8ee5d2fa1661ddd2fd058552c18f +Subproject commit 7ef88b174c5e664aa5a310ed0b76694bb82acc7d From ad0578d073a280263559132180b371f3eef99999 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Fri, 3 Feb 2017 12:39:05 +0800 Subject: [PATCH 86/98] Fix UDP relay issues --- mobile/src/main/jni/shadowsocks-libev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/jni/shadowsocks-libev b/mobile/src/main/jni/shadowsocks-libev index 7ef88b17..a906d0cf 160000 --- a/mobile/src/main/jni/shadowsocks-libev +++ b/mobile/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit 7ef88b174c5e664aa5a310ed0b76694bb82acc7d +Subproject commit a906d0cfc81750c55a9ef432e8af4fe0228f6948 From 2d86122297179b276efb107c8f6cf7aea38b3d9c Mon Sep 17 00:00:00 2001 From: Max Lv Date: Wed, 8 Feb 2017 10:57:32 +0800 Subject: [PATCH 87/98] Update shadowsocks-libev --- mobile/src/main/jni/shadowsocks-libev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/jni/shadowsocks-libev b/mobile/src/main/jni/shadowsocks-libev index a906d0cf..f4b16416 160000 --- a/mobile/src/main/jni/shadowsocks-libev +++ b/mobile/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit a906d0cfc81750c55a9ef432e8af4fe0228f6948 +Subproject commit f4b1641636942bc44c96160afad9f023fb6dbf0e From f7e2f8694d37dfab6196e745b44182c943a9dd46 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Wed, 8 Feb 2017 11:15:03 +0800 Subject: [PATCH 88/98] Add missing rules of base64.c --- mobile/src/main/jni/Android.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index 7c7b1b17..8f771c47 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -249,7 +249,7 @@ include $(CLEAR_VARS) SHADOWSOCKS_SOURCES := local.c \ cache.c udprelay.c utils.c netutils.c json.c jconf.c \ acl.c http.c tls.c rule.c \ - crypto.c aead.c stream.c \ + crypto.c aead.c stream.c base64.c \ plugin.c \ android.c @@ -285,7 +285,7 @@ include $(CLEAR_VARS) SHADOWSOCKS_SOURCES := tunnel.c \ cache.c udprelay.c utils.c netutils.c json.c jconf.c \ - crypto.c aead.c stream.c \ + crypto.c aead.c stream.c base64.c \ plugin.c \ android.c From 5d46ef45e3e3c88b7e9f6a29de80944ec17bdc96 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Wed, 8 Feb 2017 12:03:16 +0800 Subject: [PATCH 89/98] Bump version --- mobile/build.sbt | 4 ++-- plugin/build.sbt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mobile/build.sbt b/mobile/build.sbt index d3cad7c8..c20f1f1a 100644 --- a/mobile/build.sbt +++ b/mobile/build.sbt @@ -2,8 +2,8 @@ enablePlugins(AndroidApp) android.useSupportVectors name := "shadowsocks" -version := "4.0.0" -versionCode := Some(175) +version := "4.0.1" +versionCode := Some(176) proguardOptions ++= "-keep class com.github.shadowsocks.JniHelper { *; }" :: diff --git a/plugin/build.sbt b/plugin/build.sbt index c902a8b9..ca3bc9b4 100644 --- a/plugin/build.sbt +++ b/plugin/build.sbt @@ -2,7 +2,7 @@ enablePlugins(AndroidLib) android.useSupportVectors name := "plugin" -version := "0.0.2-SNAPSHOT" +version := "0.0.2" pomExtra in Global := { https://github.com/shadowsocks/shadowsocks-android From 9975b135d0d754fbe87a9e122ca8c27278462cfb Mon Sep 17 00:00:00 2001 From: Max Lv Date: Wed, 8 Feb 2017 12:19:51 +0800 Subject: [PATCH 90/98] Remove chacha20-poly1305 --- mobile/src/main/res/values/arrays.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/mobile/src/main/res/values/arrays.xml b/mobile/src/main/res/values/arrays.xml index b1bcba81..8e7b2944 100644 --- a/mobile/src/main/res/values/arrays.xml +++ b/mobile/src/main/res/values/arrays.xml @@ -18,7 +18,6 @@ AES-128-GCM AES-192-GCM AES-256-GCM - CHACHA20-POLY1305 CHACHA20-IETF-POLY1305 @@ -40,7 +39,6 @@ aes-128-gcm aes-192-gcm aes-256-gcm - chacha20-poly1305 chacha20-ietf-poly1305 From 7219ce429cac548878eb3a055d520c82af22ce8d Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 8 Feb 2017 17:07:56 +0800 Subject: [PATCH 91/98] Bump version --- plugin/README.md | 2 +- plugin/doc.md | 2 +- plugin/src/main/AndroidManifest.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/README.md b/plugin/README.md index f3a8df6c..6a48f007 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -37,7 +37,7 @@ First you need to add this library to your dependencies. This library is written and it's most convenient to use it with SBT: ```scala -libraryDependencies += "com.github.shadowsocks" %% "plugin" % "0.0.1" +libraryDependencies += "com.github.shadowsocks" %% "plugin" % "0.0.2" ``` ### Native binary configuration diff --git a/plugin/doc.md b/plugin/doc.md index b652eea3..333e1097 100644 --- a/plugin/doc.md +++ b/plugin/doc.md @@ -226,5 +226,5 @@ Plugin app must include this in their application tag: (which should be automati ``` + android:value="0.0.2"/> ``` diff --git a/plugin/src/main/AndroidManifest.xml b/plugin/src/main/AndroidManifest.xml index a5e9871d..bef42e78 100644 --- a/plugin/src/main/AndroidManifest.xml +++ b/plugin/src/main/AndroidManifest.xml @@ -6,6 +6,6 @@ + android:value="0.0.2"/> From 6a02d4b98560dd8411751f323c535ad7ebdc03a0 Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 8 Feb 2017 17:13:05 +0800 Subject: [PATCH 92/98] Add change log to plugin library --- plugin/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/README.md b/plugin/README.md index 6a48f007..f4596326 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -1,5 +1,8 @@ # shadowsocks-android plugin framework +[Documentation](https://github.com/shadowsocks/shadowsocks-android/blob/develop/plugin/doc.md) | + [Change log](https://github.com/shadowsocks/shadowsocks-android/blob/develop/plugin/CHANGES.md) + Support library for easier development on [shadowsocks plugin](https://github.com/shadowsocks/shadowsocks-org/issues/28) for Android. Also includes some useful resources to easily get consistent styling with the main app. @@ -19,9 +22,6 @@ This library is designed with Java interoperability in mind so theoretically you library with other languages and/or build tools but there isn't documentation for that yet. This guide is written for Scala + SBT. Contributions are welcome. -The following is just a simple guide to get you started, for more technical details, see - [doc.md](https://github.com/shadowsocks/shadowsocks-android/blob/develop/plugin/doc.md). - ### Package name There are no arbitrary restrictions/requirements on package name, component name and content From 684698c8e4b2e6ea1e9596eabb36b414e7a755f8 Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 8 Feb 2017 17:18:04 +0800 Subject: [PATCH 93/98] Refine URLs --- plugin/README.md | 3 +-- plugin/doc.md | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/plugin/README.md b/plugin/README.md index f4596326..cfd062f8 100644 --- a/plugin/README.md +++ b/plugin/README.md @@ -1,7 +1,6 @@ # shadowsocks-android plugin framework -[Documentation](https://github.com/shadowsocks/shadowsocks-android/blob/develop/plugin/doc.md) | - [Change log](https://github.com/shadowsocks/shadowsocks-android/blob/develop/plugin/CHANGES.md) +[Documentation](doc.md) | [Change log](CHANGES.md) Support library for easier development on [shadowsocks plugin](https://github.com/shadowsocks/shadowsocks-org/issues/28) for Android. Also includes some diff --git a/plugin/doc.md b/plugin/doc.md index 333e1097..b88904b0 100644 --- a/plugin/doc.md +++ b/plugin/doc.md @@ -196,7 +196,7 @@ This feature hasn't been implemented yet. Plugins are certified using package signatures and shadowsocks-android will consider these signatures as trusted: -* Signatures by [trusted sources](https://github.com/shadowsocks/shadowsocks-android/blob/develop/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala#L22) +* Signatures by [trusted sources](/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala#L22) which includes: - @madeye, i.e. the signer of the main repo; - The main repo doesn't contain any other trusted signatures. Third-party forks should add their From fd2c67d0b8b555b07d1e71a338cd61379ecf2c27 Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 8 Feb 2017 17:19:22 +0800 Subject: [PATCH 94/98] Add missing file --- plugin/CHANGES.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 plugin/CHANGES.md diff --git a/plugin/CHANGES.md b/plugin/CHANGES.md new file mode 100644 index 00000000..78ee27d1 --- /dev/null +++ b/plugin/CHANGES.md @@ -0,0 +1,4 @@ +* 0.0.2: + * Add `getOrDefault` to `PluginOptions`; + * Update support library version to 25.1.1. +* 0.0.1: Initial release. From 3ac77e1f8db4640e3d8aee95f07878007e2f0b59 Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 8 Feb 2017 18:47:23 +0800 Subject: [PATCH 95/98] Fix crash on back pressed after MainActivity recreated --- .../com/github/shadowsocks/MainActivity.scala | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala index d55e1a0c..01ca9687 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -84,11 +84,6 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe private var txRateText: TextView = _ private var rxRateText: TextView = _ - private var currentFragment: ToolbarFragment = _ - private lazy val profilesFragment = new ProfilesFragment() - private lazy val customRulesFragment = new CustomRulesFragment() - private lazy val globalSettingsFragment = new GlobalSettingsFragment() - private lazy val aboutFragment = new AboutFragment() private lazy val customTabsIntent = new CustomTabsIntent.Builder() .setToolbarColor(ContextCompat.getColor(this, R.color.material_primary_500)) .build() @@ -255,7 +250,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe val tf = Typefaces.get(this, "fonts/Iceland.ttf") if (tf != null) title.setTypeface(tf) - if (savedInstanceState == null) displayFragment(profilesFragment) + if (savedInstanceState == null) displayFragment(new ProfilesFragment) statusText = findViewById(R.id.status).asInstanceOf[TextView] txText = findViewById(R.id.tx).asInstanceOf[TextView] txRateText = findViewById(R.id.txRate).asInstanceOf[TextView] @@ -364,14 +359,13 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe } private def displayFragment(fragment: ToolbarFragment) { - currentFragment = fragment getFragmentManager.beginTransaction().replace(R.id.fragment_holder, fragment).commitAllowingStateLoss() drawer.closeDrawer() } override def onItemClick(view: View, position: Int, drawerItem: IDrawerItem[_, _ <: ViewHolder]): Boolean = { drawerItem.getIdentifier match { - case DRAWER_PROFILES => displayFragment(profilesFragment) + case DRAWER_PROFILES => displayFragment(new ProfilesFragment) case DRAWER_RECOVERY => app.track("GlobalConfigFragment", "reset") if (bgService != null) bgService.use(-1) @@ -384,12 +378,12 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe app.copyAssets() handler.sendEmptyMessage(0) } - case DRAWER_GLOBAL_SETTINGS => displayFragment(globalSettingsFragment) + case DRAWER_GLOBAL_SETTINGS => displayFragment(new GlobalSettingsFragment) case DRAWER_ABOUT => app.track(TAG, "about") - displayFragment(aboutFragment) + displayFragment(new AboutFragment) case DRAWER_FAQ => launchUrl(getString(R.string.faq_url)) - case DRAWER_CUSTOM_RULES => displayFragment(customRulesFragment) + case DRAWER_CUSTOM_RULES => displayFragment(new CustomRulesFragment) } true // unexpected cases will throw exception } @@ -408,12 +402,14 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe setListeningForBandwidth(true) } - override def onBackPressed(): Unit = - if (drawer.isDrawerOpen) drawer.closeDrawer() else if (!currentFragment.onBackPressed()) - if (currentFragment != profilesFragment) { - displayFragment(profilesFragment) + override def onBackPressed(): Unit = if (drawer.isDrawerOpen) drawer.closeDrawer() else { + val currentFragment = getFragmentManager.findFragmentById(R.id.fragment_holder).asInstanceOf[ToolbarFragment] + if (!currentFragment.onBackPressed()) + if (currentFragment.isInstanceOf[ProfilesFragment]) super.onBackPressed() else { + displayFragment(new ProfilesFragment) drawer.setSelection(DRAWER_PROFILES) - } else super.onBackPressed() + } + } override def onStop() { setListeningForBandwidth(false) From 293e9b5dff1433cf9b580d89543a2920522140ba Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 8 Feb 2017 18:47:53 +0800 Subject: [PATCH 96/98] Fix equals missing in Subnet --- .../src/main/scala/com/github/shadowsocks/acl/Subnet.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mobile/src/main/scala/com/github/shadowsocks/acl/Subnet.scala b/mobile/src/main/scala/com/github/shadowsocks/acl/Subnet.scala index 8d7c140c..71e6d67f 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/acl/Subnet.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/acl/Subnet.scala @@ -1,6 +1,7 @@ package com.github.shadowsocks.acl import java.net.InetAddress +import java.util.Objects import com.github.shadowsocks.utils.Utils @@ -26,6 +27,12 @@ class Subnet(val address: InetAddress, val prefixSize: Int) extends Comparable[S } prefixSize compare that.prefixSize } + + override def equals(other: Any): Boolean = other match { + case that: Subnet => address == that.address && prefixSize == that.prefixSize + case _ => false + } + override def hashCode: Int = Objects.hash(address, prefixSize: Integer) } object Subnet { From 24c6aca85cefe228a1b02a849d0639736da3ac74 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Thu, 9 Feb 2017 09:57:48 +0800 Subject: [PATCH 97/98] Add remote DNS to route --- .../scala/com/github/shadowsocks/ShadowsocksVpnService.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 48626a84..38219492 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -251,6 +251,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { val subnet = Subnet.fromString(cidr) builder.addRoute(subnet.address.getHostAddress, subnet.prefixSize) }) + builder.addRoute(profile.remoteDns, 32) } conn = builder.establish() From 491710679a737c7aa8f353f8a267620624d4f60d Mon Sep 17 00:00:00 2001 From: Max Lv Date: Thu, 9 Feb 2017 10:50:44 +0800 Subject: [PATCH 98/98] Fix #1111 --- .../src/main/scala/com/github/shadowsocks/utils/Parser.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala index a571bad6..2645a809 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala @@ -34,7 +34,7 @@ object Parser { pattern.findAllMatchIn(if (data == null) "" else data).map(m => { val uri = Uri.parse(m.matched) uri.getUserInfo match { - case null => uri.getHost match { + case null => new String(Base64.decode(uri.getHost, Base64.NO_PADDING), "UTF-8") match { case legacyPattern(_, method, password, host, port) => // legacy uri val profile = new Profile profile.method = method.toLowerCase @@ -49,7 +49,7 @@ object Parser { null } case userInfo => - new String(Base64.decode(userInfo, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE)) match { + new String(Base64.decode(userInfo, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE), "UTF-8") match { case userInfoPattern(method, password) => val profile = new Profile profile.method = method