Commit 3e8b2541 authored by Mygod's avatar Mygod

Initial draft for porting this project to Gradle + Kotlin

This is one of my stupidest idea ever.
parent 2f682ef3
target
# Gradle & Android Studio
local.properties
local.sbt
/proguard-sbt.txt
.gradle/
.externalNativeBuild/
build/
captures/
#Intellij IDEA
.idea/
.d
gen/
*.iml
mobile/src/main/libs
mobile/src/main/obj
mobile/src/main/jni/overture
core/src/main/libs
core/src/main/obj
core/src/main/jni/overture
mobile/src/overture/.deps
mobile/src/overture/bin
mobile/src/overture/pkg
core/src/overture/.deps
core/src/overture/bin
core/src/overture/pkg
mobile/src/overture/src/github.com/Sirupsen
mobile/src/overture/src/github.com/mattn
mobile/src/overture/src/github.com/miekg
mobile/src/overture/src/github.com/shawn1m
mobile/src/overture/src/github.com/tools
mobile/src/overture/src/golang.org
mobile/src/overture/src/gopkg.in
core/src/overture/src/github.com/Sirupsen
core/src/overture/src/github.com/mattn
core/src/overture/src/github.com/miekg
core/src/overture/src/github.com/shawn1m
core/src/overture/src/github.com/tools
core/src/overture/src/golang.org
core/src/overture/src/gopkg.in
# work in progress
tv/
things/
[submodule "mobile/src/main/jni/shadowsocks-libev"]
path = mobile/src/main/jni/shadowsocks-libev
[submodule "core/src/main/jni/shadowsocks-libev"]
path = core/src/main/jni/shadowsocks-libev
url = https://github.com/shadowsocks/shadowsocks-libev
[submodule "mobile/src/main/jni/badvpn"]
path = mobile/src/main/jni/badvpn
[submodule "core/src/main/jni/badvpn"]
path = core/src/main/jni/badvpn
url = https://github.com/shadowsocks/badvpn.git
branch = shadowsocks-android
[submodule "mobile/src/main/jni/libancillary"]
path = mobile/src/main/jni/libancillary
[submodule "core/src/main/jni/libancillary"]
path = core/src/main/jni/libancillary
url = https://github.com/shadowsocks/libancillary.git
branch = shadowsocks-android
[submodule "mobile/src/main/jni/libevent"]
path = mobile/src/main/jni/libevent
[submodule "core/src/main/jni/libevent"]
path = core/src/main/jni/libevent
url = https://github.com/shadowsocks/libevent.git
branch = shadowsocks-android
[submodule "mobile/src/main/jni/redsocks"]
path = mobile/src/main/jni/redsocks
[submodule "core/src/main/jni/redsocks"]
path = core/src/main/jni/redsocks
url = https://github.com/shadowsocks/redsocks.git
branch = shadowsocks-android
[submodule "mobile/src/main/jni/mbedtls"]
path = mobile/src/main/jni/mbedtls
[submodule "core/src/main/jni/mbedtls"]
path = core/src/main/jni/mbedtls
url = https://github.com/ARMmbed/mbedtls
[submodule "mobile/src/main/jni/pcre"]
path = mobile/src/main/jni/pcre
[submodule "core/src/main/jni/pcre"]
path = core/src/main/jni/pcre
url = https://android.googlesource.com/platform/external/pcre
[submodule "mobile/src/main/jni/libsodium"]
path = mobile/src/main/jni/libsodium
[submodule "core/src/main/jni/libsodium"]
path = core/src/main/jni/libsodium
url = https://github.com/jedisct1/libsodium.git
branch = stable
[submodule "mobile/src/main/jni/libev"]
path = mobile/src/main/jni/libev
[submodule "core/src/main/jni/libev"]
path = core/src/main/jni/libev
url = https://github.com/shadowsocks/libev.git
[submodule "mobile/src/overture/go"]
path = mobile/src/overture/go
[submodule "core/src/overture/go"]
path = core/src/overture/go
url = https://github.com/shadowsocks/go.git
[submodule "mobile/src/overture/src/github.com/shadowsocks/overture"]
path = mobile/src/overture/src/github.com/shadowsocks/overture
[submodule "core/src/overture/src/github.com/shadowsocks/overture"]
path = core/src/overture/src/github.com/shadowsocks/overture
url = https://github.com/shadowsocks/overture.git
# Scala source
".*\\.scala" -> frame open:"/*" line:"*" close:"*/"
-Xms1g
-Xmx3g
-Xss2m
-XX:+CMSClassUnloadingEnabled
-XX:+UseConcMarkSweepGC
language: android
jdk:
- oraclejdk8
env:
global:
- NDK_VERSION=r16
- NDK_CCACHE=ccache
- GOROOT_BOOTSTRAP=$GOROOT
- ANDROID_NDK_HOME=$HOME/.android/android-ndk-${NDK_VERSION}
- SBTPATH=$HOME/.sbt
- PATH=${ANDROID_NDK_HOME}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools:${PATH}
scala:
- 2.11.11
before_cache:
- find $HOME/.sbt -name "*.lock" | xargs rm
- find $HOME/.ivy2 -name "*.lock" | xargs rm
cache:
directories:
- $HOME/.ivy2
- $HOME/.sbt
android:
components:
- tools
- build-tools-27.0.0
- extra-android-m2repository
- extra-google-m2repository
install:
- >
if [ ! -d "$ANDROID_NDK_HOME" ]; then
mkdir -p $ANDROID_NDK_HOME;
pushd $HOME/.android;
export ARCH=`uname -m`;
wget -q http://dl.google.com/android/repository/android-ndk-${NDK_VERSION}-linux-${ARCH}.zip;
unzip -q android-ndk-${NDK_VERSION}-linux-${ARCH}.zip;
popd;
fi
- >
if [ ! -f "$SBTPATH/sbt" ]; then
pushd $SBTPATH;
wget -q https://raw.githubusercontent.com/paulp/sbt-extras/master/sbt;
chmod a+x sbt;
popd;
fi
script:
- $SBTPATH/sbt go-build android:package
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
kotlinVersion = '1.2.0'
minSdkVersion = 19
sdkVersion = 27
supportLibraryVersion = '27.0.2'
takisoftFixVersion = '27.0.2.0'
playServicesVersion = '11.6.2'
junitVersion = '4.12'
androidTestVersion = '1.0.1'
androidEspressoVersion = '3.0.1'
}
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.google.gms:google-services:3.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
lazy val commonSettings = Seq(
scalaVersion := "2.11.12",
dexMaxHeap := "4g",
organization := "com.github.shadowsocks",
platformTarget := "android-27",
compileOrder := CompileOrder.JavaThenScala,
javacOptions ++= "-source" :: "1.7" :: "-target" :: "1.7" :: Nil,
scalacOptions ++= "-target:jvm-1.7" :: "-Xexperimental" :: Nil,
ndkArgs := "-j" :: java.lang.Runtime.getRuntime.availableProcessors.toString :: Nil,
ndkAbiFilter := Seq("armeabi-v7a", "arm64-v8a", "x86"),
proguardVersion := "5.3.3",
proguardCache := Seq(),
shrinkResources := true,
typedResources := false,
resConfigs := Seq("fa", "ja", "ko", "ru", "zh-rCN", "zh-rTW"),
resolvers += Resolver.jcenterRepo,
resolvers += Resolver.bintrayRepo("gericop", "maven"),
resolvers += "google" at "https://maven.google.com"
)
val supportLibsVersion = "27.0.2"
val takisoftFixVersion = "27.0.2.0"
lazy val root = Project(id = "shadowsocks-android", base = file("."))
.settings(commonSettings)
.aggregate(plugin, mobile)
install in Android := (install in (mobile, Android)).value
run in Android := (run in (mobile, Android)).evaluated
lazy val plugin = project
.settings(commonSettings)
.settings(
libraryDependencies ++=
"com.android.support" % "preference-v14" % supportLibsVersion ::
"com.takisoft.fix" % "preference-v7" % takisoftFixVersion ::
"com.takisoft.fix" % "preference-v7-simplemenu" % takisoftFixVersion ::
Nil
)
lazy val mobile = project
.settings(commonSettings)
.settings(
libraryDependencies ++=
"com.android.support" % "customtabs" % supportLibsVersion ::
"com.android.support" % "design" % supportLibsVersion ::
"com.android.support" % "gridlayout-v7" % supportLibsVersion ::
Nil
)
.dependsOn(plugin)
apply plugin: 'com.android.library'
android {
compileSdkVersion rootProject.sdkVersion
defaultConfig {
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.sdkVersion
versionCode 1
versionName "1.0.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
ndkBuild {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86'
arguments "-j${Runtime.runtime.availableProcessors()}"
}
}
}
buildTypes {
release {
minifyEnabled false
}
}
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}
}
dependencies {
api project(':plugin')
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "com.android.support.test:runner:$androidTestVersion"
androidTestImplementation "com.android.support.test.espresso:espresso-core:$androidEspressoVersion"
}
package com.github.shadowsocks;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.github.shadowsockscorelibraries.test", appContext.getPackageName());
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.shadowsocks.core"/>
File moved
File moved
File moved
File moved
File moved
File moved
pcre @ 222bbf4b
File moved
File moved
File moved
File moved
package com.github.shadowsocks;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
\ No newline at end of file
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#Fri Dec 01 17:03:17 PST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
import java.util.regex.Matcher
import java.util.regex.Pattern
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
def getCurrentFlavor() {
Gradle gradle = getGradle()
String tskReqStr = gradle.getStartParameter().getTaskRequests().toString()
Pattern pattern;
if( tskReqStr.contains( "assemble" ) )
pattern = Pattern.compile("assemble(\\w+)(Release|Debug)")
else
pattern = Pattern.compile("generate(\\w+)(Release|Debug)")
Matcher matcher = pattern.matcher( tskReqStr )
if( matcher.find() )
return matcher.group(1).toLowerCase()
else
{
println "NO MATCH FOUND"
return "";
}
}
android {
compileSdkVersion rootProject.sdkVersion
defaultConfig {
applicationId "com.github.shadowsocks"
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.sdkVersion
versionCode 196
versionName "4.3.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets.main.jniLibs.srcDirs +=
new File(project(':core').buildDir, "intermediates/bundles/${getCurrentFlavor()}/jni")
sourceSets.main.jniLibs.srcDirs += new File(project(':core').projectDir, "src/main/jni/overture")
}
dependencies {
implementation project(':core')
implementation "com.android.support:customtabs:$supportLibraryVersion"
implementation "com.android.support:design:$supportLibraryVersion"
implementation "com.android.support:gridlayout-v7:$supportLibraryVersion"
implementation 'com.futuremind.recyclerfastscroll:fastscroll:0.2.5'
implementation 'com.evernote:android-job:1.2.1'
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01'
implementation "com.google.android.gms:play-services-ads:$playServicesVersion"
implementation "com.google.android.gms:play-services-analytics:$playServicesVersion"
implementation "com.google.android.gms:play-services-gcm:$playServicesVersion"
implementation "com.google.firebase:firebase-config:$playServicesVersion"
implementation 'com.j256.ormlite:ormlite-android:5.0'
implementation 'com.mikepenz:crossfader:1.5.1'
implementation 'com.mikepenz:fastadapter:3.0.4'
implementation 'com.mikepenz:iconics-core:3.0.0'
implementation 'com.mikepenz:materialdrawer:6.0.2'
implementation 'com.mikepenz:materialize:1.1.2'
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
implementation "com.takisoft.fix:preference-v7-simplemenu:$takisoftFixVersion"
implementation 'com.twofortyfouram:android-plugin-api-for-locale:1.0.2'
implementation 'dnsjava:dnsjava:2.1.8'
implementation 'eu.chainfire:libsuperuser:1.0.0.201704021214'
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation 'net.glxn.qrgen:android:2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "com.android.support.test:runner:$androidTestVersion"
androidTestImplementation "com.android.support.test.espresso:espresso-core:$androidEspressoVersion"
}
repositories {
mavenCentral()
}
apply plugin: 'com.google.gms.google-services'
enablePlugins(AndroidApp)
enablePlugins(AndroidGms)
android.useSupportVectors
name := "shadowsocks"
version := "4.3.0"
versionCode := Some(196)
proguardOptions ++=
"-dontwarn com.google.android.gms.internal.**" ::
"-dontwarn com.j256.ormlite.**" ::
"-dontwarn okhttp3.**" ::
"-dontwarn okio.**" ::
"-dontwarn org.xbill.**" ::
"-keep class com.github.shadowsocks.JniHelper { *; }" ::
"-dontwarn com.evernote.android.job.gcm.**" ::
"-dontwarn com.evernote.android.job.util.GcmAvailableHelper" ::
"-keep public class com.evernote.android.job.v21.PlatformJobService" ::
"-keep public class com.evernote.android.job.v14.PlatformAlarmService" ::
"-keep public class com.evernote.android.job.v14.PlatformAlarmReceiver" ::
"-keep public class com.evernote.android.job.JobBootReceiver" ::
"-keep public class com.evernote.android.job.JobRescheduleService" ::
Nil
val playServicesVersion = "11.6.2"
resolvers += Resolver.jcenterRepo
libraryDependencies ++=
"com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" ::
"com.evernote" % "android-job" % "1.2.1" ::
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" ::
"com.google.android.gms" % "play-services-ads" % playServicesVersion ::
"com.google.android.gms" % "play-services-analytics" % playServicesVersion ::
"com.google.android.gms" % "play-services-gcm" % playServicesVersion ::
"com.google.firebase" % "firebase-config" % playServicesVersion ::
"com.j256.ormlite" % "ormlite-android" % "5.0" ::
"com.mikepenz" % "crossfader" % "1.5.1" ::
"com.mikepenz" % "fastadapter" % "3.0.4" ::
"com.mikepenz" % "iconics-core" % "3.0.0" ::
"com.mikepenz" % "materialdrawer" % "6.0.2" ::
"com.mikepenz" % "materialize" % "1.1.2" ::
"com.squareup.okhttp3" % "okhttp" % "3.9.1" ::
"com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2" ::
"dnsjava" % "dnsjava" % "2.1.8" ::
"eu.chainfire" % "libsuperuser" % "1.0.0.201704021214" ::
"me.dm7.barcodescanner" % "zxing" % "1.9.8" ::
"net.glxn.qrgen" % "android" % "2.0" ::
Nil
packagingOptions := PackagingOptions(excludes =
"META-INF/maven/com.squareup.okio/okio/pom.properties" ::
"META-INF/maven/com.squareup.okio/okio/pom.xml" ::
"META-INF/maven/com.squareup.okhttp3/okhttp/pom.properties" ::
"META-INF/maven/com.squareup.okhttp3/okhttp/pom.xml" ::
Nil)
lazy val goClean: TaskKey[Unit] = TaskKey[Unit]("go-clean", "Clean go build dependencies")
goClean := {
IO.delete(baseDirectory(base => base / "src/overture/.deps").value)
IO.delete(baseDirectory(base => base / "src/overture/bin").value)
IO.delete(baseDirectory(base => base / "src/overture/go/bin").value)
IO.delete(baseDirectory(base => base / "src/main/jni/overture").value)
}
lazy val goBuild: TaskKey[Unit] = TaskKey[Unit]("go-build", "Build go and overture")
goBuild := {
Process(Seq("mobile/src/overture/make.bash", minSdkVersion.value)) ! streams.value.log match {
case 0 => // Success!
case n => sys.error(s"Native build script exit code: $n")
}
}
<issue id="NewApi">
<ignore path="src/main/res/drawable"/>
</issue>
key.alias: your_key_alias
key.store: /path/to/your/key/store
key.store.password: your_key_password
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
package com.github.shadowsocks;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.github.shadowsocks", appContext.getPackageName());
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" package="com.github.shadowsocks">
xmlns:tools="http://schemas.android.com/tools"
package="com.github.shadowsocks">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
......@@ -15,15 +16,12 @@
<uses-feature android:name="android.hardware.camera"
android:required="false"/>
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="27"/>
<application
android:name=".ShadowsocksApplication"
android:name=".App"
android:icon="@mipmap/ic_launcher"
android:backupAgent=".ShadowsocksBackupAgent"
android:backupAgent=".ConfigBackupHelper"
android:label="@string/app_name"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
android:banner="@drawable/ic_start_connected">
......@@ -33,6 +31,9 @@
android:value="@integer/google_play_services_version"/>
<meta-data android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI_zVxZthz2HDuz9toTvkYvL0L5GA-OjeUIfBeXg"/>
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts"/>
<activity
android:name=".MainActivity"
......@@ -68,7 +69,7 @@
android:launchMode="singleTask"/>
<activity
android:name=".ShadowsocksRunnerActivity"
android:name=".VpnRequestActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:excludeFromRecents="true"
android:taskAffinity=""
......@@ -87,7 +88,7 @@
android:parentActivityName=".MainActivity"
android:excludeFromRecents="true"/>
<activity android:name=".TaskerActivity"
<activity android:name=".tasker.ConfigActivity"
android:icon="@mipmap/ic_launcher">
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
......@@ -130,7 +131,7 @@
android:exported="false">
</service>
<service android:name=".bg.ServiceTileService" android:label="@string/quick_toggle"
<service android:name=".bg.TileService" android:label="@string/quick_toggle"
android:process=":bg"
android:icon="@drawable/ic_start_connected"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
......@@ -145,7 +146,7 @@
</intent-filter>
</receiver>
<receiver android:name=".TaskerReceiver" android:process=":bg" tools:ignore="ExportedReceiver">
<receiver android:name=".tasker.ActionListener" android:process=":bg" tools:ignore="ExportedReceiver">
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING"/>
</intent-filter>
......@@ -165,5 +166,4 @@
<receiver android:name="com.evernote.android.job.JobBootReceiver"
android:process=":bg"/>
</application>
</manifest>
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks
import android.annotation.TargetApi
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
class AboutFragment : ToolbarFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.layout_about, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
toolbar.title = getString(R.string.about_title, BuildConfig.VERSION_NAME)
val web = view.findViewById<WebView>(R.id.web_view)
web.loadUrl("file:///android_asset/pages/about.html")
web.webViewClient = object : WebViewClient() {
@Suppress("OverridingDeprecatedMember")
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
(activity as MainActivity).launchUrl(url)
return true
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest): Boolean {
(activity as MainActivity).launchUrl(request.url)
return true
}
}
}
}
This diff is collapsed.
This diff is collapsed.
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import com.github.shadowsocks.App.Companion.app
class BootReceiver : BroadcastReceiver() {
companion object {
private val componentName by lazy { ComponentName(app, BootReceiver::class.java) }
var enabled: Boolean
get() = app.packageManager.getComponentEnabledSetting(componentName) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
set(value) = app.packageManager.setComponentEnabledSetting(componentName,
if (value) PackageManager.COMPONENT_ENABLED_STATE_ENABLED
else PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP)
}
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) app.startService()
}
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks
import android.app.backup.BackupAgentHelper
import android.app.backup.FileBackupHelper
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.utils.Key
class ConfigBackupHelper : BackupAgentHelper() {
override fun onCreate() = addHelper("com.github.shadowsocks.database.profile",
FileBackupHelper(this, "../databases/" + Key.PROFILE, Acl.CUSTOM_RULES + ".acl"))
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.github.shadowsocks.utils.childFragManager
class GlobalSettingsFragment : ToolbarFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.layout_global_settings, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
toolbar.setTitle(R.string.settings)
val fm = childFragmentManager
fm.beginTransaction().replace(R.id.content, GlobalSettingsPreferenceFragment()).commit()
fm.executePendingTransactions()
}
override fun onDetach() {
super.onDetach()
childFragManager = null
}
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference
import android.support.v7.preference.Preference
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.TcpFastOpen
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompatDividers
class GlobalSettingsPreferenceFragment : PreferenceFragmentCompatDividers() {
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.preferenceDataStore = DataStore
DataStore.initGlobal()
addPreferencesFromResource(R.xml.pref_global)
val switch = findPreference(Key.isAutoConnect) as SwitchPreference
switch.setOnPreferenceChangeListener { _, value ->
BootReceiver.enabled = value as Boolean
true
}
switch.isChecked = BootReceiver.enabled
val tfo = findPreference(Key.tfo) as SwitchPreference
tfo.isChecked = TcpFastOpen.sendEnabled
tfo.setOnPreferenceChangeListener { _, value ->
val result = TcpFastOpen.enabled(value as Boolean)
if (result != null && result != "Success.")
Snackbar.make(activity!!.findViewById(R.id.snackbar), result, Snackbar.LENGTH_LONG).show()
value == TcpFastOpen.sendEnabled
}
if (!TcpFastOpen.supported) {
tfo.isEnabled = false
tfo.summary = getString(R.string.tcp_fastopen_summary_unsupported, System.getProperty("os.version"))
}
val serviceMode = findPreference(Key.serviceMode)
val portProxy = findPreference(Key.portProxy)
val portLocalDns = findPreference(Key.portLocalDns)
val portTransproxy = findPreference(Key.portTransproxy)
val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue ->
val (enabledLocalDns, enabledTransproxy) = when (newValue as String?) {
Key.modeProxy -> Pair(false, false)
Key.modeVpn -> Pair(true, false)
Key.modeTransproxy -> Pair(true, true)
else -> throw IllegalArgumentException()
}
portLocalDns.isEnabled = enabledLocalDns
portTransproxy.isEnabled = enabledTransproxy
true
}
val listener: (Int) -> Unit = {
when (it) {
BaseService.IDLE, BaseService.STOPPED -> {
serviceMode.isEnabled = true
portProxy.isEnabled = true
onServiceModeChange.onPreferenceChange(null, DataStore.serviceMode)
}
else -> {
serviceMode.isEnabled = false
portProxy.isEnabled = false
portLocalDns.isEnabled = false
portTransproxy.isEnabled = false
}
}
}
listener((activity as MainActivity).state)
MainActivity.stateListener = listener
serviceMode.onPreferenceChangeListener = onServiceModeChange
}
override fun onDestroy() {
MainActivity.stateListener = null
super.onDestroy()
}
}
This diff is collapsed.
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import com.github.shadowsocks.plugin.PluginContract
import com.github.shadowsocks.preference.DataStore
class ProfileConfigActivity : AppCompatActivity() {
companion object {
const val REQUEST_CODE_PLUGIN_HELP = 1
}
private val child by lazy { supportFragmentManager.findFragmentById(R.id.content) as ProfileConfigFragment }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_profile_config)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.setTitle(R.string.profile_config)
toolbar.setNavigationIcon(R.drawable.ic_navigation_close)
toolbar.setNavigationOnClickListener { onBackPressed() }
toolbar.inflateMenu(R.menu.profile_config_menu)
toolbar.setOnMenuItemClickListener(child)
}
override fun onBackPressed() {
if (DataStore.dirty) AlertDialog.Builder(this)
.setTitle(R.string.unsaved_changes_prompt)
.setPositiveButton(R.string.yes, { _, _ -> child.saveAndExit() })
.setNegativeButton(R.string.no, { _, _ -> finish() })
.setNeutralButton(android.R.string.cancel, null)
.create()
.show() else super.onBackPressed()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode != REQUEST_CODE_PLUGIN_HELP) super.onActivityResult(requestCode, resultCode, data)
else if (resultCode == Activity.RESULT_OK) AlertDialog.Builder(this)
.setTitle("?")
.setMessage(data?.getCharSequenceExtra(PluginContract.EXTRA_HELP_MESSAGE))
.show()
}
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks
import android.app.Activity
import android.content.Intent
import android.content.pm.ShortcutManager
import android.os.Build
import android.os.Bundle
import android.os.PersistableBundle
import android.support.v4.content.pm.ShortcutInfoCompat
import android.support.v4.content.pm.ShortcutManagerCompat
import android.support.v4.graphics.drawable.IconCompat
import android.support.v7.app.AppCompatActivity
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.bg.BaseService
class QuickToggleShortcut : AppCompatActivity(), ShadowsocksConnection.Interface {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
if (intent.action == Intent.ACTION_CREATE_SHORTCUT) {
setResult(Activity.RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this,
ShortcutInfoCompat.Builder(this, "toggle")
.setIntent(Intent(this, QuickToggleShortcut::class.java).setAction(Intent.ACTION_MAIN))
.setIcon(IconCompat.createWithResource(this, R.drawable.ic_qu_shadowsocks_launcher))
.setShortLabel(getString(R.string.quick_toggle))
.build()))
finish()
} else {
connection.connect()
if (Build.VERSION.SDK_INT >= 25) getSystemService(ShortcutManager::class.java).reportShortcutUsed("toggle")
}
}
override fun onServiceConnected(service: IShadowsocksService) {
when (service.state) {
BaseService.STOPPED -> app.startService()
BaseService.CONNECTED -> app.stopService()
}
finish()
}
override fun onDestroy() {
connection.disconnect()
super.onDestroy()
}
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks
import android.content.pm.PackageManager
import android.content.pm.ShortcutManager
import android.os.Build
import android.os.Bundle
import android.support.v4.app.ActivityCompat
import android.support.v4.app.TaskStackBuilder
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.widget.Toast
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.google.zxing.Result
import me.dm7.barcodescanner.zxing.ZXingScannerView
class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
companion object {
private const val MY_PERMISSIONS_REQUEST_CAMERA = 1
}
private lateinit var scannerView: ZXingScannerView
private fun navigateUp() {
val intent = parentActivityIntent
if (shouldUpRecreateTask(intent) || isTaskRoot)
TaskStackBuilder.create(this).addNextIntentWithParentStack(intent).startActivities()
else finish()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_scanner)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.title = title
toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_material)
toolbar.setNavigationOnClickListener { navigateUp() }
scannerView = findViewById(R.id.scanner)
if (Build.VERSION.SDK_INT >= 25) getSystemService(ShortcutManager::class.java).reportShortcutUsed("scan")
}
override fun onResume() {
super.onResume()
val permissionCheck = ContextCompat.checkSelfPermission(this,
android.Manifest.permission.CAMERA)
if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
scannerView.setResultHandler(this) // Register ourselves as a handler for scan results.
scannerView.startCamera() // Start camera on resume
} else ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA),
MY_PERMISSIONS_REQUEST_CAMERA)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode == MY_PERMISSIONS_REQUEST_CAMERA)
if (grantResults.getOrNull(0) == PackageManager.PERMISSION_GRANTED) {
scannerView.setResultHandler(this)
scannerView.startCamera()
} else {
Toast.makeText(this, R.string.add_profile_scanner_permission_required, Toast.LENGTH_SHORT).show()
finish()
}
else super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
override fun onPause() {
super.onPause()
scannerView.stopCamera() // Stop camera on pause
}
override fun handleResult(rawResult: Result?) {
Profile.findAll(rawResult?.text).forEach { ProfileManager.createProfile(it) }
navigateUp()
}
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.os.RemoteException
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.utils.Action
import java.util.*
class ShadowsocksConnection(private val instance: Interface) : ServiceConnection {
companion object {
private val connections = WeakHashMap<Interface, ShadowsocksConnection>()
}
interface Interface : IBinder.DeathRecipient {
val serviceCallback: IShadowsocksServiceCallback? get() = null
val connection: ShadowsocksConnection
get() = connections.getOrPut(this, { ShadowsocksConnection(this) })
fun onServiceConnected(service: IShadowsocksService) { }
/**
* Different from Android framework, this method will be called even when you call `detachService`.
*/
fun onServiceDisconnected() { }
override fun binderDied() { }
}
private var connectionActive = false
private var callbackRegistered = false
private var binder: IBinder? = null
var listeningForBandwidth = false
set(value) {
val service = service
if (listeningForBandwidth != value && service != null && instance.serviceCallback != null)
if (value) service.startListeningForBandwidth(instance.serviceCallback)
else service.stopListeningForBandwidth(instance.serviceCallback)
field = value
}
var service: IShadowsocksService? = null
override fun onServiceConnected(name: ComponentName?, binder: IBinder) {
this.binder = binder
binder.linkToDeath(instance, 0)
val service = IShadowsocksService.Stub.asInterface(binder)!!
this.service = service
if (instance.serviceCallback != null && !callbackRegistered) try {
service.registerCallback(instance.serviceCallback)
callbackRegistered = true
if (listeningForBandwidth) service.startListeningForBandwidth(instance.serviceCallback)
} catch (_: RemoteException) { }
instance.onServiceConnected(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
unregisterCallback()
instance.onServiceDisconnected()
service = null
binder = null
}
private fun unregisterCallback() {
val service = service
if (service != null && instance.serviceCallback != null && callbackRegistered) try {
service.unregisterCallback(instance.serviceCallback)
} catch (_: RemoteException) { }
callbackRegistered = false
}
fun connect() {
if (connectionActive) return
connectionActive = true
val intent = Intent(instance as Context, BaseService.serviceClass.java).setAction(Action.SERVICE)
instance.bindService(intent, this, Context.BIND_AUTO_CREATE)
}
fun disconnect() {
unregisterCallback()
instance.onServiceDisconnected()
if (connectionActive) try {
(instance as Context).unbindService(this)
} catch (_: IllegalArgumentException) { } // ignore
connectionActive = false
binder?.unlinkToDeath(instance, 0)
binder = null
service = null
}
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.Toolbar
import android.view.View
/**
* @author Mygod
*/
open class ToolbarFragment : Fragment() {
protected lateinit var toolbar: Toolbar
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
toolbar = view.findViewById<Toolbar>(R.id.toolbar)
val activity = activity as MainActivity
if (activity.crossfader == null) activity.drawer.setToolbar(activity, toolbar, true)
}
open fun onTrafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { }
open fun onBackPressed(): Boolean = false
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks
import android.app.KeyguardManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.VpnService
import android.os.Bundle
import android.os.PersistableBundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.utils.broadcastReceiver
class VpnRequestActivity : AppCompatActivity(), ShadowsocksConnection.Interface {
companion object {
private const val TAG = "VpnRequestActivity"
private const val REQUEST_CONNECT = 1
}
private var receiver: BroadcastReceiver? = null
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
if (!BaseService.usingVpnMode) {
finish()
return
}
val km = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
if (km.inKeyguardRestrictedInputMode()) {
receiver = broadcastReceiver { _, _ -> connection.connect() }
registerReceiver(receiver, IntentFilter(Intent.ACTION_USER_PRESENT))
} else connection.connect()
}
override fun onServiceConnected(service: IShadowsocksService) {
app.handler.postDelayed({
val intent = VpnService.prepare(this)
if (intent == null) onActivityResult(REQUEST_CONNECT, RESULT_OK, null)
else startActivityForResult(intent, REQUEST_CONNECT)
}, 1000)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == RESULT_OK) app.startService() else Log.e(TAG, "Failed to start VpnService")
finish()
}
override fun onDestroy() {
super.onDestroy()
connection.disconnect()
if (receiver != null) unregisterReceiver(receiver)
}
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.acl
import android.support.v7.util.SortedList
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.utils.Subnet
import com.github.shadowsocks.utils.asIterable
import com.j256.ormlite.field.DatabaseField
import java.io.File
import java.io.FileNotFoundException
import java.io.Reader
import java.net.URL
class Acl {
companion object {
const val TAG = "Acl"
const val ALL = "all"
const val BYPASS_LAN = "bypass-lan"
const val BYPASS_CHN = "bypass-china"
const val BYPASS_LAN_CHN = "bypass-lan-china"
const val GFWLIST = "gfwlist"
const val CHINALIST = "china-list"
const val CUSTOM_RULES = "custom-rules"
const val CUSTOM_RULES_FLATTENED = "custom-rules-flattened"
val networkAclParser = "^IMPORT_URL\\s*<(.+)>\\s*$".toRegex()
fun getFile(id: String) = File(app.filesDir, id + ".acl")
val customRules: Acl get() {
val acl = Acl()
try {
acl.fromId(CUSTOM_RULES)
} catch (_: FileNotFoundException) { }
acl.bypass = true
acl.bypassHostnames.clear() // everything is bypassed
return acl
}
fun save(id: String, acl: Acl): Unit = getFile(id).bufferedWriter().use { it.write(acl.toString()) }
}
private abstract class BaseSorter<T> : SortedList.Callback<T>() {
override fun onInserted(position: Int, count: Int) { }
override fun areContentsTheSame(oldItem: T?, newItem: T?): Boolean = oldItem == newItem
override fun onMoved(fromPosition: Int, toPosition: Int) { }
override fun onChanged(position: Int, count: Int) { }
override fun onRemoved(position: Int, count: Int) { }
override fun areItemsTheSame(item1: T?, item2: T?): Boolean = item1 == item2
override fun compare(o1: T?, o2: T?): Int =
if (o1 == null) if (o2 == null) 0 else 1 else if (o2 == null) -1 else compareNonNull(o1, o2)
abstract fun compareNonNull(o1: T, o2: T): Int
}
private open class DefaultSorter<T : Comparable<T>> : BaseSorter<T>() {
override fun compareNonNull(o1: T, o2: T): Int = o1.compareTo(o2)
}
private object StringSorter : DefaultSorter<String>()
private object SubnetSorter : DefaultSorter<Subnet>()
private object URLSorter : BaseSorter<URL>() {
private val ordering = compareBy<URL>({ it.host }, { it.port }, { it.file }, { it.protocol })
override fun compareNonNull(o1: URL, o2: URL): Int = ordering.compare(o1, o2)
}
@DatabaseField(generatedId = true)
var id = 0
val bypassHostnames = SortedList(String::class.java, StringSorter)
val proxyHostnames = SortedList(String::class.java, StringSorter)
val subnets = SortedList(Subnet::class.java, SubnetSorter)
val urls = SortedList(URL::class.java, URLSorter)
@DatabaseField
var bypass = false
fun fromAcl(other: Acl): Acl {
bypassHostnames.clear()
for (item in other.bypassHostnames.asIterable()) bypassHostnames.add(item)
proxyHostnames.clear()
for (item in other.proxyHostnames.asIterable()) proxyHostnames.add(item)
subnets.clear()
for (item in other.subnets.asIterable()) subnets.add(item)
urls.clear()
for (item in other.urls.asIterable()) urls.add(item)
bypass = other.bypass
return this
}
fun fromReader(reader: Reader, defaultBypass: Boolean = false): Acl {
bypassHostnames.clear()
proxyHostnames.clear()
subnets.clear()
urls.clear()
bypass = defaultBypass
val bypassSubnets by lazy { SortedList(Subnet::class.java, SubnetSorter) }
val proxySubnets by lazy { SortedList(Subnet::class.java, SubnetSorter) }
var hostnames: SortedList<String>? = if (defaultBypass) proxyHostnames else bypassHostnames
var subnets: SortedList<Subnet>? = if (defaultBypass) proxySubnets else bypassSubnets
reader.useLines {
for (line in it) {
val blocks = line.split('#', limit = 2)
val url = networkAclParser.matchEntire(blocks.getOrElse(1, {""}))?.groupValues?.getOrNull(1)
if (url != null) urls.add(URL(url))
val input = blocks[0].trim()
when (input) {
"[outbound_block_list]" -> {
hostnames = null
subnets = null
}
"[black_list]", "[bypass_list]" -> {
hostnames = bypassHostnames
subnets = bypassSubnets
}
"[white_list]", "[proxy_list]" -> {
hostnames = proxyHostnames
subnets = proxySubnets
}
"[reject_all]", "[bypass_all]" -> bypass = true
"[accept_all]", "[proxy_all]" -> bypass = false
else -> if (subnets != null && input.isNotEmpty()) try {
subnets!!.add(Subnet.fromString(input))
} catch (_: IllegalArgumentException) {
hostnames!!.add(input)
}
}
}
}
for (item in (if (bypass) proxySubnets else bypassSubnets).asIterable()) this.subnets.add(item)
return this
}
fun fromId(id: String): Acl = fromReader(Acl.getFile(id).bufferedReader())
fun flatten(depth: Int): Acl {
if (depth > 0) for (url in urls.asIterable()) {
val child = Acl().fromReader(url.openStream().bufferedReader(), bypass).flatten(depth - 1)
if (bypass != child.bypass) {
Log.w(TAG, "Imported network ACL has a conflicting mode set. " +
"This will probably not work as intended. URL: $url")
child.subnets.clear() // subnets for the different mode are discarded
child.bypass = bypass
}
for (item in child.bypassHostnames.asIterable()) bypassHostnames.add(item)
for (item in child.proxyHostnames.asIterable()) proxyHostnames.add(item)
for (item in child.subnets.asIterable()) subnets.add(item)
}
urls.clear()
return this
}
override fun toString(): String {
val result = StringBuilder()
result.append(if (bypass) "[bypass_all]\n" else "[proxy_all]\n")
val bypassList = (if (bypass) bypassHostnames.asIterable().asSequence() else
subnets.asIterable().asSequence().map(Subnet::toString) + proxyHostnames.asIterable().asSequence()).toList()
val proxyList = (if (bypass) subnets.asIterable().asSequence().map(Subnet::toString) +
proxyHostnames.asIterable().asSequence() else bypassHostnames.asIterable().asSequence()).toList()
if (bypassList.isNotEmpty()) {
result.append("[bypass_list]\n")
result.append(bypassList.joinToString("\n"))
result.append('\n')
}
if (proxyList.isNotEmpty()) {
result.append("[proxy_list]\n")
result.append(proxyList.joinToString("\n"))
result.append('\n')
}
result.append(urls.asIterable().joinToString("") { "#IMPORT_URL <$it>\n" })
return result.toString()
}
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.acl
import android.util.Log
import com.evernote.android.job.Job
import com.evernote.android.job.JobCreator
import com.evernote.android.job.JobRequest
import java.io.IOException
import java.net.URL
import java.util.concurrent.TimeUnit
class AclSyncJob(private val route: String) : Job() {
companion object : JobCreator {
private const val TAG = "AclSyncJob"
private const val MAX_RESCHEDULE = 3
override fun create(tag: String): AclSyncJob? {
val parts = tag.split(':', limit = 2)
return when (parts[0]) {
TAG -> AclSyncJob(parts[1])
else -> {
Log.w(TAG, "Unknown job tag: " + tag)
null
}
}
}
fun schedule(route: String): Int = JobRequest.Builder(TAG + ':' + route)
.setExecutionWindow(TimeUnit.SECONDS.toMillis(10), TimeUnit.DAYS.toMillis(28))
.setRequirementsEnforced(true)
.setRequiredNetworkType(JobRequest.NetworkType.UNMETERED)
.setRequiresCharging(true)
.setUpdateCurrent(true)
.build().schedule()
}
override fun onRunJob(params: Params): Result = try {
val acl = URL("https://shadowsocks.org/acl/android/v1/$route.acl").openStream().bufferedReader()
.use { it.readText() }
Acl.getFile(route).printWriter().use { it.write(acl) }
Result.SUCCESS
} catch (e: IOException) {
e.printStackTrace()
if (params.failureCount < AclSyncJob.MAX_RESCHEDULE) Result.RESCHEDULE else Result.FAILURE
} catch (e: Exception) { // unknown failures, probably shouldn't retry
e.printStackTrace()
Result.FAILURE
}
}
This diff is collapsed.
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.bg
import android.content.Context
import android.net.ConnectivityManager
import android.net.LinkProperties
import android.os.Build
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.BuildConfig
import okhttp3.Dns as Okdns
import org.xbill.DNS.*
import java.net.Inet6Address
import java.net.InetAddress
import java.net.NetworkInterface
import java.net.UnknownHostException
import java.util.*
object Dns {
private const val TAG = "Dns"
@Throws(Exception::class)
fun getDnsResolver(context: Context): String {
val dnsResolvers = getDnsResolvers(context)
if (dnsResolvers.isEmpty()) throw Exception("Couldn't find an active DNS resolver")
val dnsResolver = dnsResolvers.iterator().next().toString()
if (dnsResolver.startsWith("/")) return dnsResolver.substring(1)
return dnsResolver
}
@Throws(Exception::class)
private fun getDnsResolvers(context: Context): Collection<InetAddress> {
val addresses = ArrayList<InetAddress>()
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val classLinkProperties = Class.forName("android.net.LinkProperties")
val getActiveLinkPropertiesMethod = ConnectivityManager::class.java.getMethod("getActiveLinkProperties")
val linkProperties = getActiveLinkPropertiesMethod.invoke(connectivityManager)
if (linkProperties != null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
val dnses = classLinkProperties.getMethod("getDnses").invoke(linkProperties) as Collection<*>
dnses.mapTo(addresses) { it as InetAddress }
} else addresses.addAll((linkProperties as LinkProperties).dnsServers)
}
return addresses
}
private val hasIPv6Support get() = try {
val result = NetworkInterface.getNetworkInterfaces().asSequence().flatMap { it.inetAddresses.asSequence() }
.count { it is Inet6Address && !it.isLoopbackAddress && !it.isLinkLocalAddress } > 0
if (result && BuildConfig.DEBUG) Log.d(TAG, "IPv6 address detected")
result
} catch (ex: Exception) {
Log.e(TAG, "Failed to get interfaces' addresses.")
app.track(ex)
false
}
private fun resolve(host: String, addrType: Int): String? {
try {
val lookup = Lookup(host, addrType)
val resolver = SimpleResolver("208.67.220.220")
resolver.setTCP(true)
resolver.setPort(443)
resolver.setTimeout(5)
lookup.setResolver(resolver)
val records = (lookup.run() ?: return null).toMutableList()
records.shuffle()
for (r in records) {
when (addrType) {
Type.A -> return (r as ARecord).address.hostAddress
Type.AAAA -> return (r as AAAARecord).address.hostAddress
}
}
} catch (_: Exception) { }
return null
}
private fun resolve(host: String): String? = try {
InetAddress.getByName(host).hostAddress
} catch (_: UnknownHostException) {
null
}
fun resolve(host: String, enableIPv6: Boolean): String? =
(if (enableIPv6 && hasIPv6Support) resolve(host, Type.AAAA) else null)
?: resolve(host, Type.A)
?: resolve(host)
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.bg
import android.text.TextUtils
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.JniHelper
import java.io.File
object Executable {
const val REDSOCKS = "libredsocks.so"
const val PDNSD = "libpdnsd.so"
const val SS_LOCAL = "libss-local.so"
const val SS_TUNNEL = "libss-tunnel.so"
const val TUN2SOCKS = "libtun2socks.so"
const val OVERTURE = "liboverture.so"
val EXECUTABLES = setOf(SS_LOCAL, SS_TUNNEL, PDNSD, REDSOCKS, TUN2SOCKS, OVERTURE)
fun killAll() {
for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) }) {
val exe = File(File(process, "cmdline").bufferedReader().use { it.readText() }
.split(Character.MIN_VALUE, limit = 2).first())
if (exe.parent == app.applicationInfo.nativeLibraryDir && EXECUTABLES.contains(exe.name)) {
val errno = JniHelper.sigkill(process.name.toInt())
if (errno != 0) Log.w("kill", "SIGKILL ${exe.absolutePath} (${process.name}) failed with $errno")
}
}
}
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.bg
import android.os.Build
import android.os.SystemClock
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.BuildConfig
import com.github.shadowsocks.JniHelper
import com.github.shadowsocks.utils.Commandline
import java.io.IOException
import java.io.InputStream
import java.util.concurrent.Semaphore
class GuardedProcess(private val cmd: List<String>) {
companion object {
const val TAG = "GuardedProcess"
}
private lateinit var guardThread: Thread
@Volatile
private var isDestroyed = false
@Volatile
private lateinit var process: Process
private fun streamLogger(input: InputStream, logger: (String, String) -> Int) = Thread {
try {
input.bufferedReader().useLines { it.forEach { logger(TAG, it) } }
} catch (_: IOException) { } // ignore
}
fun start(onRestartCallback: (() -> Unit)? = null): GuardedProcess {
val semaphore = Semaphore(1)
semaphore.acquire()
var ioException: IOException? = null
guardThread = Thread({
try {
var callback: (() -> Unit)? = null
while (!isDestroyed) {
if (BuildConfig.DEBUG) Log.d(TAG, "start process: " + Commandline.toString(cmd))
val startTime = SystemClock.elapsedRealtime()
process = ProcessBuilder(cmd)
.redirectErrorStream(true)
.directory(app.filesDir)
.start()
streamLogger(process.inputStream, Log::i).start()
streamLogger(process.errorStream, Log::e).start()
if (callback == null) callback = onRestartCallback else callback()
semaphore.release()
process.waitFor()
synchronized(this) {
if (SystemClock.elapsedRealtime() - startTime < 1000) {
Log.w(TAG, "process exit too fast, stop guard: " + Commandline.toString(cmd))
isDestroyed = true
}
}
}
} catch (_: InterruptedException) {
if (BuildConfig.DEBUG) Log.d(TAG, "thread interrupt, destroy process: " + Commandline.toString(cmd))
destroyProcess()
} catch (e: IOException) {
ioException = e
} finally {
semaphore.release()
}
}, "GuardThread-" + cmd.first())
guardThread.start()
semaphore.acquire()
if (ioException != null) throw ioException!!
return this
}
fun destroy() {
isDestroyed = true
guardThread.interrupt()
destroyProcess()
try {
guardThread.join()
} catch (_: InterruptedException) { }
}
private fun destroyProcess() {
if (Build.VERSION.SDK_INT < 24) @Suppress("DEPRECATION") {
JniHelper.sigtermCompat(process)
JniHelper.waitForCompat(process, 500)
}
process.destroy()
}
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.bg
import android.content.Context
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.parseNumericAddress
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
import java.net.Inet6Address
import java.util.*
/**
* This object also uses WeakMap to simulate the effects of multi-inheritance, but more lightweight.
*/
object LocalDnsService {
interface Interface : BaseService.Interface {
var overtureProcess: GuardedProcess?
get() = overtureProcesses[this]
set(value) {
if (value == null) overtureProcesses.remove(this) else overtureProcesses[this] = value
}
override fun startNativeProcesses() {
super.startNativeProcesses()
val profile = data.profile!!
fun buildOvertureConfig(file: String): String {
val config = JSONObject()
.put("BindAddress", "127.0.0.1:" + DataStore.portLocalDns)
.put("RedirectIPv6Record", true)
.put("DomainBase64Decode", true)
.put("HostsFile", "hosts")
.put("MinimumTTL", 3600)
.put("CacheSize", 4096)
fun makeDns(name: String, address: String, edns: Boolean = true) = {
val dns = JSONObject()
.put("Name", name)
.put("Address", (when (address.parseNumericAddress()) {
is Inet6Address -> "[$address]"
else -> address
}) + ":53")
.put("Timeout", 6)
.put("EDNSClientSubnet", JSONObject().put("Policy", "disable"))
if (edns) dns
.put("Protocol", "tcp")
.put("Socks5Address", "127.0.0.1:" + DataStore.portProxy)
else dns.put("Protocol", "udp")
dns
}
val remoteDns = JSONArray(profile.remoteDns.split(",")
.mapIndexed { i, dns -> makeDns("UserDef-" + i, dns.trim())() })
val localDns = JSONArray(arrayOf(
makeDns("Primary-1", "119.29.29.29", false),
makeDns("Primary-2", "114.114.114.114", false)
))
this as Context
try {
val localLinkDns = Dns.getDnsResolver(this)
localDns.put(makeDns("Primary-3", localLinkDns, false))
} catch (_: Exception) { } // ignore
when (profile.route) {
Acl.BYPASS_CHN, Acl.BYPASS_LAN_CHN, Acl.GFWLIST, Acl.CUSTOM_RULES -> config
.put("PrimaryDNS", localDns)
.put("AlternativeDNS", remoteDns)
.put("IPNetworkFile", "china_ip_list.txt")
.put("DomainFile", "gfwlist.txt")
Acl.CHINALIST -> config
.put("PrimaryDNS", localDns)
.put("AlternativeDNS", remoteDns)
else -> config
.put("PrimaryDNS", remoteDns)
// no need to setup AlternativeDNS in Acl.ALL/BYPASS_LAN mode
.put("OnlyPrimaryDNS", true)
}
File(filesDir, file).bufferedWriter().use { it.write(config.toString()) }
return file
}
if (!profile.udpdns) overtureProcess = GuardedProcess(buildAdditionalArguments(arrayListOf(
File(app.applicationInfo.nativeLibraryDir, Executable.OVERTURE).absolutePath,
"-c", buildOvertureConfig("overture.conf")
))).start()
}
override fun killProcesses() {
super.killProcesses()
overtureProcess?.destroy()
overtureProcess = null
}
}
private val overtureProcesses = WeakHashMap<Interface, GuardedProcess>()
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.bg
import android.net.LocalServerSocket
import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import java.io.File
import java.io.IOException
import java.util.concurrent.atomic.AtomicReference
abstract class LocalSocketListener(private val tag: String) : Thread() {
init {
setUncaughtExceptionHandler(app::track)
}
protected abstract val socketFile: File
private val serverSocket = AtomicReference<LocalServerSocket?>()
/**
* Inherited class do not need to close input/output streams as they will be closed automatically.
*/
protected abstract fun accept(socket: LocalSocket)
override fun run() {
socketFile.delete()
try {
val localSocket = LocalSocket()
localSocket.bind(LocalSocketAddress(socketFile.absolutePath, LocalSocketAddress.Namespace.FILESYSTEM))
serverSocket.set(LocalServerSocket(localSocket.fileDescriptor))
} catch (e: IOException) {
Log.e(tag, "unable to bind", e)
return
}
while (true) {
val serverSocket = serverSocket.get() ?: return
try {
try {
serverSocket.accept()
} catch (e: IOException) {
Log.e(tag, "Error when accept socket", e)
app.track(e)
null
}?.use(this::accept)
} catch (e: Exception) {
Log.e(tag, "Error when recv traffic stat", e)
app.track(e)
}
}
}
fun stopThread() {
val old = serverSocket.getAndSet(null) ?: return
try {
old.close()
} catch (_: Exception) { } // ignore
}
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.bg
import android.app.Service
import android.content.Intent
import android.os.IBinder
/**
* Shadowsocks service at its minimum.
*/
class ProxyService : Service(), BaseService.Interface {
init {
BaseService.register(this)
}
override val tag: String get() = "ShadowsocksProxyService"
override fun createNotification(): ServiceNotification =
ServiceNotification(this, data.profile!!.name, "service-proxy", true)
override fun onBind(intent: Intent): IBinder? = super.onBind(intent)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int =
super<BaseService.Interface>.onStartCommand(intent, flags, startId)
}
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.bg
import android.app.KeyguardManager
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.PowerManager
import android.support.v4.app.NotificationCompat
import android.support.v4.content.ContextCompat
import com.github.shadowsocks.MainActivity
import com.github.shadowsocks.R
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.broadcastReceiver
import java.util.*
class ServiceNotification(private val service: BaseService.Interface, profileName: String,
channel: String, private val visible: Boolean = false) {
private val keyGuard = (service as Context).getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
private val nm by lazy {
(service as Context).getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
private val callback by lazy {
object : IShadowsocksServiceCallback.Stub() {
override fun stateChanged(state: Int, profileName: String?, msg: String?) { } // ignore
override fun trafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
service as Context
val txr = service.getString(R.string.speed, TrafficMonitor.formatTraffic(txRate))
val rxr = service.getString(R.string.speed, TrafficMonitor.formatTraffic(rxRate))
builder.setContentText("$txr↑\t$rxr↓")
style.bigText(service.getString(R.string.stat_summary).format(Locale.ENGLISH, txr, rxr,
TrafficMonitor.formatTraffic(txTotal), TrafficMonitor.formatTraffic(rxTotal)))
show()
}
override fun trafficPersisted(profileId: Int) { }
}
}
private val lockReceiver = broadcastReceiver { _, intent -> update(intent.action) }
private var callbackRegistered = false
private val builder = NotificationCompat.Builder(service as Context, channel)
.setWhen(0)
.setColor(ContextCompat.getColor(service, R.color.material_primary_500))
.setTicker(service.getString(R.string.forward_success))
.setContentTitle(profileName)
.setContentIntent(PendingIntent.getActivity(service, 0, Intent(service, MainActivity::class.java)
.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), 0))
.setSmallIcon(R.drawable.ic_stat_shadowsocks)
private val style = NotificationCompat.BigTextStyle(builder)
private var isVisible = true
init {
service as Context
if (Build.VERSION.SDK_INT < 24) builder.addAction(R.drawable.ic_navigation_close,
service.getString(R.string.stop), PendingIntent.getBroadcast(service, 0, Intent(Action.CLOSE), 0))
val power = service.getSystemService(Context.POWER_SERVICE) as PowerManager
update(if (if (Build.VERSION.SDK_INT >= 20) power.isInteractive else @Suppress("DEPRECATION") power.isScreenOn)
Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true)
val screenFilter = IntentFilter()
screenFilter.addAction(Intent.ACTION_SCREEN_ON)
screenFilter.addAction(Intent.ACTION_SCREEN_OFF)
if (visible && Build.VERSION.SDK_INT in 21 until 26) screenFilter.addAction(Intent.ACTION_USER_PRESENT)
service.registerReceiver(lockReceiver, screenFilter)
}
private fun update(action: String, forceShow: Boolean = false) {
if (forceShow || service.data.state == BaseService.CONNECTED) when (action) {
Intent.ACTION_SCREEN_OFF -> {
setVisible(visible && Build.VERSION.SDK_INT < 21, forceShow)
unregisterCallback() // unregister callback to save battery
}
Intent.ACTION_SCREEN_ON -> {
setVisible(visible && (Build.VERSION.SDK_INT < 21 || !keyGuard.inKeyguardRestrictedInputMode()),
forceShow)
service.data.binder.registerCallback(callback)
service.data.binder.startListeningForBandwidth(callback)
callbackRegistered = true
}
Intent.ACTION_USER_PRESENT -> setVisible(true, forceShow)
}
}
private fun unregisterCallback() {
if (callbackRegistered) {
service.data.binder.unregisterCallback(callback)
callbackRegistered = false
}
}
private fun setVisible(visible: Boolean, forceShow: Boolean = false) {
if (isVisible != visible) {
isVisible = visible
builder.priority = if (visible) NotificationCompat.PRIORITY_LOW else NotificationCompat.PRIORITY_MIN
show()
} else if (forceShow) show()
}
private fun show() = (service as Service).startForeground(1, builder.build())
fun destroy() {
(service as Service).unregisterReceiver(lockReceiver)
unregisterCallback()
service.stopForeground(true)
nm.cancel(1)
}
}
This diff is collapsed.
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.bg
import android.net.LocalSocket
import com.github.shadowsocks.App.Companion.app
import java.io.File
import java.io.IOException
import java.nio.ByteBuffer
import java.nio.ByteOrder
class TrafficMonitorThread : LocalSocketListener("TrafficMonitorThread") {
override val socketFile = File(app.filesDir, "/stat_path")
override fun accept(socket: LocalSocket) {
val buffer = ByteArray(16)
if (socket.inputStream.read(buffer) != 16) throw IOException("Unexpected traffic stat length")
val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN)
TrafficMonitor.update(stat.getLong(0), stat.getLong(8))
socket.outputStream.write(0)
}
}
This diff is collapsed.
package com.github.shadowsocks.plugin
import com.github.shadowsocks.App.Companion.app
object NoPlugin : Plugin() {
override val id: String get() = ""
override val label: CharSequence get() = app.getText(com.github.shadowsocks.R.string.plugin_disabled)
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment