├── .gitignore
├── .idea
└── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── libs
│ ├── arm64-v8a
│ │ ├── libpdnsd.so
│ │ ├── libproxychains4.so
│ │ ├── libssr-local.so
│ │ └── libtun2socks.so
│ ├── armeabi-v7a
│ │ ├── libpdnsd.so
│ │ ├── libproxychains4.so
│ │ ├── libssr-local.so
│ │ └── libtun2socks.so
│ ├── x86
│ │ ├── libpdnsd.so
│ │ ├── libproxychains4.so
│ │ ├── libssr-local.so
│ │ └── libtun2socks.so
│ └── x86_64
│ │ ├── libpdnsd.so
│ │ ├── libproxychains4.so
│ │ ├── libssr-local.so
│ │ └── libtun2socks.so
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── aidl
│ └── com
│ │ └── bige0
│ │ └── shadowsocksr
│ │ └── aidl
│ │ ├── IShadowsocksService.aidl
│ │ └── IShadowsocksServiceCallback.aidl
│ ├── assets
│ ├── acl
│ │ ├── bypass-china.acl
│ │ ├── bypass-lan-china.acl
│ │ ├── bypass-lan.acl
│ │ ├── china-list.acl
│ │ └── gfwlist.acl
│ ├── fonts
│ │ └── Iceland.ttf
│ └── pages
│ │ └── about.html
│ ├── java
│ └── com
│ │ └── bige0
│ │ └── shadowsocksr
│ │ ├── AppManager.kt
│ │ ├── BaseService.kt
│ │ ├── BaseVpnService.kt
│ │ ├── BootReceiver.kt
│ │ ├── GuardedProcess.kt
│ │ ├── ProfileManagerActivity.kt
│ │ ├── QuickToggleShortcut.kt
│ │ ├── ScannerActivity.kt
│ │ ├── ServiceBoundContext.kt
│ │ ├── ServiceBoundService.kt
│ │ ├── Shadowsocks.kt
│ │ ├── ShadowsocksApplication.kt
│ │ ├── ShadowsocksNotification.kt
│ │ ├── ShadowsocksQuickSwitchActivity.kt
│ │ ├── ShadowsocksRunnerActivity.kt
│ │ ├── ShadowsocksRunnerService.kt
│ │ ├── ShadowsocksSettings.kt
│ │ ├── ShadowsocksTileService.kt
│ │ ├── ShadowsocksVpnService.kt
│ │ ├── ShadowsocksVpnThread.kt
│ │ ├── TaskerActivity.kt
│ │ ├── TaskerReceiver.kt
│ │ ├── database
│ │ ├── DBHelper.kt
│ │ ├── Profile.kt
│ │ ├── ProfileManager.kt
│ │ ├── SSRSub.kt
│ │ └── SSRSubManager.kt
│ │ ├── job
│ │ ├── AclSyncJob.kt
│ │ ├── DonaldTrump.kt
│ │ └── SSRSubUpdateJob.kt
│ │ ├── network
│ │ ├── ping
│ │ │ ├── PingCallback.kt
│ │ │ └── PingHelper.kt
│ │ ├── request
│ │ │ ├── RequestCallback.kt
│ │ │ └── RequestHelper.kt
│ │ └── ssrsub
│ │ │ ├── SubUpdateCallback.kt
│ │ │ └── SubUpdateHelper.kt
│ │ ├── preferences
│ │ ├── DropDownPreference.kt
│ │ ├── NumberPickerPreference.kt
│ │ ├── PasswordEditTextPreference.kt
│ │ ├── SummaryDialogPreference.kt
│ │ ├── SummaryEditTextPreference.kt
│ │ └── SummaryPreference.kt
│ │ ├── shortcuts
│ │ └── ShortcutUtils.kt
│ │ ├── utils
│ │ ├── Base64.kt
│ │ ├── Constants.kt
│ │ ├── IOUtils.kt
│ │ ├── Parser.kt
│ │ ├── TaskerSettings.kt
│ │ ├── ToastUtils.kt
│ │ ├── TrafficMonitor.kt
│ │ ├── TrafficMonitorThread.kt
│ │ ├── Typefaces.kt
│ │ ├── Utils.kt
│ │ └── VayLog.kt
│ │ └── widget
│ │ ├── FloatingActionMenuBehavior.kt
│ │ └── UndoSnackBarManager.kt
│ └── res
│ ├── drawable-anydpi-v21
│ ├── ic_action_settings.xml
│ ├── ic_navigation_close.xml
│ ├── ic_qu_camera_launcher.xml
│ ├── ic_qu_shadowsocks_launcher.xml
│ └── ic_qu_shadowsocks_launcher_disabled.xml
│ ├── drawable-hdpi
│ ├── ic_action_settings.png
│ ├── ic_navigation_close.png
│ └── ic_stat_shadowsocks.png
│ ├── drawable-mdpi
│ ├── ic_action_settings.png
│ ├── ic_navigation_close.png
│ └── ic_stat_shadowsocks.png
│ ├── drawable-v21
│ └── background_stat.xml
│ ├── drawable-xhdpi
│ ├── ic_action_settings.png
│ ├── ic_navigation_close.png
│ └── ic_stat_shadowsocks.png
│ ├── drawable-xxhdpi
│ ├── ic_action_settings.png
│ ├── ic_navigation_close.png
│ └── ic_stat_shadowsocks.png
│ ├── drawable-xxxhdpi
│ ├── ic_action_settings.png
│ └── ic_navigation_close.png
│ ├── drawable
│ ├── abc_ic_ab_back_material.xml
│ ├── background_stat.xml
│ ├── ic_arrow_drop_down.xml
│ ├── ic_click.xml
│ ├── ic_click_white.xml
│ ├── ic_content_copy.xml
│ ├── ic_content_create.xml
│ ├── ic_content_paste.xml
│ ├── ic_device_nfc.xml
│ ├── ic_down.xml
│ ├── ic_image_camera_alt.xml
│ ├── ic_rss.xml
│ ├── ic_social_share.xml
│ ├── ic_start_busy.xml
│ ├── ic_start_connected.xml
│ └── ic_start_idle.xml
│ ├── layout
│ ├── layout_apps.xml
│ ├── layout_apps_item.xml
│ ├── layout_edittext.xml
│ ├── layout_front_proxy.xml
│ ├── layout_main.xml
│ ├── layout_profiles.xml
│ ├── layout_profiles_item.xml
│ ├── layout_quick_switch.xml
│ ├── layout_scanner.xml
│ ├── layout_ssr_sub.xml
│ ├── layout_ssr_sub_item.xml
│ ├── layout_tasker.xml
│ └── toolbar_light_dark.xml
│ ├── menu
│ ├── app_manager_menu.xml
│ └── profile_manager_menu.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── raw
│ └── gtm_default_container
│ ├── values-v21
│ ├── dimen.xml
│ ├── strings.xml
│ └── styles.xml
│ ├── values-zh-rCN-v21
│ └── strings.xml
│ ├── values-zh-rCN
│ └── strings.xml
│ ├── values-zh-rTW-v21
│ └── strings.xml
│ ├── values-zh-rTW
│ └── strings.xml
│ ├── values
│ ├── arrays.xml
│ ├── attrs.xml
│ ├── colors.xml
│ ├── configs.xml
│ ├── dimen.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ ├── network_security_config.xml
│ ├── pref_all.xml
│ ├── shortcuts.xml
│ └── tracker.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 | *.aab
5 |
6 | # Files for the ART/Dalvik VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # Generated files
13 | bin/
14 | gen/
15 | out/
16 | release/
17 |
18 | # Gradle files
19 | .gradle/
20 | build/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Proguard folder generated by Eclipse
26 | proguard/
27 |
28 | # Log Files
29 | *.log
30 |
31 | # Android Studio Navigation editor temp files
32 | .navigation/
33 |
34 | # Android Studio captures folder
35 | captures/
36 |
37 | # IntelliJ
38 | *.iml
39 | .idea/workspace.xml
40 | .idea/tasks.xml
41 | .idea/gradle.xml
42 | .idea/assetWizardSettings.xml
43 | .idea/dictionaries
44 | .idea/libraries
45 | # Android Studio 3 in .gitignore file.
46 | .idea/caches
47 | .idea/modules.xml
48 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
49 | .idea/navEditor.xml
50 |
51 | # Keystore files
52 | # Uncomment the following lines if you do not want to check your keystore files in.
53 | #*.jks
54 | #*.keystore
55 |
56 | # External native build folder generated in Android Studio 2.2 and later
57 | .externalNativeBuild
58 |
59 | # Google Services (e.g. APIs or Firebase)
60 | # google-services.json
61 |
62 | # Freeline
63 | freeline.py
64 | freeline/
65 | freeline_project_description.json
66 |
67 | # fastlane
68 | fastlane/report.xml
69 | fastlane/Preview.html
70 | fastlane/screenshots
71 | fastlane/test_output
72 | fastlane/readme.md
73 |
74 | # Version control
75 | vcs.xml
76 |
77 | # lint
78 | lint/intermediates/
79 | lint/generated/
80 | lint/outputs/
81 | lint/tmp/
82 | # lint/reports/
83 |
84 | # NDK
85 |
86 | .gradle
87 | .idea/*
88 | !.idea/codeStyles/
89 | **/*.iml
90 | local.properties
91 | build
92 | *~
93 | .externalNativeBuild
94 | libwebp
95 | .DS_Store
96 | **/ndkHelperBin
97 | **/.cxx
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | xmlns:android
20 |
21 | ^$
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | xmlns:.*
31 |
32 | ^$
33 |
34 |
35 | BY_NAME
36 |
37 |
38 |
39 |
40 |
41 |
42 | .*:id
43 |
44 | http://schemas.android.com/apk/res/android
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | .*:name
54 |
55 | http://schemas.android.com/apk/res/android
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | name
65 |
66 | ^$
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | style
76 |
77 | ^$
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | .*
87 |
88 | ^$
89 |
90 |
91 | BY_NAME
92 |
93 |
94 |
95 |
96 |
97 |
98 | .*
99 |
100 | http://schemas.android.com/apk/res/android
101 |
102 |
103 | ANDROID_ATTRIBUTE_ORDER
104 |
105 |
106 |
107 |
108 |
109 |
110 | .*
111 |
112 | .*
113 |
114 |
115 | BY_NAME
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ShadowsocksR
2 | A ShadowsocksR client for Android, written in Kotlin.
3 |
4 | ### PREREQUISITES
5 |
6 | * JDK 1.8
7 | * Android Studio 4.1.1
8 |
9 | # LICENSE
10 |
11 | Copyright (C) 2016 by Max Lv <>
12 | Copyright (C) 2016 by Mygod Studio <>
13 |
14 | This program is free software: you can redistribute it and/or modify
15 | it under the terms of the GNU General Public License as published by
16 | the Free Software Foundation, either version 3 of the License, or
17 | (at your option) any later version.
18 |
19 | This program is distributed in the hope that it will be useful,
20 | but WITHOUT ANY WARRANTY; without even the implied warranty of
21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 | GNU General Public License for more details.
23 |
24 | You should have received a copy of the GNU General Public License
25 | along with this program. If not, see .
26 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion rootProject.compileSdkVersion
6 | defaultConfig {
7 | applicationId "com.bige0.shadowsocksr"
8 | minSdkVersion rootProject.minSdkVersion
9 | targetSdkVersion rootProject.sdkVersion
10 | versionCode rootProject.versionCode
11 | versionName rootProject.versionName
12 | multiDexEnabled true
13 | proguardFiles
14 | vectorDrawables.useSupportLibrary = true
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | sourceSets {
23 | main {
24 | jniLibs.srcDirs = ['libs']
25 | }
26 | }
27 | compileOptions {
28 | sourceCompatibility javaVersion
29 | targetCompatibility javaVersion
30 | }
31 |
32 | kotlinOptions.jvmTarget = javaVersion
33 | packagingOptions {
34 | jniLibs {
35 | excludes += ['**/*.kotlin_*']
36 | }
37 | resources {
38 | excludes += ['**/*.kotlin_*']
39 | }
40 | }
41 | namespace 'com.bige0.shadowsocksr'
42 |
43 | }
44 |
45 | dependencies {
46 | implementation fileTree(dir: 'libs', include: ['*.jar'])
47 |
48 | implementation 'androidx.appcompat:appcompat:1.4.0'
49 | implementation 'androidx.gridlayout:gridlayout:1.0.0'
50 | implementation 'androidx.recyclerview:recyclerview:1.2.1'
51 | implementation 'com.github.devv911:android-job:1.4.5'
52 | implementation 'androidx.work:work-runtime-ktx:2.7.1'
53 | implementation "com.github.clans:fab:1.6.4"
54 | implementation "com.github.jorgecastilloprz:fabprogresscircle:1.01"
55 | implementation 'com.google.android.gms:play-services-analytics:18.0.1'
56 | implementation 'com.j256.ormlite:ormlite-android:5.6'
57 | implementation 'com.mikepenz:iconics-core:5.3.0'
58 | implementation 'com.mikepenz:materialdrawer:8.4.3'
59 | implementation "com.twofortyfouram:android-plugin-api-for-locale:1.0.4"
60 | implementation 'dnsjava:dnsjava:3.5.2'
61 | implementation "me.dm7.barcodescanner:zxing:1.9.13"
62 | implementation "net.glxn.qrgen:android:2.0"
63 | implementation 'com.squareup.okhttp3:okhttp:4.10.0'
64 | implementation "com.google.code.findbugs:jsr305:3.0.2"
65 | implementation 'com.google.android.material:material:1.4.0'
66 | }
67 |
68 | repositories {
69 | mavenCentral()
70 | }
71 |
--------------------------------------------------------------------------------
/app/libs/arm64-v8a/libpdnsd.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/arm64-v8a/libpdnsd.so
--------------------------------------------------------------------------------
/app/libs/arm64-v8a/libproxychains4.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/arm64-v8a/libproxychains4.so
--------------------------------------------------------------------------------
/app/libs/arm64-v8a/libssr-local.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/arm64-v8a/libssr-local.so
--------------------------------------------------------------------------------
/app/libs/arm64-v8a/libtun2socks.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/arm64-v8a/libtun2socks.so
--------------------------------------------------------------------------------
/app/libs/armeabi-v7a/libpdnsd.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/armeabi-v7a/libpdnsd.so
--------------------------------------------------------------------------------
/app/libs/armeabi-v7a/libproxychains4.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/armeabi-v7a/libproxychains4.so
--------------------------------------------------------------------------------
/app/libs/armeabi-v7a/libssr-local.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/armeabi-v7a/libssr-local.so
--------------------------------------------------------------------------------
/app/libs/armeabi-v7a/libtun2socks.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/armeabi-v7a/libtun2socks.so
--------------------------------------------------------------------------------
/app/libs/x86/libpdnsd.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/x86/libpdnsd.so
--------------------------------------------------------------------------------
/app/libs/x86/libproxychains4.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/x86/libproxychains4.so
--------------------------------------------------------------------------------
/app/libs/x86/libssr-local.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/x86/libssr-local.so
--------------------------------------------------------------------------------
/app/libs/x86/libtun2socks.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/x86/libtun2socks.so
--------------------------------------------------------------------------------
/app/libs/x86_64/libpdnsd.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/x86_64/libpdnsd.so
--------------------------------------------------------------------------------
/app/libs/x86_64/libproxychains4.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/x86_64/libproxychains4.so
--------------------------------------------------------------------------------
/app/libs/x86_64/libssr-local.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/x86_64/libssr-local.so
--------------------------------------------------------------------------------
/app/libs/x86_64/libtun2socks.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/libs/x86_64/libtun2socks.so
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -keep class okhttp3.** { *; }
24 | -keep interface okhttp3.** { *; }
25 | -keep class okio.** { *; }
26 | -keep interface okio.** { *; }
27 | -dontwarn okio.**
28 | -dontwarn com.google.android.gms.internal.**
29 | -dontwarn com.j256.ormlite.**
30 | -dontwarn org.xbill.**
--------------------------------------------------------------------------------
/app/src/main/aidl/com/bige0/shadowsocksr/aidl/IShadowsocksService.aidl:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.aidl;
2 |
3 | import com.bige0.shadowsocksr.aidl.IShadowsocksServiceCallback;
4 |
5 | interface IShadowsocksService {
6 | int getState();
7 | String getProfileName();
8 |
9 | oneway void registerCallback(IShadowsocksServiceCallback cb);
10 | oneway void unregisterCallback(IShadowsocksServiceCallback cb);
11 |
12 | oneway void use(in int profileId);
13 | void useSync(in int profileId);
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/aidl/com/bige0/shadowsocksr/aidl/IShadowsocksServiceCallback.aidl:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.aidl;
2 |
3 | interface IShadowsocksServiceCallback {
4 | oneway void stateChanged(int state, String profileName, String msg);
5 | oneway void trafficUpdated(long txRate, long rxRate, long txTotal, long rxTotal);
6 | }
7 |
--------------------------------------------------------------------------------
/app/src/main/assets/acl/bypass-lan.acl:
--------------------------------------------------------------------------------
1 | [proxy_all]
2 |
3 | [bypass_list]
4 | ^(.*\.)?local$
5 | ^(.*\.)?localhost$
6 | ^(.*\.)?ip6-localhost$
7 | ^(.*\.)?ip6-loopback$
8 |
9 | 0.0.0.0/8
10 | 10.0.0.0/8
11 | 100.64.0.0/10
12 | 127.0.0.0/8
13 | 169.254.0.0/16
14 | 172.16.0.0/12
15 | 192.0.0.0/24
16 | 192.0.2.0/24
17 | 192.88.99.0/24
18 | 192.168.0.0/16
19 | 198.18.0.0/15
20 | 198.51.100.0/24
21 | 203.0.113.0/24
22 | 224.0.0.0/4
23 | 233.252.0.0/24
24 | 240.0.0.0/4
25 |
--------------------------------------------------------------------------------
/app/src/main/assets/fonts/Iceland.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/assets/fonts/Iceland.ttf
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/BootReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr
2 |
3 | import android.content.*
4 | import android.content.pm.*
5 | import android.os.*
6 | import androidx.core.content.*
7 | import com.bige0.shadowsocksr.ShadowsocksApplication.Companion.app
8 | import com.bige0.shadowsocksr.utils.*
9 |
10 | class BootReceiver : BroadcastReceiver()
11 | {
12 | override fun onReceive(context: Context, intent: Intent)
13 | {
14 | val doStart = when (intent.action)
15 | {
16 | Intent.ACTION_BOOT_COMPLETED -> !Utils.directBootSupported
17 | Intent.ACTION_LOCKED_BOOT_COMPLETED -> Utils.directBootSupported
18 | else -> Utils.directBootSupported || Build.VERSION.SDK_INT >= 24 && app.getSystemService()?.isUserUnlocked != false
19 | }
20 | if (doStart) Utils.startSsService(context)
21 | }
22 |
23 | companion object
24 | {
25 | private val componentName by lazy { ComponentName(app, BootReceiver::class.java) }
26 | var enabled: Boolean
27 | get() = app.packageManager.getComponentEnabledSetting(componentName) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
28 | set(value) = app.packageManager.setComponentEnabledSetting(componentName, if (value) PackageManager.COMPONENT_ENABLED_STATE_ENABLED
29 | else PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/GuardedProcess.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr
2 |
3 | import android.util.*
4 | import com.bige0.shadowsocksr.utils.*
5 | import java.io.*
6 | import java.util.concurrent.*
7 |
8 | class GuardedProcess(private val cmd: List)
9 | {
10 | private var guardThread: Thread? = null
11 | private var isDestroyed: Boolean = false
12 | private var process: Process? = null
13 | private var isRestart = false
14 |
15 | fun start(onRestartCallback: (() -> Boolean)? = null): GuardedProcess
16 | {
17 | val semaphore = Semaphore(1)
18 | semaphore.acquire()
19 |
20 | guardThread = Thread(object : Runnable
21 | {
22 | override fun run()
23 | {
24 | try
25 | {
26 | var callback: (() -> Boolean)? = null
27 | while (!isDestroyed)
28 | {
29 | VayLog.i(TAG, "start process: $cmd")
30 | val startTime = System.currentTimeMillis()
31 |
32 | process = ProcessBuilder(cmd).redirectErrorStream(true)
33 | .start()
34 |
35 | val inputStream = process!!.inputStream
36 | StreamLogger(inputStream, TAG).start()
37 |
38 | if (callback == null)
39 | {
40 | callback = onRestartCallback
41 | }
42 | else
43 | {
44 | callback()
45 | }
46 |
47 | semaphore.release()
48 | process!!.waitFor()
49 |
50 | synchronized(this) {
51 | if (isRestart)
52 | {
53 | isRestart = false
54 | }
55 | else
56 | {
57 | if (System.currentTimeMillis() - startTime < 1000)
58 | {
59 | Log.w(TAG, "process exit too fast, stop guard: $cmd")
60 | isDestroyed = true
61 | }
62 | }
63 | }
64 | }
65 | }
66 | catch (ignored: Exception)
67 | {
68 | VayLog.i(TAG, "thread interrupt, destroy process: $cmd")
69 |
70 | if (process == null)
71 | {
72 | return
73 | }
74 |
75 | if (process!!.javaClass.name.equals("java.lang.UNIXProcess"))
76 | {
77 | val fPid = process!!.javaClass.getDeclaredField("pid").apply { isAccessible = true }
78 | val pid = fPid.getInt(process)
79 | android.os.Process.killProcess(pid)
80 | }
81 | else
82 | {
83 | process!!.destroy() // wtf??? not work >= android S
84 | }
85 | }
86 | finally
87 | {
88 | semaphore.release()
89 | }
90 | }
91 | }, "GuardThread-$cmd")
92 |
93 | guardThread!!.start()
94 | semaphore.acquire()
95 | return this
96 | }
97 |
98 | fun destroy()
99 | {
100 | isDestroyed = true
101 | guardThread?.interrupt()
102 | process?.destroy()
103 | try
104 | {
105 | guardThread?.join()
106 | }
107 | catch (e: InterruptedException)
108 | {
109 | // Ignored
110 | }
111 | }
112 |
113 | inner class StreamLogger(private val inputStream: InputStream, private val tag: String) : Thread()
114 | {
115 | override fun run()
116 | {
117 | var bufferedReader: BufferedReader? = null
118 | try
119 | {
120 | bufferedReader = BufferedReader(InputStreamReader(inputStream))
121 | var temp = bufferedReader.readLine()
122 | while (temp != null)
123 | {
124 | VayLog.e(tag, temp)
125 | temp = bufferedReader.readLine()
126 | }
127 | }
128 | catch (e: Exception)
129 | {
130 | // Ignore
131 | }
132 | finally
133 | {
134 | try
135 | {
136 | bufferedReader?.close()
137 | }
138 | catch (e: IOException)
139 | {
140 | // Ignore
141 | }
142 | }
143 | }
144 | }
145 |
146 | companion object
147 | {
148 | private const val TAG = "GuardedProcess"
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/QuickToggleShortcut.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr
2 |
3 | import android.app.*
4 | import android.content.*
5 | import android.content.pm.*
6 | import android.os.*
7 | import androidx.core.content.*
8 | import androidx.core.content.pm.*
9 | import com.bige0.shadowsocksr.shortcuts.*
10 | import com.bige0.shadowsocksr.utils.*
11 |
12 | class QuickToggleShortcut : Activity()
13 | {
14 | private lateinit var mServiceBoundContext: ServiceBoundContext
15 |
16 | override fun attachBaseContext(newBase: Context)
17 | {
18 | super.attachBaseContext(newBase)
19 | mServiceBoundContext = object : ServiceBoundContext(newBase)
20 | {
21 | override fun onServiceConnected()
22 | {
23 | try
24 | {
25 | when (bgService?.state)
26 | {
27 | Constants.State.STOPPED ->
28 | {
29 | ToastUtils.showShort(R.string.loading)
30 | Utils.startSsService(this)
31 | val connectedShortcut = makeToggleShortcut(context = this, isConnected = true)
32 | ShortcutManagerCompat.pushDynamicShortcut(this, connectedShortcut)
33 | }
34 |
35 | Constants.State.CONNECTED ->
36 | {
37 | Utils.stopSsService(this)
38 | val disconnectedShortcut = makeToggleShortcut(context = this, isConnected = false)
39 | ShortcutManagerCompat.pushDynamicShortcut(this, disconnectedShortcut)
40 | }
41 |
42 | else ->
43 | {
44 | }
45 | } // ignore
46 | }
47 | catch (e: RemoteException)
48 | {
49 | e.printStackTrace()
50 | }
51 |
52 | finish()
53 | }
54 | }
55 | }
56 |
57 | override fun onCreate(savedInstanceState: Bundle?)
58 | {
59 | super.onCreate(savedInstanceState)
60 |
61 | if (intent.action == Intent.ACTION_MAIN)
62 | {
63 | mServiceBoundContext.attachService()
64 | if (Build.VERSION.SDK_INT >= 25)
65 | {
66 | getSystemService()!!.reportShortcutUsed("toggle")
67 | }
68 | }
69 | }
70 |
71 | override fun onDestroy()
72 | {
73 | mServiceBoundContext.detachService()
74 | super.onDestroy()
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/ScannerActivity.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr
2 |
3 | import android.*
4 | import android.app.TaskStackBuilder
5 | import android.content.pm.*
6 | import android.os.*
7 | import android.text.*
8 | import androidx.appcompat.app.*
9 | import androidx.appcompat.widget.*
10 | import androidx.core.app.*
11 | import androidx.core.content.*
12 | import com.bige0.shadowsocksr.utils.*
13 | import com.google.zxing.*
14 | import me.dm7.barcodescanner.zxing.*
15 |
16 | class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler
17 | {
18 | private lateinit var scannerView: ZXingScannerView
19 |
20 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray)
21 | {
22 | if (requestCode == MY_PERMISSIONS_REQUEST_CAMERA)
23 | {
24 | // If request is cancelled, the result arrays are empty.
25 | if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)
26 | {
27 | scannerView.setResultHandler(this)
28 | scannerView.startCamera()
29 | }
30 | else
31 | {
32 | ToastUtils.showShort(R.string.add_profile_scanner_permission_required)
33 | finish()
34 | }
35 | }
36 | }
37 |
38 | private fun navigateUp()
39 | {
40 | val intent = parentActivityIntent
41 | if (shouldUpRecreateTask(intent) || isTaskRoot)
42 | {
43 | TaskStackBuilder.create(this)
44 | .addNextIntentWithParentStack(intent)
45 | .startActivities()
46 | }
47 | else
48 | {
49 | finish()
50 | }
51 | }
52 |
53 | override fun onCreate(savedInstanceState: Bundle?)
54 | {
55 | super.onCreate(savedInstanceState)
56 | setContentView(R.layout.layout_scanner)
57 | val toolbar = findViewById(R.id.toolbar)
58 | toolbar.title = title
59 | toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_material)
60 | toolbar.setNavigationOnClickListener { navigateUp() }
61 |
62 | scannerView = findViewById(R.id.scanner)
63 |
64 | if (Build.VERSION.SDK_INT >= 25)
65 | {
66 | getSystemService()!!.reportShortcutUsed("scan")
67 | }
68 | }
69 |
70 | override fun onResume()
71 | {
72 | super.onResume()
73 | val permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
74 | if (permissionCheck == PackageManager.PERMISSION_GRANTED)
75 | {
76 | // Register ourselves as a handler for scan results.
77 | scannerView.setResultHandler(this)
78 | scannerView.setAutoFocus(true)
79 | // Start camera on resume
80 | scannerView.startCamera()
81 | }
82 | else
83 | {
84 | ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), MY_PERMISSIONS_REQUEST_CAMERA)
85 | }
86 | }
87 |
88 | override fun onPause()
89 | {
90 | super.onPause()
91 | // Stop camera on pause
92 | scannerView.stopCamera()
93 | }
94 |
95 | override fun handleResult(rawResult: Result)
96 | {
97 | val uri = rawResult.text
98 | if (!TextUtils.isEmpty(uri))
99 | {
100 | val all = Parser.findAllSs(uri)
101 | if (all.isNotEmpty())
102 | {
103 | for (p in all)
104 | {
105 | ShadowsocksApplication.app.profileManager.createProfile(p)
106 | }
107 | }
108 |
109 | val allSSR = Parser.findAllSsr(uri)
110 | if (allSSR.isNotEmpty())
111 | {
112 | for (p in allSSR)
113 | {
114 | ShadowsocksApplication.app.profileManager.createProfile(p)
115 | }
116 | }
117 | }
118 | navigateUp()
119 | }
120 |
121 | companion object
122 | {
123 | private const val MY_PERMISSIONS_REQUEST_CAMERA = 1
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/ServiceBoundContext.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr
2 |
3 | import android.content.*
4 | import android.os.*
5 | import com.bige0.shadowsocksr.aidl.*
6 | import com.bige0.shadowsocksr.utils.*
7 |
8 | open class ServiceBoundContext(base: Context) : ContextWrapper(base), IBinder.DeathRecipient
9 | {
10 | companion object
11 | {
12 | private const val TAG = "ServiceBoundContext"
13 | }
14 |
15 | var bgService: IShadowsocksService? = null
16 | private var binder: IBinder? = null
17 | private var callback: IShadowsocksServiceCallback? = null
18 | private var connection: ShadowsocksServiceConnection? = null
19 | private var callbackRegistered: Boolean = false
20 |
21 | /**
22 | * register callback
23 | */
24 | fun registerCallback()
25 | {
26 | if (bgService != null && callback != null && !callbackRegistered)
27 | {
28 | try
29 | {
30 | bgService!!.registerCallback(callback)
31 | callbackRegistered = true
32 | }
33 | catch (e: Exception)
34 | {
35 | VayLog.e(TAG, "registerCallback", e)
36 | }
37 |
38 | }
39 | }
40 |
41 | /**
42 | * unregister callback
43 | */
44 | fun unregisterCallback()
45 | {
46 | if (bgService != null && callback != null && callbackRegistered)
47 | {
48 | try
49 | {
50 | bgService!!.unregisterCallback(callback)
51 | }
52 | catch (e: Exception)
53 | {
54 | VayLog.e(TAG, "unregisterCallback", e)
55 | }
56 |
57 | callbackRegistered = false
58 | }
59 | }
60 |
61 | protected open fun onServiceConnected()
62 | {
63 | }
64 |
65 | protected open fun onServiceDisconnected()
66 | {
67 | }
68 |
69 | override fun binderDied()
70 | {
71 | }
72 |
73 | fun attachService(callback: IShadowsocksServiceCallback.Stub? = null)
74 | {
75 | this.callback = callback
76 | if (bgService == null)
77 | {
78 | val clazz: Class<*>
79 | clazz = ShadowsocksVpnService::class.java
80 |
81 | val intent = Intent(this, clazz)
82 | intent.action = Constants.Action.SERVICE
83 |
84 | connection = ShadowsocksServiceConnection()
85 | bindService(intent, connection!!, Context.BIND_AUTO_CREATE)
86 | }
87 | }
88 |
89 | /**
90 | * detach service
91 | */
92 | fun detachService()
93 | {
94 | unregisterCallback()
95 | callback = null
96 | if (connection != null)
97 | {
98 | try
99 | {
100 | unbindService(connection!!)
101 | }
102 | catch (e: Exception)
103 | {
104 | VayLog.e(TAG, "detachService", e)
105 | }
106 |
107 | connection = null
108 | }
109 |
110 | if (binder != null)
111 | {
112 | binder!!.unlinkToDeath(this, 0)
113 | binder = null
114 | }
115 |
116 | bgService = null
117 | }
118 |
119 | inner class ShadowsocksServiceConnection : ServiceConnection
120 | {
121 | override fun onServiceConnected(name: ComponentName, service: IBinder)
122 | {
123 | try
124 | {
125 | binder = service
126 | service.linkToDeath(this@ServiceBoundContext, 0)
127 | bgService = IShadowsocksService.Stub.asInterface(service)
128 | registerCallback()
129 | this@ServiceBoundContext.onServiceConnected()
130 | }
131 | catch (e: RemoteException)
132 | {
133 | VayLog.e(TAG, "onServiceConnected", e)
134 | }
135 |
136 | }
137 |
138 | override fun onServiceDisconnected(name: ComponentName)
139 | {
140 | unregisterCallback()
141 | this@ServiceBoundContext.onServiceDisconnected()
142 | bgService = null
143 | binder = null
144 | }
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/ServiceBoundService.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr
2 |
3 | import android.app.*
4 | import android.content.*
5 | import android.os.*
6 | import com.bige0.shadowsocksr.aidl.*
7 | import com.bige0.shadowsocksr.utils.*
8 |
9 | abstract class ServiceBoundService : Service(), IBinder.DeathRecipient
10 | {
11 | companion object
12 | {
13 | private const val TAG = "ServiceBoundService"
14 | }
15 |
16 | protected var bgService: IShadowsocksService? = null
17 | private var binder: IBinder? = null
18 | private var callback: IShadowsocksServiceCallback? = null
19 | private var connection: ShadowsocksServiceConnection? = null
20 | private var callbackRegistered: Boolean = false
21 |
22 | /**
23 | * register callback
24 | */
25 | private fun registerCallback()
26 | {
27 | if (bgService != null && callback != null && !callbackRegistered)
28 | {
29 | try
30 | {
31 | bgService!!.registerCallback(callback)
32 | callbackRegistered = true
33 | }
34 | catch (e: Exception)
35 | {
36 | VayLog.e(TAG, "registerCallback", e)
37 | }
38 |
39 | }
40 | }
41 |
42 | /**
43 | * unregister callback
44 | */
45 | protected fun unregisterCallback()
46 | {
47 | if (bgService != null && callback != null && callbackRegistered)
48 | {
49 | try
50 | {
51 | bgService!!.unregisterCallback(callback)
52 | }
53 | catch (e: Exception)
54 | {
55 | VayLog.e(TAG, "unregisterCallback", e)
56 | }
57 |
58 | callbackRegistered = false
59 | }
60 | }
61 |
62 | protected open fun onServiceConnected()
63 | {
64 | }
65 |
66 | protected open fun onServiceDisconnected()
67 | {
68 | }
69 |
70 | override fun binderDied()
71 | {
72 | }
73 |
74 | fun attachService(callback: IShadowsocksServiceCallback.Stub? = null)
75 | {
76 | this.callback = callback
77 | if (bgService == null)
78 | {
79 | val intent = Intent(this, ShadowsocksVpnService::class.java)
80 | intent.action = Constants.Action.SERVICE
81 |
82 | connection = ShadowsocksServiceConnection()
83 | bindService(intent, connection!!, Context.BIND_AUTO_CREATE)
84 | }
85 | }
86 |
87 | /**
88 | * detach service
89 | */
90 | fun detachService()
91 | {
92 | unregisterCallback()
93 | callback = null
94 | if (connection != null)
95 | {
96 | try
97 | {
98 | unbindService(connection!!)
99 | }
100 | catch (e: Exception)
101 | {
102 | VayLog.e(TAG, "detachService", e)
103 | }
104 |
105 | connection = null
106 | }
107 |
108 | if (binder != null)
109 | {
110 | binder!!.unlinkToDeath(this, 0)
111 | binder = null
112 | }
113 |
114 | bgService = null
115 | }
116 |
117 | inner class ShadowsocksServiceConnection : ServiceConnection
118 | {
119 | override fun onServiceConnected(name: ComponentName, service: IBinder)
120 | {
121 | try
122 | {
123 | binder = service
124 | service.linkToDeath(this@ServiceBoundService, 0)
125 | bgService = IShadowsocksService.Stub.asInterface(service)
126 | registerCallback()
127 | this@ServiceBoundService.onServiceConnected()
128 | }
129 | catch (e: RemoteException)
130 | {
131 | VayLog.e(TAG, "onServiceConnected", e)
132 | }
133 |
134 | }
135 |
136 | override fun onServiceDisconnected(name: ComponentName)
137 | {
138 | unregisterCallback()
139 | this@ServiceBoundService.onServiceDisconnected()
140 | bgService = null
141 | binder = null
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/ShadowsocksNotification.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr
2 |
3 | import android.app.*
4 | import android.app.PendingIntent.*
5 | import android.content.*
6 | import android.os.*
7 | import androidx.core.app.*
8 | import androidx.core.content.*
9 | import com.bige0.shadowsocksr.aidl.*
10 | import com.bige0.shadowsocksr.utils.*
11 | import java.util.*
12 |
13 | class ShadowsocksNotification constructor(private val service: Service, private val profileName: String, private val visible: Boolean = false)
14 | {
15 | private val callback by lazy {
16 | object : IShadowsocksServiceCallback.Stub()
17 | {
18 | override fun stateChanged(state: Int, profileName: String, msg: String)
19 | {
20 | }
21 |
22 | override fun trafficUpdated(txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long)
23 | {
24 | val txr = TrafficMonitor.formatTraffic(txRate)
25 | val rxr = TrafficMonitor.formatTraffic(rxRate)
26 | builder.setContentText(String.format(Locale.ENGLISH, service.getString(R.string.traffic_summary), txr, rxr))
27 |
28 | style.bigText(String.format(Locale.ENGLISH,
29 | service.getString(R.string.stat_summary),
30 | txr,
31 | rxr,
32 | TrafficMonitor.formatTraffic(txTotal),
33 | TrafficMonitor.formatTraffic(rxTotal)))
34 | show()
35 | }
36 | }
37 | }
38 |
39 | private val pm: PowerManager = service.getSystemService(Context.POWER_SERVICE) as PowerManager
40 | private val keyGuard: KeyguardManager = service.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
41 | private val nm: NotificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
42 | private var callbackRegistered: Boolean = false
43 | private lateinit var builder: NotificationCompat.Builder
44 | private val style: NotificationCompat.BigTextStyle
45 | private var isVisible = true
46 |
47 | private var lockReceiver: BroadcastReceiver? = object : BroadcastReceiver()
48 | {
49 | override fun onReceive(context: Context, intent: Intent)
50 | {
51 | update(intent.action)
52 | }
53 | }
54 |
55 | private val serviceState: Int
56 | get()
57 | {
58 | var state = 0
59 | if (service is BaseVpnService)
60 | {
61 | state = service.currentState
62 | }
63 | else if (service is BaseService)
64 | {
65 | state = service.currentState
66 | }
67 | return state
68 | }
69 |
70 | init
71 | {
72 | // init notification builder
73 | initNotificationBuilder()
74 | style = NotificationCompat.BigTextStyle(builder)
75 |
76 | // init with update action
77 | initWithUpdateAction()
78 |
79 | // register lock receiver
80 | registerLockReceiver(service, visible)
81 | }
82 |
83 | private fun update(action: String?, forceShow: Boolean = false)
84 | {
85 | if (forceShow || serviceState == Constants.State.CONNECTED)
86 | {
87 | when (action)
88 | {
89 | Intent.ACTION_SCREEN_OFF ->
90 | {
91 | setVisible(visible && !Utils.isLollipopOrAbove, forceShow)
92 | // unregister callback to save battery
93 | unregisterCallback()
94 | }
95 | Intent.ACTION_SCREEN_ON ->
96 | {
97 | setVisible(visible && Utils.isLollipopOrAbove && !keyGuard.isKeyguardLocked, forceShow)
98 | try
99 | {
100 | registerServiceCallback(callback)
101 | }
102 | catch (e: RemoteException)
103 | {
104 | // Ignored
105 | }
106 | callbackRegistered = true
107 | }
108 | Intent.ACTION_USER_PRESENT -> setVisible(true, forceShow)
109 | }
110 | }
111 | }
112 |
113 | fun destroy()
114 | {
115 | if (lockReceiver != null)
116 | {
117 | service.unregisterReceiver(lockReceiver)
118 | lockReceiver = null
119 | }
120 | unregisterCallback()
121 | service.stopForeground(true)
122 | nm.cancel(1)
123 | }
124 |
125 | private fun setVisible(visible: Boolean, forceShow: Boolean)
126 | {
127 | if (isVisible != visible)
128 | {
129 | isVisible = visible
130 | builder.priority = if (visible) NotificationCompat.PRIORITY_LOW else NotificationCompat.PRIORITY_MIN
131 | show()
132 | }
133 | else if (forceShow)
134 | {
135 | show()
136 | }
137 | }
138 |
139 | fun show()
140 | {
141 | service.startForeground(1, builder.build())
142 | }
143 |
144 | private fun unregisterCallback()
145 | {
146 | if (callbackRegistered)
147 | {
148 | try
149 | {
150 | unregisterServiceCallback(callback)
151 | }
152 | catch (e: RemoteException)
153 | {
154 | e.printStackTrace()
155 | }
156 |
157 | callbackRegistered = false
158 | }
159 | }
160 |
161 | private fun registerLockReceiver(service: Service, visible: Boolean)
162 | {
163 | val screenFilter = IntentFilter()
164 | screenFilter.addAction(Intent.ACTION_SCREEN_ON)
165 | screenFilter.addAction(Intent.ACTION_SCREEN_OFF)
166 | if (visible && Utils.isLollipopOrAbove)
167 | {
168 | screenFilter.addAction(Intent.ACTION_USER_PRESENT)
169 | }
170 | service.registerReceiver(lockReceiver, screenFilter)
171 | }
172 |
173 | private fun initWithUpdateAction()
174 | {
175 | val action = if (pm.isInteractive) Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF
176 | update(action, true)
177 | }
178 |
179 | private fun initNotificationBuilder()
180 | {
181 | val channelId = "net_speed"
182 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
183 | {
184 | val channel = NotificationChannel(channelId, "NetSpeed", NotificationManager.IMPORTANCE_MIN)
185 | channel.setSound(null, null)
186 | nm.createNotificationChannel(channel)
187 | }
188 | builder = NotificationCompat.Builder(service, channelId)
189 | .setWhen(0)
190 | .setColor(ContextCompat.getColor(service, R.color.material_accent_500))
191 | .setTicker(service.getString(R.string.forward_success))
192 | .setContentTitle(profileName)
193 | .setContentIntent(PendingIntent.getActivity(service, 0, Intent(service, Shadowsocks::class.java)
194 | .setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), FLAG_IMMUTABLE))
195 | .setSmallIcon(R.drawable.ic_stat_shadowsocks)
196 | builder.addAction(R.drawable.ic_navigation_close,
197 | service.getString(R.string.stop),
198 | PendingIntent.getBroadcast(service, 0, Intent(Constants.Action.CLOSE), FLAG_IMMUTABLE))
199 |
200 | val profiles = ShadowsocksApplication.app.profileManager.allProfiles
201 | if (profiles.isNotEmpty())
202 | {
203 | builder.addAction(R.drawable.ic_action_settings, service.getString(R.string.quick_switch),
204 | PendingIntent.getActivity(service, 0, Intent(Constants.Action.QUICK_SWITCH), FLAG_IMMUTABLE))
205 | }
206 | }
207 |
208 | private fun registerServiceCallback(callback: IShadowsocksServiceCallback)
209 | {
210 | var binder: IShadowsocksService.Stub? = null
211 | if (service is BaseVpnService)
212 | {
213 | binder = service.binder
214 | }
215 | else if (service is BaseService)
216 | {
217 | binder = service.binder
218 | }
219 | binder?.registerCallback(callback)
220 | }
221 |
222 | private fun unregisterServiceCallback(callback: IShadowsocksServiceCallback)
223 | {
224 | var binder: IShadowsocksService.Stub? = null
225 | if (service is BaseVpnService)
226 | {
227 | binder = service.binder
228 | }
229 | else if (service is BaseService)
230 | {
231 | binder = service.binder
232 | }
233 | binder?.unregisterCallback(callback)
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/ShadowsocksQuickSwitchActivity.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr
2 |
3 | import android.content.pm.*
4 | import android.content.res.*
5 | import android.os.*
6 | import android.view.*
7 | import android.widget.*
8 | import androidx.appcompat.app.*
9 | import androidx.appcompat.widget.Toolbar
10 | import androidx.core.content.*
11 | import androidx.recyclerview.widget.*
12 | import com.bige0.shadowsocksr.database.*
13 | import com.bige0.shadowsocksr.utils.*
14 | import java.util.*
15 |
16 | class ShadowsocksQuickSwitchActivity : AppCompatActivity()
17 | {
18 | private lateinit var profilesAdapter: ProfilesAdapter
19 |
20 | override fun onCreate(savedInstanceState: Bundle?)
21 | {
22 | super.onCreate(savedInstanceState)
23 | setContentView(R.layout.layout_quick_switch)
24 | profilesAdapter = ProfilesAdapter()
25 |
26 | val toolbar = findViewById(R.id.toolbar)
27 | toolbar.setTitle(R.string.quick_switch)
28 |
29 | val profilesList = findViewById(R.id.profilesList)
30 | val lm = LinearLayoutManager(this)
31 | profilesList.layoutManager = lm
32 | profilesList.itemAnimator = DefaultItemAnimator()
33 | profilesList.adapter = profilesAdapter
34 | if (ShadowsocksApplication.app.profileId() >= 0)
35 | {
36 | var position = 0
37 | val profiles = profilesAdapter.profiles
38 | for (i in profiles!!.indices)
39 | {
40 | val profile = profiles[i]
41 | if (profile.id == ShadowsocksApplication.app.profileId())
42 | {
43 | position = i + 1
44 | break
45 | }
46 | }
47 | lm.scrollToPosition(position)
48 | }
49 |
50 | if (Build.VERSION.SDK_INT >= 25)
51 | {
52 | getSystemService()!!.reportShortcutUsed("switch")
53 | }
54 | }
55 |
56 | private inner class ProfileViewHolder(view: View) : RecyclerView.ViewHolder(view), View.OnClickListener
57 | {
58 | private var item: Profile? = null
59 | private val text: CheckedTextView
60 |
61 | init
62 | {
63 | val typedArray = obtainStyledAttributes(intArrayOf(android.R.attr.selectableItemBackground))
64 | view.setBackgroundResource(typedArray.getResourceId(0, 0))
65 | typedArray.recycle()
66 |
67 | text = itemView.findViewById(android.R.id.text1)
68 | itemView.setOnClickListener(this)
69 | }
70 |
71 | fun bind(item: Profile)
72 | {
73 | this.item = item
74 | text.text = item.name
75 | text.isChecked = item.id == ShadowsocksApplication.app.profileId()
76 | }
77 |
78 | override fun onClick(v: View)
79 | {
80 | ShadowsocksApplication.app.switchProfile(item!!.id)
81 | Utils.startSsService(this@ShadowsocksQuickSwitchActivity)
82 | finish()
83 | }
84 | }
85 |
86 | private inner class ProfilesAdapter : RecyclerView.Adapter()
87 | {
88 | private val name: String
89 | var profiles: List? = null
90 |
91 | init
92 | {
93 | val profiles = ShadowsocksApplication.app.profileManager.allProfiles
94 | if (profiles.isEmpty())
95 | {
96 | this.profiles = ArrayList()
97 | }
98 | else
99 | {
100 | this.profiles = profiles
101 | }
102 |
103 | val version = if (Build.VERSION.SDK_INT >= 21) "material" else "holo"
104 | name = "select_dialog_singlechoice_$version"
105 | }
106 |
107 | override fun onCreateViewHolder(vg: ViewGroup, viewType: Int): ProfileViewHolder
108 | {
109 | val view = LayoutInflater.from(vg.context)
110 | .inflate(Resources.getSystem().getIdentifier(name, "layout", "android"), vg, false)
111 | return ProfileViewHolder(view)
112 | }
113 |
114 | override fun onBindViewHolder(vh: ProfileViewHolder, i: Int)
115 | {
116 | vh.bind(profiles!![i])
117 | }
118 |
119 | override fun getItemCount(): Int
120 | {
121 | return profiles!!.size
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/ShadowsocksRunnerActivity.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr
2 |
3 | import android.app.*
4 | import android.content.*
5 | import android.net.*
6 | import android.os.*
7 | import com.bige0.shadowsocksr.utils.*
8 |
9 | class ShadowsocksRunnerActivity : Activity()
10 | {
11 | companion object
12 | {
13 | private const val TAG = "ShadowsocksRunnerActivity"
14 | private const val REQUEST_CONNECT = 1
15 | }
16 |
17 | private val handler = Handler()
18 |
19 | private var receiver: BroadcastReceiver? = null
20 |
21 | private lateinit var mServiceBoundContext: ServiceBoundContext
22 |
23 | override fun attachBaseContext(newBase: Context)
24 | {
25 | super.attachBaseContext(newBase)
26 | mServiceBoundContext = object : ServiceBoundContext(newBase)
27 | {
28 | override fun onServiceConnected()
29 | {
30 | handler.postDelayed({
31 | if (bgService != null)
32 | {
33 | startBackgroundService()
34 | }
35 | }, 1000)
36 | }
37 | }
38 | }
39 |
40 | private fun startBackgroundService()
41 | {
42 | val intent = VpnService.prepare(this@ShadowsocksRunnerActivity)
43 | if (intent != null)
44 | {
45 | startActivityForResult(intent, REQUEST_CONNECT)
46 | }
47 | else
48 | {
49 | onActivityResult(REQUEST_CONNECT, RESULT_OK, null)
50 | }
51 | }
52 |
53 | override fun onCreate(savedInstanceState: Bundle?)
54 | {
55 | super.onCreate(savedInstanceState)
56 | val km = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
57 | if (km.isKeyguardLocked)
58 | {
59 | val filter = IntentFilter(Intent.ACTION_USER_PRESENT)
60 | receiver = object : BroadcastReceiver()
61 | {
62 | override fun onReceive(context: Context, intent: Intent)
63 | {
64 | when (intent.action)
65 | {
66 | Intent.ACTION_USER_PRESENT -> mServiceBoundContext.attachService()
67 | }
68 | }
69 | }
70 | registerReceiver(receiver, filter)
71 | }
72 | else
73 | {
74 | mServiceBoundContext.attachService()
75 | }
76 | finish()
77 | }
78 |
79 | override fun onDestroy()
80 | {
81 | super.onDestroy()
82 | mServiceBoundContext.detachService()
83 | if (receiver != null)
84 | {
85 | unregisterReceiver(receiver)
86 | receiver = null
87 | }
88 | }
89 |
90 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
91 | {
92 | super.onActivityResult(requestCode, resultCode, data)
93 | if (resultCode == RESULT_OK)
94 | {
95 | if (mServiceBoundContext.bgService != null)
96 | {
97 | try
98 | {
99 | mServiceBoundContext.bgService!!.use(ShadowsocksApplication.app.profileId())
100 | }
101 | catch (e: RemoteException)
102 | {
103 | e.printStackTrace()
104 | }
105 | }
106 | }
107 | else
108 | {
109 | VayLog.e(TAG, "Failed to start VpnService")
110 | }
111 | finish()
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/ShadowsocksRunnerService.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr
2 |
3 | import android.content.*
4 | import android.net.*
5 | import android.os.*
6 | import com.bige0.shadowsocksr.aidl.*
7 | import com.bige0.shadowsocksr.utils.*
8 |
9 | class ShadowsocksRunnerService : ServiceBoundService()
10 | {
11 | companion object
12 | {
13 | private const val TAG = "ShadowsocksRunnerService"
14 | }
15 |
16 | private val handler = Handler()
17 |
18 | private val mCallback = object : IShadowsocksServiceCallback.Stub()
19 | {
20 | override fun stateChanged(state: Int, profileName: String, msg: String)
21 | {
22 | }
23 |
24 | override fun trafficUpdated(txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long)
25 | {
26 | }
27 | }
28 |
29 | private val mStopSelfRunnable = Runnable { stopSelf() }
30 |
31 | override fun onBind(intent: Intent): IBinder?
32 | {
33 | return null
34 | }
35 |
36 | override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int
37 | {
38 | detachService()
39 | attachService(mCallback)
40 | return START_STICKY
41 | }
42 |
43 | override fun onServiceConnected()
44 | {
45 | if (bgService != null)
46 | {
47 | if (VpnService.prepare(this@ShadowsocksRunnerService) == null)
48 | {
49 | startBackgroundService()
50 | }
51 | else
52 | {
53 | handler.postDelayed(mStopSelfRunnable, 10000)
54 | }
55 | }
56 | }
57 |
58 | private fun startBackgroundService()
59 | {
60 | try
61 | {
62 | bgService!!.use(ShadowsocksApplication.app.profileId())
63 | }
64 | catch (e: RemoteException)
65 | {
66 | VayLog.e(TAG, "startBackgroundService", e)
67 | ShadowsocksApplication.app.track(e)
68 | }
69 | }
70 |
71 | override fun onDestroy()
72 | {
73 | super.onDestroy()
74 | detachService()
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/ShadowsocksTileService.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr
2 |
3 | import android.annotation.*
4 | import android.content.*
5 | import android.graphics.drawable.*
6 | import android.os.*
7 | import android.service.quicksettings.*
8 | import com.bige0.shadowsocksr.aidl.*
9 | import com.bige0.shadowsocksr.utils.*
10 |
11 | @TargetApi(Build.VERSION_CODES.N)
12 | class ShadowsocksTileService : TileService()
13 | {
14 | private lateinit var mServiceBoundContext: ServiceBoundContext
15 |
16 | private lateinit var iconIdle: Icon
17 | private lateinit var iconBusy: Icon
18 | private lateinit var iconConnected: Icon
19 |
20 | private val callback = object : IShadowsocksServiceCallback.Stub()
21 | {
22 | override fun trafficUpdated(txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long)
23 | {
24 | }
25 |
26 | override fun stateChanged(state: Int, profileName: String?, msg: String?)
27 | {
28 | val tile = qsTile
29 | if (tile != null)
30 | {
31 | when (state)
32 | {
33 | Constants.State.STOPPED ->
34 | {
35 | tile.icon = iconIdle
36 | tile.label = getString(R.string.app_name)
37 | tile.state = Tile.STATE_INACTIVE
38 | }
39 | Constants.State.CONNECTED ->
40 | {
41 | tile.icon = iconConnected
42 | val label = profileName ?: getString(R.string.app_name)
43 | tile.label = label
44 | tile.state = Tile.STATE_ACTIVE
45 | }
46 | else ->
47 | {
48 | tile.icon = iconBusy
49 | tile.label = getString(R.string.app_name)
50 | tile.state = Tile.STATE_UNAVAILABLE
51 | }
52 | }
53 | tile.updateTile()
54 | }
55 | }
56 | }
57 |
58 | override fun attachBaseContext(base: Context)
59 | {
60 | super.attachBaseContext(base)
61 |
62 | iconIdle = Icon.createWithResource(this, R.drawable.ic_start_idle)
63 | .setTint(-0x7f000001)
64 | iconBusy = Icon.createWithResource(this, R.drawable.ic_start_busy)
65 | iconConnected = Icon.createWithResource(this, R.drawable.ic_start_connected)
66 |
67 | mServiceBoundContext = object : ServiceBoundContext(base)
68 | {
69 | override fun onServiceConnected()
70 | {
71 | try
72 | {
73 | if (bgService != null)
74 | {
75 | callback.stateChanged(bgService!!.state, bgService!!.profileName, null)
76 | }
77 | }
78 | catch (e: RemoteException)
79 | {
80 | e.printStackTrace()
81 | }
82 | }
83 | }
84 | }
85 |
86 | override fun onStartListening()
87 | {
88 | super.onStartListening()
89 | mServiceBoundContext.attachService(callback)
90 | }
91 |
92 | override fun onStopListening()
93 | {
94 | super.onStopListening()
95 | // just in case the user switches to NAT mode, also saves battery
96 | mServiceBoundContext.detachService()
97 | }
98 |
99 | override fun onClick()
100 | {
101 | super.onClick()
102 | if (isLocked)
103 | {
104 | unlockAndRun { toggle() }
105 | }
106 | else
107 | {
108 | toggle()
109 | }
110 | }
111 |
112 | private fun toggle()
113 | {
114 | if (mServiceBoundContext.bgService != null)
115 | {
116 | try
117 | {
118 | when (mServiceBoundContext.bgService!!.state)
119 | {
120 | Constants.State.STOPPED -> Utils.startSsService(this)
121 | Constants.State.CONNECTED -> Utils.stopSsService(this)
122 | else ->
123 | {
124 | } // ignore
125 | }
126 | }
127 | catch (e: RemoteException)
128 | {
129 | e.printStackTrace()
130 | }
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/ShadowsocksVpnThread.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr
2 |
3 | import android.annotation.*
4 | import android.net.*
5 | import android.system.*
6 | import com.bige0.shadowsocksr.utils.*
7 | import java.io.*
8 | import java.util.concurrent.*
9 |
10 | class ShadowsocksVpnThread(private val vpnService: ShadowsocksVpnService) : Thread()
11 | {
12 | companion object
13 | {
14 | private const val TAG = "ShadowsocksVpnThread"
15 | private val PATH = BaseVpnService.protectPath
16 |
17 | @SuppressLint("DiscouragedPrivateApi")
18 | private val getInt = FileDescriptor::class.java.getDeclaredMethod("getInt$")
19 | }
20 |
21 | private var isRunning = true
22 | private var serverSocket: LocalServerSocket? = null
23 | private val pool: ScheduledExecutorService
24 |
25 | init
26 | {
27 | pool = ScheduledThreadPoolExecutor(4, ThreadFactory { r ->
28 | val thread = Thread(r)
29 | thread.name = TAG
30 | thread
31 | })
32 | }
33 |
34 | private fun closeServerSocket()
35 | {
36 | if (serverSocket != null)
37 | {
38 | try
39 | {
40 | serverSocket!!.close()
41 | }
42 | catch (e: Exception)
43 | {
44 | // Ignore
45 | }
46 | serverSocket = null
47 | }
48 | }
49 |
50 | fun stopThread()
51 | {
52 | isRunning = false
53 | closeServerSocket()
54 | }
55 |
56 | override fun run()
57 | {
58 | val deleteFlag = File(PATH).delete()
59 | VayLog.d(TAG, "run() delete file = $deleteFlag")
60 |
61 | if (!initServerSocket())
62 | {
63 | return
64 | }
65 |
66 | while (isRunning)
67 | {
68 | try
69 | {
70 | val socket = serverSocket!!.accept()
71 | // handle local socket
72 | handleLocalSocket(socket)
73 | }
74 | catch (e: IOException)
75 | {
76 | VayLog.e(TAG, "Error when accept socket", e)
77 | ShadowsocksApplication.app.track(e)
78 |
79 | initServerSocket()
80 | }
81 | }
82 | }
83 |
84 | /**
85 | * handle local socket
86 | *
87 | * @param socket local socket object
88 | */
89 | private fun handleLocalSocket(socket: LocalSocket)
90 | {
91 | pool.execute {
92 | try
93 | {
94 | val input = socket.inputStream
95 | val output = socket.outputStream
96 |
97 | // check read state
98 | val state = input.read()
99 | VayLog.d(TAG, "handleLocalSocket() read state = $state")
100 |
101 | val fds = socket.ancillaryFileDescriptors
102 |
103 | if (!fds.isNullOrEmpty())
104 | {
105 | val fd = getInt.invoke(fds[0]) as Int
106 |
107 | val ret = vpnService.protect(fd)
108 |
109 | Os.close(fds[0])
110 | output.write(if (ret) 0 else 1)
111 | }
112 |
113 | // close stream
114 | IOUtils.close(input)
115 | IOUtils.close(output)
116 | }
117 | catch (e: Exception)
118 | {
119 | VayLog.e(TAG, "handleLocalSocket() Error when protect socket", e)
120 | ShadowsocksApplication.app.track(e)
121 | }
122 | finally
123 | {
124 | try
125 | {
126 | socket.close()
127 | }
128 | catch (e: Exception)
129 | {
130 | // Ignore;
131 | }
132 | }
133 | }
134 | }
135 |
136 | /**
137 | * init server socket
138 | *
139 | * @return init failed return false.
140 | */
141 | private fun initServerSocket(): Boolean
142 | {
143 | // if not running, do not init
144 | if (!isRunning)
145 | {
146 | return false
147 | }
148 |
149 | return try
150 | {
151 | val localSocket = LocalSocket()
152 | localSocket.bind(LocalSocketAddress(PATH, LocalSocketAddress.Namespace.FILESYSTEM))
153 | serverSocket = LocalServerSocket(localSocket.fileDescriptor)
154 | true
155 | }
156 | catch (e: IOException)
157 | {
158 | VayLog.e(TAG, "unable to bind", e)
159 | ShadowsocksApplication.app.track(e)
160 | false
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/TaskerActivity.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr
2 |
3 | import android.app.*
4 | import android.content.res.*
5 | import android.os.*
6 | import android.view.*
7 | import android.widget.*
8 | import androidx.appcompat.app.*
9 | import androidx.appcompat.widget.Toolbar
10 | import androidx.recyclerview.widget.*
11 | import com.bige0.shadowsocksr.ShadowsocksApplication.Companion.app
12 | import com.bige0.shadowsocksr.database.*
13 | import com.bige0.shadowsocksr.utils.*
14 |
15 | class TaskerActivity : AppCompatActivity()
16 | {
17 | private lateinit var taskerOption: TaskerSettings
18 | private lateinit var mSwitch: Switch
19 | private lateinit var profilesAdapter: ProfilesAdapter
20 |
21 | override fun onCreate(savedInstanceState: Bundle?)
22 | {
23 | super.onCreate(savedInstanceState)
24 | setContentView(R.layout.layout_tasker)
25 |
26 | profilesAdapter = ProfilesAdapter()
27 |
28 | val toolbar = findViewById(R.id.toolbar)
29 | toolbar.setTitle(R.string.app_name)
30 | toolbar.setNavigationIcon(R.drawable.ic_navigation_close)
31 | toolbar.setNavigationOnClickListener { finish() }
32 |
33 | taskerOption = TaskerSettings.fromIntent(intent)
34 | mSwitch = findViewById(R.id.serviceSwitch)
35 | mSwitch.isChecked = taskerOption.switchOn
36 | val profilesList = findViewById(R.id.profilesList)
37 | val lm = LinearLayoutManager(this)
38 | profilesList.layoutManager = lm
39 | profilesList.itemAnimator = DefaultItemAnimator()
40 | profilesList.adapter = profilesAdapter
41 |
42 | if (taskerOption.profileId >= 0)
43 | {
44 | var position = 0
45 | val profiles = profilesAdapter.getCurrentProfiles()
46 | for ((i, profile) in profiles.withIndex())
47 | {
48 | if (profile.id == taskerOption.profileId)
49 | {
50 | position = i + 1
51 | break
52 | }
53 | }
54 | lm.scrollToPosition(position)
55 | }
56 | }
57 |
58 | private inner class ProfileViewHolder(view: View) : RecyclerView.ViewHolder(view), View.OnClickListener
59 | {
60 | private var item: Profile? = null
61 | private val text: CheckedTextView
62 |
63 | init
64 | {
65 | val typedArray = obtainStyledAttributes(intArrayOf(android.R.attr.selectableItemBackground))
66 | view.setBackgroundResource(typedArray.getResourceId(0, 0))
67 | typedArray.recycle()
68 |
69 | text = itemView.findViewById(android.R.id.text1)
70 | itemView.setOnClickListener(this)
71 | }
72 |
73 | fun bindDefault()
74 | {
75 | item = null
76 | text.setText(R.string.profile_default)
77 | text.isChecked = taskerOption.profileId < 0
78 | }
79 |
80 | fun bind(item: Profile)
81 | {
82 | this.item = item
83 | text.text = item.name
84 | text.isChecked = taskerOption.profileId == item.id
85 | }
86 |
87 | override fun onClick(v: View)
88 | {
89 | taskerOption.switchOn = mSwitch.isChecked
90 | taskerOption.profileId = if (item == null) -1 else item!!.id
91 | setResult(Activity.RESULT_OK, taskerOption.toIntent(this@TaskerActivity))
92 | finish()
93 | }
94 | }
95 |
96 | private inner class ProfilesAdapter : RecyclerView.Adapter()
97 | {
98 | private val name: String
99 |
100 | init
101 | {
102 | val version = if (Build.VERSION.SDK_INT >= 21) "material" else "holo"
103 | name = "select_dialog_singlechoice_$version"
104 | }
105 |
106 | fun getCurrentProfiles(): List
107 | {
108 | return app.profileManager.allProfiles
109 | }
110 |
111 | override fun getItemCount(): Int
112 | {
113 | return 1 + getCurrentProfiles().size
114 | }
115 |
116 | override fun onBindViewHolder(vh: ProfileViewHolder, i: Int)
117 | {
118 | if (i == 0)
119 | {
120 | vh.bindDefault()
121 | }
122 | else
123 | {
124 | vh.bind(getCurrentProfiles()[i - 1])
125 | }
126 | }
127 |
128 | override fun onCreateViewHolder(vg: ViewGroup, i: Int): ProfileViewHolder
129 | {
130 | val view = LayoutInflater.from(vg.context)
131 | .inflate(Resources.getSystem().getIdentifier(name, "layout", "android"), vg, false)
132 | return ProfileViewHolder(view)
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/TaskerReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr
2 |
3 | import android.content.*
4 | import com.bige0.shadowsocksr.utils.*
5 |
6 | class TaskerReceiver : BroadcastReceiver()
7 | {
8 | override fun onReceive(context: Context, intent: Intent)
9 | {
10 | val settings = TaskerSettings.fromIntent(intent)
11 | val profile = ShadowsocksApplication.app.profileManager.getProfile(settings.profileId)
12 |
13 | if (profile != null)
14 | {
15 | ShadowsocksApplication.app.switchProfile(settings.profileId)
16 | }
17 |
18 | if (settings.switchOn)
19 | {
20 | Utils.startSsService(context)
21 | }
22 | else
23 | {
24 | Utils.stopSsService(context)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/database/DBHelper.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.database
2 |
3 | import android.content.*
4 | import android.database.sqlite.*
5 | import com.bige0.shadowsocksr.*
6 | import com.bige0.shadowsocksr.utils.*
7 | import com.j256.ormlite.android.apptools.*
8 | import com.j256.ormlite.dao.*
9 | import com.j256.ormlite.support.*
10 | import com.j256.ormlite.table.*
11 | import java.sql.*
12 |
13 | class DBHelper(context: Context) : OrmLiteSqliteOpenHelper(context, PROFILE, null, VERSION)
14 | {
15 | internal lateinit var profileDao: Dao
16 | internal lateinit var ssrsubDao: Dao
17 |
18 | init
19 | {
20 | try
21 | {
22 | profileDao = getDao(Profile::class.java)
23 | }
24 | catch (e: SQLException)
25 | {
26 | VayLog.e(TAG, "", e)
27 | }
28 |
29 | try
30 | {
31 | ssrsubDao = getDao(SSRSub::class.java)
32 | }
33 | catch (e: SQLException)
34 | {
35 | VayLog.e(TAG, "", e)
36 | }
37 |
38 | }
39 |
40 | override fun onCreate(database: SQLiteDatabase, connectionSource: ConnectionSource?)
41 | {
42 | try
43 | {
44 | TableUtils.createTable(connectionSource!!, Profile::class.java)
45 | TableUtils.createTable(connectionSource, SSRSub::class.java)
46 | }
47 | catch (e: SQLException)
48 | {
49 | VayLog.e(TAG, "onCreate", e)
50 | }
51 |
52 | }
53 |
54 | override fun onUpgrade(database: SQLiteDatabase, connectionSource: ConnectionSource, oldVersion: Int, newVersion: Int)
55 | {
56 | if (oldVersion != newVersion)
57 | {
58 | try
59 | {
60 |
61 | }
62 | catch (e: Exception)
63 | {
64 | VayLog.e(TAG, "onUpgrade", e)
65 | ShadowsocksApplication.app.track(e)
66 |
67 | try
68 | {
69 | profileDao.executeRawNoArgs("DROP TABLE IF EXISTS 'profile';")
70 | }
71 | catch (e1: SQLException)
72 | {
73 | VayLog.e(TAG, "onUpgrade", e)
74 | }
75 |
76 | onCreate(database, connectionSource)
77 | }
78 |
79 | }
80 | }
81 |
82 | companion object
83 | {
84 | const val PROFILE = "profile.db"
85 | private const val TAG = "DBHelper"
86 | private const val VERSION = 1
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/database/Profile.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.database
2 |
3 | import android.util.Base64
4 | import com.bige0.shadowsocksr.utils.*
5 | import com.j256.ormlite.field.*
6 | import java.util.*
7 |
8 | class Profile
9 | {
10 | @DatabaseField
11 | val date: Date = Date()
12 | @DatabaseField(generatedId = true)
13 | var id = 0
14 | @DatabaseField
15 | var name = "ShadowsocksR"
16 | @DatabaseField
17 | var host = Constants.DefaultHostName
18 | @DatabaseField
19 | var localPort = 1080
20 | @DatabaseField
21 | //TODO:UShort
22 | var remotePort = 8388
23 | @DatabaseField
24 | var password = ""
25 | @DatabaseField
26 | var protocol = "origin"
27 | @DatabaseField
28 | var protocol_param = ""
29 | @DatabaseField
30 | var obfs = "plain"
31 | @DatabaseField
32 | var obfs_param = ""
33 | @DatabaseField
34 | var method = "aes-256-cfb"
35 | @DatabaseField
36 | var route = "bypass-lan-china"
37 | @DatabaseField
38 | var proxyApps = false
39 | @DatabaseField
40 | var bypass = false
41 | @DatabaseField
42 | var udpdns = false
43 | @DatabaseField
44 | var url_group = "Default Group"
45 | @DatabaseField
46 | var dns = "8.8.8.8:53"
47 | @DatabaseField
48 | var china_dns = "114.114.114.114:53,223.5.5.5:53"
49 | @DatabaseField
50 | var ipv6 = false
51 | @DatabaseField(dataType = DataType.LONG_STRING)
52 | var individual = ""
53 | @DatabaseField
54 | var tx: Long = 0
55 | @DatabaseField
56 | var rx: Long = 0
57 | @DatabaseField
58 | var elapsed: Long = 0
59 | @DatabaseField
60 | var userOrder: Long = 0
61 |
62 | /**
63 | * is method unsafe
64 | */
65 | val isMethodUnsafe: Boolean
66 | get() = "table".equals(method, ignoreCase = true) || "rc4".equals(method, ignoreCase = true)
67 |
68 | override fun toString(): String
69 | {
70 | //TODO
71 | val result = Base64.encodeToString(String.format(Locale.ENGLISH, "%s:%d:%s:%s:%s:%s/?obfsparam=%s&protoparam=%s&remarks=%s&group=%s", host, remotePort, protocol, method, obfs, Base64.encodeToString(String.format(Locale.ENGLISH, "%s", password).toByteArray(), Base64.NO_PADDING or Base64.URL_SAFE or Base64.NO_WRAP), Base64.encodeToString(String.format(Locale.ENGLISH, "%s", obfs_param).toByteArray(), Base64.NO_PADDING or Base64.URL_SAFE or Base64.NO_WRAP), Base64.encodeToString(String.format(Locale.ENGLISH, "%s", protocol_param).toByteArray(), Base64.NO_PADDING or Base64.URL_SAFE or Base64.NO_WRAP), Base64.encodeToString(String.format(Locale.ENGLISH, "%s", name).toByteArray(), Base64.NO_PADDING or Base64.URL_SAFE or Base64.NO_WRAP), Base64.encodeToString(String.format(Locale.ENGLISH, "%s", url_group).toByteArray(), Base64.NO_PADDING or Base64.URL_SAFE or Base64.NO_WRAP)).toByteArray(), Base64.NO_PADDING or Base64.URL_SAFE or Base64.NO_WRAP)
72 | return "ssr://$result"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/database/SSRSub.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.database
2 |
3 | import com.j256.ormlite.field.*
4 |
5 | class SSRSub
6 | {
7 | @DatabaseField(generatedId = true)
8 | var id = 0
9 |
10 | @DatabaseField
11 | var url = ""
12 |
13 | @DatabaseField
14 | var url_group = ""
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/database/SSRSubManager.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.database
2 |
3 | import com.bige0.shadowsocksr.*
4 | import com.bige0.shadowsocksr.utils.*
5 | import java.util.*
6 |
7 | class SSRSubManager(private val dbHelper: DBHelper)
8 | {
9 | private val mSSRSubAddedListeners: MutableList?
10 |
11 | val allSSRSubs: List
12 | get()
13 | {
14 | return try
15 | {
16 | dbHelper.ssrsubDao.query(dbHelper.ssrsubDao.queryBuilder().prepare())
17 | }
18 | catch (e: Exception)
19 | {
20 | VayLog.e(TAG, "getAllSSRSubs", e)
21 | ShadowsocksApplication.app.track(e)
22 | emptyList()
23 | }
24 | }
25 |
26 | init
27 | {
28 | mSSRSubAddedListeners = ArrayList(20)
29 | }
30 |
31 | fun createSSRSub(p: SSRSub?): SSRSub
32 | {
33 | val ssrsub: SSRSub = p ?: SSRSub()
34 | ssrsub.id = 0
35 |
36 | try
37 | {
38 | dbHelper.ssrsubDao.createOrUpdate(ssrsub)
39 | invokeSSRSubAdded(ssrsub)
40 | }
41 | catch (e: Exception)
42 | {
43 | VayLog.e(TAG, "addSSRSub", e)
44 | ShadowsocksApplication.app.track(e)
45 | }
46 |
47 | return ssrsub
48 | }
49 |
50 | fun delSSRSub(id: Int): Boolean
51 | {
52 | return try
53 | {
54 | dbHelper.ssrsubDao.deleteById(id)
55 | true
56 | }
57 | catch (e: Exception)
58 | {
59 | VayLog.e(TAG, "delSSRSub", e)
60 | ShadowsocksApplication.app.track(e)
61 | false
62 | }
63 | }
64 |
65 | /**
66 | * add ssr sub added listener
67 | *
68 | * @param l callback
69 | */
70 | fun addSSRSubAddedListener(l: SSRSubAddedListener)
71 | {
72 | if (mSSRSubAddedListeners == null)
73 | {
74 | return
75 | }
76 |
77 | // adding listener
78 | if (!mSSRSubAddedListeners.contains(l))
79 | {
80 | mSSRSubAddedListeners.add(l)
81 | }
82 | }
83 |
84 | /**
85 | * remove ssr sub added listener
86 | *
87 | * @param l callback
88 | */
89 | fun removeSSRSubAddedListener(l: SSRSubAddedListener)
90 | {
91 | if (mSSRSubAddedListeners == null || mSSRSubAddedListeners.isEmpty())
92 | {
93 | return
94 | }
95 |
96 | // remove listener
97 | mSSRSubAddedListeners.remove(l)
98 | }
99 |
100 | /**
101 | * invoke ssr sub added listener
102 | *
103 | * @param ssrSub ssr sub param
104 | */
105 | private fun invokeSSRSubAdded(ssrSub: SSRSub)
106 | {
107 | if (mSSRSubAddedListeners == null || mSSRSubAddedListeners.isEmpty())
108 | {
109 | return
110 | }
111 |
112 | // iteration invoke listener
113 | for (l in mSSRSubAddedListeners)
114 | {
115 | l.onSSRSubAdded(ssrSub)
116 | }
117 | }
118 |
119 | interface SSRSubAddedListener
120 | {
121 | /**
122 | * ssr sub added
123 | *
124 | * @param ssrSub ssr sub object
125 | */
126 | fun onSSRSubAdded(ssrSub: SSRSub)
127 | }
128 |
129 | companion object
130 | {
131 | private const val TAG = "SSRSubManager"
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/job/AclSyncJob.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.job
2 |
3 | import com.bige0.shadowsocksr.*
4 | import com.bige0.shadowsocksr.utils.*
5 | import com.evernote.android.job.*
6 | import java.io.*
7 | import java.net.*
8 | import java.util.concurrent.*
9 |
10 | class AclSyncJob(private val route: String) : Job()
11 | {
12 | override fun onRunJob(params: Params): Result
13 | {
14 | val filename = "$route.acl"
15 | var inputStream: InputStream? = null
16 | try
17 | {
18 | if ("self" == route)
19 | {
20 |
21 | inputStream = URL("https://raw.githubusercontent.com/HMBSbige/Text_Translation/master/ShadowsocksR/$filename").openConnection()
22 | .getInputStream()
23 | IOUtils.writeString(ShadowsocksApplication.app.applicationInfo.dataDir + '/'.toString() + filename, IOUtils.readString(inputStream!!))
24 | }
25 | return Result.SUCCESS
26 | }
27 | catch (e: IOException)
28 | {
29 | VayLog.e(TAG, "onRunJob", e)
30 | ShadowsocksApplication.app.track(e)
31 | return Result.RESCHEDULE
32 | }
33 | catch (e: Exception)
34 | {
35 | // unknown failures, probably shouldn't retry
36 | VayLog.e(TAG, "onRunJob", e)
37 | ShadowsocksApplication.app.track(e)
38 | return Result.FAILURE
39 | }
40 | finally
41 | {
42 | try
43 | {
44 | inputStream?.close()
45 | }
46 | catch (e: IOException)
47 | {
48 | VayLog.e(TAG, "onRunJob", e)
49 | ShadowsocksApplication.app.track(e)
50 | }
51 |
52 | }
53 | }
54 |
55 | companion object
56 | {
57 | const val TAG = "AclSyncJob"
58 |
59 | fun schedule(route: String): Int
60 | {
61 | return JobRequest.Builder("$TAG:$route")
62 | .setExecutionWindow(1, TimeUnit.DAYS.toMillis(28))
63 | .setRequirementsEnforced(true)
64 | .setRequiredNetworkType(JobRequest.NetworkType.UNMETERED)
65 | .setRequiresCharging(true)
66 | .setUpdateCurrent(true)
67 | .build()
68 | .schedule()
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/job/DonaldTrump.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.job
2 |
3 | import com.bige0.shadowsocksr.utils.*
4 | import com.evernote.android.job.*
5 |
6 | class DonaldTrump : JobCreator
7 | {
8 | override fun create(tag: String): Job?
9 | {
10 | val parts = tag.split(":")
11 | .dropLastWhile { it.isEmpty() }
12 | .toTypedArray()
13 |
14 | return when
15 | {
16 | AclSyncJob.TAG == parts[0] -> AclSyncJob(parts[1])
17 | SSRSubUpdateJob.TAG == parts[0] -> SSRSubUpdateJob()
18 | else ->
19 | {
20 | VayLog.w(TAG, "Unknown job tag: $tag")
21 | null
22 | }
23 | }
24 | }
25 |
26 | companion object
27 | {
28 | private const val TAG = "DonaldTrump"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/job/SSRSubUpdateJob.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.job
2 |
3 | import com.bige0.shadowsocksr.*
4 | import com.bige0.shadowsocksr.R
5 | import com.bige0.shadowsocksr.network.ssrsub.*
6 | import com.bige0.shadowsocksr.utils.*
7 | import com.evernote.android.job.*
8 | import java.util.concurrent.*
9 |
10 | class SSRSubUpdateJob : Job()
11 | {
12 | override fun onRunJob(params: Params): Result
13 | {
14 | if (ShadowsocksApplication.app.settings.getInt(Constants.Key.ssrsub_autoupdate, 0) == 1)
15 | {
16 | val subs = ShadowsocksApplication.app.ssrSubManager.allSSRSubs
17 | SubUpdateHelper.instance()
18 | .updateSub(subs, object : SubUpdateCallback()
19 | {
20 | override fun onSuccess(subName: String)
21 | {
22 | VayLog.d(TAG, "onRunJob() update sub success!")
23 | ToastUtils.showShort(context.getString(R.string.sub_autoupdate_success, subName))
24 | }
25 |
26 | override fun onFailed()
27 | {
28 | VayLog.e(TAG, "onRunJob() update sub failed!")
29 | }
30 | })
31 | return Result.SUCCESS
32 | }
33 | return Result.RESCHEDULE
34 | }
35 |
36 | companion object
37 | {
38 | const val TAG = "SSRSubUpdateJob"
39 |
40 | fun schedule(): Int
41 | {
42 | return JobRequest.Builder(TAG)
43 | .setPeriodic(TimeUnit.HOURS.toMillis(1))
44 | .setRequirementsEnforced(true)
45 | .setRequiresCharging(false)
46 | .setUpdateCurrent(true)
47 | .build()
48 | .schedule()
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/network/ping/PingCallback.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.network.ping
2 |
3 | import com.bige0.shadowsocksr.database.*
4 |
5 | open class PingCallback
6 | {
7 | /**
8 | * test result message
9 | */
10 | open var resultMsg: String = ""
11 |
12 | /**
13 | * ping success
14 | *
15 | * @param elapsed ping elapsed
16 | */
17 | open fun onSuccess(profile: Profile, elapsed: Long)
18 | {
19 | }
20 |
21 | /**
22 | * ping failed
23 | */
24 | open fun onFailed(profile: Profile?)
25 | {
26 | }
27 |
28 | /**
29 | * ping finished
30 | */
31 | open fun onFinished(profile: Profile?)
32 | {
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/network/request/RequestCallback.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.network.request
2 |
3 | open class RequestCallback
4 | {
5 |
6 | var start: Long = 0
7 |
8 | /**
9 | * request success
10 | *
11 | * @param code response code
12 | * @param response response result
13 | */
14 | open fun onSuccess(code: Int, response: String)
15 | {
16 | }
17 |
18 | /**
19 | * request failed
20 | *
21 | * @param code failed code
22 | * @param msg failed msg
23 | */
24 | open fun onFailed(code: Int, msg: String)
25 | {
26 | }
27 |
28 | /**
29 | * request finished
30 | */
31 | open fun onFinished()
32 | {
33 | }
34 |
35 | /**
36 | * is request ok
37 | *
38 | * @param code response code
39 | */
40 | open fun isRequestOk(code: Int): Boolean
41 | {
42 | return code == 200 || code == 204
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/network/request/RequestHelper.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.network.request
2 |
3 | import android.os.*
4 | import android.text.*
5 | import okhttp3.*
6 | import java.util.concurrent.*
7 |
8 | class RequestHelper private constructor(builder: OkHttpClient.Builder? = null)
9 | {
10 |
11 | private var mClientBuilder: OkHttpClient.Builder? = null
12 | private var mClient: OkHttpClient? = null
13 | private val mThreadPool: ScheduledThreadPoolExecutor
14 | private val mUIHandler: Handler
15 | private val mDefaultRequestCallback: RequestCallback
16 |
17 | init
18 | {
19 | // create thread pool
20 | mThreadPool = ScheduledThreadPoolExecutor(10, ThreadFactory { r ->
21 | val thread = Thread(r)
22 | thread.name = "request_helper-thread"
23 | thread
24 | })
25 |
26 | // init handler
27 | mUIHandler = Handler(Looper.getMainLooper())
28 |
29 | // create default request callback
30 | mDefaultRequestCallback = RequestCallback()
31 |
32 | // set default builder
33 | setClientBuilder(builder)
34 | }
35 |
36 | /**
37 | * network request by get
38 | *
39 | * @param url request url
40 | * @param callback request callback
41 | */
42 | operator fun get(url: String, callback: RequestCallback)
43 | {
44 | val request = createRequest(url, callback) ?: return
45 |
46 | // request
47 | request(request, callback)
48 | }
49 |
50 | /**
51 | * network request
52 | *
53 | * @param request request object
54 | * @param _callback request callback
55 | */
56 | fun request(request: Request, _callback: RequestCallback?)
57 | {
58 | var callback = _callback
59 | // no allow callback is null
60 | if (callback == null)
61 | {
62 | callback = mDefaultRequestCallback
63 | }
64 |
65 | // start request task
66 | startRequestTask(request, callback)
67 | }
68 |
69 | /**
70 | * request by thread
71 | *
72 | * @param request request object
73 | * @return response object
74 | */
75 | private fun requestByThread(request: Request): Response
76 | {
77 | return mClient!!.newCall(request)
78 | .execute()
79 | }
80 |
81 | //===================================================================================================//
82 | //========================================= private method =========================================//
83 | //=================================================================================================//
84 |
85 | /**
86 | * create request object
87 | */
88 | private fun createRequest(url: String, callback: RequestCallback): Request?
89 | {
90 | return createRequest(url, null, callback)
91 | }
92 |
93 | /**
94 | * create request object
95 | *
96 | * @param _url request url
97 | * @param body request body
98 | * @return create failed return null.
99 | */
100 | private fun createRequest(_url: String, body: RequestBody?, callback: RequestCallback): Request?
101 | {
102 | var url = _url
103 | // format url string
104 | url = formatUrl(url)
105 | try
106 | {
107 | val builder = Request.Builder()
108 | builder.url(url)
109 | // body not null, set body
110 | if (body != null)
111 | {
112 | builder.post(body)
113 | }
114 | return builder.build()
115 | }
116 | catch (e: Exception)
117 | {
118 | callback.onFailed(404, e.message!!)
119 | callback.onFinished()
120 | return null
121 | }
122 |
123 | }
124 |
125 | /**
126 | * format url string
127 | *
128 | * @param _url request url
129 | * @return url
130 | */
131 | private fun formatUrl(_url: String): String
132 | {
133 | var url = _url
134 | url = url.replace(" ", "")
135 | url = url.replace("\n", "")
136 | url = url.replace("\r", "")
137 | return url
138 | }
139 |
140 | /**
141 | * set client builder
142 | *
143 | * @param builder client builder object
144 | */
145 | private fun setClientBuilder(builder: OkHttpClient.Builder?)
146 | {
147 | mClientBuilder = builder ?: mDefaultBuilder
148 | mClient = mClientBuilder!!.build()
149 | }
150 |
151 | /**
152 | * open request task
153 | *
154 | * @param request okhttp request object
155 | * @param callback request callback
156 | */
157 | private fun startRequestTask(request: Request, callback: RequestCallback)
158 | {
159 | mThreadPool.execute {
160 | var body: ResponseBody? = null
161 | try
162 | {
163 | val response = requestByThread(request)
164 | val code = response.code
165 | body = response.body
166 | var result: String? = null
167 | if (body != null)
168 | {
169 | result = body.string()
170 | }
171 | invokeRequestCallback(callback, code, result)
172 | }
173 | catch (e: Exception)
174 | {
175 | invokeRequestCallback(callback, 404, e.message)
176 | }
177 | finally
178 | {
179 | // close
180 | body?.close()
181 |
182 | // invoke finished
183 | invokeFinished(callback)
184 | }
185 | }
186 | }
187 |
188 | /**
189 | * invoke request callback
190 | *
191 | * @param callback request callback
192 | * @param code response code
193 | * @param response body
194 | */
195 | private fun invokeRequestCallback(callback: RequestCallback?, code: Int, response: String?)
196 | {
197 | if (callback == null)
198 | {
199 | return
200 | }
201 | // run to main thread
202 | mUIHandler.post {
203 | try
204 | {
205 | if (callback.isRequestOk(code))
206 | {
207 | var result: String? = ""
208 | if (!TextUtils.isEmpty(response))
209 | {
210 | result = response
211 | }
212 | // invoke success callback
213 | callback.onSuccess(code, result!!)
214 | }
215 | else
216 | {
217 | var result: String? = "null"
218 | if (!TextUtils.isEmpty(response))
219 | {
220 | result = response
221 | }
222 | callback.onFailed(code, result!!)
223 | }
224 | }
225 | catch (e: Exception)
226 | {
227 | callback.onFailed(404, e.message!!)
228 | }
229 | }
230 | }
231 |
232 | /**
233 | * invoke finished
234 | *
235 | * @param callback request callback
236 | */
237 | private fun invokeFinished(callback: RequestCallback?)
238 | {
239 | if (callback == null)
240 | {
241 | return
242 | }
243 | // run to main thread
244 | mUIHandler.post { callback.onFinished() }
245 | }
246 |
247 | companion object
248 | {
249 |
250 | private var sInstance: RequestHelper? = null
251 | private val mDefaultBuilder: OkHttpClient.Builder = OkHttpClient.Builder()
252 | .connectTimeout(5, TimeUnit.SECONDS)
253 | .writeTimeout(5, TimeUnit.SECONDS)
254 | .readTimeout(5, TimeUnit.SECONDS)
255 | .followRedirects(false)
256 | .followSslRedirects(false)
257 |
258 | /**
259 | * init
260 | *
261 | * @param builder client builder object
262 | */
263 | fun init(builder: OkHttpClient.Builder? = null)
264 | {
265 | if (sInstance == null)
266 | {
267 | synchronized(RequestHelper::class.java) {
268 | if (sInstance == null)
269 | {
270 | sInstance = RequestHelper(builder)
271 | }
272 | }
273 | }
274 | }
275 |
276 | /**
277 | * get instance
278 | */
279 | fun instance(): RequestHelper?
280 | {
281 | init()
282 | return sInstance
283 | }
284 | }
285 | }
286 | /**
287 | * init
288 | *
289 | * @see .init
290 | */
291 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/network/ssrsub/SubUpdateCallback.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.network.ssrsub
2 |
3 | open class SubUpdateCallback
4 | {
5 | /**
6 | * success
7 | */
8 | open fun onSuccess(subName: String)
9 | {
10 | }
11 |
12 | /**
13 | * failed
14 | */
15 | open fun onFailed()
16 | {
17 | }
18 |
19 | /**
20 | * finished
21 | */
22 | open fun onFinished()
23 | {
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/network/ssrsub/SubUpdateHelper.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.network.ssrsub
2 |
3 | import android.os.*
4 | import android.text.*
5 | import com.bige0.shadowsocksr.*
6 | import com.bige0.shadowsocksr.database.*
7 | import com.bige0.shadowsocksr.network.request.*
8 | import com.bige0.shadowsocksr.utils.*
9 | import com.bige0.shadowsocksr.utils.Base64
10 | import java.util.*
11 | import java.util.concurrent.*
12 |
13 | class SubUpdateHelper private constructor()
14 | {
15 | private val mThreadPool: ScheduledThreadPoolExecutor
16 |
17 | private val mUIHandler: Handler
18 |
19 | init
20 | {
21 | // init thread pool
22 | mThreadPool = ScheduledThreadPoolExecutor(10, ThreadFactory { r ->
23 | val thread = Thread(r)
24 | thread.name = "sub_update_helper-thread"
25 | thread
26 | })
27 |
28 | // init ui handler
29 | mUIHandler = Handler(Looper.getMainLooper())
30 | }
31 |
32 | /**
33 | * update sub
34 | *
35 | * @see .updateSub
36 | */
37 | fun updateSub(subs: List, callback: SubUpdateCallback)
38 | {
39 | updateSub(subs, 0, callback)
40 | }
41 |
42 | /**
43 | * update sub
44 | *
45 | * @param subs sub list
46 | */
47 | fun updateSub(subs: List?, position: Int, callback: SubUpdateCallback)
48 | {
49 | mThreadPool.execute { updateSubTask(subs, position, callback) }
50 | }
51 |
52 | //===================================================================================================//
53 | //========================================= private method =========================================//
54 | //=================================================================================================//
55 |
56 | /**
57 | * update sub task
58 | *
59 | * @param subs sub list
60 | * @param position list start index
61 | * @param callback request callback
62 | */
63 | private fun updateSubTask(subs: List?, position: Int, callback: SubUpdateCallback)
64 | {
65 | if (subs == null || subs.isEmpty())
66 | {
67 | callback.onFailed()
68 | callback.onFinished()
69 | return
70 | }
71 |
72 | if (position < subs.size)
73 | {
74 | val sub = subs[position]
75 | // start request
76 | RequestHelper.instance()!![sub.url, object : RequestCallback()
77 | {
78 |
79 | override fun onSuccess(code: Int, response: String)
80 | {
81 | mThreadPool.execute {
82 | handleResponse(sub, response, object : SubUpdateCallback()
83 | {
84 | override fun onFinished()
85 | {
86 | updateSub(subs, position + 1, callback)
87 | }
88 | })
89 | }
90 | }
91 |
92 | override fun onFailed(code: Int, msg: String)
93 | {
94 | callback.onFailed()
95 | callback.onFinished()
96 | }
97 | }]
98 | }
99 | else
100 | {
101 | callback.onFinished()
102 | }
103 | }
104 |
105 | /**
106 | * handle response
107 | *
108 | * @param response response string
109 | */
110 | private fun handleResponse(sub: SSRSub, response: String, callback: SubUpdateCallback)
111 | {
112 | val deleteProfiles = ShadowsocksApplication.app.profileManager.getAllProfilesByGroup(sub.url_group)
113 | .toMutableList()
114 | val responseString = Base64.decodeUrlSafe(response)
115 | val profiles = Parser.findAllSsr(responseString)
116 |
117 | for (profile in profiles)
118 | {
119 | val resultCode = ShadowsocksApplication.app.profileManager.createProfileSub(profile)
120 | if (resultCode != 0)
121 | {
122 | val tempList = ArrayList()
123 | for (item in deleteProfiles)
124 | {
125 | if (item.id != resultCode)
126 | {
127 | tempList.add(item)
128 | }
129 | }
130 | deleteProfiles.clear()
131 | deleteProfiles.addAll(tempList)
132 | }
133 | }
134 |
135 | for (profile in deleteProfiles)
136 | {
137 | if (profile.id != ShadowsocksApplication.app.profileId())
138 | {
139 | ShadowsocksApplication.app.profileManager.delProfile(profile.id)
140 | }
141 | }
142 |
143 | // invoke callback
144 | mUIHandler.post {
145 | callback.onSuccess(sub.url_group)
146 | callback.onFinished()
147 | }
148 | }
149 |
150 | companion object
151 | {
152 |
153 | private var sInstance: SubUpdateHelper? = null
154 |
155 | /**
156 | * get instance
157 | */
158 | fun instance(): SubUpdateHelper
159 | {
160 | if (sInstance == null)
161 | {
162 | synchronized(SubUpdateHelper::class.java) {
163 | if (sInstance == null)
164 | {
165 | sInstance = SubUpdateHelper()
166 | }
167 | }
168 | }
169 | return sInstance!!
170 | }
171 |
172 | /**
173 | * parse string to SSRSub object
174 | *
175 | * @param subUrl ssr sub url
176 | * @param base64text base64 content
177 | * @return parse failed return null
178 | */
179 | fun parseSSRSub(subUrl: String, base64text: String): SSRSub?
180 | {
181 | val profilesSSR = Parser.findAllSsr(Base64.decodeUrlSafe(base64text))
182 | if (profilesSSR.isNotEmpty())
183 | {
184 | if (!TextUtils.isEmpty(profilesSSR[0].url_group))
185 | {
186 | val ssrSub = SSRSub()
187 | ssrSub.url = subUrl
188 | ssrSub.url_group = profilesSSR[0].url_group
189 | return ssrSub
190 | }
191 | }
192 | return null
193 | }
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/preferences/DropDownPreference.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.preferences
2 |
3 | import android.content.*
4 | import android.content.res.*
5 | import android.util.*
6 | import android.view.*
7 | import android.widget.*
8 | import androidx.appcompat.widget.*
9 | import com.bige0.shadowsocksr.*
10 |
11 | class DropDownPreference(mContext: Context, attrs: AttributeSet) : SummaryPreference(mContext, attrs)
12 | {
13 | private val mAdapter: ArrayAdapter = ArrayAdapter(mContext, android.R.layout.simple_spinner_dropdown_item)
14 | private val mSpinner: AppCompatSpinner = AppCompatSpinner(mContext)
15 |
16 | private var mEntries: Array? = null
17 | private var mEntryValues: Array? = null
18 | private var mSelectedIndex = -1
19 |
20 | init
21 | {
22 | mSpinner.visibility = View.INVISIBLE
23 | mSpinner.adapter = mAdapter
24 | mSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener
25 | {
26 | override fun onNothingSelected(parent: AdapterView<*>)
27 | {
28 | }
29 |
30 | override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long)
31 | {
32 | val value = getValue(position)
33 | if (position != mSelectedIndex && callChangeListener(value))
34 | {
35 | setValue(position, value)
36 | }
37 | }
38 | }
39 |
40 | setOnPreferenceClickListener {
41 | // TODO: not working with scrolling
42 | // mSpinner.setDropDownVerticalOffset(Utils.dpToPx(getContext, -48 * mSelectedIndex).toInt)
43 | mSpinner.performClick()
44 | true
45 | }
46 |
47 | val a = mContext.obtainStyledAttributes(attrs, R.styleable.DropDownPreference)
48 | setEntries(a.getTextArray(R.styleable.DropDownPreference_android_entries))
49 | setEntryValues(a.getTextArray(R.styleable.DropDownPreference_android_entryValues))
50 | a.recycle()
51 | }
52 |
53 | override fun getSummaryValue(): Any?
54 | {
55 | return getEntry()
56 | }
57 |
58 | private fun setEntries(entries: Array?)
59 | {
60 | mEntries = entries
61 | mAdapter.clear()
62 | if (entries != null)
63 | {
64 | for (entry in entries)
65 | {
66 | mAdapter.add(entry.toString())
67 | }
68 | }
69 | }
70 |
71 | private fun setEntryValues(entryValues: Array?)
72 | {
73 | mEntryValues = entryValues
74 | }
75 |
76 | private fun getValue(index: Int): String?
77 | {
78 | return if (mEntryValues == null)
79 | {
80 | null
81 | }
82 | else
83 | {
84 | mEntryValues!![index].toString()
85 | }
86 | }
87 |
88 | private fun setValue(index: Int, value: String?)
89 | {
90 | persistString(value)
91 | mSelectedIndex = index
92 | mSpinner.setSelection(index)
93 | notifyChanged()
94 | }
95 |
96 | fun getValue(): String?
97 | {
98 | return if (mEntryValues == null || mSelectedIndex < 0)
99 | {
100 | null
101 | }
102 | else
103 | {
104 | mEntryValues!![mSelectedIndex].toString()
105 | }
106 | }
107 |
108 | fun setValue(value: String?)
109 | {
110 | setValue(findIndexOfValue(value), value)
111 | }
112 |
113 | private fun getEntry(): CharSequence?
114 | {
115 | val index = mSelectedIndex
116 | return if (index >= 0 && mEntries != null)
117 | {
118 | mEntries!![index]
119 | }
120 | else
121 | {
122 | null
123 | }
124 | }
125 |
126 | private fun findIndexOfValue(value: String?): Int
127 | {
128 | if (value != null && mEntryValues != null)
129 | {
130 | var i = mEntryValues!!.size - 1
131 | while (i >= 0)
132 | {
133 | if (mEntryValues!![i] == value)
134 | {
135 | return i
136 | }
137 | i -= 1
138 | }
139 | }
140 | return -1
141 | }
142 |
143 | override fun onGetDefaultValue(a: TypedArray, index: Int): Any?
144 | {
145 | return a.getString(index)
146 | }
147 |
148 | override fun onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any)
149 | {
150 | val value = if (restorePersistedValue) getPersistedString(getValue()) else defaultValue.toString()
151 | setValue(value)
152 | }
153 |
154 | override fun onBindView(view: View)
155 | {
156 | super.onBindView(view)
157 | val parent = mSpinner.parent as ViewGroup?
158 |
159 | if (view === parent)
160 | {
161 | return
162 | }
163 |
164 | parent?.removeView(mSpinner)
165 |
166 | (view as ViewGroup).addView(mSpinner, 0)
167 | val lp = mSpinner.layoutParams
168 | lp.width = 0
169 | mSpinner.layoutParams = lp
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/preferences/NumberPickerPreference.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.preferences
2 |
3 | import android.content.*
4 | import android.content.res.*
5 | import android.os.*
6 | import android.util.*
7 | import android.view.*
8 | import android.widget.*
9 | import com.bige0.shadowsocksr.*
10 |
11 | class NumberPickerPreference(context: Context, attrs: AttributeSet) : SummaryDialogPreference(context, attrs)
12 | {
13 | private val picker: NumberPicker = NumberPicker(context)
14 |
15 | var value: Int = 0
16 | set(i)
17 | {
18 | if (i == value)
19 | {
20 | return
21 | }
22 | picker.value = i
23 | field = picker.value
24 | persistInt(value)
25 | notifyChanged()
26 | }
27 |
28 | var min: Int
29 | get() = picker.minValue
30 | set(value)
31 | {
32 | picker.minValue = value
33 | }
34 |
35 | init
36 | {
37 | val a = context.obtainStyledAttributes(attrs, R.styleable.NumberPickerPreference)
38 | min = a.getInt(R.styleable.NumberPickerPreference_min, 0)
39 | setMax(a.getInt(R.styleable.NumberPickerPreference_max, Integer.MAX_VALUE - 1))
40 | a.recycle()
41 | }
42 |
43 | private fun setMax(value: Int)
44 | {
45 | picker.maxValue = value
46 | }
47 |
48 | override fun showDialog(state: Bundle?)
49 | {
50 | super.showDialog(state)
51 | val window = dialog.window
52 | window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
53 | }
54 |
55 | override fun onCreateDialogView(): View
56 | {
57 | val parent = picker.parent as ViewGroup?
58 | parent?.removeView(picker)
59 | return picker
60 | }
61 |
62 | override fun onDialogClosed(positiveResult: Boolean)
63 | {
64 | picker.clearFocus()
65 | super.onDialogClosed(positiveResult)
66 |
67 | if (positiveResult)
68 | {
69 | val value = picker.value
70 | if (callChangeListener(value))
71 | {
72 | this.value = value
73 | return
74 | }
75 | }
76 | picker.value = value
77 | }
78 |
79 | override fun onGetDefaultValue(a: TypedArray, index: Int): Any
80 | {
81 | return a.getInt(index, min)
82 | }
83 |
84 | override fun onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any)
85 | {
86 | val defValue = defaultValue as Int
87 | value = if (restorePersistedValue) getPersistedInt(defValue) else defValue
88 | }
89 |
90 | override val summaryValue: Any?
91 | get() = value
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/preferences/PasswordEditTextPreference.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.preferences
2 |
3 | import android.content.*
4 | import android.preference.*
5 | import android.util.*
6 |
7 | class PasswordEditTextPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet, defStyle: Int = android.R.attr.editTextPreferenceStyle) : EditTextPreference(context, attrs, defStyle)
8 | {
9 | private val mDefaultSummary: CharSequence = summary
10 |
11 | override fun setText(text: String)
12 | {
13 | super.setText(text)
14 | summary = text
15 | }
16 |
17 | override fun setSummary(summary: CharSequence)
18 | {
19 | if (summary.isEmpty())
20 | {
21 | super.setSummary(mDefaultSummary)
22 | }
23 | else
24 | {
25 | super.setSummary("*".repeat(summary.length))
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/preferences/SummaryDialogPreference.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.preferences
2 |
3 | import android.content.*
4 | import android.preference.*
5 | import android.util.*
6 | import java.util.*
7 |
8 | abstract class SummaryDialogPreference internal constructor(context: Context, attrs: AttributeSet) : DialogPreference(context, attrs)
9 | {
10 | abstract val summaryValue: Any?
11 |
12 | override fun getSummary(): CharSequence
13 | {
14 | return String.format(Locale.ENGLISH, super.getSummary().toString(), summaryValue)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/preferences/SummaryEditTextPreference.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.preferences
2 |
3 | import android.content.*
4 | import android.preference.*
5 | import android.util.*
6 |
7 | class SummaryEditTextPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int = android.R.attr.editTextPreferenceStyle) : EditTextPreference(context, attrs, defStyleAttr)
8 | {
9 | private val mDefaultSummary: CharSequence? = summary
10 |
11 | override fun setText(text: String)
12 | {
13 | super.setText(text)
14 | summary = text
15 | }
16 |
17 | override fun setSummary(summary: CharSequence)
18 | {
19 | if (summary.isEmpty())
20 | {
21 | super.setSummary(mDefaultSummary)
22 | }
23 | else
24 | {
25 | super.setSummary(summary)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/preferences/SummaryPreference.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.preferences
2 |
3 | import android.content.*
4 | import android.preference.*
5 | import android.util.*
6 | import java.util.*
7 |
8 | abstract class SummaryPreference(context: Context, attrs: AttributeSet) : Preference(context, attrs)
9 | {
10 | abstract fun getSummaryValue(): Any?
11 |
12 | override fun getSummary(): CharSequence
13 | {
14 | return String.format(Locale.ENGLISH, super.getSummary().toString(), getSummaryValue())
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/shortcuts/ShortcutUtils.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.shortcuts
2 |
3 | import android.content.*
4 | import androidx.core.content.pm.*
5 | import androidx.core.graphics.drawable.*
6 | import com.bige0.shadowsocksr.*
7 |
8 | fun makeToggleShortcut(context: Context, isConnected: Boolean): ShortcutInfoCompat
9 | {
10 | val icon = when (isConnected)
11 | {
12 | true -> R.drawable.ic_qu_shadowsocks_launcher
13 | false -> R.drawable.ic_qu_shadowsocks_launcher_disabled
14 | }
15 |
16 | return ShortcutInfoCompat.Builder(context, "toggle")
17 | .setIntent(Intent(context, QuickToggleShortcut::class.java).setAction(Intent.ACTION_MAIN))
18 | .setIcon(IconCompat.createWithResource(context, icon))
19 | .setShortLabel(context.getString(if (isConnected) R.string.proxy_is_on else R.string.proxy_is_off))
20 | .build()
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/utils/Base64.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.utils
2 |
3 | import android.util.Base64
4 |
5 | object Base64
6 | {
7 | fun decodeUrlSafe(str: String): String
8 | {
9 | val data = str.replace('/', '_')
10 | .replace('+', '-')
11 | .replace("=", "")
12 | val byte = Base64.decode(data, Base64.URL_SAFE or Base64.NO_PADDING)
13 | return byte.toString(Charsets.UTF_8)
14 | }
15 |
16 | fun encode(bytes: ByteArray): String
17 | {
18 | return Base64.encode(bytes, Base64.DEFAULT)
19 | .toString(Charsets.UTF_8)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/utils/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.utils
2 |
3 | object Constants
4 | {
5 | const val DefaultHostName = "198.199.101.152"
6 |
7 | object Executable
8 | {
9 | const val PDNSD = "libpdnsd.so"
10 | const val PROXYCHAINS4 = "libproxychains4.so"
11 | const val SS_LOCAL = "libssr-local.so"
12 | const val TUN2SOCKS = "libtun2socks.so"
13 | }
14 |
15 | object ConfigUtils
16 | {
17 | const val SHADOWSOCKS = "{\"server\": \"%s\", \"server_port\": %d, \"local_port\": %d, \"password\": \"%s\", \"method\":\"%s\", \"timeout\": %d, \"protocol\": \"%s\", \"obfs\": \"%s\", \"obfs_param\": \"%s\", \"protocol_param\": \"%s\"}"
18 | const val PROXYCHAINS = "strict_chain\n" + "localnet 127.0.0.0/255.0.0.0\n" + "[ProxyList]\n" + "%s %s %s %s %s"
19 | const val PDNSD_LOCAL = "global {" + "perm_cache = 2048;" + "%s" + "cache_dir = \"%s\";" + "server_ip = %s;" + "server_port = %d;" + "query_method = tcp_only;" + "min_ttl = 15m;" + "max_ttl = 1w;" + "timeout = 10;" + "daemon = off;" + "}" + "" + "server {" + "label = \"local\";" + "ip = 127.0.0.1;" + "port = %d;" + "reject = %s;" + "reject_policy = negate;" + "reject_recursively = on;" + "}" + "" + "rr {" + "name=localhost;" + "reverse=on;" + "a=127.0.0.1;" + "owner=localhost;" + "soa=localhost,root.localhost,42,86400,900,86400,86400;" + "}"
20 | const val PDNSD_DIRECT = "global {" + "perm_cache = 2048;" + "%s" + "cache_dir = \"%s\";" + "server_ip = %s;" + "server_port = %d;" + "query_method = udp_only;" + "min_ttl = 15m;" + "max_ttl = 1w;" + "timeout = 10;" + "daemon = off;" + "par_queries = 4;" + "}" + "" + "%s" + "" + "server {" + "label = \"local-server\";" + "ip = 127.0.0.1;" + "query_method = tcp_only;" + "port = %d;" + "reject = %s;" + "reject_policy = negate;" + "reject_recursively = on;" + "}" + "" + "rr {" + "name=localhost;" + "reverse=on;" + "a=127.0.0.1;" + "owner=localhost;" + "soa=localhost,root.localhost,42,86400,900,86400,86400;" + "}"
21 | const val REMOTE_SERVER = "server {" + "label = \"remote-servers\";" + "ip = %s;" + "port = %d;" + "timeout = 3;" + "query_method = udp_only;" + "%s" + "policy = included;" + "reject = %s;" + "reject_policy = fail;" + "reject_recursively = on;" + "}"
22 |
23 | fun escapedJson(OriginString: String): String
24 | {
25 | return OriginString.replace("\\\\", "\\\\\\\\")
26 | .replace("\"", "\\\\\"")
27 | }
28 | }
29 |
30 | object Key
31 | {
32 | const val id = "profileId"
33 | const val name = "profileName"
34 |
35 | const val individual = "Proxyed"
36 |
37 | const val route = "route"
38 | const val aclurl = "aclurl"
39 |
40 | const val isAutoConnect = "isAutoConnect"
41 |
42 | const val proxyApps = "isProxyApps"
43 | const val udpdns = "isUdpDns"
44 | const val ipv6 = "isIpv6"
45 |
46 | const val host = "proxy"
47 | const val password = "sitekey"
48 | const val method = "encMethod"
49 | const val remotePort = "remotePortNum"
50 | const val localPort = "localPortNum"
51 |
52 | const val profileTip = "profileTip"
53 |
54 | const val obfs = "obfs"
55 | const val obfs_param = "obfs_param"
56 | const val protocol = "protocol"
57 | const val protocol_param = "protocol_param"
58 | const val dns = "dns"
59 | const val china_dns = "china_dns"
60 |
61 | const val currentVersionCode = "currentVersionCode"
62 | const val frontproxy = "frontproxy"
63 | const val ssrsub_autoupdate = "ssrsub_autoupdate"
64 | const val group_name = "groupName"
65 | }
66 |
67 | object State
68 | {
69 | const val CONNECTING = 1
70 | const val CONNECTED = 2
71 | const val STOPPING = 3
72 | const val STOPPED = 4
73 | }
74 |
75 | object Action
76 | {
77 | const val SERVICE = "com.bige0.shadowsocksr.SERVICE"
78 | const val CLOSE = "com.bige0.shadowsocksr.CLOSE"
79 | const val QUICK_SWITCH = "com.bige0.shadowsocksr.QUICK_SWITCH"
80 | const val SCAN = "com.bige0.shadowsocksr.intent.action.SCAN"
81 | const val SORT = "com.bige0.shadowsocksr.intent.action.SORT"
82 | }
83 |
84 | object Route
85 | {
86 | const val ALL = "all"
87 | const val BYPASS_LAN = "bypass-lan"
88 | const val BYPASS_CHN = "bypass-china"
89 | const val BYPASS_LAN_CHN = "bypass-lan-china"
90 | const val GFWLIST = "gfwlist"
91 | const val CHINALIST = "china-list"
92 | const val ACL = "self"
93 | }
94 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/utils/IOUtils.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.utils
2 |
3 | import java.io.*
4 | import java.net.*
5 |
6 | object IOUtils
7 | {
8 | private const val TAG = "IOUtils"
9 | private const val BUFFER_SIZE = 32 * 1024
10 |
11 | /**
12 | * inputStream copy to out
13 | *
14 | * @param inputStream input stream
15 | * @param out output stream
16 | */
17 | fun copy(inputStream: InputStream, out: OutputStream)
18 | {
19 | val buffer = ByteArray(BUFFER_SIZE)
20 | var temp: Int
21 | while (true)
22 | {
23 | temp = inputStream.read(buffer)
24 | if (temp == -1)
25 | {
26 | break
27 | }
28 | out.write(buffer, 0, temp)
29 | }
30 | out.flush()
31 | }
32 |
33 | /**
34 | * read string by input stream
35 | *
36 | * @param `inputStream` input stream
37 | * @return read failed return ""
38 | */
39 | fun readString(inputStream: InputStream): String
40 | {
41 | val builder = StringBuilder()
42 | val buffer = ByteArray(BUFFER_SIZE)
43 | var temp: Int
44 | while (true)
45 | {
46 | temp = inputStream.read(buffer)
47 | if (temp == -1)
48 | {
49 | break
50 | }
51 | builder.append(String(buffer, 0, temp))
52 | }
53 | return builder.toString()
54 | }
55 |
56 | /**
57 | * write string
58 | *
59 | * @param file file path
60 | * @param content string content
61 | * @return write failed return false.
62 | */
63 | fun writeString(file: String, content: String): Boolean
64 | {
65 | var writer: FileWriter? = null
66 | try
67 | {
68 | writer = FileWriter(file)
69 | writer.write(content)
70 | return true
71 | }
72 | catch (e: IOException)
73 | {
74 | VayLog.e(TAG, "writeString", e)
75 | return false
76 | }
77 | finally
78 | {
79 | try
80 | {
81 | writer?.close()
82 | }
83 | catch (e: IOException)
84 | {
85 | e.printStackTrace()
86 | }
87 | }
88 | }
89 |
90 | fun close(inputStream: InputStream?)
91 | {
92 | try
93 | {
94 | inputStream?.close()
95 | }
96 | catch (e: IOException)
97 | {
98 | // ignored
99 | }
100 | }
101 |
102 | fun close(outputStream: OutputStream?)
103 | {
104 | try
105 | {
106 | outputStream?.close()
107 | }
108 | catch (e: IOException)
109 | {
110 | // Ignored
111 | }
112 | }
113 |
114 | fun close(writer: Writer?)
115 | {
116 | try
117 | {
118 | writer?.close()
119 | }
120 | catch (e: IOException)
121 | {
122 | // Ignored
123 | }
124 | }
125 |
126 | fun close(reader: Reader?)
127 | {
128 | try
129 | {
130 | reader?.close()
131 | }
132 | catch (e: IOException)
133 | {
134 | // Ignored
135 | }
136 | }
137 |
138 | fun disconnect(conn: HttpURLConnection?)
139 | {
140 | conn?.disconnect()
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/utils/Parser.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.utils
2 |
3 | import com.bige0.shadowsocksr.*
4 | import com.bige0.shadowsocksr.database.*
5 | import java.net.*
6 | import java.util.*
7 |
8 | object Parser
9 | {
10 | const val TAG = "Parser"
11 |
12 | private const val pattern_ss_regex = "(?i)ss://([A-Za-z0-9+-/=_]+)(#(.+))?"
13 | private const val decodedPattern_ss_regex = "(?i)^((.+?)(-auth)??:(.*)@(.+?):(\\d+?))$"
14 |
15 | private const val pattern_ssr_regex = "(?i)ssr://([A-Za-z0-9+-/=_]+)"
16 | private const val decodedPattern_ssr_regex = "(?i)^((.+):(\\d+?):(.*):(.+):(.*):([^/]+))"
17 | private const val decodedPattern_ssr_obfsparam_regex = "(?i)[?&]obfsparam=([A-Za-z0-9+-/=_]*)"
18 | private const val decodedPattern_ssr_remarks_regex = "(?i)[?&]remarks=([A-Za-z0-9+-/=_]*)"
19 | private const val decodedPattern_ssr_protocolparam_regex = "(?i)[?&]protoparam=([A-Za-z0-9+-/=_]*)"
20 | private const val decodedPattern_ssr_groupparam_regex = "(?i)[?&]group=([A-Za-z0-9+-/=_]*)"
21 |
22 | fun findAllSs(data: CharSequence): List
23 | {
24 | val pattern = Regex(pattern_ss_regex)
25 | val decodedPattern = Regex(decodedPattern_ss_regex)
26 |
27 | val input: CharSequence = if (data.isNotEmpty()) data else ""
28 | val list = ArrayList()
29 | try
30 | {
31 | pattern.findAll(input)
32 | .forEach {
33 | val ss = decodedPattern.matchEntire(Base64.decodeUrlSafe(it.groupValues[1]))
34 | if (ss != null)
35 | {
36 | val profile = Profile()
37 | val port = ss.groupValues[6].toUShortOrNull()
38 | if (port != null)
39 | {
40 | profile.remotePort = port.toInt()
41 | }
42 | else
43 | {
44 | return@forEach
45 | }
46 | profile.method = ss.groupValues[2].toLowerCase(Locale.ENGLISH)
47 | if (ss.groups[3] != null)
48 | {
49 | profile.protocol = "verify_sha1"
50 | }
51 | profile.password = ss.groupValues[4]
52 | profile.name = ss.groupValues[5]
53 | profile.host = profile.name
54 | if (it.groups[2] != null)
55 | {
56 | profile.name = URLDecoder.decode(it.groupValues[3], "utf-8")
57 | }
58 | list.add(profile)
59 | }
60 | }
61 | }
62 | catch (e: Exception)
63 | {
64 | VayLog.e(TAG, "findAllSs", e)
65 | ShadowsocksApplication.app.track(e)
66 | }
67 | finally
68 | {
69 | return list
70 | }
71 |
72 | }
73 |
74 | fun findAllSsr(data: CharSequence): MutableList
75 | {
76 | val input = if (data.isNotEmpty()) data else ""
77 | val list = ArrayList()
78 | try
79 | {
80 | Regex(pattern_ssr_regex).findAll(input)
81 | .forEach {
82 | val uri = Base64.decodeUrlSafe(it.groupValues[1])
83 | val ss = Regex(decodedPattern_ssr_regex).find(uri)
84 | if (ss != null)
85 | {
86 | val profile = Profile()
87 | val port = ss.groupValues[3].toUShortOrNull()
88 | if (port != null)
89 | {
90 | profile.remotePort = port.toInt()
91 | }
92 | else
93 | {
94 | return@forEach
95 | }
96 | profile.host = ss.groupValues[2].toLowerCase(Locale.ENGLISH)
97 | profile.protocol = ss.groupValues[4].toLowerCase(Locale.ENGLISH)
98 | profile.method = ss.groupValues[5].toLowerCase(Locale.ENGLISH)
99 | profile.obfs = ss.groupValues[6].toLowerCase(Locale.ENGLISH)
100 | profile.password = Base64.decodeUrlSafe(ss.groupValues[7])
101 |
102 | if (profile.obfs == "tls1.2_ticket_fastauth")
103 | {
104 | profile.obfs = "tls1.2_ticket_auth"
105 | }
106 | var param = Regex(decodedPattern_ssr_obfsparam_regex).find(uri)
107 | if (param != null)
108 | {
109 | profile.obfs_param = Base64.decodeUrlSafe(param.groupValues[1])
110 | }
111 |
112 | param = Regex(decodedPattern_ssr_protocolparam_regex).find(uri)
113 | if (param != null)
114 | {
115 | profile.protocol_param = Base64.decodeUrlSafe(param.groupValues[1])
116 | }
117 |
118 | param = Regex(decodedPattern_ssr_remarks_regex).find(uri)
119 | if (param != null)
120 | {
121 | profile.name = Base64.decodeUrlSafe(param.groupValues[1])
122 | }
123 | else
124 | {
125 | profile.name = profile.host
126 | }
127 |
128 | param = Regex(decodedPattern_ssr_groupparam_regex).find(uri)
129 | if (param != null)
130 | {
131 | profile.url_group = Base64.decodeUrlSafe(param.groupValues[1])
132 | }
133 |
134 | list.add(profile)
135 | }
136 | }
137 | }
138 | catch (e: Exception)
139 | {
140 | VayLog.e(TAG, "findAllSsr", e)
141 | ShadowsocksApplication.app.track(e)
142 | }
143 | finally
144 | {
145 | return list
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/utils/TaskerSettings.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.utils
2 |
3 | import android.content.*
4 | import android.os.*
5 | import com.bige0.shadowsocksr.*
6 |
7 | class TaskerSettings(bundle: Bundle)
8 | {
9 | var switchOn = false
10 | var profileId = 0
11 |
12 | init
13 | {
14 | switchOn = bundle.getBoolean(KEY_SWITCH_ON, true)
15 | profileId = bundle.getInt(KEY_PROFILE_ID, -1)
16 | }
17 |
18 | fun toIntent(context: Context): Intent
19 | {
20 | val bundle = Bundle()
21 | if (!switchOn)
22 | {
23 | bundle.putBoolean(KEY_SWITCH_ON, false)
24 | }
25 |
26 | if (profileId >= 0)
27 | {
28 | bundle.putInt(KEY_PROFILE_ID, profileId)
29 | }
30 |
31 | val p = ShadowsocksApplication.app.profileManager.getProfile(profileId)
32 | val value = if (p != null)
33 | {
34 | val strId = if (switchOn) R.string.start_service else R.string.stop_service
35 | context.getString(strId, p.name)
36 | }
37 | else
38 | {
39 | val strId = if (switchOn) R.string.start_service_default else R.string.stop
40 | context.getString(strId)
41 | }
42 | val intent = Intent()
43 | intent.putExtra(com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE, bundle)
44 | intent.putExtra(com.twofortyfouram.locale.api.Intent.EXTRA_STRING_BLURB, value)
45 | return intent
46 | }
47 |
48 | companion object
49 | {
50 | private const val KEY_SWITCH_ON = "switch_on"
51 | private const val KEY_PROFILE_ID = "profile_id"
52 |
53 | fun fromIntent(intent: Intent): TaskerSettings
54 | {
55 | val bundle = if (intent.hasExtra(com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE))
56 | {
57 | intent.getBundleExtra(com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE)
58 | }
59 | else
60 | {
61 | Bundle.EMPTY
62 | }
63 | return TaskerSettings(bundle!!)
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/utils/ToastUtils.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.utils
2 |
3 | import android.widget.*
4 | import com.bige0.shadowsocksr.*
5 |
6 | object ToastUtils
7 | {
8 | fun showLong(strId: Int)
9 | {
10 | val context = ShadowsocksApplication.app.applicationContext
11 | Toast.makeText(context, strId, Toast.LENGTH_LONG)
12 | .show()
13 | }
14 |
15 | fun showShort(str: String)
16 | {
17 | val context = ShadowsocksApplication.app.applicationContext
18 | Toast.makeText(context, str, Toast.LENGTH_SHORT)
19 | .show()
20 | }
21 |
22 | fun showShort(strId: Int)
23 | {
24 | val context = ShadowsocksApplication.app.applicationContext
25 | Toast.makeText(context, strId, Toast.LENGTH_SHORT)
26 | .show()
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/utils/TrafficMonitor.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.utils
2 |
3 | import com.bige0.shadowsocksr.R
4 | import com.bige0.shadowsocksr.ShadowsocksApplication
5 | import java.lang.System
6 | import java.text.*
7 |
8 | object TrafficMonitor
9 | {
10 | // Bytes per second
11 | var txRate = 0L
12 | var rxRate = 0L
13 |
14 | // Bytes for the current session
15 | var txTotal = 0L
16 | var rxTotal = 0L
17 |
18 | // Bytes for the last query
19 | var txLast = 0L
20 | var rxLast = 0L
21 | var timestampLast = 0L
22 | var dirty = true
23 |
24 | private val units = arrayOf("KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "NB", "DB", "CB")
25 | private val numberFormat = DecimalFormat("@@@")
26 |
27 | fun formatTraffic(size: Long): String
28 | {
29 | var n = size.toDouble()
30 | var i = -1
31 | while (n >= 1000)
32 | {
33 | n /= 1024.0
34 | ++i
35 | }
36 | return if (i < 0)
37 | {
38 | "$size ${ShadowsocksApplication.app.resources.getQuantityString(R.plurals.bytes, size.toInt())}"
39 | }
40 | else
41 | {
42 | "${numberFormat.format(n)} ${units[i]}"
43 | }
44 | }
45 |
46 | fun updateRate(): Boolean
47 | {
48 | val now = System.currentTimeMillis()
49 | val delta = now - timestampLast
50 | var updated = false
51 | if (delta != 0L)
52 | {
53 | if (dirty)
54 | {
55 | txRate = (txTotal - txLast) * 1000 / delta
56 | rxRate = (rxTotal - rxLast) * 1000 / delta
57 | txLast = txTotal
58 | rxLast = rxTotal
59 | dirty = false
60 | updated = true
61 | }
62 | else
63 | {
64 | if (txRate != 0L)
65 | {
66 | txRate = 0
67 | updated = true
68 | }
69 | if (rxRate != 0L)
70 | {
71 | rxRate = 0
72 | updated = true
73 | }
74 | }
75 | timestampLast = now
76 | }
77 | return updated
78 | }
79 |
80 | fun update(tx: Long, rx: Long)
81 | {
82 | if (txTotal != tx)
83 | {
84 | txTotal = tx
85 | dirty = true
86 | }
87 |
88 | if (rxTotal != rx)
89 | {
90 | rxTotal = rx
91 | dirty = true
92 | }
93 | }
94 |
95 | fun reset()
96 | {
97 | txRate = 0
98 | rxRate = 0
99 | txTotal = 0
100 | rxTotal = 0
101 | txLast = 0
102 | rxLast = 0
103 | dirty = true
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/utils/TrafficMonitorThread.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.utils
2 |
3 | import android.content.*
4 | import android.net.*
5 | import com.bige0.shadowsocksr.*
6 | import java.io.*
7 | import java.nio.*
8 | import java.util.concurrent.*
9 |
10 | class TrafficMonitorThread(context: Context) : Thread()
11 | {
12 | private val path: String = "${context.applicationInfo.dataDir}${File.separator}stat_path"
13 | private val pool: ScheduledExecutorService
14 | private var serverSocket: LocalServerSocket? = null
15 | private var isRunning = true
16 |
17 | init
18 | {
19 | pool = ScheduledThreadPoolExecutor(3, ThreadFactory { r ->
20 | val thread = Thread(r)
21 | thread.name = TAG
22 | thread
23 | })
24 | }
25 |
26 | private fun closeServerSocket()
27 | {
28 | if (serverSocket != null)
29 | {
30 | try
31 | {
32 | serverSocket!!.close()
33 | }
34 | catch (e: Exception)
35 | {
36 | // ignore
37 | }
38 |
39 | serverSocket = null
40 | }
41 | }
42 |
43 | fun stopThread()
44 | {
45 | isRunning = false
46 | closeServerSocket()
47 | }
48 |
49 | override fun run()
50 | {
51 | val deleteResult = File(path).delete()
52 | VayLog.d(TAG, "run() delete file = $deleteResult")
53 |
54 | if (!initServerSocket())
55 | {
56 | return
57 | }
58 |
59 | while (isRunning)
60 | {
61 | try
62 | {
63 | val socket = serverSocket!!.accept()
64 | // handle socket
65 | handleLocalSocket(socket)
66 | }
67 | catch (e: Exception)
68 | {
69 | VayLog.e(TAG, "Error when accept socket", e)
70 | ShadowsocksApplication.app.track(e)
71 |
72 | initServerSocket()
73 | }
74 |
75 | }
76 | }
77 |
78 | /**
79 | * handle local socket
80 | *
81 | * @param socket local socket object
82 | */
83 | private fun handleLocalSocket(socket: LocalSocket)
84 | {
85 | pool.execute {
86 | val input: InputStream
87 | val output: OutputStream
88 | try
89 | {
90 | input = socket.inputStream
91 | output = socket.outputStream
92 |
93 | val buffer = ByteArray(16)
94 | if (input.read(buffer) != 16)
95 | {
96 | throw IOException("Unexpected traffic stat length")
97 | }
98 | val stat = ByteBuffer.wrap(buffer)
99 | .order(ByteOrder.LITTLE_ENDIAN)
100 | TrafficMonitor.update(stat.getLong(0), stat.getLong(8))
101 | output.write(0)
102 | output.flush()
103 |
104 | // close stream
105 | IOUtils.close(input)
106 | IOUtils.close(output)
107 | }
108 | catch (e: Exception)
109 | {
110 | VayLog.e(TAG, "handleLocalSocket() Error when recv traffic stat", e)
111 | ShadowsocksApplication.app.track(e)
112 | }
113 | finally
114 | {
115 | // close socket
116 | try
117 | {
118 | socket.close()
119 | }
120 | catch (e: IOException)
121 | {
122 | // ignore
123 | }
124 | }
125 | }
126 | }
127 |
128 | /**
129 | * init server socket
130 | *
131 | * @return init failed return false.
132 | */
133 | private fun initServerSocket(): Boolean
134 | {
135 | // if not running, do not init
136 | if (!isRunning)
137 | {
138 | return false
139 | }
140 |
141 | return try
142 | {
143 | val localSocket = LocalSocket()
144 | localSocket.bind(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
145 | serverSocket = LocalServerSocket(localSocket.fileDescriptor)
146 | true
147 | }
148 | catch (e: IOException)
149 | {
150 | VayLog.e(TAG, "unable to bind", e)
151 | false
152 | }
153 |
154 | }
155 |
156 | companion object
157 | {
158 | private const val TAG = "TrafficMonitorThread"
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/utils/Typefaces.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.utils
2 |
3 | import android.content.*
4 | import android.graphics.*
5 | import com.bige0.shadowsocksr.*
6 | import java.util.*
7 |
8 | object Typefaces
9 | {
10 | private const val TAG = "Typefaces"
11 | private val cache = Hashtable()
12 |
13 | operator fun get(c: Context, assetPath: String): Typeface?
14 | {
15 | synchronized(cache) {
16 | if (!cache.containsKey(assetPath))
17 | {
18 | try
19 | {
20 | cache[assetPath] = Typeface.createFromAsset(c.assets, assetPath)
21 | }
22 | catch (e: Exception)
23 | {
24 | VayLog.e(TAG, """Could not get typeface '$assetPath' because ${e.message}""")
25 | ShadowsocksApplication.app.track(e)
26 | return null
27 | }
28 | }
29 | return cache[assetPath]
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/utils/VayLog.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.utils
2 |
3 | import android.util.*
4 | import com.bige0.shadowsocksr.*
5 |
6 | object VayLog
7 | {
8 | private const val DEFAULT_TAG = "VayLog"
9 |
10 | private val LOGGABLE = BuildConfig.DEBUG
11 |
12 | fun d(str: String)
13 | {
14 | d(DEFAULT_TAG, str)
15 | }
16 |
17 | fun d(tag: String, str: String)
18 | {
19 | if (LOGGABLE)
20 | {
21 | Log.d(tag, str + "")
22 | }
23 | }
24 |
25 | fun w(str: String)
26 | {
27 | w(DEFAULT_TAG, str)
28 | }
29 |
30 | fun w(tag: String, str: String)
31 | {
32 | if (LOGGABLE)
33 | {
34 | Log.w(tag, str + "")
35 | }
36 | }
37 |
38 | fun e(str: String)
39 | {
40 | e(DEFAULT_TAG, str)
41 | }
42 |
43 | fun e(tag: String, str: String)
44 | {
45 | e(tag, str, null)
46 | }
47 |
48 | fun e(tag: String, msg: String, e: Throwable?)
49 | {
50 | if (LOGGABLE)
51 | {
52 | Log.e(tag, msg + "", e)
53 | }
54 | }
55 |
56 | fun i(str: String)
57 | {
58 | i(DEFAULT_TAG, str + "")
59 | }
60 |
61 | fun i(tag: String, str: String)
62 | {
63 | if (LOGGABLE)
64 | {
65 | Log.i(tag, str + "")
66 | }
67 | }
68 |
69 | fun v(str: String)
70 | {
71 | v(DEFAULT_TAG, str + "")
72 | }
73 |
74 | fun v(tag: String, str: String)
75 | {
76 | if (LOGGABLE)
77 | {
78 | Log.v(tag, str + "")
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/widget/FloatingActionMenuBehavior.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.widget
2 |
3 | import android.animation.*
4 | import android.content.*
5 | import android.util.*
6 | import android.view.*
7 | import androidx.coordinatorlayout.widget.*
8 | import androidx.core.view.*
9 | import androidx.interpolator.view.animation.*
10 | import com.github.clans.fab.*
11 | import com.google.android.material.snackbar.*
12 | import kotlin.math.*
13 |
14 | class FloatingActionMenuBehavior(context: Context, attrs: AttributeSet) : CoordinatorLayout.Behavior(context, attrs)
15 | {
16 | private var fabTranslationYAnimator: ValueAnimator? = null
17 | private var fabTranslationY: Float = 0F
18 |
19 | override fun layoutDependsOn(parent: CoordinatorLayout, child: FloatingActionMenu, dependency: View): Boolean
20 | {
21 | return dependency is Snackbar.SnackbarLayout
22 | }
23 |
24 | override fun onDependentViewChanged(parent: CoordinatorLayout, child: FloatingActionMenu, dependency: View): Boolean
25 | {
26 | var targetTransY = 0f
27 | val dependencies = parent.getDependencies(child)
28 | for (view in dependencies)
29 | {
30 | if (view is Snackbar.SnackbarLayout && parent.doViewsOverlap(child, view))
31 | {
32 | val value = view.getTranslationY() - view.getHeight()
33 | if (value <= targetTransY)
34 | {
35 | targetTransY = value
36 | }
37 | }
38 | }
39 |
40 | if (targetTransY > 0)
41 | {
42 | targetTransY = 0f
43 | }
44 |
45 | if (fabTranslationY != targetTransY)
46 | {
47 | val currentTransY = child.translationY
48 | if (fabTranslationYAnimator != null && fabTranslationYAnimator!!.isRunning)
49 | {
50 | fabTranslationYAnimator!!.cancel()
51 | }
52 | if (child.isShown && abs(currentTransY - targetTransY) > child.height * 0.667f)
53 | {
54 | if (fabTranslationYAnimator == null)
55 | {
56 | fabTranslationYAnimator = ValueAnimator()
57 | fabTranslationYAnimator!!.interpolator = FastOutSlowInInterpolator()
58 | fabTranslationYAnimator!!.addUpdateListener { animation -> child.translationY = animation.animatedValue as Float }
59 | }
60 | fabTranslationYAnimator!!.setFloatValues(currentTransY, targetTransY)
61 | fabTranslationYAnimator!!.start()
62 | }
63 | else
64 | {
65 | child.translationY = targetTransY
66 | }
67 | fabTranslationY = targetTransY
68 | }
69 | return false
70 | }
71 |
72 | override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: FloatingActionMenu, directTargetChild: View, target: View, nestedScrollAxes: Int): Boolean
73 | {
74 | return true
75 | }
76 |
77 | override fun onNestedScroll(parent: CoordinatorLayout, child: FloatingActionMenu, target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, type: Int)
78 | {
79 | super.onNestedScroll(parent, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, ViewCompat.TYPE_TOUCH)
80 | val dy = dyConsumed + dyUnconsumed
81 | if (child.isMenuButtonHidden)
82 | {
83 | if (dy < 0)
84 | {
85 | child.showMenuButton(true)
86 | }
87 | }
88 | else if (dy > 0)
89 | {
90 | child.hideMenuButton(true)
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/com/bige0/shadowsocksr/widget/UndoSnackBarManager.kt:
--------------------------------------------------------------------------------
1 | package com.bige0.shadowsocksr.widget
2 |
3 | import android.util.*
4 | import android.view.*
5 | import com.bige0.shadowsocksr.*
6 | import com.google.android.material.snackbar.*
7 |
8 | class UndoSnackBarManager(private val view: View, private val undo: (Any) -> Unit, private val commit: (Any) -> Unit)
9 | {
10 | private val recycleBin: SparseArray = SparseArray()
11 | private var last: Snackbar? = null
12 |
13 | private val removedCallback = object : Snackbar.Callback()
14 | {
15 | override fun onDismissed(transientBottomBar: Snackbar?, event: Int)
16 | {
17 | if (event == DISMISS_EVENT_SWIPE || event == DISMISS_EVENT_MANUAL || event == DISMISS_EVENT_TIMEOUT)
18 | {
19 | commit(recycleBin)
20 | recycleBin.clear()
21 | }
22 | last = null
23 | }
24 | }
25 |
26 | fun remove(index: Int, item: T)
27 | {
28 | recycleBin.append(index, item)
29 | val count = recycleBin.size()
30 | last = Snackbar.make(view, view.resources.getQuantityString(R.plurals.removed, count, count), Snackbar.LENGTH_LONG)
31 | .addCallback(removedCallback)
32 | .setAction(R.string.undo) {
33 | undo(recycleBin)
34 | recycleBin.clear()
35 | }
36 | last!!.show()
37 | }
38 |
39 | fun flush()
40 | {
41 | if (last != null)
42 | {
43 | last!!.dismiss()
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi-v21/ic_action_settings.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi-v21/ic_navigation_close.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi-v21/ic_qu_camera_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
15 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi-v21/ic_qu_shadowsocks_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
17 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi-v21/ic_qu_shadowsocks_launcher_disabled.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
17 |
22 |
25 |
28 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/drawable-hdpi/ic_action_settings.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_navigation_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/drawable-hdpi/ic_navigation_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_stat_shadowsocks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/drawable-hdpi/ic_stat_shadowsocks.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/drawable-mdpi/ic_action_settings.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_navigation_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/drawable-mdpi/ic_navigation_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_stat_shadowsocks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/drawable-mdpi/ic_stat_shadowsocks.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/background_stat.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | -
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/drawable-xhdpi/ic_action_settings.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_navigation_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/drawable-xhdpi/ic_navigation_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_stat_shadowsocks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/drawable-xhdpi/ic_stat_shadowsocks.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/drawable-xxhdpi/ic_action_settings.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_navigation_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/drawable-xxhdpi/ic_navigation_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_stat_shadowsocks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/drawable-xxhdpi/ic_stat_shadowsocks.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_action_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/drawable-xxxhdpi/ic_action_settings.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_navigation_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/drawable-xxxhdpi/ic_navigation_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/abc_ic_ab_back_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
24 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/background_stat.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_drop_down.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_click.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_click_white.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_content_copy.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_content_create.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_content_paste.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_device_nfc.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_down.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_image_camera_alt.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_rss.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_social_share.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_start_busy.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_start_connected.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_start_idle.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
16 |
19 |
22 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_apps.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
18 |
19 |
32 |
33 |
46 |
47 |
48 |
52 |
53 |
59 |
60 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_apps_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
18 |
19 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_edittext.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_front_proxy.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
22 |
23 |
30 |
31 |
38 |
39 |
46 |
47 |
53 |
54 |
61 |
62 |
68 |
69 |
76 |
77 |
83 |
84 |
91 |
92 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_profiles.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
21 |
22 |
28 |
29 |
30 |
34 |
35 |
36 |
37 |
49 |
50 |
59 |
60 |
69 |
70 |
79 |
80 |
89 |
90 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_profiles_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
30 |
31 |
42 |
43 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_quick_switch.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
14 |
15 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_scanner.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_ssr_sub.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
22 |
23 |
30 |
31 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_ssr_sub_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_tasker.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
14 |
15 |
22 |
23 |
24 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toolbar_light_dark.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/app_manager_menu.xml:
--------------------------------------------------------------------------------
1 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/profile_manager_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/raw/gtm_default_container:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/app/src/main/res/raw/gtm_default_container
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/dimen.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -28dp
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Set proxy for selected apps
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN-v21/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 为应用程序分别设置代理
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | ShadowsocksR
4 | 开关
5 |
6 | 其他
7 | 服务器设置
8 | 配置名称
9 | 服务器
10 | 远程端口
11 | %d(远程服务器的端口号)
12 | 本地端口
13 | %d(本地客户端的端口号)
14 | 密码
15 | 远程服务器的密码
16 | 协议
17 | 协议参数
18 | 您的自定义协议参数(ShadowsocksR 特性)
19 | 混淆方式
20 | 混淆参数
21 | 您的自定义混淆参数(ShadowsocksR 特性)
22 | 加密方法
23 | 功能设置
24 | 路由
25 | 分应用代理
26 | 为应用程序分别设置代理,在 Android 4.x 下需要启用 NAT 模式
27 | 开
28 | 绕行模式
29 | 自动连接
30 | 随系统启动后台服务
31 | 后台服务已开始运行。
32 | 无法连接远程服务器
33 | 服务器名无效
34 | 警告:当前使用的加密算法不安全
35 | 停止
36 | 切换
37 | 正在关闭…
38 | 后台服务启动失败:%s
39 | VPN 服务启动失败。你可能需要重启设备。
40 | 关闭
41 | 代理服务器地址及密码不能为空
42 | 连接
43 | 重置中…
44 | 正在加载…
45 |
46 | 去重添加
47 |
48 | 配置文件
49 | 重置
50 | 重置所有后台服务
51 | 忽略电池优化
52 | 请求忽略电池优化
53 | 关于
54 | ShadowsocksR %s
55 | 分享
56 | 扫描二维码或使用 Android Beam (NFC)
57 | 扫描二维码
58 | 扫描二维码\nAndroid Beam 被禁用
59 | 打开 NFC & Android Beam
60 |
61 | DNS
62 | 用于解析特殊域名的DNS,如 8.8.8.8:53
63 |
64 | China DNS
65 | 用于解析一般域名的DNS,如 223.5.5.5:53
66 |
67 | UDP 转发
68 | 由远程服务器转发 UDP 协议的数据包
69 |
70 | IPv6 路由
71 | 向远程服务器转发 IPv6 流量
72 |
73 | 网络流量
74 | 发送:\t%3$s\t↑\t%1$s/s\n接收:\t%4$s\t↓\t%2$s/s
75 |
76 | 检查网络连接
77 | 测试中…
78 | 连接有效:延时 %d 毫秒
79 | 无互联网连接
80 | 失败:%s
81 | 状态码无效:#%d
82 |
83 |
84 | 配置文件
85 | • 点击选择配置文件;\n• 向左/右滑动或按左删除;\n•
86 | 长按拖动改变顺序。
87 | 好的!
88 | 复制 URL
89 | 为 ShadowsocksR 添加此配置文件?
90 | 扫描二维码
91 | 手动设置
92 | 使用 Android Beam (NFC) 来添加配置
93 | 保持两台设备的屏幕亮起,在另外一台设备上,点击配置文件的分享按钮,将它们靠在一起(背靠背)。\n你也可以扫描 NFC 标签。
94 | 扫描二维码需要相机权限。
95 |
96 | ACL 文件 URL
97 | ACL 文件更新
98 | 更新您的 ACL 文件
99 | 很抱歉,因为您没有设置 ACL 地址,所以无法进行更新。请在路由中选择 \'自定义 ACL 文件\' 之后再进行升级尝试。
100 |
101 | 文件更新中…
102 | 文件更新成功。
103 | 文件更新失败!
104 |
105 | 应用设置到所有配置文件
106 | 导出至剪贴板
107 | 从剪贴板导入
108 | 导出至剪贴板成功。
109 | 导出至剪贴板失败!
110 | 导入成功。
111 | 导入失败!
112 |
113 | 撤销
114 | 启动服务
115 | 连接到当前服务器
116 | 连接到 %s
117 | 切换到 %s
118 | 使用当前配置
119 |
120 | 测试中…
121 | 完整HTTP延时测试
122 | 基于延时自动排序
123 | 测试延时
124 |
125 | 启用
126 | 地址
127 | 端口
128 | 用户名
129 | 密码
130 | 类型
131 | 前置代理
132 |
133 | 添加/更新 SSR 订阅
134 | 自动更新
135 | 添加订阅地址
136 | 确定并更新
137 | 订阅列表
138 | 地址不正确或网络不可用。
139 | 处理中…
140 | 请稍候…
141 | 群组名
142 | 点击查看和编辑地址
143 | 删除
144 | 您是想直接删除呢,还是想连带着删除群组和这个订阅名一样的节点呢?
145 | 直接删除
146 | 与节点一起删除
147 |
148 |
149 |
150 | 发送:
151 | 接收:
152 | 全部群组
153 | %s:订阅更新成功
154 | 代理已开启
155 | 代理已关闭
156 |
157 |
158 | - 已删除
159 | - 已删除 %d 项
160 |
161 |
162 |
163 | - 全局
164 | - 绕过局域网地址
165 | - 绕过中国大陆地址
166 | - 绕过局域网及中国大陆地址
167 | - 仅代理中国大陆无法访问的地址
168 | - 仅代理中国大陆地址
169 | - 自定义 ACL 文件
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh-rTW-v21/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 為已選擇的應用程式設定 Proxy
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | #FF4081
6 | #efb4c8
7 | #e482a3
8 | #d9507e
9 | @color/material_blue_grey_300
10 | @color/material_blue_grey_500
11 | @color/material_blue_grey_700
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/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 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimen.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -44dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
18 |
19 |
24 |
25 |
30 |
31 |
34 |
35 |
38 |
39 |
46 |
47 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/pref_all.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
11 |
15 |
20 |
27 |
34 |
40 |
47 |
54 |
58 |
65 |
69 |
70 |
71 |
72 |
73 |
74 |
81 |
86 |
91 |
96 |
101 |
106 |
107 |
108 |
109 |
110 |
115 |
116 |
120 |
124 |
128 |
131 |
134 |
135 |
138 |
139 |
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/shortcuts.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
12 |
13 |
18 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/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 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext {
5 | javaVersion = JavaVersion.VERSION_1_8
6 | kotlinVersion = '1.7.10'
7 | minSdkVersion = 21
8 | sdkVersion = 33
9 | compileSdkVersion = 33
10 | versionCode = 6
11 | versionName = '3.8.3'
12 | }
13 | repositories {
14 | google()
15 | mavenCentral()
16 | }
17 | dependencies {
18 | classpath 'com.android.tools.build:gradle:7.4.2'
19 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
20 |
21 | // NOTE: Do not place your application dependencies here; they belong
22 | // in the individual module build.gradle files
23 | }
24 | }
25 |
26 | allprojects {
27 | repositories {
28 | google()
29 | mavenCentral()
30 | maven { url 'https://maven.aliyun.com/repository/public' }
31 | maven { url 'https://jitpack.io' }
32 | }
33 | }
34 |
35 | task clean(type: Delete) {
36 | delete rootProject.buildDir
37 | }
38 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## For more details on how to configure your build environment visit
2 | # http://www.gradle.org/docs/current/userguide/build_environment.html
3 | #
4 | # Specifies the JVM arguments used for the daemon process.
5 | # The setting is particularly useful for tweaking memory settings.
6 | # Default value: -Xmx1024m -XX:MaxPermSize=256m
7 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
8 | #
9 | # When configured, Gradle will run in incubating parallel mode.
10 | # This option should only be used with decoupled projects. More details, visit
11 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
12 | # org.gradle.parallel=true
13 | #Mon Dec 21 21:12:48 CST 2020
14 | android.enableJetifier=true
15 | android.useAndroidX=true
16 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
17 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HMBSbige/ShadowsocksR-Android/79bd893d2e756e0891552c8c4787173aaf4eacfd/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 21 22:02:32 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin, switch paths to Windows format before running java
129 | if $cygwin ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=$((i+1))
158 | done
159 | case $i in
160 | (0) set -- ;;
161 | (1) set -- "$args0" ;;
162 | (2) set -- "$args0" "$args1" ;;
163 | (3) set -- "$args0" "$args1" "$args2" ;;
164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=$(save "$@")
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
185 | cd "$(dirname "$0")"
186 | fi
187 |
188 | exec "$JAVACMD" "$@"
189 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem http://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
34 |
35 | @rem Find java.exe
36 | if defined JAVA_HOME goto findJavaFromJavaHome
37 |
38 | set JAVA_EXE=java.exe
39 | %JAVA_EXE% -version >NUL 2>&1
40 | if "%ERRORLEVEL%" == "0" goto init
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
44 | echo.
45 | echo Please set the JAVA_HOME variable in your environment to match the
46 | echo location of your Java installation.
47 |
48 | goto fail
49 |
50 | :findJavaFromJavaHome
51 | set JAVA_HOME=%JAVA_HOME:"=%
52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
53 |
54 | if exist "%JAVA_EXE%" goto init
55 |
56 | echo.
57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
58 | echo.
59 | echo Please set the JAVA_HOME variable in your environment to match the
60 | echo location of your Java installation.
61 |
62 | goto fail
63 |
64 | :init
65 | @rem Get command-line arguments, handling Windows variants
66 |
67 | if not "%OS%" == "Windows_NT" goto win9xME_args
68 |
69 | :win9xME_args
70 | @rem Slurp the command line arguments.
71 | set CMD_LINE_ARGS=
72 | set _SKIP=2
73 |
74 | :win9xME_args_slurp
75 | if "x%~1" == "x" goto execute
76 |
77 | set CMD_LINE_ARGS=%*
78 |
79 | :execute
80 | @rem Setup the command line
81 |
82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
83 |
84 | @rem Execute Gradle
85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
86 |
87 | :end
88 | @rem End local scope for the variables with windows NT shell
89 | if "%ERRORLEVEL%"=="0" goto mainEnd
90 |
91 | :fail
92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
93 | rem the _cmd.exe /c_ return code!
94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
95 | exit /b 1
96 |
97 | :mainEnd
98 | if "%OS%"=="Windows_NT" endlocal
99 |
100 | :omega
101 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------