├── .gitignore ├── .gitmodules ├── .jvmopts ├── .travis.yml ├── AUTHORS ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.sbt ├── build.sh ├── deploy ├── gfwlist ├── gen.pl └── gen.py ├── kcptun └── make.bash ├── local.properties.example ├── local.properties.travis ├── project └── plugins.sbt ├── shadow-notify.png ├── shadow.png ├── src └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── com │ │ └── github │ │ └── shadowsocks │ │ └── aidl │ │ ├── IShadowsocksService.aidl │ │ └── IShadowsocksServiceCallback.aidl │ ├── assets │ ├── fonts │ │ └── Iceland.ttf │ └── pages │ │ └── about.html │ ├── java │ └── com │ │ └── github │ │ └── shadowsocks │ │ └── System.java │ ├── jni │ ├── Android.mk │ ├── Application.mk │ ├── include │ │ ├── libev │ │ │ └── config.h │ │ ├── pdnsd │ │ │ └── config.h │ │ ├── shadowsocks-libev │ │ │ └── config.h │ │ └── sodium │ │ │ └── version.h │ └── system.cpp │ ├── res │ ├── drawable-anydpi-v21 │ │ ├── ic_action_settings.xml │ │ └── ic_navigation_close.xml │ ├── drawable-hdpi │ │ ├── ic_action_settings.png │ │ ├── ic_launcher.png │ │ ├── ic_navigation_close.png │ │ └── ic_stat_shadowsocks.png │ ├── drawable-mdpi │ │ ├── ic_action_settings.png │ │ ├── ic_launcher.png │ │ ├── ic_navigation_close.png │ │ └── ic_stat_shadowsocks.png │ ├── drawable-v21 │ │ └── background_stat.xml │ ├── drawable-xhdpi │ │ ├── ic_action_settings.png │ │ ├── ic_launcher.png │ │ ├── ic_navigation_close.png │ │ └── ic_stat_shadowsocks.png │ ├── drawable-xxhdpi │ │ ├── ic_action_settings.png │ │ ├── ic_launcher.png │ │ ├── ic_navigation_close.png │ │ └── ic_stat_shadowsocks.png │ ├── drawable-xxxhdpi │ │ ├── ic_action_settings.png │ │ └── ic_navigation_close.png │ ├── drawable │ │ ├── background_stat.xml │ │ ├── ic_arrow_drop_down.xml │ │ ├── ic_content_add.xml │ │ ├── ic_content_copy.xml │ │ ├── ic_content_create.xml │ │ ├── ic_content_paste.xml │ │ ├── ic_device_nfc.xml │ │ ├── ic_done_all.xml │ │ ├── ic_image_camera_alt.xml │ │ ├── ic_social_share.xml │ │ ├── ic_start_busy.xml │ │ ├── ic_start_connected.xml │ │ └── ic_start_idle.xml │ ├── layout-w400dp │ │ └── layout_main_stat_title.xml │ ├── layout │ │ ├── layout_apps.xml │ │ ├── layout_apps_item.xml │ │ ├── layout_main.xml │ │ ├── layout_main_stat_title.xml │ │ ├── layout_profiles.xml │ │ ├── layout_profiles_item.xml │ │ ├── layout_quick_switch.xml │ │ ├── layout_scanner.xml │ │ ├── layout_tasker.xml │ │ └── toolbar_light_dark.xml │ ├── menu │ │ ├── app_manager_menu.xml │ │ └── profile_manager_menu.xml │ ├── raw │ │ └── gtm_default_container │ ├── values-ru-v21 │ │ └── strings.xml │ ├── values-ru │ │ └── strings.xml │ ├── values-v21 │ │ ├── dimen.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── values-zh-rCN-v21 │ │ └── strings.xml │ ├── values-zh-rCN │ │ └── strings.xml │ ├── values-zh-v21 │ │ └── strings.xml │ ├── values-zh │ │ └── strings.xml │ ├── values │ │ ├── arrays.xml │ │ ├── attrs.xml │ │ ├── chnroute.xml │ │ ├── colors.xml │ │ ├── configs.xml │ │ ├── dimen.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── xml │ │ ├── pref_all.xml │ │ └── tracker.xml │ └── scala │ └── com │ └── github │ └── shadowsocks │ ├── AppManager.scala │ ├── BaseService.scala │ ├── BootReceiver.scala │ ├── GuardedProcess.scala │ ├── ProfileManagerActivity.scala │ ├── ScannerActivity.scala │ ├── ServiceBoundContext.scala │ ├── Shadowsocks.scala │ ├── ShadowsocksApplication.scala │ ├── ShadowsocksBackupAgent.scala │ ├── ShadowsocksNatService.scala │ ├── ShadowsocksNotification.scala │ ├── ShadowsocksQuickSwitchActivity.scala │ ├── ShadowsocksRunnerActivity.scala │ ├── ShadowsocksRunnerService.scala │ ├── ShadowsocksSettings.scala │ ├── ShadowsocksTileService.scala │ ├── ShadowsocksVpnService.scala │ ├── ShadowsocksVpnThread.scala │ ├── TaskerActivity.scala │ ├── TaskerReceiver.scala │ ├── database │ ├── DBHelper.scala │ ├── Profile.scala │ └── ProfileManager.scala │ ├── preferences │ ├── DropDownPreference.scala │ ├── NumberPickerPreference.scala │ ├── PasswordEditTextPreference.scala │ ├── SummaryEditTextPreference.scala │ └── SummaryPreference.scala │ ├── utils │ ├── CloseUtils.scala │ ├── Constants.scala │ ├── Parser.scala │ ├── TaskerSettings.scala │ ├── TrafficMonitor.scala │ ├── TrafficMonitorThread.scala │ └── Utils.scala │ └── widget │ ├── FloatingActionMenuBehavior.scala │ └── UndoSnackbarManager.scala ├── travis-ci └── setup.sh └── travis.keystore /.gitignore: -------------------------------------------------------------------------------- 1 | classes 2 | bin 3 | gen 4 | target 5 | local.properties 6 | .classpath 7 | .project 8 | .settings 9 | tests/bin 10 | tests/gen 11 | tests/local.properties 12 | NUL 13 | gen_* 14 | *.class 15 | *.o 16 | local.sbt 17 | .deps 18 | 19 | #Intellij IDEA 20 | *.iml 21 | *.ipr 22 | *.iws 23 | .idea/ 24 | out/ 25 | .d 26 | 27 | #NDK 28 | src/main/obj 29 | src/main/libs 30 | src/main/assets/armeabi-v7a/* 31 | src/main/assets/x86/* 32 | jni/Application.mk 33 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/main/jni/badvpn"] 2 | path = src/main/jni/badvpn 3 | url = https://github.com/shadowsocks/badvpn.git 4 | branch = shadowsocks-android 5 | [submodule "src/main/jni/libancillary"] 6 | path = src/main/jni/libancillary 7 | url = https://github.com/shadowsocks/libancillary.git 8 | branch = shadowsocks-android 9 | [submodule "src/main/jni/openssl"] 10 | path = src/main/jni/openssl 11 | url = https://github.com/shadowsocks/openssl-android.git 12 | [submodule "src/main/jni/libevent"] 13 | path = src/main/jni/libevent 14 | url = https://github.com/shadowsocks/libevent.git 15 | branch = shadowsocks-android 16 | [submodule "src/main/jni/redsocks"] 17 | path = src/main/jni/redsocks 18 | url = https://github.com/shadowsocks/redsocks.git 19 | branch = shadowsocks-android 20 | [submodule "src/main/jni/pdnsd"] 21 | path = src/main/jni/pdnsd 22 | url = https://github.com/shadowsocks/pdnsd.git 23 | branch = shadowsocks-android 24 | [submodule "src/main/jni/shadowsocks-libev"] 25 | path = src/main/jni/shadowsocks-libev 26 | url = https://github.com/glzjin/shadowsocks-libev.git 27 | branch = master 28 | [submodule "kcptun/kcptun"] 29 | path = kcptun/kcptun 30 | url = https://github.com/shadowsocks/kcptun 31 | [submodule "kcptun/go"] 32 | path = kcptun/go 33 | url = https://github.com/shadowsocks/go 34 | -------------------------------------------------------------------------------- /.jvmopts: -------------------------------------------------------------------------------- 1 | -XX:MaxPermSize=512m 2 | -Xms1g 3 | -Xmx3g 4 | -Xss2m 5 | -XX:+CMSClassUnloadingEnabled 6 | -XX:+UseConcMarkSweepGC 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | sudo: false 7 | 8 | addons: 9 | apt: 10 | packages: 11 | - gcc-multilib 12 | - g++-multilib 13 | - libstdc++6:i386 14 | - libgcc1:i386 15 | - zlib1g:i386 16 | - libncurses5:i386 17 | 18 | scala: 19 | - 2.11.8 20 | 21 | before_cache: 22 | - find $HOME/.sbt -name "*.lock" | xargs rm 23 | - find $HOME/.ivy2 -name "*.lock" | xargs rm 24 | 25 | cache: 26 | directories: 27 | - $HOME/.ivy2/cache 28 | - $HOME/.sbt/boot 29 | 30 | install: 31 | - ./travis-ci/setup.sh 32 | - export NDK_CCACHE=ccache 33 | - export ARCH=`uname -m` 34 | - export GOROOT_BOOTSTRAP=$HOME/.android/go 35 | - export ANDROID_NDK_HOME=$HOME/.android/android-ndk-r12b 36 | - export ANDROID_HOME=$HOME/.android/android-sdk-linux 37 | - export PATH=${ANDROID_NDK_HOME}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools:${PATH} 38 | - cp local.properties.travis local.properties 39 | 40 | script: 41 | - sbt native-build android:package-release 42 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Shadowsocks-android was originally created in late 2013, by 2 | Max Lv . 3 | 4 | Here is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS -- 5 | people who have submitted patches, fixed bugs, added translations, and 6 | generally made shadowsocks-android that much better: 7 | 8 | https://github.com/shadowsocks/shadowsocks-android/graphs/contributors 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please check the [FAQ](https://github.com/shadowsocks/shadowsocks-android/wiki/FAQ) before submitting new issues. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Issue Feedback 2 | 3 | [https://goo.gl/forms/wcZaZmrcgewWIiPn2](https://goo.gl/forms/wcZaZmrcgewWIiPn2) 4 | 5 | ## Shadowsocks R for Android 6 | 7 | A [shadowsocks R](https://github.com/breakwa11/shadowsocks-rss/) client for Android, written in Scala. 8 | 9 | 10 | 11 | ### CI STATUS 12 | 13 | [![Build Status](https://api.travis-ci.org/shadowsocks/shadowsocks-android.svg)](https://travis-ci.org/shadowsocks/shadowsocks-android) 14 | 15 | ### PREREQUISITES 16 | 17 | * JDK 1.8 18 | * SBT 0.13.0+ 19 | * GO 1.4+ 20 | * Android SDK r25+ 21 | * Android NDK r12b+ 22 | 23 | ### BUILD 24 | 25 | * Set environment variable `ANDROID_HOME` to `/path/to/android-sdk` 26 | * Set environment variable `ANDROID_NDK_HOME` to `/path/to/android-ndk` 27 | * Set environment variable `GOROOT_BOOTSTRAP` to `/path/to/go` 28 | * Create your key following the instructions at https://developer.android.com/studio/publish/app-signing.html 29 | * Put your key in ~/.keystore 30 | * Create `local.properties` from `local.properties.example` with your own key information 31 | * Invoke the building like this 32 | 33 | ```bash 34 | git submodule update --init 35 | 36 | # Build the App 37 | sbt native-build clean android:package-release 38 | ``` 39 | 40 | ## OPEN SOURCE LICENSES 41 | 42 | * shadowsocks-libev: [GPLv3](https://github.com/shadowsocks/shadowsocks-libev/blob/master/LICENSE) 43 | * tun2socks: [BSD](https://github.com/shadowsocks/badvpn/blob/shadowsocks-android/COPYING) 44 | * redsocks: [APL 2.0](https://github.com/shadowsocks/redsocks/blob/master/README) 45 | * OpenSSL: [OpenSSL](https://github.com/shadowsocks/openssl-android/blob/master/NOTICE) 46 | * pdnsd: [GPLv3](https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/pdnsd/COPYING) 47 | * libev: [GPLv2](https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/libev/LICENSE) 48 | * libevent: [BSD](https://github.com/shadowsocks/libevent/blob/master/LICENSE) 49 | * kcptun: [MIT](https://github.com/xtaci/kcptun) 50 | 51 | ### LICENSE 52 | 53 | Copyright (C) 2016 by Max Lv <>
54 | Copyright (C) 2016 by Mygod Studio <> 55 | 56 | This program is free software: you can redistribute it and/or modify 57 | it under the terms of the GNU General Public License as published by 58 | the Free Software Foundation, either version 3 of the License, or 59 | (at your option) any later version. 60 | 61 | This program is distributed in the hope that it will be useful, 62 | but WITHOUT ANY WARRANTY; without even the implied warranty of 63 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 64 | GNU General Public License for more details. 65 | 66 | You should have received a copy of the GNU General Public License 67 | along with this program. If not, see . 68 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import android.Keys._ 2 | 3 | android.Plugin.androidBuild 4 | 5 | platformTarget in Android := "android-24" 6 | 7 | name := "shadowsocksr" 8 | 9 | applicationId := "in.zhaoj.shadowsocksr" 10 | 11 | scalaVersion := "2.11.8" 12 | 13 | compileOrder in Compile := CompileOrder.JavaThenScala 14 | 15 | javacOptions ++= Seq("-source", "1.6", "-target", "1.6") 16 | 17 | scalacOptions ++= Seq("-target:jvm-1.6", "-Xexperimental") 18 | 19 | ndkJavah in Android := List() 20 | 21 | ndkBuild in Android := List() 22 | 23 | shrinkResources in Android := true 24 | 25 | typedResources in Android := false 26 | 27 | resConfigs in Android := Seq("ru", "zh", "zh-rCN") 28 | 29 | dexMaxHeap in Android := "4g" 30 | 31 | resolvers += Resolver.jcenterRepo 32 | 33 | resolvers += "JRAF" at "http://JRAF.org/static/maven/2" 34 | 35 | useSupportVectors 36 | 37 | libraryDependencies ++= Seq( 38 | "dnsjava" % "dnsjava" % "2.1.7", 39 | "com.github.kevinsawicki" % "http-request" % "6.0", 40 | "eu.chainfire" % "libsuperuser" % "1.0.0.201607041850", 41 | "net.glxn.qrgen" % "android" % "2.0", 42 | 43 | //"com.google.android.gms" % "play-services-ads" % "9.2.1", 44 | "com.google.android.gms" % "play-services-analytics" % "9.2.1", 45 | "com.android.support" % "design" % "24.1.0", 46 | "com.android.support" % "gridlayout-v7" % "24.1.0", 47 | "com.android.support" % "cardview-v7" % "24.1.0", 48 | 49 | "com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01", 50 | "com.j256.ormlite" % "ormlite-android" % "4.48", 51 | "com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2", 52 | "com.github.clans" % "fab" % "1.6.4", 53 | "me.dm7.barcodescanner" % "zxing" % "1.8.4" 54 | ) 55 | 56 | proguardVersion in Android := "5.2.1" 57 | 58 | proguardCache in Android := Seq() 59 | 60 | proguardOptions in Android ++= Seq( 61 | "-keep class com.github.shadowsocks.System { *; }", 62 | "-dontwarn com.google.android.gms.**", 63 | "-keep class com.google.android.gms.**", 64 | "-dontnote com.google.android.gms.internal.**", 65 | "-dontnote com.j256.ormlite.**", 66 | "-dontnote me.dm7.barcodescannerview.**", 67 | "-dontnote org.xbill.**", 68 | "-dontwarn org.xbill.**") 69 | 70 | lazy val nativeBuild = TaskKey[Unit]("native-build", "Build native executables") 71 | 72 | nativeBuild := { 73 | val logger = streams.value.log 74 | Process("./build.sh") ! logger match { 75 | case 0 => // Success! 76 | case n => sys.error(s"Native build script exit code: $n") 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function try () { 4 | "$@" || exit -1 5 | } 6 | 7 | # Build native binaries 8 | pushd src/main 9 | try $ANDROID_NDK_HOME/ndk-build -j8 10 | 11 | rm -rf assets/armeabi-v7a 12 | rm -rf assets/x86 13 | mkdir -p assets/armeabi-v7a 14 | mkdir -p assets/x86 15 | 16 | #copy executables 17 | for app in pdnsd redsocks ss-local ss-tunnel tun2socks 18 | do 19 | echo $app 20 | try mv libs/armeabi-v7a/$app assets/armeabi-v7a/ 21 | try mv libs/x86/$app assets/x86/ 22 | done 23 | popd 24 | 25 | # Build kcptun 26 | pushd kcptun 27 | try ./make.bash 28 | popd 29 | -------------------------------------------------------------------------------- /deploy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd ${1} 3 | travis-artifacts upload --path ${2} --target-path nightly 4 | -------------------------------------------------------------------------------- /gfwlist/gen.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | ## ArchLinux install package via pacman: perl-net-cidr-lite 3 | use strict; 4 | use warnings; 5 | use Net::CIDR::Lite; 6 | my $cidr = Net::CIDR::Lite->new; 7 | while (my $line=<>) { 8 | $cidr->add($line); 9 | } 10 | foreach my $line( @{$cidr->list} ) { 11 | print "$line\n"; 12 | } 13 | -------------------------------------------------------------------------------- /gfwlist/gen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- encoding: utf8 -*- 3 | 4 | import itertools 5 | import math 6 | import sys 7 | 8 | import IPy 9 | 10 | 11 | def main(): 12 | china_list_set = IPy.IPSet() 13 | for line in sys.stdin: 14 | china_list_set.add(IPy.IP(line)) 15 | 16 | # 输出结果 17 | for ip in china_list_set: 18 | print '' + str(ip) + '' 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /kcptun/make.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function try () { 4 | "$@" || exit -1 5 | } 6 | 7 | [ -z "$ANDROID_NDK_HOME" ] && ANDROID_NDK_HOME=~/android-ndk-r12b 8 | 9 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 10 | DEPS=$DIR/.deps 11 | ANDROID_ARM_TOOLCHAIN=$DEPS/android-toolchain-16-arm 12 | ANDROID_X86_TOOLCHAIN=$DEPS/android-toolchain-16-x86 13 | 14 | ANDROID_ARM_CC=$ANDROID_ARM_TOOLCHAIN/bin/arm-linux-androideabi-gcc 15 | ANDROID_ARM_STRIP=$ANDROID_ARM_TOOLCHAIN/bin/arm-linux-androideabi-strip 16 | 17 | ANDROID_X86_CC=$ANDROID_X86_TOOLCHAIN/bin/i686-linux-android-gcc 18 | ANDROID_X86_STRIP=$ANDROID_X86_TOOLCHAIN/bin/i686-linux-android-strip 19 | 20 | 21 | if [ ! -d "$DEPS" ]; then 22 | mkdir -p $DEPS 23 | fi 24 | 25 | if [ ! -d "$ANDROID_ARM_TOOLCHAIN" ]; then 26 | echo "Make standalone toolchain for ARM arch" 27 | $ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py --arch arm \ 28 | --api 16 --install-dir $ANDROID_ARM_TOOLCHAIN 29 | fi 30 | 31 | if [ ! -d "$ANDROID_X86_TOOLCHAIN" ]; then 32 | echo "Make standalone toolchain for X86 arch" 33 | $ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py --arch x86 \ 34 | --api 16 --install-dir $ANDROID_X86_TOOLCHAIN 35 | fi 36 | 37 | if [ ! -d "$DIR/go/bin" ]; then 38 | echo "Build the custom go" 39 | 40 | pushd $DIR/go/src 41 | try ./make.bash 42 | popd 43 | fi 44 | 45 | export GOROOT=$DIR/go 46 | export GOPATH=$DEPS 47 | export PATH=$GOROOT/bin:$PATH 48 | 49 | pushd kcptun/client 50 | 51 | echo "Get dependences for kcptun" 52 | go get 53 | 54 | echo "Cross compile kcptun for arm" 55 | try env CGO_ENABLED=1 CC=$ANDROID_ARM_CC GOOS=android GOARCH=arm GOARM=7 go build 56 | try $ANDROID_ARM_STRIP client 57 | try mv client $DIR/../src/main/assets/armeabi-v7a/kcptun 58 | 59 | echo "Cross compile kcptun for x86" 60 | try env CGO_ENABLED=1 CC=$ANDROID_X86_CC GOOS=android GOARCH=386 go build 61 | try $ANDROID_X86_STRIP client 62 | try mv client $DIR/../src/main/assets/x86/kcptun 63 | popd 64 | 65 | echo "Successfully build kcptun" 66 | -------------------------------------------------------------------------------- /local.properties.example: -------------------------------------------------------------------------------- 1 | key.alias: your_key_alias 2 | key.store: /path/to/your/key/store 3 | key.store.password: your_key_password 4 | -------------------------------------------------------------------------------- /local.properties.travis: -------------------------------------------------------------------------------- 1 | key.alias: travis 2 | key.store: travis.keystore 3 | key.store.password: travis 4 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-android" % "sbt-android" % "1.6.10") 2 | 3 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10") 4 | 5 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.8.2") 6 | -------------------------------------------------------------------------------- /shadow-notify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/shadow-notify.png -------------------------------------------------------------------------------- /shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/shadow.png -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 16 | 18 | 20 | 21 | 24 | 25 | 34 | 35 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 63 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 104 | 105 | 111 | 112 | 114 | 115 | 116 | 117 | 118 | 119 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 133 | 134 | 135 | 139 | 140 | 141 | 147 | 148 | 149 | 150 | 151 | 152 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl: -------------------------------------------------------------------------------- 1 | package com.github.shadowsocks.aidl; 2 | 3 | import com.github.shadowsocks.aidl.IShadowsocksServiceCallback; 4 | 5 | interface IShadowsocksService { 6 | int getState(); 7 | 8 | oneway void registerCallback(IShadowsocksServiceCallback cb); 9 | oneway void unregisterCallback(IShadowsocksServiceCallback cb); 10 | 11 | oneway void use(in int profileId); 12 | void useSync(in int profileId); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksServiceCallback.aidl: -------------------------------------------------------------------------------- 1 | package com.github.shadowsocks.aidl; 2 | 3 | interface IShadowsocksServiceCallback { 4 | oneway void stateChanged(int state, String msg); 5 | oneway void trafficUpdated(long txRate, long rxRate, long txTotal, long rxTotal); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/assets/fonts/Iceland.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/assets/fonts/Iceland.ttf -------------------------------------------------------------------------------- /src/main/java/com/github/shadowsocks/System.java: -------------------------------------------------------------------------------- 1 | /* Shadowsocks - A shadowsocks client for Android 2 | * Copyright (C) 2012 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | * 17 | * 18 | * ___====-_ _-====___ 19 | * _--^^^#####// \\#####^^^--_ 20 | * _-^##########// ( ) \\##########^-_ 21 | * -############// |\^^/| \\############- 22 | * _/############// (@::@) \\############\_ 23 | * /#############(( \\// ))#############\ 24 | * -###############\\ (oo) //###############- 25 | * -#################\\ / VV \ //#################- 26 | * -###################\\/ \//###################- 27 | * _#/|##########/\######( /\ )######/\##########|\#_ 28 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 29 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 30 | * ` ` ` ` / | | | | \ ' ' ' ' 31 | * ( | | | | ) 32 | * __\ | | | | /__ 33 | * (vvv(VVV)(VVV)vvv) 34 | * 35 | * HERE BE DRAGONS 36 | * 37 | */ 38 | 39 | package com.github.shadowsocks; 40 | 41 | public class System { 42 | static { 43 | java.lang.System.loadLibrary("system"); 44 | } 45 | 46 | public static native int exec(String cmd); 47 | public static native String getABI(); 48 | public static native int sendfd(int fd, String path); 49 | public static native void jniclose(int fd); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/jni/Application.mk: -------------------------------------------------------------------------------- 1 | APP_ABI := armeabi-v7a x86 2 | APP_PLATFORM := android-16 3 | APP_STL := stlport_static 4 | NDK_TOOLCHAIN_VERSION := clang 5 | -------------------------------------------------------------------------------- /src/main/jni/include/libev/config.h: -------------------------------------------------------------------------------- 1 | /* config.h. Generated from config.h.in by configure. */ 2 | /* config.h.in. Generated from configure.ac by autoheader. */ 3 | 4 | /* Define to 1 if you have the `clock_gettime' function. */ 5 | /* #undef HAVE_CLOCK_GETTIME */ 6 | 7 | /* Define to 1 to use the syscall interface for clock_gettime */ 8 | #define HAVE_CLOCK_SYSCALL 1 9 | 10 | /* Define to 1 if you have the header file. */ 11 | #define HAVE_DLFCN_H 1 12 | 13 | /* Define to 1 if you have the `epoll_ctl' function. */ 14 | #define HAVE_EPOLL_CTL 1 15 | 16 | /* Define to 1 if you have the `eventfd' function. */ 17 | #define HAVE_EVENTFD 1 18 | 19 | /* Define to 1 if the floor function is available */ 20 | #define HAVE_FLOOR 1 21 | 22 | /* Define to 1 if you have the `inotify_init' function. */ 23 | #define HAVE_INOTIFY_INIT 1 24 | 25 | /* Define to 1 if you have the header file. */ 26 | #define HAVE_INTTYPES_H 1 27 | 28 | /* Define to 1 if you have the `kqueue' function. */ 29 | /* #undef HAVE_KQUEUE */ 30 | 31 | /* Define to 1 if you have the `rt' library (-lrt). */ 32 | /* #undef HAVE_LIBRT */ 33 | 34 | /* Define to 1 if you have the header file. */ 35 | #define HAVE_MEMORY_H 1 36 | 37 | /* Define to 1 if you have the `nanosleep' function. */ 38 | #define HAVE_NANOSLEEP 1 39 | 40 | /* Define to 1 if you have the `poll' function. */ 41 | #define HAVE_POLL 1 42 | 43 | /* Define to 1 if you have the header file. */ 44 | #define HAVE_POLL_H 1 45 | 46 | /* Define to 1 if you have the `port_create' function. */ 47 | /* #undef HAVE_PORT_CREATE */ 48 | 49 | /* Define to 1 if you have the header file. */ 50 | /* #undef HAVE_PORT_H */ 51 | 52 | /* Define to 1 if you have the `select' function. */ 53 | #define HAVE_SELECT 1 54 | 55 | /* Define to 1 if you have the `signalfd' function. */ 56 | #define HAVE_SIGNALFD 0 57 | 58 | /* Define to 1 if you have the header file. */ 59 | #define HAVE_STDINT_H 1 60 | 61 | /* Define to 1 if you have the header file. */ 62 | #define HAVE_STDLIB_H 1 63 | 64 | /* Define to 1 if you have the header file. */ 65 | #define HAVE_STRINGS_H 1 66 | 67 | /* Define to 1 if you have the header file. */ 68 | #define HAVE_STRING_H 1 69 | 70 | /* Define to 1 if you have the header file. */ 71 | #define HAVE_SYS_EPOLL_H 1 72 | 73 | /* Define to 1 if you have the header file. */ 74 | #define HAVE_SYS_EVENTFD_H 1 75 | 76 | /* Define to 1 if you have the header file. */ 77 | /* #undef HAVE_SYS_EVENT_H */ 78 | 79 | /* Define to 1 if you have the header file. */ 80 | #define HAVE_SYS_INOTIFY_H 1 81 | 82 | /* Define to 1 if you have the header file. */ 83 | #define HAVE_SYS_SELECT_H 1 84 | 85 | /* Define to 1 if you have the header file. */ 86 | #define HAVE_SYS_SIGNALFD_H 1 87 | 88 | /* Define to 1 if you have the header file. */ 89 | #define HAVE_SYS_STAT_H 1 90 | 91 | /* Define to 1 if you have the header file. */ 92 | #define HAVE_SYS_TYPES_H 1 93 | 94 | /* Define to 1 if you have the header file. */ 95 | #define HAVE_UNISTD_H 1 96 | 97 | /* Define to the sub-directory in which libtool stores uninstalled libraries. 98 | */ 99 | #define LT_OBJDIR ".libs/" 100 | 101 | /* Name of package */ 102 | #define PACKAGE "libev" 103 | 104 | /* Define to the address where bug reports for this package should be sent. */ 105 | #define PACKAGE_BUGREPORT "" 106 | 107 | /* Define to the full name of this package. */ 108 | #define PACKAGE_NAME "" 109 | 110 | /* Define to the full name and version of this package. */ 111 | #define PACKAGE_STRING "" 112 | 113 | /* Define to the one symbol short name of this package. */ 114 | #define PACKAGE_TARNAME "" 115 | 116 | /* Define to the home page for this package. */ 117 | #define PACKAGE_URL "" 118 | 119 | /* Define to the version of this package. */ 120 | #define PACKAGE_VERSION "" 121 | 122 | /* Define to 1 if you have the ANSI C header files. */ 123 | #define STDC_HEADERS 1 124 | 125 | /* Version number of package */ 126 | #define VERSION "4.11" 127 | -------------------------------------------------------------------------------- /src/main/jni/include/sodium/version.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef sodium_version_H 3 | #define sodium_version_H 4 | 5 | #include "export.h" 6 | 7 | #define SODIUM_VERSION_STRING "1.0.7" 8 | 9 | #define SODIUM_LIBRARY_VERSION_MAJOR 9 10 | #define SODIUM_LIBRARY_VERSION_MINOR 0 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | SODIUM_EXPORT 17 | const char *sodium_version_string(void); 18 | 19 | SODIUM_EXPORT 20 | int sodium_library_version_major(void); 21 | 22 | SODIUM_EXPORT 23 | int sodium_library_version_minor(void); 24 | 25 | #ifdef __cplusplus 26 | } 27 | #endif 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/main/jni/system.cpp: -------------------------------------------------------------------------------- 1 | #define LOG_TAG "Shadowsocks" 2 | 3 | #include "jni.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0) 18 | #define LOGW(...) do { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); } while(0) 19 | #define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0) 20 | 21 | jstring Java_com_github_shadowsocks_system_getabi(JNIEnv *env, jobject thiz) { 22 | AndroidCpuFamily family = android_getCpuFamily(); 23 | uint64_t features = android_getCpuFeatures(); 24 | const char *abi; 25 | 26 | if (family == ANDROID_CPU_FAMILY_X86) { 27 | abi = "x86"; 28 | } else if (family == ANDROID_CPU_FAMILY_MIPS) { 29 | abi = "mips"; 30 | } else if (family == ANDROID_CPU_FAMILY_ARM) { 31 | // if (features & ANDROID_CPU_ARM_FEATURE_ARMv7) { 32 | abi = "armeabi-v7a"; 33 | // } else { 34 | // abi = "armeabi"; 35 | // } 36 | } 37 | return env->NewStringUTF(abi); 38 | } 39 | 40 | jint Java_com_github_shadowsocks_system_exec(JNIEnv *env, jobject thiz, jstring cmd) { 41 | const char *cmd_str = env->GetStringUTFChars(cmd, 0); 42 | 43 | pid_t pid; 44 | 45 | /* Fork off the parent process */ 46 | pid = fork(); 47 | if (pid < 0) { 48 | env->ReleaseStringUTFChars(cmd, cmd_str); 49 | return -1; 50 | } 51 | 52 | if (pid > 0) { 53 | env->ReleaseStringUTFChars(cmd, cmd_str); 54 | return pid; 55 | } 56 | 57 | execl("/system/bin/sh", "sh", "-c", cmd_str, NULL); 58 | env->ReleaseStringUTFChars(cmd, cmd_str); 59 | 60 | return 1; 61 | } 62 | 63 | void Java_com_github_shadowsocks_system_jniclose(JNIEnv *env, jobject thiz, jint fd) { 64 | close(fd); 65 | } 66 | 67 | jint Java_com_github_shadowsocks_system_sendfd(JNIEnv *env, jobject thiz, jint tun_fd, jstring path) { 68 | int fd; 69 | struct sockaddr_un addr; 70 | const char *sock_str = env->GetStringUTFChars(path, 0); 71 | 72 | if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { 73 | LOGE("socket() failed: %s (socket fd = %d)\n", strerror(errno), fd); 74 | return (jint)-1; 75 | } 76 | 77 | memset(&addr, 0, sizeof(addr)); 78 | addr.sun_family = AF_UNIX; 79 | strncpy(addr.sun_path, sock_str, sizeof(addr.sun_path)-1); 80 | 81 | if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { 82 | LOGE("connect() failed: %s (fd = %d)\n", strerror(errno), fd); 83 | close(fd); 84 | return (jint)-1; 85 | } 86 | 87 | if (ancil_send_fd(fd, tun_fd)) { 88 | LOGE("ancil_send_fd: %s", strerror(errno)); 89 | close(fd); 90 | return (jint)-1; 91 | } 92 | 93 | close(fd); 94 | env->ReleaseStringUTFChars(path, sock_str); 95 | return 0; 96 | } 97 | 98 | static const char *classPathName = "com/github/shadowsocks/System"; 99 | 100 | static JNINativeMethod method_table[] = { 101 | { "jniclose", "(I)V", 102 | (void*) Java_com_github_shadowsocks_system_jniclose }, 103 | { "sendfd", "(ILjava/lang/String;)I", 104 | (void*) Java_com_github_shadowsocks_system_sendfd }, 105 | { "exec", "(Ljava/lang/String;)I", 106 | (void*) Java_com_github_shadowsocks_system_exec }, 107 | { "getABI", "()Ljava/lang/String;", 108 | (void*) Java_com_github_shadowsocks_system_getabi } 109 | }; 110 | 111 | 112 | 113 | /* 114 | * Register several native methods for one class. 115 | */ 116 | static int registerNativeMethods(JNIEnv* env, const char* className, 117 | JNINativeMethod* gMethods, int numMethods) 118 | { 119 | jclass clazz; 120 | 121 | clazz = env->FindClass(className); 122 | if (clazz == NULL) { 123 | LOGE("Native registration unable to find class '%s'", className); 124 | return JNI_FALSE; 125 | } 126 | if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { 127 | LOGE("RegisterNatives failed for '%s'", className); 128 | return JNI_FALSE; 129 | } 130 | 131 | return JNI_TRUE; 132 | } 133 | 134 | /* 135 | * Register native methods for all classes we know about. 136 | * 137 | * returns JNI_TRUE on success. 138 | */ 139 | static int registerNatives(JNIEnv* env) 140 | { 141 | if (!registerNativeMethods(env, classPathName, method_table, 142 | sizeof(method_table) / sizeof(method_table[0]))) { 143 | return JNI_FALSE; 144 | } 145 | 146 | return JNI_TRUE; 147 | } 148 | 149 | /* 150 | * This is called by the VM when the shared library is first loaded. 151 | */ 152 | 153 | typedef union { 154 | JNIEnv* env; 155 | void* venv; 156 | } UnionJNIEnvToVoid; 157 | 158 | jint JNI_OnLoad(JavaVM* vm, void* reserved) { 159 | UnionJNIEnvToVoid uenv; 160 | uenv.venv = NULL; 161 | jint result = -1; 162 | JNIEnv* env = NULL; 163 | 164 | LOGI("JNI_OnLoad"); 165 | 166 | if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) { 167 | LOGE("ERROR: GetEnv failed"); 168 | goto bail; 169 | } 170 | env = uenv.env; 171 | 172 | if (registerNatives(env) != JNI_TRUE) { 173 | LOGE("ERROR: registerNatives failed"); 174 | goto bail; 175 | } 176 | 177 | result = JNI_VERSION_1_4; 178 | 179 | bail: 180 | return result; 181 | } 182 | -------------------------------------------------------------------------------- /src/main/res/drawable-anydpi-v21/ic_action_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/res/drawable-anydpi-v21/ic_navigation_close.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/res/drawable-hdpi/ic_action_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-hdpi/ic_action_settings.png -------------------------------------------------------------------------------- /src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/drawable-hdpi/ic_navigation_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-hdpi/ic_navigation_close.png -------------------------------------------------------------------------------- /src/main/res/drawable-hdpi/ic_stat_shadowsocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-hdpi/ic_stat_shadowsocks.png -------------------------------------------------------------------------------- /src/main/res/drawable-mdpi/ic_action_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-mdpi/ic_action_settings.png -------------------------------------------------------------------------------- /src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/drawable-mdpi/ic_navigation_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-mdpi/ic_navigation_close.png -------------------------------------------------------------------------------- /src/main/res/drawable-mdpi/ic_stat_shadowsocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-mdpi/ic_stat_shadowsocks.png -------------------------------------------------------------------------------- /src/main/res/drawable-v21/background_stat.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/res/drawable-xhdpi/ic_action_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-xhdpi/ic_action_settings.png -------------------------------------------------------------------------------- /src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/drawable-xhdpi/ic_navigation_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-xhdpi/ic_navigation_close.png -------------------------------------------------------------------------------- /src/main/res/drawable-xhdpi/ic_stat_shadowsocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-xhdpi/ic_stat_shadowsocks.png -------------------------------------------------------------------------------- /src/main/res/drawable-xxhdpi/ic_action_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-xxhdpi/ic_action_settings.png -------------------------------------------------------------------------------- /src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src/main/res/drawable-xxhdpi/ic_navigation_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-xxhdpi/ic_navigation_close.png -------------------------------------------------------------------------------- /src/main/res/drawable-xxhdpi/ic_stat_shadowsocks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-xxhdpi/ic_stat_shadowsocks.png -------------------------------------------------------------------------------- /src/main/res/drawable-xxxhdpi/ic_action_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-xxxhdpi/ic_action_settings.png -------------------------------------------------------------------------------- /src/main/res/drawable-xxxhdpi/ic_navigation_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/drawable-xxxhdpi/ic_navigation_close.png -------------------------------------------------------------------------------- /src/main/res/drawable/background_stat.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_arrow_drop_down.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_content_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_content_copy.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_content_create.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_content_paste.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_device_nfc.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_done_all.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_image_camera_alt.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_social_share.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_start_busy.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 15 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_start_connected.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 14 | -------------------------------------------------------------------------------- /src/main/res/drawable/ic_start_idle.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 16 | 19 | 22 | 25 | -------------------------------------------------------------------------------- /src/main/res/layout-w400dp/layout_main_stat_title.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/res/layout/layout_apps.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 28 | 40 | 41 | 44 | 48 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/main/res/layout/layout_apps_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 18 | 19 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/res/layout/layout_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 17 | 18 | 20 | 24 | 28 | 32 | 36 | 40 | 44 | 47 | 51 | 52 | 53 | 54 | 57 | 68 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/main/res/layout/layout_main_stat_title.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/res/layout/layout_profiles.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 13 | 25 | 26 | 36 | 46 | 56 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/main/res/layout/layout_profiles_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 30 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/res/layout/layout_quick_switch.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/res/layout/layout_scanner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/res/layout/layout_tasker.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 17 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/res/layout/toolbar_light_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | -------------------------------------------------------------------------------- /src/main/res/menu/app_manager_menu.xml: -------------------------------------------------------------------------------- 1 | 3 | 9 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/res/menu/profile_manager_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /src/main/res/raw/gtm_default_container: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/src/main/res/raw/gtm_default_container -------------------------------------------------------------------------------- /src/main/res/values-ru-v21/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Установить прокси для выбранных приложений 4 | 5 | -------------------------------------------------------------------------------- /src/main/res/values-v21/dimen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -28dp 4 | 5 | -------------------------------------------------------------------------------- /src/main/res/values-v21/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Set proxy for selected apps 4 | 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/main/res/xml/pref_all.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 12 | 17 | 24 | 31 | 37 | 44 | 51 | 58 | 63 | 64 | 65 | 66 | 68 | 69 | 76 | 81 | 86 | 91 | 96 | 97 | 98 | 100 | 101 | 106 | 107 | 114 | 115 | 120 | 121 | 122 | 123 | 124 | 125 | 130 | 131 | 134 | 136 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/main/res/xml/tracker.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 604800 5 | 6 | 7 | true 8 | 9 | 10 | true 11 | 12 | 13 | 14 | Shadowsocks ScreenView 15 | 16 | 17 | 18 | Shadowsocks EcommerceView 19 | 20 | 21 | 22 | UA-37082941-1 23 | 24 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/BootReceiver.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2014 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | 40 | package com.github.shadowsocks 41 | 42 | import android.content.pm.PackageManager 43 | import android.content.{BroadcastReceiver, ComponentName, Context, Intent} 44 | import com.github.shadowsocks.utils._ 45 | 46 | object BootReceiver { 47 | def getEnabled(context: Context) = PackageManager.COMPONENT_ENABLED_STATE_ENABLED == 48 | context.getPackageManager.getComponentEnabledSetting(new ComponentName(context, classOf[BootReceiver])) 49 | def setEnabled(context: Context, enabled: Boolean) = context.getPackageManager.setComponentEnabledSetting( 50 | new ComponentName(context, classOf[BootReceiver]), 51 | if (enabled) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 52 | PackageManager.DONT_KILL_APP) 53 | } 54 | 55 | class BootReceiver extends BroadcastReceiver { 56 | def onReceive(context: Context, intent: Intent) = Utils.startSsService(context) 57 | } 58 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/GuardedProcess.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2016 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | 40 | package com.github.shadowsocks 41 | 42 | import java.io.{IOException, InputStream, OutputStream} 43 | import java.lang.System.currentTimeMillis 44 | import java.util.concurrent.Semaphore 45 | 46 | import android.util.Log 47 | 48 | import scala.collection.JavaConversions._ 49 | 50 | /** 51 | * @author ayanamist@gmail.com 52 | */ 53 | class GuardedProcess(cmd: Seq[String]) extends Process { 54 | private val TAG = classOf[GuardedProcess].getSimpleName 55 | 56 | @volatile private var guardThread: Thread = _ 57 | @volatile private var isDestroyed: Boolean = _ 58 | @volatile private var process: Process = _ 59 | @volatile private var isRestart = false 60 | 61 | def start(onRestartCallback: () => Unit = null): GuardedProcess = { 62 | val semaphore = new Semaphore(1) 63 | semaphore.acquire 64 | @volatile var ioException: IOException = null 65 | 66 | guardThread = new Thread(() => { 67 | try { 68 | var callback: () => Unit = null 69 | while (!isDestroyed) { 70 | Log.i(TAG, "start process: " + cmd) 71 | val startTime = currentTimeMillis 72 | 73 | process = new ProcessBuilder(cmd).redirectErrorStream(true).start 74 | 75 | if (callback == null) callback = onRestartCallback else callback() 76 | 77 | semaphore.release 78 | process.waitFor 79 | 80 | if (isRestart) { 81 | isRestart = false 82 | } else { 83 | if (currentTimeMillis - startTime < 1000) { 84 | Log.w(TAG, "process exit too fast, stop guard: " + cmd) 85 | isDestroyed = true 86 | } 87 | } 88 | } 89 | } catch { 90 | case ignored: InterruptedException => 91 | Log.i(TAG, "thread interrupt, destroy process: " + cmd) 92 | process.destroy() 93 | case e: IOException => ioException = e 94 | } finally semaphore.release 95 | }, "GuardThread-" + cmd) 96 | 97 | guardThread.start() 98 | semaphore.acquire 99 | 100 | if (ioException != null) { 101 | throw ioException 102 | } 103 | 104 | this 105 | } 106 | 107 | def destroy() { 108 | isDestroyed = true 109 | guardThread.interrupt() 110 | process.destroy() 111 | try guardThread.join() catch { 112 | case ignored: InterruptedException => 113 | } 114 | } 115 | 116 | def restart() { 117 | isRestart = true 118 | process.destroy() 119 | } 120 | 121 | def exitValue: Int = throw new UnsupportedOperationException 122 | def getErrorStream: InputStream = throw new UnsupportedOperationException 123 | def getInputStream: InputStream = throw new UnsupportedOperationException 124 | def getOutputStream: OutputStream = throw new UnsupportedOperationException 125 | 126 | @throws(classOf[InterruptedException]) 127 | def waitFor = { 128 | guardThread.join() 129 | 0 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/ScannerActivity.scala: -------------------------------------------------------------------------------- 1 | package com.github.shadowsocks 2 | 3 | import android.Manifest 4 | import android.app.Activity 5 | import android.content.Intent 6 | import android.content.pm.PackageManager 7 | import android.os.Bundle 8 | import android.support.v4.app.ActivityCompat 9 | import android.support.v4.content.ContextCompat 10 | import android.support.v7.app.AppCompatActivity 11 | import android.support.v7.widget.Toolbar 12 | import android.view.{MenuItem, ViewGroup} 13 | import android.widget.Toast 14 | import com.google.zxing.Result 15 | import me.dm7.barcodescanner.zxing.ZXingScannerView 16 | 17 | class ScannerActivity extends AppCompatActivity with ZXingScannerView.ResultHandler { 18 | 19 | val MY_PERMISSIONS_REQUEST_CAMERA = 1 20 | 21 | var scannerView: ZXingScannerView = _ 22 | 23 | override def onRequestPermissionsResult(requestCode: Int, permissions: Array[String], 24 | grantResults: Array[Int]) { 25 | if (requestCode == MY_PERMISSIONS_REQUEST_CAMERA) { 26 | // If request is cancelled, the result arrays are empty. 27 | if (grantResults.length > 0 28 | && grantResults(0) == PackageManager.PERMISSION_GRANTED) { 29 | scannerView.setResultHandler(this) 30 | scannerView.startCamera() 31 | } else { 32 | Toast.makeText(this, R.string.add_profile_scanner_permission_required, Toast.LENGTH_SHORT).show() 33 | finish() 34 | } 35 | } 36 | } 37 | 38 | override def onCreate(state: Bundle) { 39 | super.onCreate(state) 40 | setContentView(R.layout.layout_scanner) 41 | setupToolbar() 42 | 43 | scannerView = new ZXingScannerView(this) 44 | val contentFrame = findViewById(R.id.content_frame).asInstanceOf[ViewGroup] 45 | contentFrame.addView(scannerView) 46 | } 47 | 48 | override def onResume() { 49 | super.onResume() 50 | val permissionCheck = ContextCompat.checkSelfPermission(this, 51 | Manifest.permission.CAMERA) 52 | if (permissionCheck == PackageManager.PERMISSION_GRANTED) { 53 | scannerView.setResultHandler(this) // Register ourselves as a handler for scan results. 54 | scannerView.startCamera() // Start camera on resume 55 | } else { 56 | ActivityCompat.requestPermissions(this, 57 | Array(Manifest.permission.CAMERA), MY_PERMISSIONS_REQUEST_CAMERA) 58 | } 59 | } 60 | 61 | override def onPause() { 62 | super.onPause() 63 | scannerView.stopCamera() // Stop camera on pause 64 | } 65 | 66 | override def handleResult(rawResult: Result) = { 67 | val intent = new Intent() 68 | intent.putExtra("uri", rawResult.getText) 69 | setResult(Activity.RESULT_OK, intent) 70 | finish() 71 | } 72 | 73 | def setupToolbar() { 74 | val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar] 75 | setSupportActionBar(toolbar) 76 | val ab = getSupportActionBar() 77 | if (ab != null) { 78 | ab.setDisplayHomeAsUpEnabled(true) 79 | } 80 | } 81 | 82 | override def onOptionsItemSelected(item: MenuItem): Boolean = { 83 | item.getItemId() match { 84 | // Respond to the action bar's Up/Home button 85 | case android.R.id.home => 86 | setResult(Activity.RESULT_CANCELED, new Intent()) 87 | finish() 88 | return true 89 | case _ => // Do nothing 90 | } 91 | return super.onOptionsItemSelected(item) 92 | } 93 | 94 | } 95 | 96 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala: -------------------------------------------------------------------------------- 1 | package com.github.shadowsocks 2 | 3 | import android.content.{ComponentName, Context, Intent, ServiceConnection} 4 | import android.os.{RemoteException, IBinder} 5 | import com.github.shadowsocks.aidl.{IShadowsocksServiceCallback, IShadowsocksService} 6 | import com.github.shadowsocks.utils.Action 7 | import com.github.shadowsocks.ShadowsocksApplication.app 8 | 9 | /** 10 | * @author Mygod 11 | */ 12 | trait ServiceBoundContext extends Context with IBinder.DeathRecipient { 13 | class ShadowsocksServiceConnection extends ServiceConnection { 14 | override def onServiceConnected(name: ComponentName, service: IBinder) { 15 | binder = service 16 | service.linkToDeath(ServiceBoundContext.this, 0) 17 | bgService = IShadowsocksService.Stub.asInterface(service) 18 | registerCallback 19 | ServiceBoundContext.this.onServiceConnected() 20 | } 21 | override def onServiceDisconnected(name: ComponentName) { 22 | unregisterCallback 23 | ServiceBoundContext.this.onServiceDisconnected() 24 | bgService = null 25 | binder = null 26 | } 27 | } 28 | 29 | protected def registerCallback = if (bgService != null && callback != null && !callbackRegistered) try { 30 | bgService.registerCallback(callback) 31 | callbackRegistered = true 32 | } catch { 33 | case ignored: RemoteException => // Nothing 34 | } 35 | 36 | protected def unregisterCallback = { 37 | if (bgService != null && callback != null && callbackRegistered) try bgService.unregisterCallback(callback) catch { 38 | case ignored: RemoteException => 39 | } 40 | callbackRegistered = false 41 | } 42 | 43 | def onServiceConnected() = () 44 | def onServiceDisconnected() = () 45 | override def binderDied = () 46 | 47 | private var callback: IShadowsocksServiceCallback.Stub = _ 48 | private var connection: ShadowsocksServiceConnection = _ 49 | private var callbackRegistered: Boolean = _ 50 | 51 | // Variables 52 | var binder: IBinder = _ 53 | var bgService: IShadowsocksService = _ 54 | 55 | def attachService(callback: IShadowsocksServiceCallback.Stub = null) { 56 | this.callback = callback 57 | if (bgService == null) { 58 | val s = if (app.isNatEnabled) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService] 59 | 60 | val intent = new Intent(this, s) 61 | intent.setAction(Action.SERVICE) 62 | 63 | connection = new ShadowsocksServiceConnection() 64 | bindService(intent, connection, Context.BIND_AUTO_CREATE) 65 | } 66 | } 67 | 68 | def detachService() { 69 | unregisterCallback 70 | callback = null 71 | if (connection != null) { 72 | try unbindService(connection) catch { 73 | case _: IllegalArgumentException => // ignore 74 | } 75 | connection = null 76 | } 77 | if (binder != null) { 78 | binder.unlinkToDeath(this, 0) 79 | binder = null 80 | } 81 | bgService = null 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2014 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | 40 | package com.github.shadowsocks 41 | 42 | import java.util 43 | import java.util.concurrent.TimeUnit 44 | 45 | import android.app.Application 46 | import android.content.pm.PackageManager 47 | import android.preference.PreferenceManager 48 | import android.support.v7.app.AppCompatDelegate 49 | import com.github.shadowsocks.database.{DBHelper, ProfileManager} 50 | import com.github.shadowsocks.utils.{Key, Utils} 51 | import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders} 52 | import com.google.android.gms.common.api.ResultCallback 53 | import com.google.android.gms.tagmanager.{ContainerHolder, TagManager} 54 | import com.j256.ormlite.logger.LocalLog 55 | 56 | object ShadowsocksApplication { 57 | var app: ShadowsocksApplication = _ 58 | } 59 | 60 | class ShadowsocksApplication extends Application { 61 | import ShadowsocksApplication._ 62 | 63 | final val SIG_FUNC = "getSignature" 64 | var containerHolder: ContainerHolder = _ 65 | lazy val tracker = GoogleAnalytics.getInstance(this).newTracker(R.xml.tracker) 66 | lazy val settings = PreferenceManager.getDefaultSharedPreferences(this) 67 | lazy val editor = settings.edit 68 | lazy val profileManager = new ProfileManager(new DBHelper(this)) 69 | 70 | def isNatEnabled = settings.getBoolean(Key.isNAT, false) 71 | def isVpnEnabled = !isNatEnabled 72 | 73 | def getVersionName = try { 74 | getPackageManager.getPackageInfo(getPackageName, 0).versionName 75 | } catch { 76 | case _: PackageManager.NameNotFoundException => "Package name not found" 77 | case _: Throwable => null 78 | } 79 | 80 | // send event 81 | def track(category: String, action: String) = tracker.send(new HitBuilders.EventBuilder() 82 | .setAction(action) 83 | .setLabel(getVersionName) 84 | .build()) 85 | 86 | def profileId = settings.getInt(Key.id, -1) 87 | def profileId(i: Int) = editor.putInt(Key.id, i).apply 88 | def currentProfile = profileManager.getProfile(profileId) 89 | 90 | def switchProfile(id: Int) = { 91 | profileId(id) 92 | profileManager.getProfile(id) getOrElse profileManager.createProfile() 93 | } 94 | 95 | override def onCreate() { 96 | java.lang.System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR") 97 | app = this 98 | AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) 99 | val tm = TagManager.getInstance(this) 100 | val pending = tm.loadContainerPreferNonDefault("GTM-NT8WS8", R.raw.gtm_default_container) 101 | val callback = new ResultCallback[ContainerHolder] { 102 | override def onResult(holder: ContainerHolder) { 103 | if (!holder.getStatus.isSuccess) { 104 | return 105 | } 106 | containerHolder = holder 107 | val container = holder.getContainer 108 | container.registerFunctionCallMacroCallback(SIG_FUNC, 109 | (functionName: String, parameters: util.Map[String, AnyRef]) => { 110 | if (functionName == SIG_FUNC) { 111 | Utils.getSignature(getApplicationContext) 112 | } 113 | null 114 | }) 115 | } 116 | } 117 | pending.setResultCallback(callback, 2, TimeUnit.SECONDS) 118 | } 119 | 120 | def refreshContainerHolder { 121 | val holder = app.containerHolder 122 | if (holder != null) holder.refresh() 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/ShadowsocksBackupAgent.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2014 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | 40 | package com.github.shadowsocks 41 | 42 | import android.app.backup.{BackupAgentHelper, FileBackupHelper, SharedPreferencesBackupHelper} 43 | import com.github.shadowsocks.database.DBHelper 44 | 45 | class ShadowsocksBackupAgent extends BackupAgentHelper { 46 | 47 | // The names of the SharedPreferences groups that the application maintains. These 48 | // are the same strings that are passed to getSharedPreferences(String, int). 49 | val PREFS_DISPLAY = "com.github.shadowsocks_preferences" 50 | 51 | // An arbitrary string used within the BackupAgentHelper implementation to 52 | // identify the SharedPreferencesBackupHelper's data. 53 | val MY_PREFS_BACKUP_KEY = "com.github.shadowsocks" 54 | 55 | val DATABASE = "com.github.shadowsocks.database.profile" 56 | 57 | override def onCreate() { 58 | val helper = new SharedPreferencesBackupHelper(this, PREFS_DISPLAY) 59 | addHelper(MY_PREFS_BACKUP_KEY, helper) 60 | addHelper(DATABASE, new FileBackupHelper(this, "../databases/" + DBHelper.PROFILE)) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala: -------------------------------------------------------------------------------- 1 | package com.github.shadowsocks 2 | 3 | import java.util.Locale 4 | 5 | import android.app.{KeyguardManager, NotificationManager, PendingIntent} 6 | import android.content.{BroadcastReceiver, Context, Intent, IntentFilter} 7 | import android.os.PowerManager 8 | import android.support.v4.app.NotificationCompat 9 | import android.support.v4.app.NotificationCompat.BigTextStyle 10 | import android.support.v4.content.ContextCompat 11 | import com.github.shadowsocks.aidl.IShadowsocksServiceCallback.Stub 12 | import com.github.shadowsocks.utils.{TrafficMonitor, Action, State, Utils} 13 | import com.github.shadowsocks.ShadowsocksApplication.app 14 | 15 | /** 16 | * @author Mygod 17 | */ 18 | class ShadowsocksNotification(private val service: BaseService, profileName: String, visible: Boolean = false) { 19 | private val keyGuard = service.getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager] 20 | private lazy val nm = service.getSystemService(Context.NOTIFICATION_SERVICE).asInstanceOf[NotificationManager] 21 | private lazy val callback = new Stub { 22 | override def stateChanged(state: Int, msg: String) = () // ignore 23 | override def trafficUpdated(txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { 24 | val txr = TrafficMonitor.formatTraffic(txRate) 25 | val rxr = TrafficMonitor.formatTraffic(rxRate) 26 | builder.setContentText(service.getString(R.string.traffic_summary).formatLocal(Locale.ENGLISH, txr, rxr)) 27 | style.bigText(service.getString(R.string.stat_summary).formatLocal(Locale.ENGLISH, txr, rxr, 28 | TrafficMonitor.formatTraffic(txTotal), TrafficMonitor.formatTraffic(rxTotal))) 29 | show() 30 | } 31 | } 32 | private var lockReceiver: BroadcastReceiver = _ 33 | private var callbackRegistered: Boolean = _ 34 | 35 | private val builder = new NotificationCompat.Builder(service) 36 | .setWhen(0) 37 | .setColor(ContextCompat.getColor(service, R.color.material_accent_500)) 38 | .setTicker(service.getString(R.string.forward_success)) 39 | .setContentTitle(profileName) 40 | .setContentIntent(PendingIntent.getActivity(service, 0, new Intent(service, classOf[Shadowsocks]) 41 | .setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), 0)) 42 | .setSmallIcon(R.drawable.ic_stat_shadowsocks) 43 | if (!ShadowsocksTileService.running) builder.addAction(R.drawable.ic_navigation_close, 44 | service.getString(R.string.stop), PendingIntent.getBroadcast(service, 0, new Intent(Action.CLOSE), 0)) 45 | app.profileManager.getAllProfiles match { 46 | case Some(profiles) => if (profiles.length > 1) 47 | builder.addAction(R.drawable.ic_action_settings, service.getString(R.string.quick_switch), 48 | PendingIntent.getActivity(service, 0, new Intent(Action.QUICK_SWITCH), 0)) 49 | case _ => 50 | } 51 | private lazy val style = new BigTextStyle(builder) 52 | private val showOnUnlock = visible && Utils.isLollipopOrAbove 53 | private var isVisible = true 54 | update(if (service.getSystemService(Context.POWER_SERVICE).asInstanceOf[PowerManager].isScreenOn) 55 | Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true) 56 | lockReceiver = (context: Context, intent: Intent) => update(intent.getAction) 57 | val screenFilter = new IntentFilter() 58 | screenFilter.addAction(Intent.ACTION_SCREEN_ON) 59 | screenFilter.addAction(Intent.ACTION_SCREEN_OFF) 60 | if (showOnUnlock) screenFilter.addAction(Intent.ACTION_USER_PRESENT) 61 | service.registerReceiver(lockReceiver, screenFilter) 62 | 63 | private def update(action: String, forceShow: Boolean = false) = 64 | if (forceShow || service.getState == State.CONNECTED) action match { 65 | case Intent.ACTION_SCREEN_OFF => 66 | setVisible(false, forceShow) 67 | unregisterCallback // unregister callback to save battery 68 | case Intent.ACTION_SCREEN_ON => 69 | setVisible(showOnUnlock && !keyGuard.inKeyguardRestrictedInputMode, forceShow) 70 | service.binder.registerCallback(callback) 71 | callbackRegistered = true 72 | case Intent.ACTION_USER_PRESENT => setVisible(showOnUnlock, forceShow) 73 | } 74 | 75 | private def unregisterCallback = if (callbackRegistered) { 76 | service.binder.unregisterCallback(callback) 77 | callbackRegistered = false 78 | } 79 | 80 | def setVisible(visible: Boolean, forceShow: Boolean = false) = if (isVisible != visible) { 81 | isVisible = visible 82 | builder.setPriority(if (visible) NotificationCompat.PRIORITY_LOW else NotificationCompat.PRIORITY_MIN) 83 | show() 84 | } else if (forceShow) show() 85 | 86 | def show() = service.startForeground(1, builder.build) 87 | 88 | def destroy() { 89 | if (lockReceiver != null) { 90 | service.unregisterReceiver(lockReceiver) 91 | lockReceiver = null 92 | } 93 | unregisterCallback 94 | service.stopForeground(true) 95 | nm.cancel(1) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/ShadowsocksQuickSwitchActivity.scala: -------------------------------------------------------------------------------- 1 | package com.github.shadowsocks 2 | 3 | import android.content.res.Resources 4 | import android.os.{Build, Bundle} 5 | import android.support.v7.app.AppCompatActivity 6 | import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar} 7 | import android.view.{LayoutInflater, View, ViewGroup} 8 | import android.widget.CheckedTextView 9 | import com.github.shadowsocks.database.Profile 10 | import com.github.shadowsocks.utils.Utils 11 | import com.github.shadowsocks.ShadowsocksApplication.app 12 | 13 | /** 14 | * Created by Lucas on 3/10/16. 15 | */ 16 | class ShadowsocksQuickSwitchActivity extends AppCompatActivity { 17 | 18 | private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener { 19 | { 20 | val typedArray = obtainStyledAttributes(Array(android.R.attr.selectableItemBackground)) 21 | view.setBackgroundResource(typedArray.getResourceId(0, 0)) 22 | typedArray.recycle 23 | } 24 | private var item: Profile = _ 25 | private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView] 26 | itemView.setOnClickListener(this) 27 | 28 | def bind(item: Profile) { 29 | this.item = item 30 | text.setText(item.name) 31 | text.setChecked(item.id == app.profileId) 32 | } 33 | 34 | def onClick(v: View) { 35 | app.switchProfile(item.id) 36 | Utils.startSsService(ShadowsocksQuickSwitchActivity.this) 37 | finish 38 | } 39 | } 40 | 41 | private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] { 42 | val profiles = app.profileManager.getAllProfiles.getOrElse(List.empty[Profile]) 43 | 44 | def getItemCount = profiles.length 45 | 46 | def onBindViewHolder(vh: ProfileViewHolder, i: Int) = i match { 47 | case _ => vh.bind(profiles(i)) 48 | } 49 | 50 | private val name = "select_dialog_singlechoice_" + (if (Build.VERSION.SDK_INT >= 21) "material" else "holo") 51 | 52 | def onCreateViewHolder(vg: ViewGroup, i: Int) = new ProfileViewHolder(LayoutInflater.from(vg.getContext) 53 | .inflate(Resources.getSystem.getIdentifier(name, "layout", "android"), vg, false)) 54 | } 55 | 56 | private val profilesAdapter = new ProfilesAdapter 57 | 58 | override def onCreate(savedInstanceState: Bundle) { 59 | super.onCreate(savedInstanceState) 60 | setContentView(R.layout.layout_quick_switch) 61 | 62 | val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar] 63 | toolbar.setTitle(R.string.quick_switch) 64 | 65 | val profilesList = findViewById(R.id.profilesList).asInstanceOf[RecyclerView] 66 | val lm = new LinearLayoutManager(this) 67 | profilesList.setLayoutManager(lm) 68 | profilesList.setItemAnimator(new DefaultItemAnimator) 69 | profilesList.setAdapter(profilesAdapter) 70 | if (app.profileId >= 0) lm.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst { 71 | case (profile, i) if profile.id == app.profileId => i + 1 72 | }.getOrElse(0)) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2014 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | 40 | package com.github.shadowsocks 41 | 42 | import android.app.{Activity, KeyguardManager} 43 | import android.content.{BroadcastReceiver, Context, Intent, IntentFilter} 44 | import android.net.VpnService 45 | import android.os.{Bundle, Handler} 46 | import android.util.Log 47 | import com.github.shadowsocks.utils.ConfigUtils 48 | import com.github.shadowsocks.ShadowsocksApplication.app 49 | 50 | object ShadowsocksRunnerActivity { 51 | private final val TAG = "ShadowsocksRunnerActivity" 52 | private final val REQUEST_CONNECT = 1 53 | } 54 | 55 | class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext { 56 | import ShadowsocksRunnerActivity._ 57 | 58 | val handler = new Handler() 59 | 60 | // Variables 61 | var receiver: BroadcastReceiver = _ 62 | 63 | override def onServiceConnected() { 64 | handler.postDelayed(() => if (bgService != null) startBackgroundService(), 1000) 65 | } 66 | 67 | def startBackgroundService() { 68 | if (app.isNatEnabled) { 69 | bgService.use(app.profileId) 70 | finish() 71 | } else { 72 | val intent = VpnService.prepare(ShadowsocksRunnerActivity.this) 73 | if (intent != null) { 74 | startActivityForResult(intent, REQUEST_CONNECT) 75 | } else { 76 | onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null) 77 | } 78 | } 79 | } 80 | 81 | override def onCreate(savedInstanceState: Bundle) { 82 | super.onCreate(savedInstanceState) 83 | val km = getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager] 84 | val locked = km.inKeyguardRestrictedInputMode 85 | if (locked) { 86 | val filter = new IntentFilter(Intent.ACTION_USER_PRESENT) 87 | receiver = (context: Context, intent: Intent) => { 88 | if (intent.getAction == Intent.ACTION_USER_PRESENT) { 89 | attachService() 90 | } 91 | } 92 | registerReceiver(receiver, filter) 93 | } else { 94 | attachService() 95 | } 96 | finish 97 | } 98 | 99 | override def onDestroy() { 100 | super.onDestroy() 101 | detachService() 102 | if (receiver != null) { 103 | unregisterReceiver(receiver) 104 | receiver = null 105 | } 106 | } 107 | 108 | override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) { 109 | resultCode match { 110 | case Activity.RESULT_OK => 111 | if (bgService != null) { 112 | bgService.use(app.profileId) 113 | } 114 | case _ => 115 | Log.e(TAG, "Failed to start VpnService") 116 | } 117 | finish() 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2014 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | 40 | package com.github.shadowsocks 41 | 42 | import android.app.Service 43 | import android.content.Intent 44 | import android.net.VpnService 45 | import android.os.{IBinder, Handler} 46 | import com.github.shadowsocks.utils.ConfigUtils 47 | import com.github.shadowsocks.ShadowsocksApplication.app 48 | 49 | class ShadowsocksRunnerService extends Service with ServiceBoundContext { 50 | val handler = new Handler() 51 | 52 | override def onBind(intent: Intent): IBinder = { 53 | null 54 | } 55 | 56 | override def onServiceConnected() { 57 | handler.postDelayed(() => if (bgService != null) { 58 | if (app.isNatEnabled) startBackgroundService() 59 | else if (VpnService.prepare(ShadowsocksRunnerService.this) == null) startBackgroundService() 60 | stopSelf() 61 | }, 1000) 62 | } 63 | 64 | def startBackgroundService() = bgService.useSync(app.profileId) 65 | 66 | override def onCreate() { 67 | super.onCreate() 68 | attachService() 69 | } 70 | 71 | override def onDestroy() { 72 | super.onDestroy() 73 | detachService() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/ShadowsocksTileService.scala: -------------------------------------------------------------------------------- 1 | package com.github.shadowsocks 2 | 3 | import android.annotation.TargetApi 4 | import android.graphics.drawable.Icon 5 | import android.service.quicksettings.{Tile, TileService} 6 | import com.github.shadowsocks.aidl.IShadowsocksServiceCallback 7 | import com.github.shadowsocks.utils.{State, Utils} 8 | import com.github.shadowsocks.ShadowsocksApplication.app 9 | 10 | /** 11 | * @author Mygod 12 | */ 13 | object ShadowsocksTileService { 14 | var running: Boolean = _ 15 | } 16 | 17 | @TargetApi(24) 18 | final class ShadowsocksTileService extends TileService with ServiceBoundContext { 19 | import ShadowsocksTileService._ 20 | 21 | private lazy val iconIdle = Icon.createWithResource(this, R.drawable.ic_start_idle).setTint(0x80ffffff) 22 | private lazy val iconBusy = Icon.createWithResource(this, R.drawable.ic_start_busy) 23 | private lazy val iconConnected = Icon.createWithResource(this, R.drawable.ic_start_connected) 24 | private lazy val callback = new IShadowsocksServiceCallback.Stub { 25 | def trafficUpdated(txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) = () 26 | def stateChanged(state: Int, msg: String) { 27 | val tile = getQsTile 28 | state match { 29 | case State.STOPPED => 30 | tile.setIcon(iconIdle) 31 | tile.setLabel(getString(R.string.app_name)) 32 | tile.setState(Tile.STATE_INACTIVE) 33 | case State.CONNECTED => 34 | tile.setIcon(iconConnected) 35 | tile.setLabel(app.currentProfile match { 36 | case Some(profile) => profile.name 37 | case None => getString(R.string.app_name) 38 | }) 39 | tile.setState(Tile.STATE_ACTIVE) 40 | case _ => 41 | tile.setIcon(iconBusy) 42 | tile.setLabel(getString(R.string.app_name)) 43 | tile.setState(Tile.STATE_UNAVAILABLE) 44 | } 45 | tile.updateTile 46 | } 47 | } 48 | 49 | override def onServiceConnected() = callback.stateChanged(bgService.getState, null) 50 | 51 | override def onCreate { 52 | super.onCreate 53 | running = true 54 | } 55 | override def onDestroy { 56 | super.onDestroy 57 | running = false 58 | } 59 | 60 | override def onStartListening { 61 | super.onStartListening 62 | attachService(callback) 63 | } 64 | override def onStopListening { 65 | super.onStopListening 66 | detachService // just in case the user switches to NAT mode, also saves battery 67 | } 68 | 69 | override def onClick() = if (isLocked) unlockAndRun(toggle) else toggle() 70 | 71 | private def toggle() = if (bgService != null) bgService.getState match { 72 | case State.STOPPED => Utils.startSsService(this) 73 | case State.CONNECTED => Utils.stopSsService(this) 74 | case _ => // ignore 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2015 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | 40 | package com.github.shadowsocks 41 | 42 | import java.io.{File, FileDescriptor, IOException} 43 | import java.util.concurrent.Executors 44 | 45 | import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress} 46 | import android.util.Log 47 | 48 | object ShadowsocksVpnThread { 49 | val getInt = classOf[FileDescriptor].getDeclaredMethod("getInt$") 50 | } 51 | 52 | class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread { 53 | import ShadowsocksVpnThread._ 54 | 55 | val TAG = "ShadowsocksVpnService" 56 | lazy val PATH = vpnService.getApplicationInfo.dataDir + "/protect_path" 57 | 58 | @volatile var isRunning: Boolean = true 59 | @volatile var serverSocket: LocalServerSocket = null 60 | 61 | def closeServerSocket() { 62 | if (serverSocket != null) { 63 | try { 64 | serverSocket.close() 65 | } catch { 66 | case _: Exception => // ignore 67 | } 68 | serverSocket = null 69 | } 70 | } 71 | 72 | def stopThread() { 73 | isRunning = false 74 | closeServerSocket() 75 | } 76 | 77 | override def run() { 78 | 79 | try { 80 | new File(PATH).delete() 81 | } catch { 82 | case _: Exception => // ignore 83 | } 84 | 85 | try { 86 | val localSocket = new LocalSocket 87 | localSocket.bind(new LocalSocketAddress(PATH, LocalSocketAddress.Namespace.FILESYSTEM)) 88 | serverSocket = new LocalServerSocket(localSocket.getFileDescriptor) 89 | } catch { 90 | case e: IOException => 91 | Log.e(TAG, "unable to bind", e) 92 | return 93 | } 94 | 95 | val pool = Executors.newFixedThreadPool(4) 96 | 97 | while (isRunning) { 98 | try { 99 | val socket = serverSocket.accept() 100 | 101 | pool.execute(() => { 102 | try { 103 | val input = socket.getInputStream 104 | val output = socket.getOutputStream 105 | 106 | input.read() 107 | 108 | val fds = socket.getAncillaryFileDescriptors 109 | 110 | if (fds.nonEmpty) { 111 | val fd = getInt.invoke(fds(0)).asInstanceOf[Int] 112 | val ret = vpnService.protect(fd) 113 | 114 | // Trick to close file decriptor 115 | System.jniclose(fd) 116 | 117 | if (ret) { 118 | output.write(0) 119 | } else { 120 | output.write(1) 121 | } 122 | } 123 | 124 | input.close() 125 | output.close() 126 | 127 | } catch { 128 | case e: Exception => 129 | Log.e(TAG, "Error when protect socket", e) 130 | } 131 | 132 | // close socket 133 | try { 134 | socket.close() 135 | } catch { 136 | case _: Exception => // ignore 137 | } 138 | 139 | }) 140 | } catch { 141 | case e: IOException => 142 | Log.e(TAG, "Error when accept socket", e) 143 | return 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/TaskerActivity.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2014 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | package com.github.shadowsocks 40 | 41 | import android.app.Activity 42 | import android.content.res.Resources 43 | import android.os.{Build, Bundle} 44 | import android.support.v7.app.AppCompatActivity 45 | import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar} 46 | import android.view.{LayoutInflater, View, ViewGroup} 47 | import android.widget.{CheckedTextView, Switch} 48 | import com.github.shadowsocks.database.Profile 49 | import com.github.shadowsocks.utils.TaskerSettings 50 | import com.github.shadowsocks.ShadowsocksApplication.app 51 | 52 | /** 53 | * @author CzBiX 54 | */ 55 | class TaskerActivity extends AppCompatActivity { 56 | private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener { 57 | { 58 | val typedArray = obtainStyledAttributes(Array(android.R.attr.selectableItemBackground)) 59 | view.setBackgroundResource(typedArray.getResourceId(0, 0)) 60 | typedArray.recycle 61 | } 62 | private var item: Profile = _ 63 | private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView] 64 | itemView.setOnClickListener(this) 65 | 66 | def bindDefault { 67 | item = null 68 | text.setText(R.string.profile_default) 69 | text.setChecked(taskerOption.profileId < 0) 70 | } 71 | def bind(item: Profile) { 72 | this.item = item 73 | text.setText(item.name) 74 | text.setChecked(taskerOption.profileId == item.id) 75 | } 76 | 77 | def onClick(v: View) { 78 | taskerOption.switchOn = switch.isChecked 79 | taskerOption.profileId = if (item == null) -1 else item.id 80 | setResult(Activity.RESULT_OK, taskerOption.toIntent(TaskerActivity.this)) 81 | finish 82 | } 83 | } 84 | 85 | private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] { 86 | val profiles = app.profileManager.getAllProfiles.getOrElse(List.empty[Profile]) 87 | def getItemCount = 1 + profiles.length 88 | def onBindViewHolder(vh: ProfileViewHolder, i: Int) = i match { 89 | case 0 => vh.bindDefault 90 | case _ => vh.bind(profiles(i - 1)) 91 | } 92 | private val name = "select_dialog_singlechoice_" + (if (Build.VERSION.SDK_INT >= 21) "material" else "holo") 93 | def onCreateViewHolder(vg: ViewGroup, i: Int) = new ProfileViewHolder(LayoutInflater.from(vg.getContext) 94 | .inflate(Resources.getSystem.getIdentifier(name, "layout", "android"), vg, false)) 95 | } 96 | 97 | private var taskerOption: TaskerSettings = _ 98 | private var switch: Switch = _ 99 | private val profilesAdapter = new ProfilesAdapter 100 | 101 | override def onCreate(savedInstanceState: Bundle) { 102 | super.onCreate(savedInstanceState) 103 | setContentView(R.layout.layout_tasker) 104 | 105 | val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar] 106 | toolbar.setTitle(R.string.app_name) 107 | toolbar.setNavigationIcon(R.drawable.ic_navigation_close) 108 | toolbar.setNavigationOnClickListener(_ => finish()) 109 | 110 | taskerOption = TaskerSettings.fromIntent(getIntent) 111 | switch = findViewById(R.id.serviceSwitch).asInstanceOf[Switch] 112 | switch.setChecked(taskerOption.switchOn) 113 | val profilesList = findViewById(R.id.profilesList).asInstanceOf[RecyclerView] 114 | val lm = new LinearLayoutManager(this) 115 | profilesList.setLayoutManager(lm) 116 | profilesList.setItemAnimator(new DefaultItemAnimator) 117 | profilesList.setAdapter(profilesAdapter) 118 | if (taskerOption.profileId >= 0) lm.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst { 119 | case (profile, i) if profile.id == taskerOption.profileId => i + 1 120 | }.getOrElse(0)) 121 | } 122 | } 123 | 124 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/TaskerReceiver.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2014 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | package com.github.shadowsocks 40 | 41 | import android.content.{BroadcastReceiver, Context, Intent} 42 | import com.github.shadowsocks.utils.{TaskerSettings, Utils} 43 | import com.github.shadowsocks.ShadowsocksApplication.app 44 | 45 | /** 46 | * @author CzBiX 47 | */ 48 | class TaskerReceiver extends BroadcastReceiver { 49 | override def onReceive(context: Context, intent: Intent) { 50 | val settings = TaskerSettings.fromIntent(intent) 51 | val switched = app.profileManager.getProfile(settings.profileId) match { 52 | case Some(p) => 53 | app.switchProfile(settings.profileId) 54 | true 55 | case _ => false 56 | } 57 | if (settings.switchOn) Utils.startSsService(context) else Utils.stopSsService(context) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/database/DBHelper.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2013 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | 40 | package com.github.shadowsocks.database 41 | 42 | import android.content.Context 43 | import android.content.pm.ApplicationInfo 44 | import android.database.sqlite.SQLiteDatabase 45 | import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper 46 | import com.j256.ormlite.dao.Dao 47 | import com.j256.ormlite.support.ConnectionSource 48 | import com.j256.ormlite.table.TableUtils 49 | 50 | import scala.collection.JavaConverters._ 51 | import scala.collection.mutable 52 | 53 | object DBHelper { 54 | final val PROFILE = "profile.db" 55 | private var apps: mutable.Buffer[ApplicationInfo] = _ 56 | 57 | def isAllDigits(x: String) = !x.isEmpty && (x forall Character.isDigit) 58 | 59 | def updateProxiedApps(context: Context, old: String) = { 60 | synchronized(if (apps == null) apps = context.getPackageManager.getInstalledApplications(0).asScala) 61 | val uidSet = old.split('|').filter(isAllDigits).map(_.toInt).toSet 62 | apps.filter(ai => uidSet.contains(ai.uid)).map(_.packageName).mkString("\n") 63 | } 64 | } 65 | 66 | class DBHelper(val context: Context) 67 | extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 19) { 68 | import DBHelper._ 69 | 70 | lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile]) 71 | 72 | def onCreate(database: SQLiteDatabase, connectionSource: ConnectionSource) { 73 | TableUtils.createTable(connectionSource, classOf[Profile]) 74 | } 75 | 76 | def onUpgrade(database: SQLiteDatabase, connectionSource: ConnectionSource, oldVersion: Int, 77 | newVersion: Int) { 78 | if (oldVersion != newVersion) { 79 | if (oldVersion < 7) { 80 | profileDao.executeRawNoArgs("DROP TABLE IF EXISTS 'profile';") 81 | onCreate(database, connectionSource) 82 | return 83 | } 84 | if (oldVersion < 8) { 85 | profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;") 86 | profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR;") 87 | } 88 | if (oldVersion < 9) { 89 | profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR;") 90 | } 91 | if (oldVersion < 10) { 92 | profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN auth SMALLINT;") 93 | } 94 | if (oldVersion < 11) { 95 | profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;") 96 | } 97 | if (oldVersion < 12) { 98 | profileDao.executeRawNoArgs("BEGIN TRANSACTION;") 99 | profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;") 100 | TableUtils.createTable(connectionSource, classOf[Profile]) 101 | profileDao.executeRawNoArgs( 102 | "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," + 103 | " udpdns, auth, ipv6, individual) " + 104 | "SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, auth," + 105 | " ipv6, individual FROM `tmp`;") 106 | profileDao.executeRawNoArgs("DROP TABLE `tmp`;") 107 | profileDao.executeRawNoArgs("COMMIT;") 108 | } else if (oldVersion < 13) { 109 | profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN tx LONG;") 110 | profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN rx LONG;") 111 | profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN date VARCHAR;") 112 | } 113 | 114 | if (oldVersion < 15) { 115 | if (oldVersion >= 12) profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN userOrder LONG;") 116 | var i = 0 117 | for (profile <- profileDao.queryForAll.asScala) { 118 | if (oldVersion < 14) profile.individual = updateProxiedApps(context, profile.individual) 119 | profile.userOrder = i 120 | profileDao.update(profile) 121 | i += 1 122 | } 123 | } 124 | 125 | if (oldVersion < 16) { 126 | profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'bypass-lan-china' WHERE route = 'bypass-china'") 127 | } 128 | 129 | if (oldVersion < 17) { 130 | profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN kcp SMALLINT;") 131 | profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN kcpcli VARCHAR;") 132 | } 133 | 134 | if (oldVersion < 18) { 135 | profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN kcpPort INTEGER;") 136 | } 137 | 138 | if (oldVersion < 19) { 139 | profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN dns VARCHAR DEFAULT '8.8.8.8:53';") 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/database/Profile.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2014 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | 40 | package com.github.shadowsocks.database 41 | 42 | import java.util.Locale 43 | 44 | import android.util.Base64 45 | import com.j256.ormlite.field.{DataType, DatabaseField} 46 | 47 | class Profile { 48 | @DatabaseField(generatedId = true) 49 | var id: Int = 0 50 | 51 | @DatabaseField 52 | var name: String = "Untitled" 53 | 54 | @DatabaseField 55 | var host: String = "" 56 | 57 | @DatabaseField 58 | var localPort: Int = 1080 59 | 60 | @DatabaseField 61 | var remotePort: Int = 8388 62 | 63 | @DatabaseField 64 | var password: String = "" 65 | 66 | @DatabaseField 67 | var protocol: String = "origin" 68 | 69 | @DatabaseField 70 | var obfs: String = "plain" 71 | 72 | @DatabaseField 73 | var obfs_param: String = "" 74 | 75 | @DatabaseField 76 | var method: String = "aes-256-cfb" 77 | 78 | @DatabaseField 79 | var route: String = "all" 80 | 81 | @DatabaseField 82 | var proxyApps: Boolean = false 83 | 84 | @DatabaseField 85 | var bypass: Boolean = false 86 | 87 | @DatabaseField 88 | var udpdns: Boolean = false 89 | 90 | @DatabaseField 91 | var dns: String = "8.8.8.8:53" 92 | 93 | @DatabaseField 94 | var ipv6: Boolean = false 95 | 96 | @DatabaseField(dataType = DataType.LONG_STRING) 97 | var individual: String = "" 98 | 99 | @DatabaseField 100 | var tx: Long = 0 101 | 102 | @DatabaseField 103 | var rx: Long = 0 104 | 105 | @DatabaseField 106 | val date: java.util.Date = new java.util.Date() 107 | 108 | @DatabaseField 109 | var userOrder: Long = _ 110 | 111 | override def toString = "ssr://" + Base64.encodeToString("%s:%d:%s:%s:%s:%s/?obfsparam=%s&remarks=%s".formatLocal(Locale.ENGLISH, 112 | host, remotePort, protocol, method, obfs, Base64.encodeToString("%s".formatLocal(Locale.ENGLISH, 113 | password).getBytes, Base64.URL_SAFE | Base64.NO_WRAP), Base64.encodeToString("%s".formatLocal(Locale.ENGLISH, 114 | obfs_param).getBytes, Base64.URL_SAFE | Base64.NO_WRAP), Base64.encodeToString("%s".formatLocal(Locale.ENGLISH, 115 | name).getBytes, Base64.URL_SAFE | Base64.NO_WRAP)).getBytes, Base64.URL_SAFE | Base64.NO_WRAP) 116 | 117 | @DatabaseField 118 | var kcp: Boolean = false 119 | 120 | @DatabaseField 121 | var kcpPort: Int = 8399 122 | 123 | @DatabaseField 124 | var kcpcli: String = "" 125 | 126 | 127 | def isMethodUnsafe = "table".equalsIgnoreCase(method) || "rc4".equalsIgnoreCase(method) 128 | } 129 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/database/ProfileManager.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2014 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | 40 | package com.github.shadowsocks.database 41 | 42 | import android.util.Log 43 | import com.github.shadowsocks.ShadowsocksApplication.app 44 | 45 | object ProfileManager { 46 | private final val TAG = "ProfileManager" 47 | } 48 | 49 | class ProfileManager(dbHelper: DBHelper) { 50 | import ProfileManager._ 51 | 52 | var profileAddedListener: Profile => Any = _ 53 | def setProfileAddedListener(listener: Profile => Any) = this.profileAddedListener = listener 54 | 55 | def createProfile(p: Profile = null): Profile = { 56 | try { 57 | val profile = if (p == null) new Profile else p 58 | profile.id = 0 59 | app.currentProfile match { 60 | case Some(oldProfile) => 61 | // Copy Feature Settings from old profile 62 | profile.route = oldProfile.route 63 | profile.ipv6 = oldProfile.ipv6 64 | profile.proxyApps = oldProfile.proxyApps 65 | profile.bypass = oldProfile.bypass 66 | profile.individual = oldProfile.individual 67 | profile.udpdns = oldProfile.udpdns 68 | case _ => 69 | } 70 | val last = dbHelper.profileDao.queryRaw(dbHelper.profileDao.queryBuilder.selectRaw("MAX(userOrder)") 71 | .prepareStatementString).getFirstResult 72 | if (last != null && last.length == 1 && last(0) != null) profile.userOrder = last(0).toInt + 1 73 | dbHelper.profileDao.createOrUpdate(profile) 74 | if (profileAddedListener != null) profileAddedListener(profile) 75 | profile 76 | } catch { 77 | case ex: Exception => 78 | Log.e(TAG, "addProfile", ex) 79 | p 80 | } 81 | } 82 | 83 | def updateProfile(profile: Profile): Boolean = { 84 | try { 85 | dbHelper.profileDao.update(profile) 86 | true 87 | } catch { 88 | case ex: Exception => 89 | Log.e(TAG, "updateProfile", ex) 90 | false 91 | } 92 | } 93 | 94 | def getProfile(id: Int): Option[Profile] = { 95 | try { 96 | dbHelper.profileDao.queryForId(id) match { 97 | case profile: Profile => Option(profile) 98 | case _ => None 99 | } 100 | } catch { 101 | case ex: Exception => 102 | Log.e(TAG, "getProfile", ex) 103 | None 104 | } 105 | } 106 | 107 | def delProfile(id: Int): Boolean = { 108 | try { 109 | dbHelper.profileDao.deleteById(id) 110 | true 111 | } catch { 112 | case ex: Exception => 113 | Log.e(TAG, "delProfile", ex) 114 | false 115 | } 116 | } 117 | 118 | def getFirstProfile = { 119 | try { 120 | val result = dbHelper.profileDao.query(dbHelper.profileDao.queryBuilder.limit(1L).prepare) 121 | if (result.size == 1) Option(result.get(0)) else None 122 | } catch { 123 | case ex: Exception => 124 | Log.e(TAG, "getAllProfiles", ex) 125 | None 126 | } 127 | } 128 | 129 | def getAllProfiles: Option[List[Profile]] = { 130 | try { 131 | import scala.collection.JavaConversions._ 132 | Option(dbHelper.profileDao.query(dbHelper.profileDao.queryBuilder.orderBy("userOrder", true).prepare).toList) 133 | } catch { 134 | case ex: Exception => 135 | Log.e(TAG, "getAllProfiles", ex) 136 | None 137 | } 138 | } 139 | 140 | def createDefault(): Profile = { 141 | val profile = new Profile { 142 | name = "Default" 143 | host = "198.199.101.152" 144 | remotePort = 443 145 | password = "u1rRWTssNv0p" 146 | } 147 | createProfile(profile) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/preferences/DropDownPreference.scala: -------------------------------------------------------------------------------- 1 | package com.github.shadowsocks.preferences 2 | 3 | import android.content.Context 4 | import android.content.res.TypedArray 5 | import android.preference.Preference 6 | import android.support.annotation.{ArrayRes, NonNull} 7 | import android.support.v7.widget.AppCompatSpinner 8 | import android.util.AttributeSet 9 | import android.view.{View, ViewGroup} 10 | import android.widget.AdapterView.OnItemSelectedListener 11 | import android.widget.{AdapterView, ArrayAdapter} 12 | import com.github.shadowsocks.R 13 | 14 | /** 15 | * @author Mygod 16 | */ 17 | final class DropDownPreference(private val mContext: Context, attrs: AttributeSet = null) 18 | extends Preference(mContext, attrs) with SummaryPreference { 19 | 20 | private val mAdapter = new ArrayAdapter[String](mContext, android.R.layout.simple_spinner_dropdown_item) 21 | private val mSpinner = new AppCompatSpinner(mContext) 22 | private var mEntries: Array[CharSequence] = _ 23 | private var mEntryValues: Array[CharSequence] = _ 24 | private var mSelectedIndex: Int = -1 25 | 26 | mSpinner.setVisibility(View.INVISIBLE) 27 | mSpinner.setAdapter(mAdapter) 28 | mSpinner.setOnItemSelectedListener(new OnItemSelectedListener { 29 | def onNothingSelected(parent: AdapterView[_]) { } 30 | def onItemSelected(parent: AdapterView[_], view: View, position: Int, id: Long) = { 31 | val value = getValue(position) 32 | if (position != mSelectedIndex && callChangeListener(value)) setValue(position, value) 33 | } 34 | }) 35 | setOnPreferenceClickListener((preference: Preference) => { 36 | // TODO: not working with scrolling 37 | // mSpinner.setDropDownVerticalOffset(Utils.dpToPx(getContext, -48 * mSelectedIndex).toInt) 38 | mSpinner.performClick 39 | true 40 | }) 41 | val a: TypedArray = mContext.obtainStyledAttributes(attrs, R.styleable.DropDownPreference) 42 | setEntries(a.getTextArray(R.styleable.DropDownPreference_android_entries)) 43 | setEntryValues(a.getTextArray(R.styleable.DropDownPreference_android_entryValues)) 44 | a.recycle 45 | 46 | protected override def getSummaryValue = getEntry 47 | 48 | /** 49 | * Sets the human-readable entries to be shown in the list. This will be shown in subsequent dialogs. 50 | * 51 | * Each entry must have a corresponding index in [[setEntryValues(CharSequence[])]]. 52 | * 53 | * @param entries The entries. 54 | * @see [[setEntryValues(CharSequence[])]] 55 | */ 56 | def setEntries(entries: Array[CharSequence]) { 57 | mEntries = entries 58 | mAdapter.clear 59 | if (entries != null) for (entry <- entries) mAdapter.add(entry.toString) 60 | } 61 | /** 62 | * @see #setEntries(CharSequence[]) 63 | * @param entriesResId The entries array as a resource. 64 | */ 65 | def setEntries(@ArrayRes entriesResId: Int): Unit = setEntries(getContext.getResources.getTextArray(entriesResId)) 66 | /** 67 | * The list of entries to be shown in the list in subsequent dialogs. 68 | * 69 | * @return The list as an array. 70 | */ 71 | def getEntries = mEntries 72 | 73 | /** 74 | * The array to find the value to save for a preference when an entry from 75 | * entries is selected. If a user clicks on the second item in entries, the 76 | * second item in this array will be saved to the preference. 77 | * 78 | * @param entryValues The array to be used as values to save for the preference. 79 | */ 80 | def setEntryValues(entryValues: Array[CharSequence]) = mEntryValues = entryValues 81 | /** 82 | * @see #setEntryValues(CharSequence[]) 83 | * @param entryValuesResId The entry values array as a resource. 84 | */ 85 | def setEntryValues(@ArrayRes entryValuesResId: Int): Unit = 86 | setEntryValues(getContext.getResources.getTextArray(entryValuesResId)) 87 | /** 88 | * Returns the array of values to be saved for the preference. 89 | * 90 | * @return The array of values. 91 | */ 92 | def getEntryValues: Array[CharSequence] = mEntryValues 93 | 94 | private def getValue(index: Int) = (if (mEntryValues == null) index else mEntryValues(index)).toString 95 | /** 96 | * Sets the value of the key. This should be one of the entries in [[getEntryValues]]. 97 | * 98 | * @param value The value to set for the key. 99 | */ 100 | def setValue(value: String): Unit = setValue(findIndexOfValue(value), value) 101 | 102 | /** 103 | * Sets the value to the given index from the entry values. 104 | * 105 | * @param index The index of the value to set. 106 | */ 107 | def setValueIndex(index: Int) = setValue(index, getValue(index)) 108 | 109 | private def setValue(index: Int, value: String) { 110 | persistString(value) 111 | mSelectedIndex = index 112 | mSpinner.setSelection(index) 113 | notifyChanged 114 | } 115 | 116 | /** 117 | * Returns the value of the key. This should be one of the entries in [[getEntryValues]]. 118 | * 119 | * @return The value of the key. 120 | */ 121 | def getValue = if (mEntryValues == null || mSelectedIndex < 0) null else mEntryValues(mSelectedIndex).toString 122 | 123 | /** 124 | * Returns the entry corresponding to the current value. 125 | * 126 | * @return The entry corresponding to the current value, or null. 127 | */ 128 | def getEntry = { 129 | val index = getValueIndex 130 | if (index >= 0 && mEntries != null) mEntries(index) else null 131 | } 132 | 133 | /** 134 | * Returns the index of the given value (in the entry values array). 135 | * 136 | * @param value The value whose index should be returned. 137 | * @return The index of the value, or -1 if not found. 138 | */ 139 | def findIndexOfValue(value: String): Int = { 140 | if (value != null && mEntryValues != null) { 141 | var i = mEntryValues.length - 1 142 | while (i >= 0) { 143 | if (mEntryValues(i) == value) return i 144 | i -= 1 145 | } 146 | } 147 | -1 148 | } 149 | 150 | def getValueIndex = mSelectedIndex 151 | 152 | protected override def onGetDefaultValue(a: TypedArray, index: Int) = a.getString(index) 153 | protected override def onSetInitialValue (restoreValue: Boolean, defaultValue: AnyRef) = 154 | setValue(if (restoreValue) getPersistedString(getValue) else defaultValue.asInstanceOf[String]) 155 | 156 | def setDropDownWidth(dimenResId: Int) = 157 | mSpinner.setDropDownWidth(mContext.getResources.getDimensionPixelSize(dimenResId)) 158 | 159 | protected override def onBindView(@NonNull view: View) { 160 | super.onBindView(view) 161 | val parent = mSpinner.getParent.asInstanceOf[ViewGroup] 162 | if (view eq parent) return 163 | if (parent != null) parent.removeView(mSpinner) 164 | view.asInstanceOf[ViewGroup].addView(mSpinner, 0) 165 | val lp = mSpinner.getLayoutParams 166 | lp.width = 0 167 | mSpinner.setLayoutParams(lp) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/preferences/NumberPickerPreference.scala: -------------------------------------------------------------------------------- 1 | package com.github.shadowsocks.preferences 2 | 3 | import android.content.Context 4 | import android.content.res.TypedArray 5 | import android.os.Bundle 6 | import android.preference.DialogPreference 7 | import android.util.AttributeSet 8 | import android.view.{ViewGroup, WindowManager} 9 | import android.widget.NumberPicker 10 | import com.github.shadowsocks.R 11 | 12 | /** 13 | * @author Mygod 14 | */ 15 | final class NumberPickerPreference(context: Context, attrs: AttributeSet = null) 16 | extends DialogPreference(context, attrs) with SummaryPreference { 17 | private val picker = new NumberPicker(context) 18 | private var value: Int = _ 19 | 20 | { 21 | val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.NumberPickerPreference) 22 | setMin(a.getInt(R.styleable.NumberPickerPreference_min, 0)) 23 | setMax(a.getInt(R.styleable.NumberPickerPreference_max, Int.MaxValue - 1)) 24 | a.recycle 25 | } 26 | 27 | def getValue = value 28 | def getMin = if (picker == null) 0 else picker.getMinValue 29 | def getMax = picker.getMaxValue 30 | def setValue(i: Int) { 31 | if (i == getValue) return 32 | picker.setValue(i) 33 | value = picker.getValue 34 | persistInt(value) 35 | notifyChanged 36 | } 37 | def setMin(value: Int) = picker.setMinValue(value) 38 | def setMax(value: Int) = picker.setMaxValue(value) 39 | 40 | override protected def showDialog(state: Bundle) { 41 | super.showDialog(state) 42 | getDialog.getWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) 43 | } 44 | override protected def onCreateDialogView = { 45 | val parent = picker.getParent.asInstanceOf[ViewGroup] 46 | if (parent != null) parent.removeView(picker) 47 | picker 48 | } 49 | override protected def onDialogClosed(positiveResult: Boolean) { 50 | picker.clearFocus // commit changes 51 | super.onDialogClosed(positiveResult) // forward compatibility 52 | if (positiveResult) { 53 | val value = picker.getValue 54 | if (callChangeListener(value)) { 55 | setValue(value) 56 | return 57 | } 58 | } 59 | picker.setValue(value) 60 | } 61 | override protected def onGetDefaultValue(a: TypedArray, index: Int) = a.getInt(index, getMin).asInstanceOf[AnyRef] 62 | override protected def onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any) { 63 | val default = defaultValue.asInstanceOf[Int] 64 | setValue(if (restorePersistedValue) getPersistedInt(default) else default) 65 | } 66 | protected def getSummaryValue: AnyRef = getValue.asInstanceOf[AnyRef] 67 | } 68 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/preferences/PasswordEditTextPreference.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2013 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | 40 | package com.github.shadowsocks.preferences 41 | 42 | import android.content.Context 43 | import android.preference.EditTextPreference 44 | import android.util.AttributeSet 45 | 46 | class PasswordEditTextPreference(context: Context, attrs: AttributeSet, defStyle: Int) 47 | extends EditTextPreference(context, attrs, defStyle) { 48 | 49 | def this(context: Context, attrs: AttributeSet) = { 50 | this(context, attrs, android.R.attr.editTextPreferenceStyle) 51 | mDefaultSummary = getSummary 52 | } 53 | 54 | override def setText(text: String) { 55 | super.setText(text) 56 | setSummary(text) 57 | } 58 | 59 | override def setSummary(summary: CharSequence) { 60 | if (summary.toString.isEmpty) { 61 | super.setSummary(mDefaultSummary) 62 | } else { 63 | super.setSummary(summary.toString.map(c => "*").mkString) 64 | } 65 | } 66 | 67 | private var mDefaultSummary: CharSequence = getSummary 68 | } 69 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/preferences/SummaryEditTextPreference.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2013 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | 40 | package com.github.shadowsocks.preferences 41 | 42 | import android.content.Context 43 | import android.preference.EditTextPreference 44 | import android.util.AttributeSet 45 | 46 | class SummaryEditTextPreference(context: Context, attrs: AttributeSet, defStyle: Int) 47 | extends EditTextPreference(context, attrs, defStyle) { 48 | 49 | def this(context: Context, attrs: AttributeSet) = { 50 | this(context, attrs, android.R.attr.editTextPreferenceStyle) 51 | mDefaultSummary = getSummary 52 | } 53 | 54 | override def setText(text: String) { 55 | if(text != null) 56 | { 57 | super.setText(text) 58 | setSummary(text) 59 | } 60 | } 61 | 62 | override def setSummary(summary: CharSequence) { 63 | if (summary.toString.isEmpty) { 64 | super.setSummary(mDefaultSummary) 65 | } else { 66 | super.setSummary(summary) 67 | } 68 | } 69 | 70 | private var mDefaultSummary: CharSequence = getSummary 71 | } 72 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/preferences/SummaryPreference.scala: -------------------------------------------------------------------------------- 1 | package com.github.shadowsocks.preferences 2 | 3 | import java.util.Locale 4 | 5 | import android.preference.Preference 6 | 7 | /** 8 | * Make your preference support %s in summary. Override getSummaryValue to customize what to put in. 9 | * @author Mygod 10 | */ 11 | trait SummaryPreference extends Preference { 12 | protected def getSummaryValue: AnyRef 13 | 14 | /** 15 | * Returns the summary of this SummaryPreference. If the summary has a String formatting marker in it 16 | * (i.e. "%s" or "%1$s"), then the current entry value will be substituted in its place. 17 | * 18 | * @return the summary with appropriate string substitution 19 | */ 20 | override def getSummary = super.getSummary.toString.formatLocal(Locale.ENGLISH, getSummaryValue) 21 | } 22 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/utils/CloseUtils.scala: -------------------------------------------------------------------------------- 1 | package com.github.shadowsocks.utils 2 | 3 | /** 4 | * @author Mygod 5 | */ 6 | object CloseUtils { 7 | type Closeable = { 8 | def close() 9 | } 10 | type Disconnectable = { 11 | def disconnect() 12 | } 13 | 14 | def autoClose[A <: Closeable, B](x: => A)(block: A => B): B = { 15 | var a: Option[A] = None 16 | try { 17 | a = Some(x) 18 | block(a.get) 19 | } finally if (a.nonEmpty) try { 20 | val v = a.get 21 | if (v ne null) v.close 22 | } catch { 23 | case ignore: Exception => 24 | } 25 | } 26 | def autoDisconnect[A <: Disconnectable, B](x: => A)(block: A => B): B = { 27 | var a: Option[A] = None 28 | try { 29 | a = Some(x) 30 | block(a.get) 31 | } finally if (a.nonEmpty) try { 32 | val v = a.get 33 | if (v ne null) v.disconnect 34 | } catch { 35 | case ignore: Exception => 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/utils/Constants.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2014 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | 40 | package com.github.shadowsocks.utils 41 | 42 | object Executable { 43 | val REDSOCKS = "redsocks" 44 | val PDNSD = "pdnsd" 45 | val SS_LOCAL = "ss-local" 46 | val SS_TUNNEL = "ss-tunnel" 47 | val TUN2SOCKS = "tun2socks" 48 | val KCPTUN = "kcptun" 49 | } 50 | 51 | object ConfigUtils { 52 | val SHADOWSOCKS = "{\"server\": \"%s\", \"server_port\": %d, \"local_port\": %d, \"password\": \"%s\", \"method\":\"%s\", \"timeout\": %d, \"protocol\": \"%s\", \"obfs\": \"%s\", \"obfs_param\": \"%s\"}" 53 | val REDSOCKS = "base {\n" + 54 | " log_debug = off;\n" + 55 | " log_info = off;\n" + 56 | " log = stderr;\n" + 57 | " daemon = off;\n" + 58 | " redirector = iptables;\n" + 59 | "}\n" + 60 | "redsocks {\n" + 61 | " local_ip = 127.0.0.1;\n" + 62 | " local_port = 8123;\n" + 63 | " ip = 127.0.0.1;\n" + 64 | " port = %d;\n" + 65 | " type = socks5;\n" + 66 | "}\n" 67 | 68 | val PDNSD_LOCAL = 69 | """ 70 | |global { 71 | | perm_cache = 2048; 72 | | cache_dir = "%s"; 73 | | server_ip = %s; 74 | | server_port = %d; 75 | | query_method = tcp_only; 76 | | run_ipv4 = on; 77 | | min_ttl = 15m; 78 | | max_ttl = 1w; 79 | | timeout = 10; 80 | | daemon = off; 81 | |} 82 | | 83 | |server { 84 | | label = "local"; 85 | | ip = 127.0.0.1; 86 | | port = %d; 87 | | %s 88 | | reject_policy = negate; 89 | | reject_recursively = on; 90 | | timeout = 5; 91 | |} 92 | | 93 | |rr { 94 | | name=localhost; 95 | | reverse=on; 96 | | a=127.0.0.1; 97 | | owner=localhost; 98 | | soa=localhost,root.localhost,42,86400,900,86400,86400; 99 | |} 100 | """.stripMargin 101 | 102 | val PDNSD_DIRECT = 103 | """ 104 | |global { 105 | | perm_cache = 2048; 106 | | cache_dir = "%s"; 107 | | server_ip = %s; 108 | | server_port = %d; 109 | | query_method = tcp_only; 110 | | run_ipv4 = on; 111 | | min_ttl = 15m; 112 | | max_ttl = 1w; 113 | | timeout = 10; 114 | | daemon = off; 115 | |} 116 | | 117 | |server { 118 | | label = "china-servers"; 119 | | ip = 114.114.114.114, 112.124.47.27; 120 | | timeout = 4; 121 | | exclude = %s; 122 | | policy = included; 123 | | uptest = none; 124 | | preset = on; 125 | |} 126 | | 127 | |server { 128 | | label = "local-server"; 129 | | ip = 127.0.0.1; 130 | | port = %d; 131 | | %s 132 | | reject_policy = negate; 133 | | reject_recursively = on; 134 | |} 135 | | 136 | |rr { 137 | | name=localhost; 138 | | reverse=on; 139 | | a=127.0.0.1; 140 | | owner=localhost; 141 | | soa=localhost,root.localhost,42,86400,900,86400,86400; 142 | |} 143 | """.stripMargin 144 | } 145 | 146 | object Key { 147 | val id = "profileId" 148 | val name = "profileName" 149 | 150 | val individual = "Proxyed" 151 | 152 | val isNAT = "isNAT" 153 | val route = "route" 154 | 155 | val isAutoConnect = "isAutoConnect" 156 | 157 | val proxyApps = "isProxyApps" 158 | val bypass = "isBypassApps" 159 | val udpdns = "isUdpDns" 160 | val dns = "dns" 161 | val auth = "isAuth" 162 | val ipv6 = "isIpv6" 163 | 164 | val host = "proxy" 165 | val password = "sitekey" 166 | val method = "encMethod" 167 | val obfs = "obfs" 168 | val obfs_param = "obfs_param" 169 | val protocol = "protocol" 170 | val remotePort = "remotePortNum" 171 | val localPort = "localPortNum" 172 | 173 | val profileTip = "profileTip" 174 | 175 | val kcp = "kcp" 176 | val kcpPort = "kcpPort" 177 | val kcpcli = "kcpcli" 178 | } 179 | 180 | object State { 181 | val CONNECTING = 1 182 | val CONNECTED = 2 183 | val STOPPING = 3 184 | val STOPPED = 4 185 | def isAvailable(state: Int): Boolean = state != CONNECTED && state != CONNECTING 186 | } 187 | 188 | object Action { 189 | val SERVICE = "com.github.shadowsocks.SERVICE" 190 | val CLOSE = "com.github.shadowsocks.CLOSE" 191 | val QUICK_SWITCH = "com.github.shadowsocks.QUICK_SWITCH" 192 | } 193 | 194 | object Route { 195 | val ALL = "all" 196 | val BYPASS_LAN = "bypass-lan" 197 | val BYPASS_CHN = "bypass-china" 198 | val BYPASS_LAN_CHN = "bypass-lan-china" 199 | } 200 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/utils/Parser.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2014 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | 40 | package com.github.shadowsocks.utils 41 | 42 | import android.util.{Base64, Log} 43 | import com.github.shadowsocks.database.Profile 44 | 45 | object Parser { 46 | val TAG = "ShadowParser" 47 | private val pattern = "(?i)ss://([A-Za-z0-9+-/=_]+)".r 48 | private val decodedPattern = "(?i)^((.+?)(-auth)??:(.*)@(.+?):(\\d+?))$".r 49 | 50 | private val pattern_ssr = "(?i)ssr://([A-Za-z0-9_=-]+)".r 51 | private val decodedPattern_ssr = "(?i)^((.+):(\\d+?):(.*):(.+):(.*):([^/]+))".r 52 | private val decodedPattern_ssr_obfsparam = "(?i)[?&]obfsparam=([A-Za-z0-9_=-]*)".r 53 | private val decodedPattern_ssr_remarks = "(?i)[?&]remarks=([A-Za-z0-9_=-]*)".r 54 | 55 | def findAll(data: CharSequence) = pattern.findAllMatchIn(if (data == null) "" else data).map(m => try 56 | decodedPattern.findFirstMatchIn(new String(Base64.decode(m.group(1), Base64.NO_PADDING), "UTF-8")) match { 57 | case Some(ss) => 58 | val profile = new Profile 59 | profile.method = ss.group(2).toLowerCase 60 | if (ss.group(3) != null) profile.protocol = "verify_sha1" 61 | profile.password = ss.group(4) 62 | profile.name = ss.group(5) 63 | profile.host = profile.name 64 | profile.remotePort = ss.group(6).toInt 65 | profile 66 | case _ => null 67 | } 68 | catch { 69 | case ex: Exception => 70 | Log.e(TAG, "parser error: " + m.source, ex)// Ignore 71 | null 72 | }).filter(_ != null) 73 | 74 | def findAll_ssr(data: CharSequence) = pattern_ssr.findAllMatchIn(if (data == null) "" else data).map(m => try{ 75 | val uri = new String(Base64.decode(m.group(1), Base64.NO_PADDING | Base64.URL_SAFE), "UTF-8") 76 | decodedPattern_ssr.findFirstMatchIn(uri) match { 77 | case Some(ss) => 78 | val profile = new Profile 79 | profile.host = ss.group(2).toLowerCase 80 | profile.remotePort = ss.group(3).toInt 81 | profile.protocol = ss.group(4).toLowerCase 82 | profile.method = ss.group(5).toLowerCase 83 | profile.obfs = ss.group(6).toLowerCase 84 | profile.password = new String(Base64.decode(ss.group(7), Base64.NO_PADDING | Base64.URL_SAFE), "UTF-8") 85 | 86 | decodedPattern_ssr_obfsparam.findFirstMatchIn(uri) match { 87 | case Some(param) => 88 | profile.obfs_param = new String(Base64.decode(param.group(1), Base64.NO_PADDING | Base64.URL_SAFE), "UTF-8") 89 | case _ => null 90 | } 91 | 92 | decodedPattern_ssr_remarks.findFirstMatchIn(uri) match { 93 | case Some(param) => 94 | profile.name = new String(Base64.decode(param.group(1), Base64.NO_PADDING | Base64.URL_SAFE), "UTF-8") 95 | case _ => profile.name = ss.group(2).toLowerCase 96 | } 97 | 98 | profile 99 | case _ => null 100 | } 101 | } 102 | catch { 103 | case ex: Exception => 104 | Log.e(TAG, "parser error: " + m.source, ex)// Ignore 105 | null 106 | }).filter(_ != null) 107 | } 108 | 109 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/utils/TaskerSettings.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2014 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | package com.github.shadowsocks.utils 40 | 41 | import android.content.{Context, Intent} 42 | import android.os.Bundle 43 | import com.github.shadowsocks.R 44 | import com.github.shadowsocks.ShadowsocksApplication.app 45 | import com.twofortyfouram.locale.api.{Intent => ApiIntent} 46 | 47 | object TaskerSettings { 48 | private val KEY_SWITCH_ON = "switch_on" 49 | private val KEY_PROFILE_ID = "profile_id" 50 | 51 | def fromIntent(intent: Intent) = new TaskerSettings(if (intent.hasExtra(ApiIntent.EXTRA_BUNDLE)) 52 | intent.getBundleExtra(ApiIntent.EXTRA_BUNDLE) else Bundle.EMPTY) 53 | } 54 | 55 | class TaskerSettings(bundle: Bundle) { 56 | import TaskerSettings._ 57 | 58 | var switchOn = bundle.getBoolean(KEY_SWITCH_ON, true) 59 | var profileId = bundle.getInt(KEY_PROFILE_ID, -1) 60 | 61 | def toIntent(context: Context) = { 62 | val bundle = new Bundle() 63 | if (!switchOn) bundle.putBoolean(KEY_SWITCH_ON, false) 64 | if (profileId >= 0) bundle.putInt(KEY_PROFILE_ID, profileId) 65 | new Intent().putExtra(ApiIntent.EXTRA_BUNDLE, bundle).putExtra(ApiIntent.EXTRA_STRING_BLURB, 66 | app.profileManager.getProfile(profileId) match { 67 | case Some(p) => context.getString(if (switchOn) R.string.start_service else R.string.stop_service, p.name) 68 | case None => context.getString(if (switchOn) R.string.start_service_default else R.string.stop) 69 | }) 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala: -------------------------------------------------------------------------------- 1 | package com.github.shadowsocks.utils 2 | 3 | import java.text.DecimalFormat 4 | 5 | import com.github.shadowsocks.R 6 | import com.github.shadowsocks.ShadowsocksApplication.app 7 | 8 | object TrafficMonitor { 9 | // Bytes per second 10 | var txRate: Long = _ 11 | var rxRate: Long = _ 12 | 13 | // Bytes for the current session 14 | var txTotal: Long = _ 15 | var rxTotal: Long = _ 16 | 17 | // Bytes for the last query 18 | var txLast: Long = _ 19 | var rxLast: Long = _ 20 | var timestampLast: Long = _ 21 | @volatile var dirty = true 22 | 23 | private val units = Array("KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "NB", "DB", "CB") 24 | private val numberFormat = new DecimalFormat("@@@") 25 | def formatTraffic(size: Long): String = { 26 | var n: Double = size 27 | var i = -1 28 | while (n >= 1000) { 29 | n /= 1024 30 | i = i + 1 31 | } 32 | if (i < 0) size + " " + app.getResources.getQuantityString(R.plurals.bytes, size.toInt) 33 | else numberFormat.format(n) + ' ' + units(i) 34 | } 35 | 36 | def updateRate() = { 37 | val now = System.currentTimeMillis() 38 | val delta = now - timestampLast 39 | var updated = false 40 | if (delta != 0) { 41 | if (dirty) { 42 | txRate = (txTotal - txLast) * 1000 / delta 43 | rxRate = (rxTotal - rxLast) * 1000 / delta 44 | txLast = txTotal 45 | rxLast = rxTotal 46 | dirty = false 47 | updated = true 48 | } else { 49 | if (txRate != 0) { 50 | txRate = 0 51 | updated = true 52 | } 53 | if (rxRate != 0) { 54 | rxRate = 0 55 | updated = true 56 | } 57 | } 58 | timestampLast = now 59 | } 60 | updated 61 | } 62 | 63 | def update(tx: Long, rx: Long) { 64 | if (txTotal != tx) { 65 | txTotal = tx 66 | dirty = true 67 | } 68 | if (rxTotal != rx) { 69 | rxTotal = rx 70 | dirty = true 71 | } 72 | } 73 | 74 | def reset() { 75 | txRate = 0 76 | rxRate = 0 77 | txTotal = 0 78 | rxTotal = 0 79 | txLast = 0 80 | rxLast = 0 81 | dirty = true 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Shadowsocks - A shadowsocks client for Android 3 | * Copyright (C) 2015 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | * 18 | * 19 | * ___====-_ _-====___ 20 | * _--^^^#####// \\#####^^^--_ 21 | * _-^##########// ( ) \\##########^-_ 22 | * -############// |\^^/| \\############- 23 | * _/############// (@::@) \\############\_ 24 | * /#############(( \\// ))#############\ 25 | * -###############\\ (oo) //###############- 26 | * -#################\\ / VV \ //#################- 27 | * -###################\\/ \//###################- 28 | * _#/|##########/\######( /\ )######/\##########|\#_ 29 | * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| 30 | * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' 31 | * ` ` ` ` / | | | | \ ' ' ' ' 32 | * ( | | | | ) 33 | * __\ | | | | /__ 34 | * (vvv(VVV)(VVV)vvv) 35 | * 36 | * HERE BE DRAGONS 37 | * 38 | */ 39 | 40 | package com.github.shadowsocks.utils 41 | 42 | import java.io.{File, IOException} 43 | import java.nio.{ByteBuffer, ByteOrder} 44 | import java.util.concurrent.Executors 45 | 46 | import android.content.Context 47 | import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress} 48 | import android.util.Log 49 | 50 | class TrafficMonitorThread(context: Context) extends Thread { 51 | 52 | val TAG = "TrafficMonitorThread" 53 | lazy val PATH = context.getApplicationInfo.dataDir + "/stat_path" 54 | 55 | @volatile var serverSocket: LocalServerSocket = null 56 | @volatile var isRunning: Boolean = true 57 | 58 | def closeServerSocket() { 59 | if (serverSocket != null) { 60 | try { 61 | serverSocket.close() 62 | } catch { 63 | case _: Exception => // ignore 64 | } 65 | serverSocket = null 66 | } 67 | } 68 | 69 | def stopThread() { 70 | isRunning = false 71 | closeServerSocket() 72 | } 73 | 74 | override def run() { 75 | 76 | try { 77 | new File(PATH).delete() 78 | } catch { 79 | case _: Exception => // ignore 80 | } 81 | 82 | try { 83 | val localSocket = new LocalSocket 84 | localSocket.bind(new LocalSocketAddress(PATH, LocalSocketAddress.Namespace.FILESYSTEM)) 85 | serverSocket = new LocalServerSocket(localSocket.getFileDescriptor) 86 | } catch { 87 | case e: IOException => 88 | Log.e(TAG, "unable to bind", e) 89 | return 90 | } 91 | 92 | val pool = Executors.newFixedThreadPool(1) 93 | 94 | while (isRunning) { 95 | try { 96 | val socket = serverSocket.accept() 97 | 98 | pool.execute(() => { 99 | try { 100 | val input = socket.getInputStream 101 | val output = socket.getOutputStream 102 | 103 | val buffer = new Array[Byte](16) 104 | if (input.read(buffer) != 16) throw new IOException("Unexpected traffic stat length") 105 | val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN) 106 | TrafficMonitor.update(stat.getLong(0), stat.getLong(8)) 107 | 108 | output.write(0) 109 | 110 | input.close() 111 | output.close() 112 | 113 | } catch { 114 | case e: Exception => 115 | Log.e(TAG, "Error when recv traffic stat", e) 116 | } 117 | 118 | // close socket 119 | try { 120 | socket.close() 121 | } catch { 122 | case _: Exception => // ignore 123 | } 124 | 125 | }) 126 | } catch { 127 | case e: IOException => 128 | Log.e(TAG, "Error when accept socket", e) 129 | return 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/widget/FloatingActionMenuBehavior.scala: -------------------------------------------------------------------------------- 1 | package com.github.shadowsocks.widget 2 | 3 | import android.animation.ValueAnimator 4 | import android.content.Context 5 | import android.support.design.widget.CoordinatorLayout 6 | import android.support.design.widget.CoordinatorLayout.Behavior 7 | import android.support.design.widget.Snackbar.SnackbarLayout 8 | import android.support.v4.view.animation.FastOutSlowInInterpolator 9 | import android.util.AttributeSet 10 | import android.view.View 11 | import com.github.clans.fab.FloatingActionMenu 12 | 13 | import scala.collection.JavaConverters._ 14 | 15 | /** 16 | * Behavior for com.github.clans.fab.FloatingActionMenu that is aware of Snackbars and scrolling. 17 | * 18 | * @author Mygod 19 | */ 20 | class FloatingActionMenuBehavior(context: Context, attrs: AttributeSet) 21 | extends Behavior[FloatingActionMenu](context, attrs) { 22 | private var fabTranslationYAnimator: ValueAnimator = _ 23 | private var fabTranslationY: Float = _ 24 | 25 | override def layoutDependsOn(parent: CoordinatorLayout, child: FloatingActionMenu, dependency: View) = 26 | dependency.isInstanceOf[SnackbarLayout] 27 | 28 | override def onDependentViewChanged(parent: CoordinatorLayout, child: FloatingActionMenu, dependency: View) = { 29 | dependency match { 30 | case _: SnackbarLayout => 31 | var targetTransY = parent.getDependencies(child).asScala 32 | .filter(view => view.isInstanceOf[SnackbarLayout] && parent.doViewsOverlap(child, view)) 33 | .map(view => view.getTranslationY - view.getHeight).reduceOption(_ min _).getOrElse(0F) 34 | if (targetTransY > 0) targetTransY = 0 35 | if (fabTranslationY != targetTransY) { 36 | val currentTransY = child.getTranslationY 37 | if (fabTranslationYAnimator != null && fabTranslationYAnimator.isRunning) fabTranslationYAnimator.cancel 38 | if (child.isShown && Math.abs(currentTransY - targetTransY) > child.getHeight * 0.667F) { 39 | if (fabTranslationYAnimator == null) { 40 | fabTranslationYAnimator = new ValueAnimator 41 | fabTranslationYAnimator.setInterpolator(new FastOutSlowInInterpolator) 42 | fabTranslationYAnimator.addUpdateListener(animation => 43 | child.setTranslationY(animation.getAnimatedValue.asInstanceOf[Float])) 44 | } 45 | fabTranslationYAnimator.setFloatValues(currentTransY, targetTransY) 46 | fabTranslationYAnimator.start 47 | } else child.setTranslationY(targetTransY) 48 | fabTranslationY = targetTransY 49 | } 50 | } 51 | false 52 | } 53 | 54 | override def onStartNestedScroll(parent: CoordinatorLayout, child: FloatingActionMenu, directTargetChild: View, 55 | target: View, nestedScrollAxes: Int) = true 56 | override def onNestedScroll(parent: CoordinatorLayout, child: FloatingActionMenu, target: View, dxConsumed: Int, 57 | dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int) = { 58 | super.onNestedScroll(parent, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed) 59 | val dy = dyConsumed + dyUnconsumed 60 | if (child.isMenuButtonHidden) { 61 | if (dy < 0) child.showMenuButton(true) 62 | } else if (dy > 0) child.hideMenuButton(true) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/scala/com/github/shadowsocks/widget/UndoSnackbarManager.scala: -------------------------------------------------------------------------------- 1 | package com.github.shadowsocks.widget 2 | 3 | import android.support.design.widget.Snackbar 4 | import android.view.View 5 | import com.github.shadowsocks.R 6 | 7 | import scala.collection.mutable.ArrayBuffer 8 | 9 | /** 10 | * @author Mygod 11 | * @param view The view to find a parent from. 12 | * @param undo Callback for undoing removals. 13 | * @param commit Callback for committing removals. 14 | * @tparam T Item type. 15 | */ 16 | class UndoSnackbarManager[T](view: View, undo: Iterator[(Int, T)] => Unit, 17 | commit: Iterator[(Int, T)] => Unit = null) { 18 | private val recycleBin = new ArrayBuffer[(Int, T)] 19 | private val removedCallback = new Snackbar.Callback { 20 | override def onDismissed(snackbar: Snackbar, event: Int) = { 21 | event match { 22 | case Snackbar.Callback.DISMISS_EVENT_SWIPE | Snackbar.Callback.DISMISS_EVENT_MANUAL | 23 | Snackbar.Callback.DISMISS_EVENT_TIMEOUT => 24 | if (commit != null) commit(recycleBin.iterator) 25 | recycleBin.clear 26 | case _ => 27 | } 28 | last = null 29 | } 30 | } 31 | private var last: Snackbar = _ 32 | 33 | def remove(index: Int, item: T) = { 34 | recycleBin.append((index, item)) 35 | val count = recycleBin.length 36 | last = Snackbar 37 | .make(view, view.getResources.getQuantityString(R.plurals.removed, count, count: Integer), Snackbar.LENGTH_LONG) 38 | .setCallback(removedCallback).setAction(R.string.undo, (_ => { 39 | undo(recycleBin.reverseIterator) 40 | recycleBin.clear 41 | }): View.OnClickListener) 42 | last.show 43 | } 44 | 45 | def flush = if (last != null) last.dismiss 46 | } 47 | -------------------------------------------------------------------------------- /travis-ci/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export ARCH=`uname -m` 4 | export GOROOT_BOOTSTRAP=$HOME/.android/go 5 | export ANDROID_NDK_HOME=$HOME/.android/android-ndk-r12b 6 | export ANDROID_HOME=$HOME/.android/android-sdk-linux 7 | export PATH=${ANDROID_NDK_HOME}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools:${PATH} 8 | 9 | if [ ! -d "$ANDROID_HOME" ]; then 10 | mkdir -p $ANDROID_HOME 11 | pushd $HOME/.android 12 | wget -q http://dl.google.com/android/android-sdk_r24.4.1-linux.tgz 13 | tar xf android-sdk_r24.4.1-linux.tgz 14 | popd 15 | fi 16 | 17 | if [ ! -d "$ANDROID_NDK_HOME" ]; then 18 | mkdir -p $ANDROID_NDK_HOME 19 | pushd $HOME/.android 20 | wget -q http://dl.google.com/android/repository/android-ndk-r12b-linux-${ARCH}.zip 21 | unzip -q android-ndk-r12b-linux-${ARCH}.zip 22 | popd 23 | fi 24 | 25 | if [ ! -d "$GOROOT_BOOTSTRAP" ]; then 26 | mkdir -p $GOROOT_BOOTSTRAP 27 | pushd $HOME/.android 28 | wget https://storage.googleapis.com/golang/go1.6.3.linux-amd64.tar.gz 29 | tar xf go1.6.3.linux-amd64.tar.gz 30 | popd 31 | fi 32 | 33 | ( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) | android update sdk --filter tools,platform-tools,build-tools-24.0.0,android-23,android-24,extra-google-m2repository --no-ui -a 34 | ( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) | android update sdk --filter extra-android-m2repository --no-ui -a 35 | -------------------------------------------------------------------------------- /travis.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shadowsocksr-rm/shadowsocksr-android/ad7af09df3421a2b8c159734a5834d9714ef1613/travis.keystore --------------------------------------------------------------------------------