├── shadow.png
├── .headache.cfg
├── lint.xml
├── shadow-notify.png
├── src
└── main
│ ├── ic_launcher-web.png
│ ├── assets
│ ├── fonts
│ │ └── Iceland.ttf
│ └── acl
│ │ └── bypass-lan.acl
│ ├── res
│ ├── raw
│ │ └── gtm_default_container
│ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ ├── drawable
│ │ ├── background_header.png
│ │ ├── background_stat.xml
│ │ ├── ic_action_done.xml
│ │ ├── background_selectable.xml
│ │ ├── ic_action_delete.xml
│ │ ├── ic_av_playlist_add.xml
│ │ ├── ic_action_description.xml
│ │ ├── ic_action_note_add.xml
│ │ ├── ic_content_copy.xml
│ │ ├── ic_image_edit.xml
│ │ ├── ic_start_connected.xml
│ │ ├── ic_navigation_refresh.xml
│ │ ├── ic_content_paste.xml
│ │ ├── ic_start_busy.xml
│ │ ├── ic_action_assignment.xml
│ │ ├── ic_action_help_outline.xml
│ │ ├── ic_qu_shadowsocks_launcher.xml
│ │ ├── ic_social_share.xml
│ │ ├── ic_start_idle.xml
│ │ ├── background_profile.xml
│ │ ├── ic_action_settings.xml
│ │ └── ic_action_copyright.xml
│ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ ├── values-v21
│ │ ├── dimen.xml
│ │ ├── styles.xml
│ │ └── strings.xml
│ ├── drawable-hdpi
│ │ ├── ic_navigation_close.png
│ │ └── ic_stat_shadowsocks.png
│ ├── drawable-mdpi
│ │ ├── ic_navigation_close.png
│ │ └── ic_stat_shadowsocks.png
│ ├── drawable-xhdpi
│ │ ├── ic_navigation_close.png
│ │ └── ic_stat_shadowsocks.png
│ ├── drawable-xxhdpi
│ │ ├── ic_navigation_close.png
│ │ └── ic_stat_shadowsocks.png
│ ├── drawable-xxxhdpi
│ │ └── ic_navigation_close.png
│ ├── drawable-v21
│ │ ├── background_stat.xml
│ │ └── background_selectable.xml
│ ├── values
│ │ ├── dimen.xml
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── configs.xml
│ │ └── styles.xml
│ ├── drawable-anydpi-v21
│ │ └── ic_navigation_close.xml
│ ├── menu
│ │ ├── profile_share_popup.xml
│ │ ├── profile_config_menu.xml
│ │ ├── app_manager_menu.xml
│ │ ├── profile_manager_menu.xml
│ │ └── custom_rules_menu.xml
│ ├── layout
│ │ ├── layout_about.xml
│ │ ├── toolbar_light_dark.xml
│ │ ├── layout_global_settings.xml
│ │ ├── layout_profile_config.xml
│ │ ├── dialog_acl_rule.xml
│ │ ├── layout_header.xml
│ │ ├── layout_list.xml
│ │ ├── layout_tasker.xml
│ │ ├── layout_apps_item.xml
│ │ ├── layout_custom_rules.xml
│ │ ├── layout_apps.xml
│ │ └── layout_profile.xml
│ └── xml
│ │ ├── shortcuts.xml
│ │ ├── pref_global.xml
│ │ └── tracker.xml
│ ├── jni
│ ├── Application.mk
│ ├── include
│ │ ├── sodium
│ │ │ └── version.h
│ │ └── libev
│ │ │ └── config.h
│ ├── build-shared-executable.mk
│ └── system.cpp
│ ├── aidl
│ └── com
│ │ └── github
│ │ └── shadowsocks
│ │ └── aidl
│ │ ├── IShadowsocksServiceCallback.aidl
│ │ └── IShadowsocksService.aidl
│ ├── java
│ └── com
│ │ └── github
│ │ └── shadowsocks
│ │ ├── DialogFragment.java
│ │ └── System.java
│ └── scala
│ ├── be
│ └── mygod
│ │ └── preference
│ │ ├── DialogPreferencePlus.scala
│ │ ├── PreferenceCategory.scala
│ │ ├── SummaryPreference.scala
│ │ ├── NumberPickerPreferenceDialogFragment.scala
│ │ ├── EditTextPreferenceDialogFragment.scala
│ │ ├── PreferenceFragment.scala
│ │ ├── EditTextPreference.scala
│ │ ├── NumberPickerPreference.scala
│ │ └── PreferenceGroupAdapter.scala
│ └── com
│ └── github
│ └── shadowsocks
│ ├── acl
│ ├── Subnet.scala
│ ├── DonaldTrump.scala
│ └── AclSyncJob.scala
│ ├── widget
│ ├── BoundedCardView.scala
│ ├── BoundedGridLayout.scala
│ ├── BoundedView.scala
│ └── UndoSnackbarManager.scala
│ ├── utils
│ ├── IOUtils.scala
│ ├── CloseUtils.scala
│ ├── Typefaces.scala
│ ├── TcpFastOpen.scala
│ ├── TaskerSettings.scala
│ ├── TrafficMonitor.scala
│ ├── TrafficMonitorThread.scala
│ └── Parser.scala
│ ├── TaskerReceiver.scala
│ ├── ToolbarFragment.scala
│ ├── BootReceiver.scala
│ ├── ShadowsocksRunnerService.scala
│ ├── ShadowsocksBackupAgent.scala
│ ├── AboutFragment.scala
│ ├── GlobalSettingsFragment.scala
│ ├── preferences
│ └── KcpCliPreferenceDialogFragment.scala
│ ├── QuickToggleShortcut.scala
│ ├── ProfileConfigActivity.scala
│ ├── GlobalConfigFragment.scala
│ ├── QRCodeDialog.scala
│ ├── ShadowsocksTileService.scala
│ ├── ShadowsocksRunnerActivity.scala
│ ├── GuardedProcess.scala
│ ├── ShadowsocksVpnThread.scala
│ └── database
│ └── ProfileManager.scala
├── .jvmopts
├── CONTRIBUTING.md
├── local.properties.example
├── project
└── plugins.sbt
├── .github
├── pull_request_template.md
├── issue_template.md
└── faq.md
├── gfwlist
├── gen.pl
└── gen.py
├── AUTHORS
├── .gitignore
├── LICENSE
├── .gitmodules
├── .travis.yml
├── kcptun
└── make.bash
└── README.md
/shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/shadow.png
--------------------------------------------------------------------------------
/.headache.cfg:
--------------------------------------------------------------------------------
1 | # Scala source
2 | ".*\\.scala" -> frame open:"/*" line:"*" close:"*/"
3 |
--------------------------------------------------------------------------------
/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/shadow-notify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/shadow-notify.png
--------------------------------------------------------------------------------
/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/.jvmopts:
--------------------------------------------------------------------------------
1 | -XX:MaxPermSize=512m
2 | -Xms1g
3 | -Xmx3g
4 | -Xss2m
5 | -XX:+CMSClassUnloadingEnabled
6 | -XX:+UseConcMarkSweepGC
7 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Please check the [FAQ](https://github.com/shadowsocks/shadowsocks-android/wiki/FAQ) before submitting new issues.
2 |
--------------------------------------------------------------------------------
/src/main/assets/fonts/Iceland.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/assets/fonts/Iceland.ttf
--------------------------------------------------------------------------------
/local.properties.example:
--------------------------------------------------------------------------------
1 | key.alias: your_key_alias
2 | key.store: /path/to/your/key/store
3 | key.store.password: your_key_password
4 |
--------------------------------------------------------------------------------
/src/main/res/raw/gtm_default_container:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/raw/gtm_default_container
--------------------------------------------------------------------------------
/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/drawable/background_header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/drawable/background_header.png
--------------------------------------------------------------------------------
/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/values-v21/dimen.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -28dp
4 |
5 |
--------------------------------------------------------------------------------
/src/main/res/drawable-hdpi/ic_navigation_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/drawable-hdpi/ic_navigation_close.png
--------------------------------------------------------------------------------
/src/main/res/drawable-hdpi/ic_stat_shadowsocks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/drawable-hdpi/ic_stat_shadowsocks.png
--------------------------------------------------------------------------------
/src/main/res/drawable-mdpi/ic_navigation_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/drawable-mdpi/ic_navigation_close.png
--------------------------------------------------------------------------------
/src/main/res/drawable-mdpi/ic_stat_shadowsocks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/drawable-mdpi/ic_stat_shadowsocks.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xhdpi/ic_navigation_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/drawable-xhdpi/ic_navigation_close.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xhdpi/ic_stat_shadowsocks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/drawable-xhdpi/ic_stat_shadowsocks.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/ic_navigation_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/drawable-xxhdpi/ic_navigation_close.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/ic_stat_shadowsocks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/drawable-xxhdpi/ic_stat_shadowsocks.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxxhdpi/ic_navigation_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kaneawk/shadowsocksr-android/HEAD/src/main/res/drawable-xxxhdpi/ic_navigation_close.png
--------------------------------------------------------------------------------
/src/main/jni/Application.mk:
--------------------------------------------------------------------------------
1 | APP_ABI := armeabi-v7a x86
2 | APP_PLATFORM := android-16
3 | APP_STL := stlport_static
4 | NDK_TOOLCHAIN_VERSION := clang
5 |
--------------------------------------------------------------------------------
/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/main/res/values-v21/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @string/proxied_apps_summary_v21
4 |
5 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_stat.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.2")
2 |
3 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10")
4 |
5 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.8.2")
6 |
--------------------------------------------------------------------------------
/src/main/assets/acl/bypass-lan.acl:
--------------------------------------------------------------------------------
1 | 0.0.0.0/8
2 | 10.0.0.0/8
3 | 100.64.0.0/10
4 | 127.0.0.0/8
5 | 169.254.0.0/16
6 | 172.16.0.0/12
7 | 192.0.0.0/29
8 | 192.0.2.0/24
9 | 192.88.99.0/24
10 | 192.168.0.0/16
11 | 198.18.0.0/15
12 | 198.51.100.0/24
13 | 203.0.113.0/24
14 | 224.0.0.0/3
15 |
--------------------------------------------------------------------------------
/src/main/res/drawable-v21/background_stat.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ### Type of changes
2 |
3 | _Put an `x` inside the [ ] that applies._
4 |
5 | * [ ] Bugfix
6 | * [ ] New feature
7 | * [ ] Translation
8 | * [ ] General refinement
9 | * Other:
10 |
11 | ### Details
12 |
13 | Please provide more details about changes if necessary.
14 |
15 |
--------------------------------------------------------------------------------
/src/main/res/values/dimen.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 480dp
4 |
5 | -44dp
6 |
7 |
--------------------------------------------------------------------------------
/gfwlist/gen.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 | ## ArchLinux install package via pacman: perl-net-cidr-lite
3 | use strict;
4 | use warnings;
5 | use Net::CIDR::Lite;
6 | my $cidr = Net::CIDR::Lite->new;
7 | while (my $line=<>) {
8 | $cidr->add($line);
9 | }
10 | foreach my $line( @{$cidr->list} ) {
11 | print "- $line
\n";
12 | }
13 |
--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
1 | Shadowsocks-android was originally created in late 2013, by
2 | Max Lv .
3 |
4 | Here is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS --
5 | people who have submitted patches, fixed bugs, added translations, and
6 | generally made shadowsocks-android that much better:
7 |
8 | https://github.com/shadowsocks/shadowsocks-android/graphs/contributors
9 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_action_done.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/gfwlist/gen.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- encoding: utf8 -*-
3 |
4 | import itertools
5 | import math
6 | import sys
7 |
8 | import IPy
9 |
10 |
11 | def main():
12 | china_list_set = IPy.IPSet()
13 | for line in sys.stdin:
14 | china_list_set.add(IPy.IP(line))
15 |
16 | # 输出结果
17 | for ip in china_list_set:
18 | print '- ' + str(ip) + '
'
19 |
20 |
21 | if __name__ == "__main__":
22 | main()
23 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_selectable.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | -
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_action_delete.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_av_playlist_add.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/drawable-anydpi-v21/ic_navigation_close.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | classes
2 | bin
3 | gen
4 | target
5 | local.properties
6 | .classpath
7 | .project
8 | .settings
9 | tests/bin
10 | tests/gen
11 | tests/local.properties
12 | NUL
13 | gen_*
14 | *.class
15 | *.o
16 | local.sbt
17 | .deps
18 | /proguard-sbt.txt
19 |
20 | #Intellij IDEA
21 | *.iml
22 | *.ipr
23 | *.iws
24 | .idea/
25 | out/
26 | .d
27 |
28 | #NDK
29 | src/main/obj
30 | src/main/libs
31 | src/main/assets/armeabi-v7a/*
32 | src/main/assets/x86/*
33 | jni/Application.mk
34 |
--------------------------------------------------------------------------------
/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksServiceCallback.aidl:
--------------------------------------------------------------------------------
1 | package com.github.shadowsocks.aidl;
2 |
3 | interface IShadowsocksServiceCallback {
4 | oneway void stateChanged(int state, String profileName, String msg);
5 | oneway void trafficUpdated(int profileId, long txRate, long rxRate, long txTotal, long rxTotal);
6 | // Traffic data has persisted to database, listener should refetch their data from database
7 | oneway void trafficPersisted(int profileId);
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/java/com/github/shadowsocks/DialogFragment.java:
--------------------------------------------------------------------------------
1 | package com.github.shadowsocks;
2 |
3 | import android.app.Activity;
4 |
5 | /**
6 | * This helper class is used to solve Java-Scala interop problem. Use onAttach(Context) and remove this class
7 | * when minSdkVersion >= 23.
8 | *
9 | * @author Mygod
10 | */
11 | public class DialogFragment extends android.app.DialogFragment {
12 | protected void superOnAttach(Activity activity) {
13 | super.onAttach(activity);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_action_description.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_action_note_add.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_content_copy.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_image_edit.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/menu/profile_share_popup.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_start_connected.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/res/layout/layout_about.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_navigation_refresh.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/layout/toolbar_light_dark.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/src/main/res/layout/layout_global_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/res/drawable-v21/background_selectable.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
6 |
-
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_content_paste.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/jni/include/sodium/version.h:
--------------------------------------------------------------------------------
1 |
2 | #ifndef sodium_version_H
3 | #define sodium_version_H
4 |
5 | #include "export.h"
6 |
7 | #define SODIUM_VERSION_STRING "1.0.7"
8 |
9 | #define SODIUM_LIBRARY_VERSION_MAJOR 9
10 | #define SODIUM_LIBRARY_VERSION_MINOR 0
11 |
12 | #ifdef __cplusplus
13 | extern "C" {
14 | #endif
15 |
16 | SODIUM_EXPORT
17 | const char *sodium_version_string(void);
18 |
19 | SODIUM_EXPORT
20 | int sodium_library_version_major(void);
21 |
22 | SODIUM_EXPORT
23 | int sodium_library_version_minor(void);
24 |
25 | #ifdef __cplusplus
26 | }
27 | #endif
28 |
29 | #endif
30 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_start_busy.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_action_assignment.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_action_help_outline.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/xml/shortcuts.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/main/res/layout/layout_profile_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl:
--------------------------------------------------------------------------------
1 | package com.github.shadowsocks.aidl;
2 |
3 | import com.github.shadowsocks.aidl.IShadowsocksServiceCallback;
4 |
5 | interface IShadowsocksService {
6 | int getState();
7 | String getProfileName();
8 |
9 | oneway void registerCallback(IShadowsocksServiceCallback cb);
10 | oneway void startListeningForBandwidth(IShadowsocksServiceCallback cb);
11 | oneway void stopListeningForBandwidth(IShadowsocksServiceCallback cb);
12 | oneway void unregisterCallback(IShadowsocksServiceCallback cb);
13 |
14 | oneway void use(in int profileId);
15 | void useSync(in int profileId);
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_qu_shadowsocks_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/src/main/res/menu/profile_config_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/src/main/res/xml/pref_global.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_social_share.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/layout/dialog_acl_rule.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Copyright (C) 2017 by Max Lv
3 | Copyright (C) 2017 by Mygod Studio
4 |
5 | This program is free software: you can redistribute it and/or modify
6 | it under the terms of the GNU General Public License as published by
7 | the Free Software Foundation, either version 3 of the License, or
8 | (at your option) any later version.
9 |
10 | This program is distributed in the hope that it will be useful,
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | GNU General Public License for more details.
14 |
15 | You should have received a copy of the GNU General Public License
16 | along with this program. If not, see .
17 |
18 |
--------------------------------------------------------------------------------
/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | #388E3C
6 | #00C853
7 | #CFD8DC
8 | #90A4AE
9 | #607D8B
10 | #455A64
11 | @color/material_blue_grey_100
12 | @color/material_blue_grey_300
13 | @color/material_blue_grey_500
14 | @color/material_blue_grey_700
15 | @color/material_green_a700
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/main/res/menu/app_manager_menu.xml:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/src/main/res/xml/tracker.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 604800
5 |
6 |
7 | true
8 |
9 |
10 | true
11 |
12 |
13 |
14 | Shadowsocks ScreenView
15 |
16 |
17 |
18 | Shadowsocks EcommerceView
19 |
20 |
21 |
22 | UA-37082941-1
23 |
24 |
--------------------------------------------------------------------------------
/src/main/res/layout/layout_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
11 |
12 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main/res/layout/layout_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
--------------------------------------------------------------------------------
/src/main/res/values/configs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ".appspot.com",".blogspot.com",".blogspot.kr",".blogspot.sg",".cdninstagram.com",".connect.facebook.net",".facebook.br",".facebook.com",".facebook.hu",".facebook.nl",".facebook.se",".fbcdn.net",".google.co.jp",".google.co.kr",".google.com",".google.com",".google.com.hk",".google.com.sg",".google.com.tw",".googleapis.com",".googleapps.com",".googleartproject.com",".googleblog.com",".googlecode.com",".googlecommerce.com",".googledomains.com",".googledrive.com",".googleearth.com",".googlegroups.com",".googlehosted.com",".googleideas.com",".googlelabs.com",".googlemail.com",".googleplay.com",".googleplus.com",".googlesile.com",".googlesource.com",".googleusercontent.com",".googlevideo.com",".googlezip.net",".gstatic.com",".yahoo.com",".igoogle.com",".instagram.com",".institut-tibetain.org",".nic.google",".registry.google",".thinkwithgoogle.com",".withgoogle.com",".youtube-nocookie.com",".youtube.com",".gvt1.com",".gvt2.com",".gvt3.com",".1e100.net"
4 |
5 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_start_idle.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
10 |
15 |
18 |
21 |
24 |
25 |
--------------------------------------------------------------------------------
/src/main/res/drawable/background_profile.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 | -
10 |
12 |
-
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | -
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 | Please read FAQ then answer these questions before submitting your issue. Thanks!
2 |
3 | ### Environment
4 |
5 | * Android version: *e.g. "7.0.0_r14", more detailed description is preferred*
6 | * Device: *e.g. Google Pixel XL*
7 | * Shadowsocks version: *version code "v3.0.0" or commit ID "a073f85"*
8 | * Last version that did not exhibit the issue (if applicable):
9 |
10 | ### Configuration
11 |
12 | _Put an `x` inside the [ ] that applies._
13 |
14 | * [ ] IPv4 server address
15 | * [ ] IPv6 server address
16 | * [ ] Client IPv4 availability
17 | * [ ] Client IPv6 availability
18 | * Local port: 1080
19 | * Encrypt method:
20 | * [ ] One-time authentication
21 | * Route
22 | * [ ] All
23 | * [ ] Bypass LAN
24 | * [ ] Bypass China
25 | * [ ] Bypass LAN & China
26 | * [ ] GFW List
27 | * [ ] China List
28 | * [ ] Custom rules
29 | * [ ] IPv6 route
30 | * [ ] Per-App Proxy
31 | * [ ] Bypass mode
32 | * [ ] UDP Forwarding
33 | * KCP Parameters: (leave blank if not enabled)
34 | * [ ] Auto Connect
35 | * [ ] TCP Fast Open
36 | * [ ] NAT mode
37 |
38 | ### What did you do?
39 |
40 |
41 | ### What did you expect to see?
42 |
43 |
44 | ### What did you see instead?
45 |
46 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_action_settings.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/layout/layout_tasker.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
17 |
18 |
22 |
23 |
--------------------------------------------------------------------------------
/src/main/res/layout/layout_apps_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
18 |
19 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_action_copyright.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "src/main/jni/shadowsocks-libev"]
2 | path = src/main/jni/shadowsocks-libev
3 | url = https://github.com/kaneawk/shadowsocksr-libev.git
4 | [submodule "src/main/jni/badvpn"]
5 | path = src/main/jni/badvpn
6 | url = https://github.com/shadowsocks/badvpn.git
7 | branch = shadowsocks-android
8 | [submodule "src/main/jni/libancillary"]
9 | path = src/main/jni/libancillary
10 | url = https://github.com/shadowsocks/libancillary.git
11 | branch = shadowsocks-android
12 | [submodule "src/main/jni/libevent"]
13 | path = src/main/jni/libevent
14 | url = https://github.com/shadowsocks/libevent.git
15 | branch = shadowsocks-android
16 | [submodule "src/main/jni/redsocks"]
17 | path = src/main/jni/redsocks
18 | url = https://github.com/shadowsocks/redsocks.git
19 | branch = shadowsocks-android
20 | [submodule "src/main/jni/pdnsd"]
21 | path = src/main/jni/pdnsd
22 | url = https://github.com/shadowsocks/pdnsd.git
23 | branch = shadowsocks-android
24 | [submodule "kcptun/kcptun"]
25 | path = kcptun/kcptun
26 | url = https://github.com/shadowsocks/kcptun
27 | [submodule "kcptun/go"]
28 | path = kcptun/go
29 | url = https://github.com/shadowsocks/go
30 | [submodule "src/main/jni/mbedtls"]
31 | path = src/main/jni/mbedtls
32 | url = https://github.com/ARMmbed/mbedtls
33 | [submodule "src/main/jni/pcre"]
34 | path = src/main/jni/pcre
35 | url = https://android.googlesource.com/platform/external/pcre
36 |
--------------------------------------------------------------------------------
/src/main/res/menu/profile_manager_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
31 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 |
3 | jdk:
4 | - oraclejdk8
5 |
6 | env:
7 | global:
8 | - NDK_VERSION=r12b
9 | - NDK_CCACHE=ccache
10 | - GOROOT_BOOTSTRAP=$GOROOT
11 | - ANDROID_NDK_HOME=$HOME/.android/android-ndk-${NDK_VERSION}
12 | - SBTPATH=$HOME/.sbt
13 | - PATH=${ANDROID_NDK_HOME}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools:${PATH}
14 |
15 | scala:
16 | - 2.11.8
17 |
18 | before_cache:
19 | - find $HOME/.sbt -name "*.lock" | xargs rm
20 | - find $HOME/.ivy2 -name "*.lock" | xargs rm
21 |
22 | cache:
23 | directories:
24 | - $HOME/.ivy2
25 | - $HOME/.sbt
26 |
27 | android:
28 | components:
29 | - tools
30 | - build-tools-25.0.2
31 | - extra-android-m2repository
32 | - extra-google-m2repository
33 |
34 | install:
35 | - >
36 | if [ ! -d "$ANDROID_NDK_HOME" ]; then
37 | mkdir -p $ANDROID_NDK_HOME;
38 | pushd $HOME/.android;
39 | export ARCH=`uname -m`;
40 | wget -q http://dl.google.com/android/repository/android-ndk-${NDK_VERSION}-linux-${ARCH}.zip;
41 | unzip -q android-ndk-${NDK_VERSION}-linux-${ARCH}.zip;
42 | popd;
43 | fi
44 | - >
45 | if [ ! -f "$SBTPATH/sbt" ]; then
46 | pushd $SBTPATH;
47 | wget -q https://raw.githubusercontent.com/paulp/sbt-extras/master/sbt;
48 | chmod a+x sbt;
49 | popd;
50 | fi
51 |
52 | script:
53 | - $SBTPATH/sbt native-build android:package-debug
54 |
--------------------------------------------------------------------------------
/src/main/jni/build-shared-executable.mk:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2009 The Android Open Source Project
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | #
15 | # this file is included from Android.mk files to build a target-specific
16 | # executable program
17 | #
18 | # Modified by @Mygod, based on:
19 | # https://android.googlesource.com/platform/ndk/+/f2e98f8c066aed59caf61163d4b87c2b858f9814/build/core/build-shared-library.mk
20 | # https://android.googlesource.com/platform/ndk/+/f2e98f8c066aed59caf61163d4b87c2b858f9814/build/core/build-executable.mk
21 | LOCAL_BUILD_SCRIPT := BUILD_EXECUTABLE
22 | LOCAL_MAKEFILE := $(local-makefile)
23 | $(call check-defined-LOCAL_MODULE,$(LOCAL_BUILD_SCRIPT))
24 | $(call check-LOCAL_MODULE,$(LOCAL_MAKEFILE))
25 | $(call check-LOCAL_MODULE_FILENAME)
26 | # we are building target objects
27 | my := TARGET_
28 | $(call handle-module-filename,lib,$(TARGET_SONAME_EXTENSION))
29 | $(call handle-module-built)
30 | LOCAL_MODULE_CLASS := EXECUTABLE
31 | include $(BUILD_SYSTEM)/build-module.mk
32 |
--------------------------------------------------------------------------------
/src/main/res/layout/layout_custom_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
16 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/main/scala/be/mygod/preference/DialogPreferencePlus.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package be.mygod.preference
22 |
23 | import android.app.DialogFragment
24 | import android.support.v7.preference.DialogPreference
25 |
26 | trait DialogPreferencePlus extends DialogPreference {
27 | def createDialog(): DialogFragment
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/acl/Subnet.scala:
--------------------------------------------------------------------------------
1 | package com.github.shadowsocks.acl
2 |
3 | import java.net.{Inet4Address, Inet6Address, InetAddress}
4 |
5 | import com.github.shadowsocks.utils.Utils
6 |
7 | /**
8 | * @author Mygod
9 | */
10 | @throws[IllegalArgumentException]
11 | class Subnet(val address: InetAddress, val prefixSize: Int) extends Comparable[Subnet] {
12 | if (prefixSize < 0) throw new IllegalArgumentException
13 | address match {
14 | case _: Inet4Address => if (prefixSize > 32) throw new IllegalArgumentException
15 | case _: Inet6Address => if (prefixSize > 128) throw new IllegalArgumentException
16 | }
17 |
18 | override def toString: String = if (address match {
19 | case _: Inet4Address => prefixSize == 32
20 | case _: Inet6Address => prefixSize == 128
21 | }) address.getHostAddress else address.getHostAddress + '/' + prefixSize
22 |
23 | override def compareTo(that: Subnet): Int = {
24 | val addrThis = address.getAddress
25 | val addrThat = that.address.getAddress
26 | var result = addrThis lengthCompare addrThat.length // IPv4 address goes first
27 | if (result != 0) return result
28 | for ((x, y) <- addrThis zip addrThat) {
29 | result = (x & 0xFF) compare (y & 0xFF) // undo sign extension of signed byte
30 | if (result != 0) return result
31 | }
32 | prefixSize compare that.prefixSize
33 | }
34 | }
35 |
36 | object Subnet {
37 | @throws[IllegalArgumentException]
38 | def fromString(value: String): Subnet = {
39 | val parts = value.split("/")
40 | if (!Utils.isNumeric(parts(0))) throw new IllegalArgumentException()
41 | val addr = InetAddress.getByName(parts(0))
42 | parts.length match {
43 | case 1 => new Subnet(addr, addr match {
44 | case _: Inet4Address => 32
45 | case _: Inet6Address => 128
46 | })
47 | case 2 => new Subnet(addr, parts(1).toInt)
48 | case _ => throw new IllegalArgumentException()
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/widget/BoundedCardView.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.widget
22 |
23 | import android.content.Context
24 | import android.support.v7.widget.CardView
25 | import android.util.AttributeSet
26 |
27 | /**
28 | * @author Mygod
29 | */
30 | class BoundedCardView(context: Context, attrs: AttributeSet) extends CardView(context, attrs) with BoundedView {
31 | initAttrs(context, attrs)
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/widget/BoundedGridLayout.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.widget
22 |
23 | import android.content.Context
24 | import android.support.v7.widget.GridLayout
25 | import android.util.AttributeSet
26 |
27 | /**
28 | * @author Mygod
29 | */
30 | class BoundedGridLayout(context: Context, attrs: AttributeSet) extends GridLayout(context, attrs) with BoundedView {
31 | initAttrs(context, attrs)
32 | }
33 |
--------------------------------------------------------------------------------
/kcptun/make.bash:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function try () {
4 | "$@" || exit -1
5 | }
6 |
7 | [ -z "$ANDROID_NDK_HOME" ] && ANDROID_NDK_HOME=~/android-ndk-r12b
8 |
9 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
10 | DEPS=$DIR/.deps
11 | ANDROID_ARM_TOOLCHAIN=$DEPS/android-toolchain-16-arm
12 | ANDROID_X86_TOOLCHAIN=$DEPS/android-toolchain-16-x86
13 |
14 | ANDROID_ARM_CC=$ANDROID_ARM_TOOLCHAIN/bin/arm-linux-androideabi-gcc
15 | ANDROID_ARM_STRIP=$ANDROID_ARM_TOOLCHAIN/bin/arm-linux-androideabi-strip
16 |
17 | ANDROID_X86_CC=$ANDROID_X86_TOOLCHAIN/bin/i686-linux-android-gcc
18 | ANDROID_X86_STRIP=$ANDROID_X86_TOOLCHAIN/bin/i686-linux-android-strip
19 |
20 |
21 | if [ ! -d "$DEPS" ]; then
22 | mkdir -p $DEPS
23 | fi
24 |
25 | if [ ! -d "$ANDROID_ARM_TOOLCHAIN" ]; then
26 | echo "Make standalone toolchain for ARM arch"
27 | $ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py --arch arm \
28 | --api 16 --install-dir $ANDROID_ARM_TOOLCHAIN
29 | fi
30 |
31 | if [ ! -d "$ANDROID_X86_TOOLCHAIN" ]; then
32 | echo "Make standalone toolchain for X86 arch"
33 | $ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py --arch x86 \
34 | --api 16 --install-dir $ANDROID_X86_TOOLCHAIN
35 | fi
36 |
37 | if [ ! -d "$DIR/go/bin" ]; then
38 | echo "Build the custom go"
39 |
40 | pushd $DIR/go/src
41 | try ./make.bash
42 | popd
43 | fi
44 |
45 | export GOROOT=$DIR/go
46 | export GOPATH=$DEPS/gopath
47 | export GOBIN=$GOPATH/bin
48 | mkdir -p $GOBIN
49 | export PATH=$GOROOT/bin:$PATH
50 |
51 | pushd kcptun/client
52 |
53 | echo "Get dependences for kcptun"
54 | go get -u github.com/xtaci/kcp-go
55 | go get -u github.com/xtaci/smux
56 | go get
57 |
58 | echo "Cross compile kcptun for arm"
59 | try env CGO_ENABLED=1 CC=$ANDROID_ARM_CC GOOS=android GOARCH=arm GOARM=7 go build -ldflags="-s -w"
60 | try $ANDROID_ARM_STRIP client
61 | try mv client $DIR/../src/main/libs/armeabi-v7a/libkcptun.so
62 |
63 | echo "Cross compile kcptun for x86"
64 | try env CGO_ENABLED=1 CC=$ANDROID_X86_CC GOOS=android GOARCH=386 go build -ldflags="-s -w"
65 | try $ANDROID_X86_STRIP client
66 | try mv client $DIR/../src/main/libs/x86/libkcptun.so
67 | popd
68 |
69 | echo "Successfully build kcptun"
70 |
--------------------------------------------------------------------------------
/src/main/java/com/github/shadowsocks/System.java:
--------------------------------------------------------------------------------
1 | /* Shadowsocks - A shadowsocks client for Android
2 | * Copyright (C) 2012
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | *
17 | *
18 | * ___====-_ _-====___
19 | * _--^^^#####// \\#####^^^--_
20 | * _-^##########// ( ) \\##########^-_
21 | * -############// |\^^/| \\############-
22 | * _/############// (@::@) \\############\_
23 | * /#############(( \\// ))#############\
24 | * -###############\\ (oo) //###############-
25 | * -#################\\ / VV \ //#################-
26 | * -###################\\/ \//###################-
27 | * _#/|##########/\######( /\ )######/\##########|\#_
28 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
29 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
30 | * ` ` ` ` / | | | | \ ' ' ' '
31 | * ( | | | | )
32 | * __\ | | | | /__
33 | * (vvv(VVV)(VVV)vvv)
34 | *
35 | * HERE BE DRAGONS
36 | *
37 | */
38 |
39 | package com.github.shadowsocks;
40 |
41 | public class System {
42 | static {
43 | java.lang.System.loadLibrary("system");
44 | }
45 |
46 | public static native int exec(String cmd);
47 | public static native int sendfd(int fd, String path);
48 | public static native void jniclose(int fd);
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/utils/IOUtils.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.utils
22 |
23 | import com.github.shadowsocks.utils.CloseUtils._
24 | import java.io.{FileWriter, InputStream, OutputStream}
25 |
26 | /**
27 | * @author Mygod
28 | */
29 | object IOUtils {
30 | private final val BUFFER_SIZE = 32 * 1024
31 |
32 | def copy(in: InputStream, out: OutputStream) {
33 | val buffer = new Array[Byte](BUFFER_SIZE)
34 | while (true) {
35 | val count = in.read(buffer)
36 | if (count >= 0) out.write(buffer, 0, count) else return
37 | }
38 | }
39 |
40 | def writeString(file: String, content: String): Unit =
41 | autoClose(new FileWriter(file))(writer => writer.write(content))
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/acl/DonaldTrump.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.acl
22 |
23 | import android.util.Log
24 | import com.evernote.android.job.JobCreator
25 |
26 | /**
27 | * “I create jobs all day long.
28 | * - Donald Trump, 2015
29 | *
30 | * Source: http://www.cnn.com/2015/09/24/politics/donald-trump-marco-rubio-foreign-policy/
31 | *
32 | * @author !Mygod
33 | */
34 | object DonaldTrump extends JobCreator {
35 | def create(tag: String): AclSyncJob = {
36 | val parts = tag.split(":")
37 | parts(0) match {
38 | case AclSyncJob.TAG => new AclSyncJob(parts(1))
39 | case _ =>
40 | Log.w("DonaldTrump", "Unknown job tag: " + tag)
41 | null
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/TaskerReceiver.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks
22 |
23 | import android.content.{BroadcastReceiver, Context, Intent}
24 | import com.github.shadowsocks.utils.{TaskerSettings, Utils}
25 | import com.github.shadowsocks.ShadowsocksApplication.app
26 |
27 | /**
28 | * @author CzBiX
29 | */
30 | class TaskerReceiver extends BroadcastReceiver {
31 | override def onReceive(context: Context, intent: Intent) {
32 | val settings = TaskerSettings.fromIntent(intent)
33 | app.profileManager.getProfile(settings.profileId) match {
34 | case Some(_) => app.switchProfile(settings.profileId)
35 | case _ =>
36 | }
37 | if (settings.switchOn) Utils.startSsService(context) else Utils.stopSsService(context)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/ToolbarFragment.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks
22 |
23 | import android.app.Fragment
24 | import android.os.Bundle
25 | import android.support.v7.widget.Toolbar
26 | import android.view.View
27 |
28 | class ToolbarFragment extends Fragment {
29 | var toolbar: Toolbar = _
30 |
31 | override def onViewCreated(view: View, savedInstanceState: Bundle) {
32 | super.onViewCreated(view, savedInstanceState)
33 | toolbar = view.findViewById(R.id.toolbar).asInstanceOf[Toolbar]
34 | val activity = getActivity.asInstanceOf[MainActivity]
35 | if (activity.crossfader == null) activity.drawer.setToolbar(activity, toolbar, true)
36 | }
37 |
38 | def onTrafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long): Unit = ()
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/scala/be/mygod/preference/PreferenceCategory.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package be.mygod.preference
22 |
23 | import android.content.Context
24 | import android.support.v7.preference.{PreferenceViewHolder, PreferenceCategory => Base}
25 | import android.util.AttributeSet
26 | import android.view.ViewGroup.MarginLayoutParams
27 |
28 | /**
29 | * Based on: https://github.com/Gericop/Android-Support-Preference-V7-Fix/blob/4c2bb2896895dbedb37b659b36bd2a96b33c1605/preference-v7/src/main/java/com/takisoft/fix/support/v7/preference/PreferenceCategory.java
30 | *
31 | * @author Mygod
32 | */
33 | class PreferenceCategory(context: Context, attrs: AttributeSet = null) extends Base(context, attrs) {
34 | override def onBindViewHolder(holder: PreferenceViewHolder) {
35 | super.onBindViewHolder(holder)
36 | holder.findViewById(android.R.id.title).getLayoutParams.asInstanceOf[MarginLayoutParams].bottomMargin = 0
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/.github/faq.md:
--------------------------------------------------------------------------------
1 | ### Troubleshooting
2 |
3 | Cannot connect to server:
4 |
5 | 1. Stop battery saver if it's active;
6 | 2. If an upgrade breaks anything, do a manual reset first by pressing Reset at the end of the list;
7 | 3. Check your config;
8 | 4. Wipe app data.
9 |
10 | Crash: [Submit an issue](https://github.com/shadowsocks/shadowsocks-android/issues/new) with logcat attached, or submit a crash report to Google Play. Then, try wiping app data.
11 |
12 | ### UI tips
13 |
14 | * Tap the number to enter the port you wish to use; (if the keyboard doesn't pop up automatically for some reason)
15 | * Use Tasker integration to create a desktop widget.
16 |
17 | ### Why is NAT mode deprecated?
18 |
19 | 1. Requiring ROOT permission;
20 | 2. No IPv6 support;
21 | 3. No UDP relay support.
22 |
23 | ### How to remove the exclamation mark when using VPN mode?
24 |
25 | The exclamation mark in the Wi-Fi/cellular icon appears because the system fails to connect to portal server (defaults to `clients3.google.com`) without VPN connection. To remove it, follow the instructions in [this article](https://www.noisyfox.cn/45.html). (in Simplified Chinese)
26 |
27 | ### Why are MIUI, EMUI and other AOSPs in China not officially supported?
28 |
29 | 1. Broken VPNService implementation, especially for IPv6;
30 | 2. Aggressive (or called broken) background service killing policy.
31 |
32 | Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772)
33 |
34 | ### How to pause Shadowsocks service?
35 |
36 | * For Android 7.0+: Use quick switch tile in Quick Settings;
37 | * Use Tasker integration;
38 | * Add a profile with per-app proxy enabled for Shadowsocks only, bypass mode off.
39 |
40 | ### Why does Shadowsocks consume so much battery on Android 5.0+?
41 |
42 | As Shadowsocks takes over the whole device network, any battery used by network activities from other apps are also counted as those from Shadowsocks. So, the battery usage of Shadowsocks equals to the sum of all the network activities of your device. Shadowsocks itself is a totally I/O bound application on modern Android devices, which is expected not to consume any notable battery.
43 |
44 | So if you notice a significant increase in battery usage after you use Shadowsocks, it's most likely caused by other apps. For example, Google Play services can consume more battery after being able to connecting to Google, etc.
45 |
46 | ### It works fine under Wi-Fi but can't connect through cellular data?
47 |
48 | Allow this app to consume background data in app settings.
49 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/utils/CloseUtils.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.utils
22 |
23 | /**
24 | * @author Mygod
25 | */
26 | object CloseUtils {
27 | type Disconnectable = {
28 | def disconnect()
29 | }
30 |
31 | def autoClose[A <: AutoCloseable, B](x: => A)(block: A => B): B = {
32 | var a: Option[A] = None
33 | try {
34 | a = Some(x)
35 | block(a.get)
36 | } finally if (a.nonEmpty) try {
37 | val v = a.get
38 | if (v ne null) v.close()
39 | } catch {
40 | case _: Exception =>
41 | }
42 | }
43 | def autoDisconnect[A <: Disconnectable, B](x: => A)(block: A => B): B = {
44 | var a: Option[A] = None
45 | try {
46 | a = Some(x)
47 | block(a.get)
48 | } finally if (a.nonEmpty) try {
49 | val v = a.get
50 | if (v ne null) v.disconnect()
51 | } catch {
52 | case _: Exception =>
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/utils/Typefaces.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.utils
22 |
23 | import java.util
24 |
25 | import android.content.Context
26 | import android.graphics.Typeface
27 | import android.util.Log
28 | import com.github.shadowsocks.ShadowsocksApplication.app
29 |
30 | object Typefaces {
31 | def get(c: Context, assetPath: String): Typeface = {
32 | cache synchronized {
33 | if (!cache.containsKey(assetPath)) {
34 | try {
35 | cache.put(assetPath, Typeface.createFromAsset(c.getAssets, assetPath))
36 | } catch {
37 | case e: Exception =>
38 | Log.e("Typefaces", "Could not get typeface '" + assetPath + "' because " + e.getMessage)
39 | app.track(e)
40 | return null
41 | }
42 | }
43 | return cache.get(assetPath)
44 | }
45 | }
46 |
47 | private final val cache = new util.Hashtable[String, Typeface]
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/scala/be/mygod/preference/SummaryPreference.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package be.mygod.preference
22 |
23 | import android.support.v7.preference.Preference
24 |
25 | /**
26 | * Make your preference support %s in summary. Override getSummaryValue to customize what to put in.
27 | * Based on:
28 | * https://github.com/android/platform_frameworks_base/blob/master/core/java/android/preference/ListPreference.java
29 | *
30 | * @author Mygod
31 | */
32 | trait SummaryPreference extends Preference {
33 | protected def getSummaryValue: AnyRef
34 |
35 | /**
36 | * Returns the summary of this SummaryPreference. If the summary has a String formatting marker in it
37 | * (i.e. "%s" or "%1$s"), then the current entry value will be substituted in its place.
38 | *
39 | * @return the summary with appropriate string substitution
40 | */
41 | override def getSummary: String = {
42 | val summary = super.getSummary
43 | if (summary == null) null else String.format(summary.toString, getSummaryValue)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/BootReceiver.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks
22 |
23 | import android.content.pm.PackageManager
24 | import android.content.{BroadcastReceiver, ComponentName, Context, Intent}
25 | import com.github.shadowsocks.utils._
26 |
27 | object BootReceiver {
28 | def getEnabled(context: Context): Boolean = PackageManager.COMPONENT_ENABLED_STATE_ENABLED ==
29 | context.getPackageManager.getComponentEnabledSetting(new ComponentName(context, classOf[BootReceiver]))
30 | def setEnabled(context: Context, enabled: Boolean): Unit = context.getPackageManager.setComponentEnabledSetting(
31 | new ComponentName(context, classOf[BootReceiver]),
32 | if (enabled) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
33 | PackageManager.DONT_KILL_APP)
34 | }
35 |
36 | class BootReceiver extends BroadcastReceiver {
37 | def onReceive(context: Context, intent: Intent): Unit = {
38 | Utils.startSsService(context)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/res/menu/custom_rules_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | -
11 |
12 |
17 |
22 |
27 |
32 |
33 |
34 | -
39 |
40 |
44 |
48 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks
22 |
23 | import android.app.Service
24 | import android.content.Intent
25 | import android.net.VpnService
26 | import android.os.{Handler, IBinder}
27 | import com.github.shadowsocks.ShadowsocksApplication.app
28 |
29 | class ShadowsocksRunnerService extends Service with ServiceBoundContext {
30 | val handler = new Handler()
31 |
32 | override def onBind(intent: Intent): IBinder = {
33 | null
34 | }
35 |
36 | override def onServiceConnected() {
37 | handler.postDelayed(() => {
38 | if (bgService != null) {
39 | if (app.isNatEnabled) startBackgroundService()
40 | else if (VpnService.prepare(ShadowsocksRunnerService.this) == null) startBackgroundService()
41 | handler.postDelayed(() => stopSelf(), 3000)
42 | }
43 | }, 1000)
44 | }
45 |
46 | def startBackgroundService(): Unit = bgService.useSync(app.profileId)
47 |
48 | override def onCreate() {
49 | super.onCreate()
50 | attachService()
51 | }
52 |
53 | override def onDestroy() {
54 | super.onDestroy()
55 | detachService()
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/ShadowsocksBackupAgent.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks
22 |
23 | import android.app.backup.{BackupAgentHelper, FileBackupHelper, SharedPreferencesBackupHelper}
24 | import com.github.shadowsocks.acl.Acl
25 | import com.github.shadowsocks.database.DBHelper
26 |
27 | class ShadowsocksBackupAgent extends BackupAgentHelper {
28 |
29 | // The names of the SharedPreferences groups that the application maintains. These
30 | // are the same strings that are passed to getSharedPreferences(String, int).
31 | val PREFS_DISPLAY = "com.github.shadowsocks_preferences"
32 |
33 | // An arbitrary string used within the BackupAgentHelper implementation to
34 | // identify the SharedPreferencesBackupHelper's data.
35 | val MY_PREFS_BACKUP_KEY = "com.github.shadowsocks"
36 |
37 | val DATABASE = "com.github.shadowsocks.database.profile"
38 |
39 | override def onCreate() {
40 | val helper = new SharedPreferencesBackupHelper(this, PREFS_DISPLAY)
41 | addHelper(MY_PREFS_BACKUP_KEY, helper)
42 | addHelper(DATABASE, new FileBackupHelper(this, "../databases/" + DBHelper.PROFILE,
43 | "../" + Acl.CUSTOM_RULES + ".acl"))
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/AboutFragment.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks
22 | import java.util.Locale
23 |
24 | import android.content.Intent
25 | import android.net.Uri
26 | import android.os.Bundle
27 | import android.view.{LayoutInflater, View, ViewGroup}
28 | import android.webkit.{WebView, WebViewClient}
29 |
30 | class AboutFragment extends ToolbarFragment {
31 | override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View =
32 | inflater.inflate(R.layout.layout_about, container, false)
33 |
34 | override def onViewCreated(view: View, savedInstanceState: Bundle) {
35 | super.onViewCreated(view, savedInstanceState)
36 | toolbar.setTitle(getString(R.string.about_title).formatLocal(Locale.ENGLISH, BuildConfig.VERSION_NAME))
37 | val web = view.findViewById(R.id.web_view).asInstanceOf[WebView]
38 | web.loadUrl("file:///android_asset/pages/about.html")
39 | web.setWebViewClient(new WebViewClient() {
40 | override def shouldOverrideUrlLoading(view: WebView, url: String): Boolean = {
41 | getActivity.asInstanceOf[MainActivity].launchUrl(url)
42 | true
43 | }
44 | })
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/GlobalSettingsFragment.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks
22 |
23 | import android.os.Bundle
24 | import android.view.{LayoutInflater, View, ViewGroup}
25 | import android.app.Fragment
26 |
27 | class GlobalSettingsFragment extends ToolbarFragment {
28 |
29 | override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View =
30 | inflater.inflate(R.layout.layout_global_settings, container, false)
31 |
32 | override def onViewCreated(view: View, savedInstanceState: Bundle) {
33 | super.onViewCreated(view, savedInstanceState)
34 | toolbar.setTitle(R.string.settings)
35 |
36 | val fm = getChildFragmentManager
37 | fm.beginTransaction().replace(R.id.content, new GlobalConfigFragment()).commit()
38 | fm.executePendingTransactions()
39 | }
40 |
41 | override def onDetach() {
42 | super.onDetach()
43 |
44 | try {
45 | val childFragmentManager = classOf[Fragment].getDeclaredField("mChildFragmentManager")
46 | childFragmentManager.setAccessible(true)
47 | childFragmentManager.set(this, null)
48 | } catch {
49 | case _: Exception => // ignore
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/scala/be/mygod/preference/NumberPickerPreferenceDialogFragment.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package be.mygod.preference
22 |
23 | import android.content.Context
24 | import android.support.v14.preference.PreferenceDialogFragment
25 | import android.view.{View, ViewGroup}
26 | import android.widget.NumberPicker
27 |
28 | class NumberPickerPreferenceDialogFragment extends PreferenceDialogFragment {
29 | private lazy val preference = getPreference.asInstanceOf[NumberPickerPreference]
30 | private lazy val picker = preference.picker
31 |
32 | override protected def onCreateDialogView(context: Context): NumberPicker = {
33 | val parent = picker.getParent.asInstanceOf[ViewGroup]
34 | if (parent != null) parent.removeView(picker)
35 | picker
36 | }
37 |
38 | override protected def onBindDialogView(view: View) {
39 | super.onBindDialogView(view)
40 | picker.setValue(preference.getValue)
41 | }
42 |
43 | override protected def needInputMethod = true
44 |
45 | def onDialogClosed(positiveResult: Boolean) {
46 | picker.clearFocus() // commit changes
47 | if (positiveResult) {
48 | val value = picker.getValue
49 | if (preference.callChangeListener(value)) preference.setValue(value)
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package be.mygod.preference
22 |
23 | import android.content.Context
24 | import android.support.v14.preference.PreferenceDialogFragment
25 | import android.support.v7.widget.AppCompatEditText
26 | import android.view.{View, ViewGroup}
27 |
28 | class EditTextPreferenceDialogFragment extends PreferenceDialogFragment {
29 | private lazy val preference = getPreference.asInstanceOf[EditTextPreference]
30 | private lazy val editText = preference.editText
31 |
32 | override protected def onCreateDialogView(context: Context): AppCompatEditText = {
33 | val parent = editText.getParent.asInstanceOf[ViewGroup]
34 | if (parent != null) parent.removeView(editText)
35 | editText
36 | }
37 |
38 | override protected def onBindDialogView(view: View) {
39 | super.onBindDialogView(view)
40 | editText.setText(preference.getText)
41 | val text = editText.getText
42 | if (text != null) editText.setSelection(0, text.length)
43 | }
44 |
45 | override protected def needInputMethod = true
46 |
47 | def onDialogClosed(positiveResult: Boolean): Unit = if (positiveResult) {
48 | val value = editText.getText.toString
49 | if (preference.callChangeListener(value)) preference.setText(value)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/utils/TcpFastOpen.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.utils
22 |
23 | import java.io.File
24 |
25 | import eu.chainfire.libsuperuser.Shell
26 |
27 | import scala.collection.JavaConverters._
28 |
29 | /**
30 | * @author Mygod
31 | */
32 | object TcpFastOpen {
33 | /**
34 | * Is kernel version >= 3.7.1.
35 | */
36 | lazy val supported: Boolean = "^(\\d+)\\.(\\d+)\\.(\\d+)".r.findFirstMatchIn(System.getProperty("os.version")) match {
37 | case Some(m) =>
38 | val kernel = m.group(1).toInt
39 | if (kernel < 3) false else if (kernel > 3) true else {
40 | val major = m.group(2).toInt
41 | if (major < 7) false else if (major > 7) true else m.group(3).toInt >= 1
42 | }
43 | case _ => false
44 | }
45 |
46 | def sendEnabled: Boolean = {
47 | val file = new File("/proc/sys/net/ipv4/tcp_fastopen")
48 | file.canRead && (Utils.readAllLines(file).toInt & 1) > 0
49 | }
50 |
51 | def enabled(value: Boolean): String = if (sendEnabled != value) {
52 | val res = Shell.run("su", Array(
53 | "if echo " + (if (value) 3 else 0) + " > /proc/sys/net/ipv4/tcp_fastopen; then",
54 | " echo Success.",
55 | "else",
56 | " echo Failed.",
57 | "fi"), null, true)
58 | if (res != null) res.asScala.mkString("\n") else null
59 | } else null
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/widget/BoundedView.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.widget
22 |
23 | import android.content.Context
24 | import android.util.AttributeSet
25 | import android.view.View
26 | import android.view.View.MeasureSpec
27 | import com.github.shadowsocks.R
28 |
29 | /**
30 | * Based on: http://stackoverflow.com/a/6212120/2245107
31 | *
32 | * @author Mygod
33 | */
34 | trait BoundedView extends View {
35 | private var boundedWidth: Int = _
36 | private var boundedHeight: Int = _
37 |
38 | protected def initAttrs(context: Context, attrs: AttributeSet) {
39 | val arr = context.obtainStyledAttributes(attrs, R.styleable.BoundedView)
40 | boundedWidth = arr.getDimensionPixelSize(R.styleable.BoundedView_bounded_width, 0)
41 | boundedHeight = arr.getDimensionPixelSize(R.styleable.BoundedView_bounded_height, 0)
42 | arr.recycle()
43 | }
44 |
45 | override def onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int): Unit = super.onMeasure(
46 | if (boundedWidth > 0 && boundedWidth < MeasureSpec.getSize(widthMeasureSpec))
47 | MeasureSpec.makeMeasureSpec(boundedWidth, MeasureSpec.getMode(widthMeasureSpec))
48 | else widthMeasureSpec,
49 | if (boundedHeight > 0 && boundedHeight < MeasureSpec.getSize(heightMeasureSpec))
50 | MeasureSpec.makeMeasureSpec(boundedHeight, MeasureSpec.getMode(heightMeasureSpec))
51 | else heightMeasureSpec)
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/preferences/KcpCliPreferenceDialogFragment.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.preferences
22 |
23 | import android.app.AlertDialog
24 | import android.content.DialogInterface
25 | import eu.chainfire.libsuperuser.Shell
26 | import be.mygod.preference.EditTextPreferenceDialogFragment
27 |
28 | import scala.collection.JavaConverters._
29 |
30 | /**
31 | * @author Mygod
32 | */
33 | class KcpCliPreferenceDialogFragment extends EditTextPreferenceDialogFragment {
34 | override def onPrepareDialogBuilder(builder: AlertDialog.Builder) {
35 | super.onPrepareDialogBuilder(builder)
36 | builder.setNeutralButton("?", ((_, _) => new AlertDialog.Builder(builder.getContext)
37 | .setTitle("?")
38 | .setMessage(Shell.SH.run(builder.getContext.getApplicationInfo.nativeLibraryDir + "/libkcptun.so --help")
39 | .asScala
40 | .dropWhile(line => line != "GLOBAL OPTIONS:")
41 | .drop(1)
42 | .takeWhile(line => line.length() > 3)
43 | .filter(line =>
44 | !line.startsWith(" --localaddr ") &&
45 | !line.startsWith(" --remoteaddr ") &&
46 | !line.startsWith(" --path ") &&
47 | !line.startsWith(" --help,") &&
48 | !line.startsWith(" --version,"))
49 | .mkString("\n")
50 | .replaceAll(" {2,}", "\n")
51 | .substring(1)) // remove 1st \n
52 | .show()): DialogInterface.OnClickListener)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/utils/TaskerSettings.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.utils
22 |
23 | import android.content.{Context, Intent}
24 | import android.os.Bundle
25 | import com.github.shadowsocks.R
26 | import com.github.shadowsocks.ShadowsocksApplication.app
27 | import com.twofortyfouram.locale.api.{Intent => ApiIntent}
28 |
29 | object TaskerSettings {
30 | private val KEY_SWITCH_ON = "switch_on"
31 | private val KEY_PROFILE_ID = "profile_id"
32 |
33 | def fromIntent(intent: Intent) = new TaskerSettings(if (intent.hasExtra(ApiIntent.EXTRA_BUNDLE))
34 | intent.getBundleExtra(ApiIntent.EXTRA_BUNDLE) else Bundle.EMPTY)
35 | }
36 |
37 | class TaskerSettings(bundle: Bundle) {
38 | import TaskerSettings._
39 |
40 | var switchOn: Boolean = bundle.getBoolean(KEY_SWITCH_ON, true)
41 | var profileId: Int = bundle.getInt(KEY_PROFILE_ID, -1)
42 |
43 | def toIntent(context: Context): Intent = {
44 | val bundle = new Bundle()
45 | if (!switchOn) bundle.putBoolean(KEY_SWITCH_ON, false)
46 | if (profileId >= 0) bundle.putInt(KEY_PROFILE_ID, profileId)
47 | new Intent().putExtra(ApiIntent.EXTRA_BUNDLE, bundle).putExtra(ApiIntent.EXTRA_STRING_BLURB,
48 | app.profileManager.getProfile(profileId) match {
49 | case Some(p) => context.getString(if (switchOn) R.string.start_service else R.string.stop_service, p.getName)
50 | case None => context.getString(if (switchOn) R.string.start_service_default else R.string.stop)
51 | })
52 | }
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/src/main/scala/be/mygod/preference/PreferenceFragment.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package be.mygod.preference
22 |
23 | import android.app.DialogFragment
24 | import android.os.Bundle
25 | import android.support.v14.preference.{PreferenceFragment => Base}
26 | import android.support.v7.preference.{Preference, PreferenceScreen}
27 | import android.view.{LayoutInflater, View, ViewGroup}
28 |
29 | abstract class PreferenceFragment extends Base {
30 | override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View =
31 | super.onCreateView(inflater, container, savedInstanceState)
32 |
33 | protected final def displayPreferenceDialog(key: String, fragment: DialogFragment) {
34 | val bundle = new Bundle(1)
35 | bundle.putString("key", key)
36 | fragment.setArguments(bundle)
37 | fragment.setTargetFragment(this, 0)
38 | getFragmentManager.beginTransaction()
39 | .add(fragment, "android.support.v14.preference.PreferenceFragment.DIALOG")
40 | .commitAllowingStateLoss()
41 | }
42 |
43 | override def onDisplayPreferenceDialog(preference: Preference): Unit = preference match {
44 | case dpp: DialogPreferencePlus => displayPreferenceDialog(preference.getKey, dpp.createDialog())
45 | case _ => super.onDisplayPreferenceDialog(preference)
46 | }
47 |
48 | override protected def onCreateAdapter(screen: PreferenceScreen) = new PreferenceGroupAdapter(screen)
49 |
50 | override def onResume() {
51 | super.onResume()
52 | getListView.scrollBy(0, 0)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/scala/be/mygod/preference/EditTextPreference.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package be.mygod.preference
22 |
23 | import android.content.Context
24 | import android.support.v7.preference.{EditTextPreference => Parent}
25 | import android.support.v7.widget.AppCompatEditText
26 | import android.text.InputType
27 | import android.util.AttributeSet
28 |
29 | /**
30 | * Fixed EditTextPreference + SummaryPreference with password support!
31 | * Based on: https://github.com/Gericop/Android-Support-Preference-V7-Fix/tree/master/app/src/main/java/android/support/v7/preference
32 | */
33 | class EditTextPreference(context: Context, attrs: AttributeSet = null) extends Parent(context, attrs)
34 | with DialogPreferencePlus with SummaryPreference {
35 | val editText = new AppCompatEditText(context, attrs)
36 | editText.setId(android.R.id.edit)
37 |
38 | override def createDialog() = new EditTextPreferenceDialogFragment()
39 |
40 | override protected def getSummaryValue: String = {
41 | var text = getText
42 | if (text == null) text = ""
43 | val inputType = editText.getInputType
44 | if (inputType == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD) ||
45 | inputType == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD) ||
46 | inputType == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD))
47 | "\u2022" * text.length else text
48 | }
49 |
50 | override def setText(text: String): Unit = {
51 | super.setText(text)
52 | notifyChanged()
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/res/layout/layout_apps.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
28 |
39 |
40 |
43 |
47 |
52 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/QuickToggleShortcut.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks
22 |
23 | import android.app.Activity
24 | import android.content.Intent
25 | import android.content.pm.ShortcutManager
26 | import android.os.{Build, Bundle}
27 | import com.github.shadowsocks.utils.{State, Utils}
28 |
29 | /**
30 | * @author Mygod
31 | */
32 | class QuickToggleShortcut extends Activity with ServiceBoundContext {
33 | override def onCreate(savedInstanceState: Bundle) {
34 | super.onCreate(savedInstanceState)
35 | getIntent.getAction match {
36 | case Intent.ACTION_CREATE_SHORTCUT =>
37 | setResult(Activity.RESULT_OK, new Intent()
38 | .putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent(this, classOf[QuickToggleShortcut]))
39 | .putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.quick_toggle))
40 | .putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
41 | Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher)))
42 | finish()
43 | case _ =>
44 | attachService()
45 | if (Build.VERSION.SDK_INT >= 25) getSystemService(classOf[ShortcutManager]).reportShortcutUsed("toggle")
46 | }
47 | }
48 |
49 | override def onDestroy() {
50 | detachService()
51 | super.onDestroy()
52 | }
53 |
54 | override def onServiceConnected() {
55 | bgService.getState match {
56 | case State.STOPPED => Utils.startSsService(this)
57 | case State.CONNECTED => Utils.stopSsService(this)
58 | case _ => // ignore
59 | }
60 | finish()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/acl/AclSyncJob.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.acl
22 |
23 | import java.io.IOException
24 | import java.util.concurrent.TimeUnit
25 |
26 | import com.evernote.android.job.Job.{Params, Result}
27 | import com.evernote.android.job.{Job, JobRequest}
28 | import com.github.shadowsocks.ShadowsocksApplication.app
29 | import com.github.shadowsocks.utils.IOUtils
30 |
31 | import scala.io.Source
32 |
33 | /**
34 | * @author Mygod
35 | */
36 | object AclSyncJob {
37 | final val TAG = "AclSyncJob"
38 |
39 | def schedule(route: String): Int = new JobRequest.Builder(AclSyncJob.TAG + ':' + route)
40 | .setExecutionWindow(1, TimeUnit.DAYS.toMillis(28))
41 | .setRequirementsEnforced(true)
42 | .setRequiredNetworkType(JobRequest.NetworkType.UNMETERED)
43 | .setRequiresCharging(true)
44 | .setUpdateCurrent(true)
45 | .build().schedule()
46 | }
47 |
48 | class AclSyncJob(route: String) extends Job {
49 | override def onRunJob(params: Params): Result = {
50 | val filename = route + ".acl"
51 | try {
52 | //noinspection JavaAccessorMethodCalledAsEmptyParen
53 | IOUtils.writeString(app.getApplicationInfo.dataDir + '/' + filename,
54 | Source.fromURL("https://shadowsocks.org/acl/android/v1/" + filename).mkString)
55 | Result.SUCCESS
56 | } catch {
57 | case e: IOException =>
58 | e.printStackTrace()
59 | Result.RESCHEDULE
60 | case e: Exception => // unknown failures, probably shouldn't retry
61 | e.printStackTrace()
62 | Result.FAILURE
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/ProfileConfigActivity.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks
22 |
23 | import android.app.Activity
24 | import android.content.DialogInterface
25 | import android.os.Bundle
26 | import android.support.v7.app.AlertDialog
27 | import android.support.v7.widget.Toolbar
28 | import com.github.shadowsocks.ShadowsocksApplication.app
29 | import com.github.shadowsocks.utils.Key
30 |
31 | class ProfileConfigActivity extends Activity {
32 | private lazy val child = getFragmentManager.findFragmentById(R.id.content).asInstanceOf[ProfileConfigFragment]
33 |
34 | override def onCreate(savedInstanceState: Bundle) {
35 | super.onCreate(savedInstanceState)
36 | setContentView(R.layout.layout_profile_config)
37 | val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar]
38 | toolbar.setTitle(R.string.profile_config)
39 | toolbar.setNavigationIcon(R.drawable.ic_navigation_close)
40 | toolbar.setNavigationOnClickListener(_ => onBackPressed())
41 | toolbar.inflateMenu(R.menu.profile_config_menu)
42 | toolbar.setOnMenuItemClickListener(child)
43 | }
44 |
45 | override def onBackPressed(): Unit = if (app.settings.getBoolean(Key.dirty, false)) new AlertDialog.Builder(this)
46 | .setTitle(R.string.unsaved_changes_prompt)
47 | .setPositiveButton(R.string.yes, ((_, _) => child.saveAndExit()): DialogInterface.OnClickListener)
48 | .setNegativeButton(R.string.no, ((_, _) => finish()): DialogInterface.OnClickListener)
49 | .setNeutralButton(android.R.string.cancel, null)
50 | .create()
51 | .show() else super.onBackPressed()
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
20 |
21 |
29 |
30 |
31 |
35 |
36 |
42 |
45 |
49 |
52 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks
22 |
23 | import android.os.Bundle
24 | import android.support.design.widget.Snackbar
25 | import android.support.v14.preference.SwitchPreference
26 | import be.mygod.preference.PreferenceFragment
27 | import com.github.shadowsocks.utils.{Key, TcpFastOpen}
28 |
29 | class GlobalConfigFragment extends PreferenceFragment {
30 | override def onCreatePreferences(bundle: Bundle, key: String) {
31 | addPreferencesFromResource(R.xml.pref_global)
32 | val switch = findPreference(Key.isAutoConnect).asInstanceOf[SwitchPreference]
33 | switch.setOnPreferenceChangeListener((_, value) => {
34 | BootReceiver.setEnabled(getActivity, value.asInstanceOf[Boolean])
35 | true
36 | })
37 | if (getPreferenceManager.getSharedPreferences.getBoolean(Key.isAutoConnect, false)) {
38 | BootReceiver.setEnabled(getActivity, true)
39 | getPreferenceManager.getSharedPreferences.edit.remove(Key.isAutoConnect).apply()
40 | }
41 | switch.setChecked(BootReceiver.getEnabled(getActivity))
42 |
43 | val tfo = findPreference(Key.tfo).asInstanceOf[SwitchPreference]
44 | tfo.setChecked(TcpFastOpen.sendEnabled)
45 | tfo.setOnPreferenceChangeListener((_, v) => {
46 | val value = v.asInstanceOf[Boolean]
47 | val result = TcpFastOpen.enabled(value)
48 | if (result != null && result != "Success.")
49 | Snackbar.make(getActivity.findViewById(R.id.snackbar), result, Snackbar.LENGTH_LONG).show()
50 | value == TcpFastOpen.sendEnabled
51 | })
52 | if (!TcpFastOpen.supported) {
53 | tfo.setEnabled(false)
54 | tfo.setSummary(getString(R.string.tcp_fastopen_summary_unsupported, java.lang.System.getProperty("os.version")))
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/widget/UndoSnackbarManager.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.widget
22 |
23 | import android.support.design.widget.Snackbar
24 | import android.view.View
25 | import com.github.shadowsocks.R
26 |
27 | import scala.collection.mutable.ArrayBuffer
28 |
29 | /**
30 | * @author Mygod
31 | * @param view The view to find a parent from.
32 | * @param undo Callback for undoing removals.
33 | * @param commit Callback for committing removals.
34 | * @tparam T Item type.
35 | */
36 | class UndoSnackbarManager[T](view: View, undo: Iterator[(Int, T)] => Unit,
37 | commit: Iterator[(Int, T)] => Unit = null) {
38 | private val recycleBin = new ArrayBuffer[(Int, T)]
39 | private val removedCallback = new Snackbar.Callback {
40 | override def onDismissed(snackbar: Snackbar, event: Int) {
41 | event match {
42 | case Snackbar.Callback.DISMISS_EVENT_SWIPE | Snackbar.Callback.DISMISS_EVENT_MANUAL |
43 | Snackbar.Callback.DISMISS_EVENT_TIMEOUT =>
44 | if (commit != null) commit(recycleBin.iterator)
45 | recycleBin.clear()
46 | case _ =>
47 | }
48 | last = null
49 | }
50 | }
51 | private var last: Snackbar = _
52 |
53 | def remove(items: (Int, T)*) {
54 | recycleBin.appendAll(items)
55 | val count = recycleBin.length
56 | last = Snackbar
57 | .make(view, view.getResources.getQuantityString(R.plurals.removed, count, count: Integer), Snackbar.LENGTH_LONG)
58 | .addCallback(removedCallback)
59 | .setAction(R.string.undo, (_ => {
60 | undo(recycleBin.reverseIterator)
61 | recycleBin.clear
62 | }): View.OnClickListener)
63 | last.show()
64 | }
65 |
66 | def flush(): Unit = if (last != null) last.dismiss()
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/QRCodeDialog.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks
22 |
23 | import java.nio.charset.Charset
24 |
25 | import android.app.Activity
26 | import android.nfc.{NdefMessage, NdefRecord, NfcAdapter}
27 | import android.os.Bundle
28 | import android.view.{LayoutInflater, View, ViewGroup}
29 | import android.widget.{ImageView, LinearLayout}
30 | import com.github.shadowsocks.utils.Utils
31 | import net.glxn.qrgen.android.QRCode
32 |
33 | object QRCodeDialog {
34 | private final val KEY_URL = "com.github.shadowsocks.QRCodeDialog.KEY_URL"
35 | }
36 |
37 | final class QRCodeDialog extends DialogFragment {
38 | import QRCodeDialog._
39 | def this(url: String) {
40 | this()
41 | val bundle = new Bundle()
42 | bundle.putString(KEY_URL, url)
43 | setArguments(bundle)
44 | }
45 | private def url = getArguments.getString(KEY_URL)
46 |
47 | private lazy val nfcShareItem = url.getBytes(Charset.forName("UTF-8"))
48 | private var adapter: NfcAdapter = _
49 |
50 | override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View = {
51 | val image = new ImageView(getActivity)
52 | image.setLayoutParams(new LinearLayout.LayoutParams(-1, -1))
53 | val qrcode = QRCode.from(url)
54 | .withSize(Utils.dpToPx(getActivity, 250), Utils.dpToPx(getActivity, 250))
55 | .asInstanceOf[QRCode].bitmap()
56 | image.setImageBitmap(qrcode)
57 | image
58 | }
59 |
60 | override def onAttach(activity: Activity) {
61 | superOnAttach(activity)
62 | adapter = NfcAdapter.getDefaultAdapter(getActivity)
63 | if (adapter != null) adapter.setNdefPushMessage(new NdefMessage(Array(
64 | new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI, nfcShareItem, Array[Byte](), nfcShareItem))), activity)
65 | }
66 |
67 | override def onDetach() {
68 | if (adapter != null) {
69 | adapter.setNdefPushMessage(null, getActivity)
70 | adapter = null
71 | }
72 | super.onDetach()
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/scala/be/mygod/preference/NumberPickerPreference.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package be.mygod.preference
22 |
23 | import android.content.Context
24 | import android.content.res.TypedArray
25 | import android.support.v7.preference.DialogPreference
26 | import android.util.AttributeSet
27 | import android.view.ContextThemeWrapper
28 | import android.widget.NumberPicker
29 | import com.github.shadowsocks.R
30 |
31 | class NumberPickerPreference(private val context: Context, attrs: AttributeSet = null)
32 | extends DialogPreference(context, attrs) with DialogPreferencePlus with SummaryPreference {
33 | private[preference] val picker = new NumberPicker(new ContextThemeWrapper(context, R.style.NumberPickerStyle))
34 | private var value: Int = _
35 |
36 | {
37 | val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.NumberPickerPreference)
38 | setMin(a.getInt(R.styleable.NumberPickerPreference_min, 0))
39 | setMax(a.getInt(R.styleable.NumberPickerPreference_max, Int.MaxValue - 1))
40 | a.recycle()
41 | }
42 |
43 | override def createDialog() = new NumberPickerPreferenceDialogFragment()
44 |
45 | def getValue: Int = value
46 | def getMin: Int = if (picker == null) 0 else picker.getMinValue
47 | def getMax: Int = picker.getMaxValue
48 | def setValue(i: Int) {
49 | if (i == value) return
50 | picker.setValue(i)
51 | value = picker.getValue
52 | persistInt(value)
53 | notifyChanged()
54 | }
55 | def setMin(value: Int): Unit = picker.setMinValue(value)
56 | def setMax(value: Int): Unit = picker.setMaxValue(value)
57 |
58 | override protected def onGetDefaultValue(a: TypedArray, index: Int): AnyRef =
59 | a.getInt(index, getMin).asInstanceOf[AnyRef]
60 | override protected def onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any) {
61 | val default = defaultValue.asInstanceOf[Int]
62 | setValue(if (restorePersistedValue) getPersistedInt(default) else default)
63 | }
64 | protected def getSummaryValue: AnyRef = getValue.asInstanceOf[AnyRef]
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.utils
22 |
23 | import java.text.DecimalFormat
24 |
25 | import com.github.shadowsocks.R
26 | import com.github.shadowsocks.ShadowsocksApplication.app
27 |
28 | object TrafficMonitor {
29 | // Bytes per second
30 | var txRate: Long = _
31 | var rxRate: Long = _
32 |
33 | // Bytes for the current session
34 | var txTotal: Long = _
35 | var rxTotal: Long = _
36 |
37 | // Bytes for the last query
38 | var txLast: Long = _
39 | var rxLast: Long = _
40 | var timestampLast: Long = _
41 | @volatile var dirty = true
42 |
43 | private val units = Array("KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "NB", "DB", "CB")
44 | private val numberFormat = new DecimalFormat("@@@")
45 | def formatTraffic(size: Long): String = {
46 | var n: Double = size
47 | var i = -1
48 | while (n >= 1000) {
49 | n /= 1024
50 | i = i + 1
51 | }
52 | if (i < 0) size + " " + app.getResources.getQuantityString(R.plurals.bytes, size.toInt)
53 | else numberFormat.format(n) + ' ' + units(i)
54 | }
55 |
56 | def updateRate(): Boolean = {
57 | val now = System.currentTimeMillis()
58 | val delta = now - timestampLast
59 | var updated = false
60 | if (delta != 0) {
61 | if (dirty) {
62 | txRate = (txTotal - txLast) * 1000 / delta
63 | rxRate = (rxTotal - rxLast) * 1000 / delta
64 | txLast = txTotal
65 | rxLast = rxTotal
66 | dirty = false
67 | updated = true
68 | } else {
69 | if (txRate != 0) {
70 | txRate = 0
71 | updated = true
72 | }
73 | if (rxRate != 0) {
74 | rxRate = 0
75 | updated = true
76 | }
77 | }
78 | timestampLast = now
79 | }
80 | updated
81 | }
82 |
83 | def update(tx: Long, rx: Long) {
84 | if (txTotal != tx) {
85 | txTotal = tx
86 | dirty = true
87 | }
88 | if (rxTotal != rx) {
89 | rxTotal = rx
90 | dirty = true
91 | }
92 | }
93 |
94 | def reset() {
95 | txRate = 0
96 | rxRate = 0
97 | txTotal = 0
98 | rxTotal = 0
99 | txLast = 0
100 | rxLast = 0
101 | dirty = true
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Shadowsocks R for Android
2 |
3 | A [Shadowsocks R](https://github.com/breakwa11/shadowsocks-rss/) client for Android, written in Scala.
4 |
5 | ### CI STATUS
6 |
7 | [](https://travis-ci.org/shadowsocks/shadowsocks-android)
8 |
9 | ### PREREQUISITES
10 |
11 | * JDK 1.8
12 | * SBT 0.13.0+
13 | * Go 1.4+
14 | * Android SDK
15 | - Build Tools 25+
16 | - Android Support Repository and Google Repository (see `build.sbt` for version)
17 | * Android NDK r12b+
18 |
19 | ### BUILD
20 |
21 | * Set environment variable `ANDROID_HOME` to `/path/to/android-sdk`
22 | * Set environment variable `ANDROID_NDK_HOME` to `/path/to/android-ndk`
23 | * Set environment variable `GOROOT_BOOTSTRAP` to `/path/to/go`
24 | * Create your key following the instructions at https://developer.android.com/studio/publish/app-signing.html
25 | * Create `local.properties` from `local.properties.example` with your own key information
26 | * Invoke the building like this
27 |
28 | ```bash
29 | git submodule update --init
30 |
31 | # Build the App
32 | sbt native-build clean android:package-release
33 | ```
34 |
35 | ### TRANSLATE
36 |
37 | Translators can go to [POEditor](https://poeditor.com/join/project/u5VHO9vhSf) to help translate shadowsocks-android. Guidelines:
38 |
39 | * It's okay to leave some strings untranslated if you think it should use the same string as English (US).
40 | * `faq_url` should not be changed. If you'd like to translate FAQ, submit a pull request with the translated [`faq.md`](https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md) (it should be named properly, e.g. `.github/faq.zh-CN.md`). Administrators will take care of the rest.
41 | * Do not add/edit/remove comments.
42 |
43 | ## OPEN SOURCE LICENSES
44 |
45 |
46 | - redsocks: APL 2.0
47 | - mbed TLS: APL 2.0
48 | - libevent: BSD
49 | - tun2socks: BSD
50 | - pcre: BSD
51 | - libancillary: BSD
52 | - shadowsocksr-libev: GPLv3
53 | - shadowsocksr-obfsplugin: MIT
54 | - pdnsd: GPLv3
55 | - libev: GPLv2
56 | - kcptun: MIT
57 | - libsodium: ISC
58 |
59 |
60 | ### LICENSE
61 |
62 | Copyright (C) 2017 by Max Lv <>
63 | Copyright (C) 2017 by Mygod Studio <>
64 |
65 | This program is free software: you can redistribute it and/or modify
66 | it under the terms of the GNU General Public License as published by
67 | the Free Software Foundation, either version 3 of the License, or
68 | (at your option) any later version.
69 |
70 | This program is distributed in the hope that it will be useful,
71 | but WITHOUT ANY WARRANTY; without even the implied warranty of
72 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
73 | GNU General Public License for more details.
74 |
75 | You should have received a copy of the GNU General Public License
76 | along with this program. If not, see .
77 |
--------------------------------------------------------------------------------
/src/main/jni/include/libev/config.h:
--------------------------------------------------------------------------------
1 | /* config.h. Generated from config.h.in by configure. */
2 | /* config.h.in. Generated from configure.ac by autoheader. */
3 |
4 | /* Define to 1 if you have the `clock_gettime' function. */
5 | /* #undef HAVE_CLOCK_GETTIME */
6 |
7 | /* Define to 1 to use the syscall interface for clock_gettime */
8 | #define HAVE_CLOCK_SYSCALL 1
9 |
10 | /* Define to 1 if you have the header file. */
11 | #define HAVE_DLFCN_H 1
12 |
13 | /* Define to 1 if you have the `epoll_ctl' function. */
14 | #define HAVE_EPOLL_CTL 1
15 |
16 | /* Define to 1 if you have the `eventfd' function. */
17 | #define HAVE_EVENTFD 1
18 |
19 | /* Define to 1 if the floor function is available */
20 | #define HAVE_FLOOR 1
21 |
22 | /* Define to 1 if you have the `inotify_init' function. */
23 | #define HAVE_INOTIFY_INIT 1
24 |
25 | /* Define to 1 if you have the header file. */
26 | #define HAVE_INTTYPES_H 1
27 |
28 | /* Define to 1 if you have the `kqueue' function. */
29 | /* #undef HAVE_KQUEUE */
30 |
31 | /* Define to 1 if you have the `rt' library (-lrt). */
32 | /* #undef HAVE_LIBRT */
33 |
34 | /* Define to 1 if you have the header file. */
35 | #define HAVE_MEMORY_H 1
36 |
37 | /* Define to 1 if you have the `nanosleep' function. */
38 | #define HAVE_NANOSLEEP 1
39 |
40 | /* Define to 1 if you have the `poll' function. */
41 | #define HAVE_POLL 1
42 |
43 | /* Define to 1 if you have the header file. */
44 | #define HAVE_POLL_H 1
45 |
46 | /* Define to 1 if you have the `port_create' function. */
47 | /* #undef HAVE_PORT_CREATE */
48 |
49 | /* Define to 1 if you have the header file. */
50 | /* #undef HAVE_PORT_H */
51 |
52 | /* Define to 1 if you have the `select' function. */
53 | #define HAVE_SELECT 1
54 |
55 | /* Define to 1 if you have the `signalfd' function. */
56 | #define HAVE_SIGNALFD 0
57 |
58 | /* Define to 1 if you have the header file. */
59 | #define HAVE_STDINT_H 1
60 |
61 | /* Define to 1 if you have the header file. */
62 | #define HAVE_STDLIB_H 1
63 |
64 | /* Define to 1 if you have the header file. */
65 | #define HAVE_STRINGS_H 1
66 |
67 | /* Define to 1 if you have the header file. */
68 | #define HAVE_STRING_H 1
69 |
70 | /* Define to 1 if you have the header file. */
71 | #define HAVE_SYS_EPOLL_H 1
72 |
73 | /* Define to 1 if you have the header file. */
74 | #define HAVE_SYS_EVENTFD_H 1
75 |
76 | /* Define to 1 if you have the header file. */
77 | /* #undef HAVE_SYS_EVENT_H */
78 |
79 | /* Define to 1 if you have the header file. */
80 | #define HAVE_SYS_INOTIFY_H 1
81 |
82 | /* Define to 1 if you have the header file. */
83 | #define HAVE_SYS_SELECT_H 1
84 |
85 | /* Define to 1 if you have the header file. */
86 | #define HAVE_SYS_SIGNALFD_H 1
87 |
88 | /* Define to 1 if you have the header file. */
89 | #define HAVE_SYS_STAT_H 1
90 |
91 | /* Define to 1 if you have the header file. */
92 | #define HAVE_SYS_TYPES_H 1
93 |
94 | /* Define to 1 if you have the header file. */
95 | #define HAVE_UNISTD_H 1
96 |
97 | /* Define to the sub-directory in which libtool stores uninstalled libraries.
98 | */
99 | #define LT_OBJDIR ".libs/"
100 |
101 | /* Name of package */
102 | #define PACKAGE "libev"
103 |
104 | /* Define to the address where bug reports for this package should be sent. */
105 | #define PACKAGE_BUGREPORT ""
106 |
107 | /* Define to the full name of this package. */
108 | #define PACKAGE_NAME ""
109 |
110 | /* Define to the full name and version of this package. */
111 | #define PACKAGE_STRING ""
112 |
113 | /* Define to the one symbol short name of this package. */
114 | #define PACKAGE_TARNAME ""
115 |
116 | /* Define to the home page for this package. */
117 | #define PACKAGE_URL ""
118 |
119 | /* Define to the version of this package. */
120 | #define PACKAGE_VERSION ""
121 |
122 | /* Define to 1 if you have the ANSI C header files. */
123 | #define STDC_HEADERS 1
124 |
125 | /* Version number of package */
126 | #define VERSION "4.11"
127 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/ShadowsocksTileService.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks
22 |
23 | import android.annotation.TargetApi
24 | import android.graphics.drawable.Icon
25 | import android.service.quicksettings.{Tile, TileService}
26 | import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
27 | import com.github.shadowsocks.utils.{State, Utils}
28 |
29 | /**
30 | * @author Mygod
31 | */
32 | @TargetApi(24)
33 | final class ShadowsocksTileService extends TileService with ServiceBoundContext {
34 |
35 | private lazy val iconIdle = Icon.createWithResource(this, R.drawable.ic_start_idle).setTint(0x80ffffff)
36 | private lazy val iconBusy = Icon.createWithResource(this, R.drawable.ic_start_busy)
37 | private lazy val iconConnected = Icon.createWithResource(this, R.drawable.ic_start_connected)
38 | private lazy val callback = new IShadowsocksServiceCallback.Stub {
39 | def trafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long): Unit = ()
40 | def stateChanged(state: Int, profileName: String, msg: String) {
41 | val tile = getQsTile
42 | if (tile != null) {
43 | state match {
44 | case State.STOPPED =>
45 | tile.setIcon(iconIdle)
46 | tile.setLabel(getString(R.string.app_name))
47 | tile.setState(Tile.STATE_INACTIVE)
48 | case State.CONNECTED =>
49 | tile.setIcon(iconConnected)
50 | tile.setLabel(if (profileName == null) getString(R.string.app_name) else profileName)
51 | tile.setState(Tile.STATE_ACTIVE)
52 | case _ =>
53 | tile.setIcon(iconBusy)
54 | tile.setLabel(getString(R.string.app_name))
55 | tile.setState(Tile.STATE_UNAVAILABLE)
56 | }
57 | tile.updateTile()
58 | }
59 | }
60 | override def trafficPersisted(profileId: Int): Unit = ()
61 | }
62 |
63 | override def onServiceConnected(): Unit = callback.stateChanged(bgService.getState, bgService.getProfileName, null)
64 |
65 | override def onStartListening() {
66 | super.onStartListening()
67 | attachService(callback)
68 | }
69 | override def onStopListening() {
70 | super.onStopListening()
71 | detachService() // just in case the user switches to NAT mode, also saves battery
72 | }
73 |
74 | override def onClick(): Unit = if (isLocked) unlockAndRun(toggle) else toggle()
75 |
76 | private def toggle() = if (bgService != null) bgService.getState match {
77 | case State.STOPPED => Utils.startSsService(this)
78 | case State.CONNECTED => Utils.stopSsService(this)
79 | case _ => // ignore
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks
22 |
23 | import android.app.{Activity, KeyguardManager}
24 | import android.content.{BroadcastReceiver, Context, Intent, IntentFilter}
25 | import android.net.VpnService
26 | import android.os.{Bundle, Handler}
27 | import android.util.Log
28 | import com.github.shadowsocks.ShadowsocksApplication.app
29 |
30 | object ShadowsocksRunnerActivity {
31 | private final val TAG = "ShadowsocksRunnerActivity"
32 | private final val REQUEST_CONNECT = 1
33 | }
34 |
35 | class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
36 | import ShadowsocksRunnerActivity._
37 |
38 | val handler = new Handler()
39 |
40 | // Variables
41 | var receiver: BroadcastReceiver = _
42 |
43 | override def onServiceConnected() {
44 | handler.postDelayed(() => if (bgService != null) startBackgroundService(), 1000)
45 | }
46 |
47 | def startBackgroundService() {
48 | if (app.isNatEnabled) {
49 | bgService.use(app.profileId)
50 | finish()
51 | } else {
52 | val intent = VpnService.prepare(ShadowsocksRunnerActivity.this)
53 | if (intent != null) {
54 | startActivityForResult(intent, REQUEST_CONNECT)
55 | } else {
56 | onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)
57 | }
58 | }
59 | }
60 |
61 | override def onCreate(savedInstanceState: Bundle) {
62 | super.onCreate(savedInstanceState)
63 | val km = getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager]
64 | val locked = km.inKeyguardRestrictedInputMode
65 | if (locked) {
66 | val filter = new IntentFilter(Intent.ACTION_USER_PRESENT)
67 | receiver = (_: Context, intent: Intent) => {
68 | if (intent.getAction == Intent.ACTION_USER_PRESENT) {
69 | attachService()
70 | }
71 | }
72 | registerReceiver(receiver, filter)
73 | } else {
74 | attachService()
75 | }
76 | finish()
77 | }
78 |
79 | override def onDestroy() {
80 | super.onDestroy()
81 | detachService()
82 | if (receiver != null) {
83 | unregisterReceiver(receiver)
84 | receiver = null
85 | }
86 | }
87 |
88 | override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
89 | resultCode match {
90 | case Activity.RESULT_OK =>
91 | if (bgService != null) {
92 | bgService.use(app.profileId)
93 | }
94 | case _ =>
95 | Log.e(TAG, "Failed to start VpnService")
96 | }
97 | finish()
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.utils
22 |
23 | import java.io.{File, IOException}
24 | import java.nio.{ByteBuffer, ByteOrder}
25 | import java.util.concurrent.Executors
26 |
27 | import android.content.Context
28 | import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress}
29 | import android.util.Log
30 | import com.github.shadowsocks.ShadowsocksApplication.app
31 |
32 | class TrafficMonitorThread(context: Context) extends Thread {
33 |
34 | val TAG = "TrafficMonitorThread"
35 | lazy val PATH: String = context.getApplicationInfo.dataDir + "/stat_path"
36 |
37 | @volatile var serverSocket: LocalServerSocket = _
38 | @volatile var isRunning: Boolean = true
39 |
40 | def closeServerSocket() {
41 | if (serverSocket != null) {
42 | try {
43 | serverSocket.close()
44 | } catch {
45 | case _: Exception => // ignore
46 | }
47 | serverSocket = null
48 | }
49 | }
50 |
51 | def stopThread() {
52 | isRunning = false
53 | closeServerSocket()
54 | }
55 |
56 | override def run() {
57 |
58 | new File(PATH).delete()
59 |
60 | try {
61 | val localSocket = new LocalSocket
62 | localSocket.bind(new LocalSocketAddress(PATH, LocalSocketAddress.Namespace.FILESYSTEM))
63 | serverSocket = new LocalServerSocket(localSocket.getFileDescriptor)
64 | } catch {
65 | case e: IOException =>
66 | Log.e(TAG, "unable to bind", e)
67 | return
68 | }
69 |
70 | val pool = Executors.newFixedThreadPool(1)
71 |
72 | while (isRunning) {
73 | try {
74 | val socket = serverSocket.accept()
75 |
76 | pool.execute(() => {
77 | try {
78 | val input = socket.getInputStream
79 | val output = socket.getOutputStream
80 |
81 | val buffer = new Array[Byte](16)
82 | if (input.read(buffer) != 16) throw new IOException("Unexpected traffic stat length")
83 | val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN)
84 | TrafficMonitor.update(stat.getLong(0), stat.getLong(8))
85 |
86 | output.write(0)
87 |
88 | input.close()
89 | output.close()
90 |
91 | } catch {
92 | case e: Exception =>
93 | Log.e(TAG, "Error when recv traffic stat", e)
94 | app.track(e)
95 | }
96 |
97 | // close socket
98 | try {
99 | socket.close()
100 | } catch {
101 | case _: Exception => // ignore
102 | }
103 |
104 | })
105 | } catch {
106 | case e: IOException =>
107 | Log.e(TAG, "Error when accept socket", e)
108 | app.track(e)
109 | return
110 | }
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/main/jni/system.cpp:
--------------------------------------------------------------------------------
1 | #define LOG_TAG "Shadowsocks"
2 |
3 | #include "jni.h"
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | #define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0)
17 | #define LOGW(...) do { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); } while(0)
18 | #define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0)
19 |
20 | jint Java_com_github_shadowsocks_system_exec(JNIEnv *env, jobject thiz, jstring cmd) {
21 | const char *cmd_str = env->GetStringUTFChars(cmd, 0);
22 |
23 | pid_t pid;
24 |
25 | /* Fork off the parent process */
26 | pid = fork();
27 | if (pid < 0) {
28 | env->ReleaseStringUTFChars(cmd, cmd_str);
29 | return -1;
30 | }
31 |
32 | if (pid > 0) {
33 | env->ReleaseStringUTFChars(cmd, cmd_str);
34 | return pid;
35 | }
36 |
37 | execl("/system/bin/sh", "sh", "-c", cmd_str, NULL);
38 | env->ReleaseStringUTFChars(cmd, cmd_str);
39 |
40 | return 1;
41 | }
42 |
43 | void Java_com_github_shadowsocks_system_jniclose(JNIEnv *env, jobject thiz, jint fd) {
44 | close(fd);
45 | }
46 |
47 | jint Java_com_github_shadowsocks_system_sendfd(JNIEnv *env, jobject thiz, jint tun_fd, jstring path) {
48 | int fd;
49 | struct sockaddr_un addr;
50 | const char *sock_str = env->GetStringUTFChars(path, 0);
51 |
52 | if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
53 | LOGE("socket() failed: %s (socket fd = %d)\n", strerror(errno), fd);
54 | return (jint)-1;
55 | }
56 |
57 | memset(&addr, 0, sizeof(addr));
58 | addr.sun_family = AF_UNIX;
59 | strncpy(addr.sun_path, sock_str, sizeof(addr.sun_path)-1);
60 |
61 | if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
62 | LOGE("connect() failed: %s (fd = %d)\n", strerror(errno), fd);
63 | close(fd);
64 | return (jint)-1;
65 | }
66 |
67 | if (ancil_send_fd(fd, tun_fd)) {
68 | LOGE("ancil_send_fd: %s", strerror(errno));
69 | close(fd);
70 | return (jint)-1;
71 | }
72 |
73 | close(fd);
74 | env->ReleaseStringUTFChars(path, sock_str);
75 | return 0;
76 | }
77 |
78 | static const char *classPathName = "com/github/shadowsocks/System";
79 |
80 | static JNINativeMethod method_table[] = {
81 | { "jniclose", "(I)V",
82 | (void*) Java_com_github_shadowsocks_system_jniclose },
83 | { "sendfd", "(ILjava/lang/String;)I",
84 | (void*) Java_com_github_shadowsocks_system_sendfd },
85 | { "exec", "(Ljava/lang/String;)I",
86 | (void*) Java_com_github_shadowsocks_system_exec }
87 | };
88 |
89 |
90 |
91 | /*
92 | * Register several native methods for one class.
93 | */
94 | static int registerNativeMethods(JNIEnv* env, const char* className,
95 | JNINativeMethod* gMethods, int numMethods)
96 | {
97 | jclass clazz;
98 |
99 | clazz = env->FindClass(className);
100 | if (clazz == NULL) {
101 | LOGE("Native registration unable to find class '%s'", className);
102 | return JNI_FALSE;
103 | }
104 | if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
105 | LOGE("RegisterNatives failed for '%s'", className);
106 | return JNI_FALSE;
107 | }
108 |
109 | return JNI_TRUE;
110 | }
111 |
112 | /*
113 | * Register native methods for all classes we know about.
114 | *
115 | * returns JNI_TRUE on success.
116 | */
117 | static int registerNatives(JNIEnv* env)
118 | {
119 | if (!registerNativeMethods(env, classPathName, method_table,
120 | sizeof(method_table) / sizeof(method_table[0]))) {
121 | return JNI_FALSE;
122 | }
123 |
124 | return JNI_TRUE;
125 | }
126 |
127 | /*
128 | * This is called by the VM when the shared library is first loaded.
129 | */
130 |
131 | typedef union {
132 | JNIEnv* env;
133 | void* venv;
134 | } UnionJNIEnvToVoid;
135 |
136 | jint JNI_OnLoad(JavaVM* vm, void* reserved) {
137 | UnionJNIEnvToVoid uenv;
138 | uenv.venv = NULL;
139 | jint result = -1;
140 | JNIEnv* env = NULL;
141 |
142 | LOGI("JNI_OnLoad");
143 |
144 | if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
145 | LOGE("ERROR: GetEnv failed");
146 | goto bail;
147 | }
148 | env = uenv.env;
149 |
150 | if (registerNatives(env) != JNI_TRUE) {
151 | LOGE("ERROR: registerNatives failed");
152 | goto bail;
153 | }
154 |
155 | result = JNI_VERSION_1_4;
156 |
157 | bail:
158 | return result;
159 | }
160 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/GuardedProcess.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks
22 |
23 | import java.io._
24 | import java.lang.System.currentTimeMillis
25 | import java.util.concurrent.Semaphore
26 |
27 | import android.util.Log
28 | import com.github.shadowsocks.utils.CloseUtils._
29 |
30 | import scala.collection.JavaConversions._
31 | import scala.collection.immutable.Stream
32 |
33 | class StreamLogger(is: InputStream, tag: String) extends Thread {
34 | override def run(): Unit = autoClose(new BufferedReader(new InputStreamReader(is)))(br =>
35 | try Stream.continually(br.readLine()).takeWhile(_ != null).foreach(Log.i(tag, _)) catch {
36 | case _: IOException =>
37 | })
38 | }
39 |
40 | /**
41 | * @author ayanamist@gmail.com
42 | */
43 | class GuardedProcess(cmd: Seq[String]) {
44 | private val TAG = classOf[GuardedProcess].getSimpleName
45 |
46 | @volatile private var guardThread: Thread = _
47 | @volatile private var isDestroyed: Boolean = _
48 | @volatile private var process: Process = _
49 | @volatile private var isRestart = false
50 |
51 | def start(onRestartCallback: () => Unit = null): GuardedProcess = {
52 | val semaphore = new Semaphore(1)
53 | semaphore.acquire()
54 | @volatile var ioException: IOException = null
55 |
56 | guardThread = new Thread(() => {
57 | try {
58 | var callback: () => Unit = null
59 | while (!isDestroyed) {
60 | Log.i(TAG, "start process: " + cmd)
61 | val startTime = currentTimeMillis
62 |
63 | process = new ProcessBuilder(cmd).redirectErrorStream(true).start
64 |
65 | val is = process.getInputStream
66 | new StreamLogger(is, TAG).start()
67 |
68 | if (callback == null) callback = onRestartCallback else callback()
69 |
70 | semaphore.release()
71 | process.waitFor
72 |
73 | this.synchronized {
74 | if (isRestart) {
75 | isRestart = false
76 | } else {
77 | if (currentTimeMillis - startTime < 1000) {
78 | Log.w(TAG, "process exit too fast, stop guard: " + cmd)
79 | isDestroyed = true
80 | }
81 | }
82 | }
83 | }
84 | } catch {
85 | case _: InterruptedException =>
86 | Log.i(TAG, "thread interrupt, destroy process: " + cmd)
87 | process.destroy()
88 | case e: IOException => ioException = e
89 | } finally semaphore.release()
90 | }, "GuardThread-" + cmd)
91 |
92 | guardThread.start()
93 | semaphore.acquire()
94 |
95 | if (ioException != null) throw ioException
96 |
97 | this
98 | }
99 |
100 | def destroy() {
101 | isDestroyed = true
102 | guardThread.interrupt()
103 | process.destroy()
104 | try guardThread.join() catch {
105 | case _: InterruptedException =>
106 | }
107 | }
108 |
109 | def restart() {
110 | this.synchronized {
111 | isRestart = true
112 | process.destroy()
113 | }
114 | }
115 |
116 | @throws(classOf[InterruptedException])
117 | def waitFor: Int = {
118 | guardThread.join()
119 | 0
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/main/scala/be/mygod/preference/PreferenceGroupAdapter.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package be.mygod.preference
22 |
23 | import java.lang.reflect.Field
24 | import java.util
25 |
26 | import android.os.Build
27 | import android.support.v4.content.ContextCompat
28 | import android.support.v4.view.ViewCompat
29 | import android.support.v7.preference.{PreferenceGroup, PreferenceViewHolder, PreferenceGroupAdapter => Old}
30 | import android.view.{LayoutInflater, View, ViewGroup}
31 | import com.github.shadowsocks.R
32 |
33 | /**
34 | * Fix by: https://github.com/Gericop/Android-Support-Preference-V7-Fix/commit/7de016b007e28a264001a8bb353f110a7f64bb69
35 | *
36 | * @author Mygod
37 | */
38 | object PreferenceGroupAdapter {
39 | private var preferenceLayoutsField: Field = _
40 | private var fieldResId: Field = _
41 | private var fieldWidgetResId: Field = _
42 | private val preferenceViewHolderConstructor = classOf[PreferenceViewHolder].getDeclaredConstructor(classOf[View])
43 |
44 | {
45 | val oldClass = classOf[Old]
46 | preferenceLayoutsField = oldClass.getDeclaredField("mPreferenceLayouts")
47 | preferenceLayoutsField.setAccessible(true)
48 | val c = oldClass.getDeclaredClasses.filter(c => c.getSimpleName == "PreferenceLayout").head
49 | fieldResId = c.getDeclaredField("resId")
50 | fieldResId.setAccessible(true)
51 | fieldWidgetResId = c.getDeclaredField("widgetResId")
52 | fieldWidgetResId.setAccessible(true)
53 | preferenceViewHolderConstructor.setAccessible(true)
54 | }
55 | }
56 |
57 | class PreferenceGroupAdapter(group: PreferenceGroup) extends Old(group) {
58 | import PreferenceGroupAdapter._
59 |
60 | protected lazy val preferenceLayouts: util.List[AnyRef] =
61 | preferenceLayoutsField.get(this).asInstanceOf[util.List[AnyRef]]
62 |
63 | override def onCreateViewHolder(parent: ViewGroup, viewType: Int): PreferenceViewHolder =
64 | if (Build.VERSION.SDK_INT < 21) {
65 | val context = parent.getContext
66 | val inflater = LayoutInflater.from(context)
67 | val pl = preferenceLayouts.get(viewType)
68 | val view = inflater.inflate(fieldResId.get(pl).asInstanceOf[Int], parent, false)
69 | if (view.getBackground == null) {
70 | val array = context.obtainStyledAttributes(null, R.styleable.BackgroundStyle)
71 | var background = array.getDrawable(R.styleable.BackgroundStyle_android_selectableItemBackground)
72 | if (background == null)
73 | background = ContextCompat.getDrawable(context, android.R.drawable.list_selector_background)
74 | array.recycle()
75 | val (s, t, e, b) = (ViewCompat.getPaddingStart(view), view.getPaddingTop,
76 | ViewCompat.getPaddingEnd(view), view.getPaddingBottom)
77 | view.setBackground(background)
78 | ViewCompat.setPaddingRelative(view, s, t, e, b)
79 | }
80 | val widgetFrame = view.findViewById(android.R.id.widget_frame).asInstanceOf[ViewGroup]
81 | if (widgetFrame != null) {
82 | val widgetResId = fieldWidgetResId.get(pl).asInstanceOf[Int]
83 | if (widgetResId != 0) inflater.inflate(widgetResId, widgetFrame) else widgetFrame.setVisibility(View.GONE)
84 | }
85 | preferenceViewHolderConstructor.newInstance(view)
86 | } else super.onCreateViewHolder(parent, viewType)
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks
22 |
23 | import java.io.{File, FileDescriptor, IOException}
24 | import java.lang.reflect.Method
25 | import java.util.concurrent.Executors
26 |
27 | import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress}
28 | import android.util.Log
29 | import com.github.shadowsocks.ShadowsocksApplication.app
30 |
31 | object ShadowsocksVpnThread {
32 | val getInt: Method = classOf[FileDescriptor].getDeclaredMethod("getInt$")
33 | }
34 |
35 | class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread {
36 | import ShadowsocksVpnThread._
37 |
38 | val TAG = "ShadowsocksVpnService"
39 | lazy val PATH: String = vpnService.getApplicationInfo.dataDir + "/protect_path"
40 |
41 | @volatile var isRunning: Boolean = true
42 | @volatile var serverSocket: LocalServerSocket = _
43 |
44 | def closeServerSocket() {
45 | if (serverSocket != null) {
46 | try {
47 | serverSocket.close()
48 | } catch {
49 | case _: Exception => // ignore
50 | }
51 | serverSocket = null
52 | }
53 | }
54 |
55 | def stopThread() {
56 | isRunning = false
57 | closeServerSocket()
58 | }
59 |
60 | override def run() {
61 |
62 | new File(PATH).delete()
63 |
64 | try {
65 | val localSocket = new LocalSocket
66 | localSocket.bind(new LocalSocketAddress(PATH, LocalSocketAddress.Namespace.FILESYSTEM))
67 | serverSocket = new LocalServerSocket(localSocket.getFileDescriptor)
68 | } catch {
69 | case e: IOException =>
70 | Log.e(TAG, "unable to bind", e)
71 | app.track(e)
72 | return
73 | }
74 |
75 | val pool = Executors.newFixedThreadPool(4)
76 |
77 | while (isRunning) {
78 | try {
79 | val socket = serverSocket.accept()
80 |
81 | pool.execute(() => {
82 | try {
83 | val input = socket.getInputStream
84 | val output = socket.getOutputStream
85 |
86 | input.read()
87 |
88 | val fds = socket.getAncillaryFileDescriptors
89 |
90 | if (fds.nonEmpty) {
91 | val fd = getInt.invoke(fds(0)).asInstanceOf[Int]
92 | val ret = vpnService.protect(fd)
93 |
94 | // Trick to close file decriptor
95 | System.jniclose(fd)
96 |
97 | if (ret) {
98 | output.write(0)
99 | } else {
100 | output.write(1)
101 | }
102 | }
103 |
104 | input.close()
105 | output.close()
106 |
107 | } catch {
108 | case e: Exception =>
109 | Log.e(TAG, "Error when protect socket", e)
110 | app.track(e)
111 | }
112 |
113 | // close socket
114 | try {
115 | socket.close()
116 | } catch {
117 | case _: Exception => // ignore
118 | }
119 |
120 | })
121 | } catch {
122 | case e: IOException =>
123 | Log.e(TAG, "Error when accept socket", e)
124 | app.track(e)
125 | return
126 | }
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.database
22 |
23 | import android.util.Log
24 | import com.github.shadowsocks.ProfilesFragment
25 | import com.github.shadowsocks.ShadowsocksApplication.app
26 |
27 | object ProfileManager {
28 | private final val TAG = "ProfileManager"
29 | }
30 |
31 | class ProfileManager(dbHelper: DBHelper) {
32 | import ProfileManager._
33 |
34 | def createProfile(p: Profile = null): Profile = {
35 | val profile = if (p == null) new Profile else p
36 | profile.id = 0
37 | try {
38 | app.currentProfile match {
39 | case Some(oldProfile) =>
40 | // Copy Feature Settings from old profile
41 | profile.route = oldProfile.route
42 | profile.ipv6 = oldProfile.ipv6
43 | profile.proxyApps = oldProfile.proxyApps
44 | profile.bypass = oldProfile.bypass
45 | profile.individual = oldProfile.individual
46 | profile.udpdns = oldProfile.udpdns
47 | case _ =>
48 | }
49 | val last = dbHelper.profileDao.queryRaw(dbHelper.profileDao.queryBuilder.selectRaw("MAX(userOrder)")
50 | .prepareStatementString).getFirstResult
51 | if (last != null && last.length == 1 && last(0) != null) profile.userOrder = last(0).toInt + 1
52 | dbHelper.profileDao.createOrUpdate(profile)
53 | if (ProfilesFragment.instance != null) ProfilesFragment.instance.profilesAdapter.add(profile)
54 | } catch {
55 | case ex: Exception =>
56 | Log.e(TAG, "addProfile", ex)
57 | app.track(ex)
58 | }
59 | profile
60 | }
61 |
62 | def updateProfile(profile: Profile): Boolean = {
63 | try {
64 | dbHelper.profileDao.update(profile)
65 | true
66 | } catch {
67 | case ex: Exception =>
68 | Log.e(TAG, "updateProfile", ex)
69 | app.track(ex)
70 | false
71 | }
72 | }
73 |
74 | def getProfile(id: Int): Option[Profile] = {
75 | try {
76 | dbHelper.profileDao.queryForId(id) match {
77 | case profile: Profile => Option(profile)
78 | case _ => None
79 | }
80 | } catch {
81 | case ex: Exception =>
82 | Log.e(TAG, "getProfile", ex)
83 | app.track(ex)
84 | None
85 | }
86 | }
87 |
88 | def delProfile(id: Int): Boolean = {
89 | try {
90 | dbHelper.profileDao.deleteById(id)
91 | if (ProfilesFragment.instance != null) ProfilesFragment.instance.profilesAdapter.removeId(id)
92 | true
93 | } catch {
94 | case ex: Exception =>
95 | Log.e(TAG, "delProfile", ex)
96 | app.track(ex)
97 | false
98 | }
99 | }
100 |
101 | def getFirstProfile: Option[Profile] = {
102 | try {
103 | val result = dbHelper.profileDao.query(dbHelper.profileDao.queryBuilder.limit(1L).prepare)
104 | if (result.size == 1) Option(result.get(0)) else None
105 | } catch {
106 | case ex: Exception =>
107 | Log.e(TAG, "getAllProfiles", ex)
108 | app.track(ex)
109 | None
110 | }
111 | }
112 |
113 | def getAllProfiles: Option[List[Profile]] = {
114 | try {
115 | import scala.collection.JavaConversions._
116 | Option(dbHelper.profileDao.query(dbHelper.profileDao.queryBuilder.orderBy("userOrder", true).prepare).toList)
117 | } catch {
118 | case ex: Exception =>
119 | Log.e(TAG, "getAllProfiles", ex)
120 | app.track(ex)
121 | None
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/main/res/layout/layout_profile.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
15 |
23 |
27 |
37 |
47 |
56 |
57 |
62 |
70 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/src/main/scala/com/github/shadowsocks/utils/Parser.scala:
--------------------------------------------------------------------------------
1 | /*******************************************************************************/
2 | /* */
3 | /* Copyright (C) 2017 by Max Lv */
4 | /* Copyright (C) 2017 by Mygod Studio */
5 | /* */
6 | /* This program is free software: you can redistribute it and/or modify */
7 | /* it under the terms of the GNU General Public License as published by */
8 | /* the Free Software Foundation, either version 3 of the License, or */
9 | /* (at your option) any later version. */
10 | /* */
11 | /* This program is distributed in the hope that it will be useful, */
12 | /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
13 | /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
14 | /* GNU General Public License for more details. */
15 | /* */
16 | /* You should have received a copy of the GNU General Public License */
17 | /* along with this program. If not, see . */
18 | /* */
19 | /*******************************************************************************/
20 |
21 | package com.github.shadowsocks.utils
22 |
23 | import java.net.URLDecoder
24 |
25 | import android.util.{Base64, Log}
26 | import com.github.shadowsocks.database.Profile
27 |
28 | object Parser {
29 | val TAG = "ShadowParser"
30 | private val pattern = "(?i)ss://([A-Za-z0-9+-/=_]+)(#(.+))?".r
31 | private val decodedPattern = "(?i)^((.+?)(-auth)??:(.*)@(.+?):(\\d+?))$".r
32 |
33 | private val pattern_ssr = "(?i)ssr://([A-Za-z0-9_=-]+)".r
34 | private val decodedPattern_ssr = "(?i)^((.+):(\\d+?):(.*):(.+):(.*):([^/]+))".r
35 | private val decodedPattern_ssr_obfsparam = "(?i)[?&]obfsparam=([A-Za-z0-9_=-]*)".r
36 | private val decodedPattern_ssr_remarks = "(?i)[?&]remarks=([A-Za-z0-9_=-]*)".r
37 | private val decodedPattern_ssr_protocolparam = "(?i)[?&]protoparam=([A-Za-z0-9_=-]*)".r
38 |
39 | def findAll(data: CharSequence): Iterator[Profile] =
40 | pattern.findAllMatchIn(if (data == null) "" else data).map(m => try
41 | decodedPattern.findFirstMatchIn(new String(Base64.decode(m.group(1), Base64.NO_PADDING), "UTF-8")) match {
42 | case Some(ss) =>
43 | val profile = new Profile
44 | profile.method = ss.group(2).toLowerCase
45 | if (ss.group(3) != null) profile.protocol = "verify_sha1"
46 | profile.password = ss.group(4)
47 | profile.host = ss.group(5)
48 | profile.remotePort = ss.group(6).toInt
49 | if (m.group(2) != null) profile.name = URLDecoder.decode(m.group(3), "utf-8")
50 | profile
51 | case _ => null
52 | } catch {
53 | case ex: Exception =>
54 | Log.e(TAG, "parser error: " + m.source, ex)// Ignore
55 | null
56 | }).filter(_ != null)
57 |
58 | def findAll_ssr(data: CharSequence): Iterator[Profile] =
59 | pattern_ssr.findAllMatchIn(if (data == null) "" else data).map(m => try {
60 | val uri = new String(Base64.decode(m.group(1).replaceAll("=", ""), Base64.NO_PADDING | Base64.URL_SAFE), "UTF-8")
61 | decodedPattern_ssr.findFirstMatchIn(uri) match {
62 | case Some(ss) =>
63 | val profile = new Profile
64 | profile.host = ss.group(2).toLowerCase
65 | profile.remotePort = ss.group(3).toInt
66 | profile.protocol = ss.group(4).toLowerCase
67 | profile.method = ss.group(5).toLowerCase
68 | profile.obfs = ss.group(6).toLowerCase
69 | profile.password = new String(Base64.decode(ss.group(7).replaceAll("=", ""), Base64.NO_PADDING | Base64.URL_SAFE), "UTF-8")
70 |
71 | decodedPattern_ssr_obfsparam.findFirstMatchIn(uri) match {
72 | case Some(param) =>
73 | profile.obfs_param = new String(Base64.decode(param.group(1).replaceAll("=", ""), Base64.NO_PADDING | Base64.URL_SAFE), "UTF-8")
74 | case _ => null
75 | }
76 |
77 | decodedPattern_ssr_protocolparam.findFirstMatchIn(uri) match {
78 | case Some(param) =>
79 | profile.protocol_param = new String(Base64.decode(param.group(1).replaceAll("=", ""), Base64.NO_PADDING | Base64.URL_SAFE), "UTF-8")
80 | case _ => null
81 | }
82 |
83 | decodedPattern_ssr_remarks.findFirstMatchIn(uri) match {
84 | case Some(param) =>
85 | profile.name = new String(Base64.decode(param.group(1).replaceAll("=", ""), Base64.NO_PADDING | Base64.URL_SAFE), "UTF-8")
86 | case _ => profile.name = ss.group(2).toLowerCase
87 | }
88 |
89 | profile
90 | case _ => null
91 | }
92 | } catch {
93 | case ex: Exception =>
94 | Log.e(TAG, "parser error: " + m.source, ex)// Ignore
95 | null
96 | }).filter(_ != null)
97 | }
98 |
--------------------------------------------------------------------------------