├── .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 | 9 | 10 | 11 | 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 | 137 |
138 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 3 | 9 | 16 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/menu/profile_manager_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 11 | 18 | 25 | 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 | 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 | --------------------------------------------------------------------------------