Commit c21bd743 authored by Max Lv's avatar Max Lv

Merge branch 'beta'

parents 9c97dc9b a8822330
...@@ -9,3 +9,4 @@ local.sbt ...@@ -9,3 +9,4 @@ local.sbt
mobile/src/main/libs mobile/src/main/libs
mobile/src/main/obj mobile/src/main/obj
mobile/src/main/jni/overture
...@@ -17,10 +17,6 @@ ...@@ -17,10 +17,6 @@
path = mobile/src/main/jni/redsocks path = mobile/src/main/jni/redsocks
url = https://github.com/shadowsocks/redsocks.git url = https://github.com/shadowsocks/redsocks.git
branch = shadowsocks-android branch = shadowsocks-android
[submodule "mobile/src/main/jni/pdnsd"]
path = mobile/src/main/jni/pdnsd
url = https://github.com/shadowsocks/pdnsd.git
branch = shadowsocks-android
[submodule "mobile/src/main/jni/mbedtls"] [submodule "mobile/src/main/jni/mbedtls"]
path = mobile/src/main/jni/mbedtls path = mobile/src/main/jni/mbedtls
url = https://github.com/ARMmbed/mbedtls url = https://github.com/ARMmbed/mbedtls
...@@ -37,3 +33,6 @@ ...@@ -37,3 +33,6 @@
[submodule "mobile/src/main/jni/libudns"] [submodule "mobile/src/main/jni/libudns"]
path = mobile/src/main/jni/libudns path = mobile/src/main/jni/libudns
url = https://github.com/shadowsocks/libudns.git url = https://github.com/shadowsocks/libudns.git
[submodule "mobile/src/overture"]
path = mobile/src/overture
url = https://github.com/shadowsocks/overture-android.git
...@@ -5,8 +5,9 @@ jdk: ...@@ -5,8 +5,9 @@ jdk:
env: env:
global: global:
- NDK_VERSION=r12b - NDK_VERSION=r14
- NDK_CCACHE=ccache - NDK_CCACHE=ccache
- GOROOT_BOOTSTRAP=$GOROOT
- ANDROID_NDK_HOME=$HOME/.android/android-ndk-${NDK_VERSION} - ANDROID_NDK_HOME=$HOME/.android/android-ndk-${NDK_VERSION}
- SBTPATH=$HOME/.sbt - SBTPATH=$HOME/.sbt
- PATH=${ANDROID_NDK_HOME}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools:${PATH} - PATH=${ANDROID_NDK_HOME}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools:${PATH}
...@@ -49,4 +50,4 @@ install: ...@@ -49,4 +50,4 @@ install:
fi fi
script: script:
- $SBTPATH/sbt android:package - $SBTPATH/sbt go-build android:package
...@@ -12,15 +12,17 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. ...@@ -12,15 +12,17 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala.
* JDK 1.8 * JDK 1.8
* SBT 0.13.0+ * SBT 0.13.0+
* Go 1.4+
* Android SDK * Android SDK
- Build Tools 25+ - Build Tools 25+
- Android Support Repository and Google Repository (see `build.sbt` for version) - Android Support Repository and Google Repository (see `build.sbt` for version)
* Android NDK r12b+ - Android NDK r14+
### BUILD ### BUILD
* Set environment variable `ANDROID_HOME` to `/path/to/android-sdk` * Set environment variable `ANDROID_HOME` to `/path/to/android-sdk`
* (optional) Set environment variable `ANDROID_NDK_HOME` to `/path/to/android-ndk` (default: `$ANDROID_HOME/ndk-bundle`) * (optional) Set environment variable `ANDROID_NDK_HOME` to `/path/to/android-ndk` (default: `$ANDROID_HOME/ndk-bundle`)
* Set environment variable `GOROOT_BOOTSTRAP` to `/path/to/go`
* Create your key following the instructions at https://developer.android.com/studio/publish/app-signing.html * Create your key following the instructions at https://developer.android.com/studio/publish/app-signing.html
* Create `mobile/local.properties` from `mobile/local.properties.example` with your own key information * Create `mobile/local.properties` from `mobile/local.properties.example` with your own key information
* Invoke the building like this * Invoke the building like this
...@@ -29,7 +31,7 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. ...@@ -29,7 +31,7 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala.
git submodule update --init --recursive git submodule update --init --recursive
# Build the App # Build the App
sbt clean android:package-release sbt clean go-build android:package-release
``` ```
### TRANSLATE ### TRANSLATE
...@@ -50,7 +52,7 @@ Translators can go to [POEditor](https://poeditor.com/join/project/u5VHO9vhSf) t ...@@ -50,7 +52,7 @@ Translators can go to [POEditor](https://poeditor.com/join/project/u5VHO9vhSf) t
<li>pcre: <a href="https://android.googlesource.com/platform/external/pcre/+/master/dist2/LICENCE">BSD</a></li> <li>pcre: <a href="https://android.googlesource.com/platform/external/pcre/+/master/dist2/LICENCE">BSD</a></li>
<li>libancillary: <a href="https://github.com/shadowsocks/libancillary/blob/shadowsocks-android/COPYING">BSD</a></li> <li>libancillary: <a href="https://github.com/shadowsocks/libancillary/blob/shadowsocks-android/COPYING">BSD</a></li>
<li>shadowsocks-libev: <a href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/LICENSE">GPLv3</a></li> <li>shadowsocks-libev: <a href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/LICENSE">GPLv3</a></li>
<li>pdnsd: <a href="https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/pdnsd/COPYING">GPLv3</a></li> <li>overture: <a href="https://github.com/shawn1m/overture/blob/master/LICENSE">MIT</a></li>
<li>libev: <a href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/libev/LICENSE">GPLv2</a></li> <li>libev: <a href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/libev/LICENSE">GPLv2</a></li>
<li>libsodium: <a href="https://github.com/jedisct1/libsodium/blob/master/LICENSE">ISC</a></li> <li>libsodium: <a href="https://github.com/jedisct1/libsodium/blob/master/LICENSE">ISC</a></li>
<li>libudns: <a href="https://github.com/shadowsocks/libudns/blob/master/COPYING.LGPL">LGPL</a></li> <li>libudns: <a href="https://github.com/shadowsocks/libudns/blob/master/COPYING.LGPL">LGPL</a></li>
......
...@@ -18,7 +18,7 @@ lazy val commonSettings = Seq( ...@@ -18,7 +18,7 @@ lazy val commonSettings = Seq(
typedResources := false typedResources := false
) )
val supportLibsVersion = "25.1.1" val supportLibsVersion = "25.2.0"
lazy val root = Project(id = "shadowsocks-android", base = file(".")) lazy val root = Project(id = "shadowsocks-android", base = file("."))
.settings(commonSettings) .settings(commonSettings)
.aggregate(plugin, mobile) .aggregate(plugin, mobile)
......
...@@ -2,8 +2,8 @@ enablePlugins(AndroidApp) ...@@ -2,8 +2,8 @@ enablePlugins(AndroidApp)
android.useSupportVectors android.useSupportVectors
name := "shadowsocks" name := "shadowsocks"
version := "4.0.4" version := "4.1.3"
versionCode := Some(179) versionCode := Some(183)
proguardOptions ++= proguardOptions ++=
"-keep class com.github.shadowsocks.JniHelper { *; }" :: "-keep class com.github.shadowsocks.JniHelper { *; }" ::
...@@ -19,7 +19,7 @@ val playServicesVersion = "10.2.0" ...@@ -19,7 +19,7 @@ val playServicesVersion = "10.2.0"
resolvers += Resolver.jcenterRepo resolvers += Resolver.jcenterRepo
libraryDependencies ++= libraryDependencies ++=
"com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" :: "com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" ::
"com.evernote" % "android-job" % "1.1.6" :: "com.evernote" % "android-job" % "1.1.7" ::
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" :: "com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" ::
"com.google.android.gms" % "play-services-ads" % playServicesVersion :: "com.google.android.gms" % "play-services-ads" % playServicesVersion ::
"com.google.android.gms" % "play-services-analytics" % playServicesVersion :: "com.google.android.gms" % "play-services-analytics" % playServicesVersion ::
...@@ -28,7 +28,7 @@ libraryDependencies ++= ...@@ -28,7 +28,7 @@ libraryDependencies ++=
"com.mikepenz" % "crossfader" % "1.5.0" :: "com.mikepenz" % "crossfader" % "1.5.0" ::
"com.mikepenz" % "fastadapter" % "2.1.7" :: "com.mikepenz" % "fastadapter" % "2.1.7" ::
"com.mikepenz" % "iconics-core" % "2.8.2" :: "com.mikepenz" % "iconics-core" % "2.8.2" ::
"com.mikepenz" % "materialdrawer" % "5.8.1" :: "com.mikepenz" % "materialdrawer" % "5.8.2" ::
"com.mikepenz" % "materialize" % "1.0.1" :: "com.mikepenz" % "materialize" % "1.0.1" ::
"com.squareup.okhttp3" % "okhttp" % "3.6.0" :: "com.squareup.okhttp3" % "okhttp" % "3.6.0" ::
"com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2" :: "com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2" ::
...@@ -36,3 +36,11 @@ libraryDependencies ++= ...@@ -36,3 +36,11 @@ libraryDependencies ++=
"eu.chainfire" % "libsuperuser" % "1.0.0.201608240809" :: "eu.chainfire" % "libsuperuser" % "1.0.0.201608240809" ::
"net.glxn.qrgen" % "android" % "2.0" :: "net.glxn.qrgen" % "android" % "2.0" ::
Nil Nil
lazy val goBuild = TaskKey[Unit]("go-build", "Build go and kcptun")
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")
}
}
...@@ -144,6 +144,8 @@ ...@@ -144,6 +144,8 @@
android:process=":bg"/> android:process=":bg"/>
<service android:name="com.evernote.android.job.gcm.PlatformGcmService" <service android:name="com.evernote.android.job.gcm.PlatformGcmService"
android:process=":bg"/> android:process=":bg"/>
<service android:name="com.evernote.android.job.JobRescheduleService"
android:process=":bg"/>
<receiver android:name="com.evernote.android.job.v14.PlatformAlarmReceiver" <receiver android:name="com.evernote.android.job.v14.PlatformAlarmReceiver"
android:process=":bg"/> android:process=":bg"/>
<receiver android:name="com.evernote.android.job.JobBootReceiver" <receiver android:name="com.evernote.android.job.JobBootReceiver"
......
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -296,7 +296,7 @@ along with this program. If not, see <a href="http://www.gnu.org/licenses/">http ...@@ -296,7 +296,7 @@ along with this program. If not, see <a href="http://www.gnu.org/licenses/">http
<li>pcre: <a href="https://android.googlesource.com/platform/external/pcre/+/master/dist2/LICENCE">BSD</a></li> <li>pcre: <a href="https://android.googlesource.com/platform/external/pcre/+/master/dist2/LICENCE">BSD</a></li>
<li>libancillary: <a href="https://github.com/shadowsocks/libancillary/blob/shadowsocks-android/COPYING">BSD</a></li> <li>libancillary: <a href="https://github.com/shadowsocks/libancillary/blob/shadowsocks-android/COPYING">BSD</a></li>
<li>shadowsocks-libev: <a href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/LICENSE">GPLv3</a></li> <li>shadowsocks-libev: <a href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/LICENSE">GPLv3</a></li>
<li>pdnsd: <a href="https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/pdnsd/COPYING">GPLv3</a></li> <li>overture: <a href="https://github.com/shawn1m/overture/blob/master/LICENSE">MIT</a></li>
<li>libev: <a href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/libev/LICENSE">GPLv2</a></li> <li>libev: <a href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/libev/LICENSE">GPLv2</a></li>
<li>libsodium: <a href="https://github.com/jedisct1/libsodium/blob/master/LICENSE">ISC</a></li> <li>libsodium: <a href="https://github.com/jedisct1/libsodium/blob/master/LICENSE">ISC</a></li>
<li>libudns: <a href="https://github.com/shadowsocks/libudns/blob/master/COPYING.LGPL">LGPL</a></li> <li>libudns: <a href="https://github.com/shadowsocks/libudns/blob/master/COPYING.LGPL">LGPL</a></li>
......
...@@ -54,7 +54,19 @@ public class JniHelper { ...@@ -54,7 +54,19 @@ public class JniHelper {
? new ErrnoException("kill", errno) : new Exception("kill failed: " + errno); ? new ErrnoException("kill", errno) : new Exception("kill failed: " + errno);
} }
@Deprecated // only implemented for before API 24
public static boolean waitForCompat(Process process, long millis) throws Exception {
if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!");
final Object mutex = getExitValueMutex(process);
synchronized (mutex) {
if (getExitValue(process) == null) mutex.wait(millis);
return getExitValue(process) != null;
}
}
private static native int sigterm(Process process); private static native int sigterm(Process process);
private static native Integer getExitValue(Process process);
private static native Object getExitValueMutex(Process process);
public static native int sendFd(int fd, String path); public static native int sendFd(int fd, String path);
public static native void close(int fd); public static native void close(int fd);
} }
...@@ -97,7 +97,7 @@ LIBEVENT_SOURCES := \ ...@@ -97,7 +97,7 @@ LIBEVENT_SOURCES := \
LOCAL_MODULE := event LOCAL_MODULE := event
LOCAL_SRC_FILES := $(addprefix libevent/, $(LIBEVENT_SOURCES)) LOCAL_SRC_FILES := $(addprefix libevent/, $(LIBEVENT_SOURCES))
LOCAL_CFLAGS := -O2 -I$(LOCAL_PATH)/libevent \ LOCAL_CFLAGS := -O2 -D_EVENT_HAVE_ARC4RANDOM -I$(LOCAL_PATH)/libevent \
-I$(LOCAL_PATH)/libevent/include \ -I$(LOCAL_PATH)/libevent/include \
include $(BUILD_STATIC_LIBRARY) include $(BUILD_STATIC_LIBRARY)
...@@ -239,23 +239,6 @@ LOCAL_CFLAGS := -O2 -std=gnu99 -DUSE_IPTABLES \ ...@@ -239,23 +239,6 @@ LOCAL_CFLAGS := -O2 -std=gnu99 -DUSE_IPTABLES \
include $(BUILD_SHARED_EXECUTABLE) include $(BUILD_SHARED_EXECUTABLE)
########################################################
## pdnsd
########################################################
include $(CLEAR_VARS)
PDNSD_SOURCES := $(wildcard $(LOCAL_PATH)/pdnsd/src/*.c)
LOCAL_MODULE := pdnsd
LOCAL_SRC_FILES := $(PDNSD_SOURCES:$(LOCAL_PATH)/%=%)
LOCAL_CFLAGS := -DANDROID -Wall -O2 -I$(LOCAL_PATH)/pdnsd \
-I$(LOCAL_PATH)/include/pdnsd -I$(LOCAL_PATH)/libancillary
LOCAL_STATIC_LIBRARIES := libancillary
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_EXECUTABLE)
######################################################## ########################################################
## shadowsocks-libev local ## shadowsocks-libev local
######################################################## ########################################################
...@@ -494,6 +477,17 @@ LOCAL_SRC_FILES := $(addprefix pcre/, $(libpcre_src_files)) ...@@ -494,6 +477,17 @@ LOCAL_SRC_FILES := $(addprefix pcre/, $(libpcre_src_files))
include $(BUILD_STATIC_LIBRARY) include $(BUILD_STATIC_LIBRARY)
########################################################
## overture
########################################################
include $(CLEAR_VARS)
LOCAL_MODULE := overture
LOCAL_SRC_FILES := overture/$(TARGET_ARCH_ABI)/liboverture.so
include $(PREBUILT_SHARED_LIBRARY)
# Import cpufeatures # Import cpufeatures
$(call import-module,android/cpufeatures) $(call import-module,android/cpufeatures)
APP_ABI := armeabi-v7a x86 APP_ABI := armeabi-v7a arm64-v8a x86
APP_PLATFORM := android-19 APP_PLATFORM := android-19
APP_STL := stlport_static APP_STL := stlport_static
NDK_TOOLCHAIN_VERSION := clang NDK_TOOLCHAIN_VERSION := clang
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
#define THROW(env, clazz, msg) do { env->ThrowNew(env->FindClass(clazz), msg); } while (0) #define THROW(env, clazz, msg) do { env->ThrowNew(env->FindClass(clazz), msg); } while (0)
static jclass ProcessImpl; static jclass ProcessImpl;
static jfieldID ProcessImpl_pid; static jfieldID ProcessImpl_pid, ProcessImpl_exitValue, ProcessImpl_exitValueMutex;
static int sdk_version() { static int sdk_version() {
char version[PROP_VALUE_MAX + 1]; char version[PROP_VALUE_MAX + 1];
...@@ -41,6 +41,24 @@ jint Java_com_github_shadowsocks_jnihelper_sigterm(JNIEnv *env, jobject thiz, jo ...@@ -41,6 +41,24 @@ jint Java_com_github_shadowsocks_jnihelper_sigterm(JNIEnv *env, jobject thiz, jo
return kill(pid, SIGTERM) == -1 && errno != ESRCH ? errno : 0; return kill(pid, SIGTERM) == -1 && errno != ESRCH ? errno : 0;
} }
jobject Java_com_github_shadowsocks_jnihelper_getExitValue(JNIEnv *env, jobject thiz, jobject process) {
if (!env->IsInstanceOf(process, ProcessImpl)) {
THROW(env, "java/lang/ClassCastException",
"Unsupported process object. Only java.lang.ProcessManager$ProcessImpl is accepted.");
return NULL;
}
return env->GetObjectField(process, ProcessImpl_exitValue);
}
jobject Java_com_github_shadowsocks_jnihelper_getExitValueMutex(JNIEnv *env, jobject thiz, jobject process) {
if (!env->IsInstanceOf(process, ProcessImpl)) {
THROW(env, "java/lang/ClassCastException",
"Unsupported process object. Only java.lang.ProcessManager$ProcessImpl is accepted.");
return NULL;
}
return env->GetObjectField(process, ProcessImpl_exitValueMutex);
}
void Java_com_github_shadowsocks_jnihelper_close(JNIEnv *env, jobject thiz, jint fd) { void Java_com_github_shadowsocks_jnihelper_close(JNIEnv *env, jobject thiz, jint fd) {
close(fd); close(fd);
} }
...@@ -84,11 +102,13 @@ static JNINativeMethod method_table[] = { ...@@ -84,11 +102,13 @@ static JNINativeMethod method_table[] = {
{ "sendFd", "(ILjava/lang/String;)I", { "sendFd", "(ILjava/lang/String;)I",
(void*) Java_com_github_shadowsocks_jnihelper_sendfd }, (void*) Java_com_github_shadowsocks_jnihelper_sendfd },
{ "sigterm", "(Ljava/lang/Process;)I", { "sigterm", "(Ljava/lang/Process;)I",
(void*) Java_com_github_shadowsocks_jnihelper_sigterm } (void*) Java_com_github_shadowsocks_jnihelper_sigterm },
{ "getExitValue", "(Ljava/lang/Process;)Ljava/lang/Integer;",
(void*) Java_com_github_shadowsocks_jnihelper_getExitValue },
{ "getExitValueMutex", "(Ljava/lang/Process;)Ljava/lang/Object;",
(void*) Java_com_github_shadowsocks_jnihelper_getExitValueMutex }
}; };
/* /*
* Register several native methods for one class. * Register several native methods for one class.
*/ */
...@@ -161,6 +181,14 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { ...@@ -161,6 +181,14 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.pid not found"); THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.pid not found");
goto bail; goto bail;
} }
if (!(ProcessImpl_exitValue = env->GetFieldID(ProcessImpl, "exitValue", "Ljava/lang/Integer;"))) {
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.exitValue not found");
goto bail;
}
if (!(ProcessImpl_exitValueMutex = env->GetFieldID(ProcessImpl, "exitValueMutex", "Ljava/lang/Object;"))) {
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.exitValueMutex not found");
goto bail;
}
} }
result = JNI_VERSION_1_6; result = JNI_VERSION_1_6;
......
Subproject commit f6b0a56c74ff33e3df4c00859bc7ccefa1354fa4
Subproject commit 857bb8def744a1c158056d03194cf1ee78184985 Subproject commit de0cebb62ef3b7854972f65834af7bb8aceb4bcc
...@@ -44,8 +44,8 @@ ...@@ -44,8 +44,8 @@
<string name="auto_connect_summary">"システム起動時にバックグラウンドで本サービスを開始"</string> <string name="auto_connect_summary">"システム起動時にバックグラウンドで本サービスを開始"</string>
<string name="tcp_fastopen_summary">"使用するにはROOT権限が必要"</string> <string name="tcp_fastopen_summary">"使用するにはROOT権限が必要"</string>
<string name="tcp_fastopen_summary_unsupported">"ご利用のカーネルバージョンはサポートしておりません:%s &lt; 3.7.1"</string> <string name="tcp_fastopen_summary_unsupported">"ご利用のカーネルバージョンはサポートしておりません:%s &lt; 3.7.1"</string>
<string name="udp_dns">"UDP転送"</string> <string name="udp_dns">"DNS転送"</string>
<string name="udp_dns_summary">"UDPプロトコルでリモードサーバーにパケットを転送"</string> <string name="udp_dns_summary">"DNSプロトコルでリモードサーバーにパケットを転送"</string>
<!-- notification category --> <!-- notification category -->
<string name="forward_success">"バックグラウンドでサービスを開始しました"</string> <string name="forward_success">"バックグラウンドでサービスを開始しました"</string>
......
...@@ -52,8 +52,8 @@ ...@@ -52,8 +52,8 @@
<string name="auto_connect_summary">"Запускать Shadowsocks при включении"</string> <string name="auto_connect_summary">"Запускать Shadowsocks при включении"</string>
<string name="tcp_fastopen_summary">"Переключение требует наличия ROOT прав"</string> <string name="tcp_fastopen_summary">"Переключение требует наличия ROOT прав"</string>
<string name="tcp_fastopen_summary_unsupported">"Неподдерживаемая версия ядра: %s &lt; 3.7.1"</string> <string name="tcp_fastopen_summary_unsupported">"Неподдерживаемая версия ядра: %s &lt; 3.7.1"</string>
<string name="udp_dns">"Проброс UDP"</string> <string name="udp_dns">"Проброс DNS"</string>
<string name="udp_dns_summary">"Пробрасывать UDP пакеты на удалённый сервер"</string> <string name="udp_dns_summary">"Пробрасывать DNS пакеты на удалённый сервер"</string>
<!-- notification category --> <!-- notification category -->
<string name="forward_success">"Shadowsocks запущен."</string> <string name="forward_success">"Shadowsocks запущен."</string>
......
...@@ -45,8 +45,8 @@ ...@@ -45,8 +45,8 @@
<string name="auto_connect_summary">"随系统启动后台服务"</string> <string name="auto_connect_summary">"随系统启动后台服务"</string>
<string name="tcp_fastopen_summary">"切换需要 ROOT 权限"</string> <string name="tcp_fastopen_summary">"切换需要 ROOT 权限"</string>
<string name="tcp_fastopen_summary_unsupported">"不支持的内核版本:%s &lt; 3.7.1"</string> <string name="tcp_fastopen_summary_unsupported">"不支持的内核版本:%s &lt; 3.7.1"</string>
<string name="udp_dns">"UDP 转发"</string> <string name="udp_dns">"DNS 转发"</string>
<string name="udp_dns_summary">"由远程服务器转发 UDP 协议的数据包"</string> <string name="udp_dns_summary">"由远程服务器转发 DNS 协议的数据包"</string>
<!-- notification category --> <!-- notification category -->
<string name="forward_success">"后台服务已开始运行。"</string> <string name="forward_success">"后台服务已开始运行。"</string>
......
...@@ -44,8 +44,8 @@ ...@@ -44,8 +44,8 @@
<string name="auto_connect_summary">"在裝置啟動時啟用 Shadowsocks"</string> <string name="auto_connect_summary">"在裝置啟動時啟用 Shadowsocks"</string>
<string name="tcp_fastopen_summary">"切換需要 ROOT 權限"</string> <string name="tcp_fastopen_summary">"切換需要 ROOT 權限"</string>
<string name="tcp_fastopen_summary_unsupported">"不支援的核心版本:%s &lt; 3.7.1"</string> <string name="tcp_fastopen_summary_unsupported">"不支援的核心版本:%s &lt; 3.7.1"</string>
<string name="udp_dns">"UDP 轉送"</string> <string name="udp_dns">"DNS 轉送"</string>
<string name="udp_dns_summary">"向遠端轉送 UDP 封包"</string> <string name="udp_dns_summary">"向遠端轉送 DNS 封包"</string>
<!-- notification category --> <!-- notification category -->
<string name="forward_success">"Shadowsocks 已啟動。"</string> <string name="forward_success">"Shadowsocks 已啟動。"</string>
......
...@@ -53,8 +53,8 @@ ...@@ -53,8 +53,8 @@
<string name="auto_connect_summary">Enable Shadowsocks on startup</string> <string name="auto_connect_summary">Enable Shadowsocks on startup</string>
<string name="tcp_fastopen_summary">Toggling requires ROOT permission</string> <string name="tcp_fastopen_summary">Toggling requires ROOT permission</string>
<string name="tcp_fastopen_summary_unsupported">Unsupported kernel version: %s &lt; 3.7.1</string> <string name="tcp_fastopen_summary_unsupported">Unsupported kernel version: %s &lt; 3.7.1</string>
<string name="udp_dns">UDP Forwarding</string> <string name="udp_dns">DNS Forwarding</string>
<string name="udp_dns_summary">Forward UDP packets to remote</string> <string name="udp_dns_summary">Force to forward DNS packets to remote</string>
<!-- notification category --> <!-- notification category -->
<string name="forward_success">Shadowsocks started.</string> <string name="forward_success">Shadowsocks started.</string>
......
...@@ -39,11 +39,11 @@ import com.github.shadowsocks.database.Profile ...@@ -39,11 +39,11 @@ import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.plugin.{PluginConfiguration, PluginManager, PluginOptions} import com.github.shadowsocks.plugin.{PluginConfiguration, PluginManager, PluginOptions}
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
import okhttp3.{Dns, FormBody, OkHttpClient, Request} import okhttp3.{Dns, FormBody, OkHttpClient, Request}
import org.json.JSONObject import org.json.{JSONArray, JSONObject}
import scala.collection.JavaConversions._
import scala.collection.mutable import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
import scala.collection.JavaConversions._
import scala.util.Random import scala.util.Random
trait BaseService extends Service { trait BaseService extends Service {
...@@ -328,4 +328,44 @@ trait BaseService extends Service { ...@@ -328,4 +328,44 @@ trait BaseService extends Service {
IOUtils.writeString(new File(getFilesDir, file), config.toString) IOUtils.writeString(new File(getFilesDir, file), config.toString)
file file
} }
protected final def buildOvertureConfig(file: String): String = {
val config = new JSONObject()
.put("BindAddress", ":" + (profile.localPort + 53))
.put("RedirectIPv6Record", true)
.put("DomainBase64Decode", true)
.put("HostsFile", "hosts")
.put("MinimumTTL", 3600)
.put("CacheSize", 4096)
def makeDns(name: String, address: String, edns: Boolean = true) = {
val dns = new JSONObject()
.put("Name", name)
.put("Address", address + ":53")
.put("Timeout", 6)
.put("EDNSClientSubnet", new JSONObject().put("Policy", "disable"))
if (edns) dns
.put("Protocol", "tcp")
.put("Socks5Address", "127.0.0.1:" + profile.localPort)
else dns.put("Protocol", "udp")
dns
}
profile.route match {
case Acl.BYPASS_CHN | Acl.BYPASS_LAN_CHN | Acl.GFWLIST | Acl.CUSTOM_RULES => config
.put("PrimaryDNS", new JSONArray(Array(
makeDns("Primary-1", "119.29.29.29", edns = false),
makeDns("Primary-2", "114.114.114.114", edns = false)
)))
.put("AlternativeDNS", new JSONArray().put(makeDns("Alternative", profile.remoteDns)))
.put("IPNetworkFile", "china_ip_list.txt")
.put("DomainFile", "gfwlist.txt")
case Acl.CHINALIST => config
.put("PrimaryDNS", new JSONArray().put(makeDns("Primary", "119.29.29.29")))
.put("AlternativeDNS", new JSONArray().put(makeDns("Alternative", profile.remoteDns)))
case _ => config
.put("PrimaryDNS", new JSONArray().put(makeDns("Primary", profile.remoteDns)))
.put("AlternativeDNS", new JSONArray().put(makeDns("Alternative", "208.67.222.222")))
}
IOUtils.writeString(new File(getFilesDir, file), config.toString)
file
}
} }
...@@ -115,7 +115,7 @@ class GuardedProcess(cmd: String*) { ...@@ -115,7 +115,7 @@ class GuardedProcess(cmd: String*) {
private def destroyProcess() { private def destroyProcess() {
if (Build.VERSION.SDK_INT < 24) { if (Build.VERSION.SDK_INT < 24) {
JniHelper.sigtermCompat(process) JniHelper.sigtermCompat(process)
process.waitFor() JniHelper.waitForCompat(process, 500)
} }
process.destroy() process.destroy()
} }
......
...@@ -197,15 +197,17 @@ class ShadowsocksApplication extends Application { ...@@ -197,15 +197,17 @@ class ShadowsocksApplication extends Application {
def copyAssets() { def copyAssets() {
val assetManager = getAssets val assetManager = getAssets
for (dir <- List("acl", "overture")) {
var files: Array[String] = null var files: Array[String] = null
try files = assetManager.list("acl") catch { try files = assetManager.list(dir) catch {
case e: IOException => case e: IOException =>
Log.e(TAG, e.getMessage) Log.e(TAG, e.getMessage)
app.track(e) app.track(e)
} }
if (files != null) for (file <- files) autoClose(assetManager.open("acl/" + file))(in => if (files != null) for (file <- files) autoClose(assetManager.open(dir + "/" + file))(in =>
autoClose(new FileOutputStream(new File(getFilesDir, file)))(out => autoClose(new FileOutputStream(new File(getFilesDir, file)))(out =>
IOUtils.copy(in, out))) IOUtils.copy(in, out)))
}
editor.putInt(Key.currentVersionCode, BuildConfig.VERSION_CODE).apply() editor.putInt(Key.currentVersionCode, BuildConfig.VERSION_CODE).apply()
} }
......
...@@ -49,7 +49,7 @@ class ShadowsocksNatService extends BaseService { ...@@ -49,7 +49,7 @@ class ShadowsocksNatService extends BaseService {
var sslocalProcess: GuardedProcess = _ var sslocalProcess: GuardedProcess = _
var sstunnelProcess: GuardedProcess = _ var sstunnelProcess: GuardedProcess = _
var redsocksProcess: GuardedProcess = _ var redsocksProcess: GuardedProcess = _
var pdnsdProcess: GuardedProcess = _ var overtureProcess: GuardedProcess = _
var su: Shell.Interactive = _ var su: Shell.Interactive = _
def startShadowsocksDaemon() { def startShadowsocksDaemon() {
...@@ -83,21 +83,8 @@ class ShadowsocksNatService extends BaseService { ...@@ -83,21 +83,8 @@ class ShadowsocksNatService extends BaseService {
} }
def startDnsDaemon() { def startDnsDaemon() {
val reject = if (profile.ipv6) "224.0.0.0/3" else "224.0.0.0/3, ::/0" overtureProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/liboverture.so",
IOUtils.writeString(new File(getFilesDir, "pdnsd-nat.conf"), profile.route match { "-c", buildOvertureConfig("overture-nat.conf"))
case Acl.BYPASS_CHN | Acl.BYPASS_LAN_CHN | Acl.GFWLIST | Acl.CUSTOM_RULES =>
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "", getCacheDir.getAbsolutePath,
"127.0.0.1", profile.localPort + 53, "114.114.114.114, 223.5.5.5, 1.2.4.8",
getBlackList, reject, profile.localPort + 63, reject)
case Acl.CHINALIST =>
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "", getCacheDir.getAbsolutePath,
"127.0.0.1", profile.localPort + 53, "8.8.8.8, 8.8.4.4, 208.67.222.222",
"", reject, profile.localPort + 63, reject)
case _ =>
ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, "", getCacheDir.getAbsolutePath,
"127.0.0.1", profile.localPort + 53, profile.localPort + 63, reject)
})
pdnsdProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/libpdnsd.so", "-c", "pdnsd-nat.conf")
.start() .start()
} }
...@@ -111,11 +98,13 @@ class ShadowsocksNatService extends BaseService { ...@@ -111,11 +98,13 @@ class ShadowsocksNatService extends BaseService {
/** Called when the activity is first created. */ /** Called when the activity is first created. */
def handleConnection() { def handleConnection() {
startDNSTunnel()
startRedsocksDaemon() startRedsocksDaemon()
startShadowsocksDaemon() startShadowsocksDaemon()
if (!profile.udpdns) startDnsDaemon() if (!profile.udpdns)
startDnsDaemon()
else
startDNSTunnel()
setupIptables() setupIptables()
...@@ -143,9 +132,9 @@ class ShadowsocksNatService extends BaseService { ...@@ -143,9 +132,9 @@ class ShadowsocksNatService extends BaseService {
redsocksProcess.destroy() redsocksProcess.destroy()
redsocksProcess = null redsocksProcess = null
} }
if (pdnsdProcess != null) { if (overtureProcess != null) {
pdnsdProcess.destroy() overtureProcess.destroy()
pdnsdProcess = null overtureProcess = null
} }
su.addCommand("iptables -t nat -F OUTPUT") su.addCommand("iptables -t nat -F OUTPUT")
......
...@@ -45,8 +45,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -45,8 +45,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
private var notification: ShadowsocksNotification = _ private var notification: ShadowsocksNotification = _
var sslocalProcess: GuardedProcess = _ var sslocalProcess: GuardedProcess = _
var sstunnelProcess: GuardedProcess = _ var overtureProcess: GuardedProcess = _
var pdnsdProcess: GuardedProcess = _
var tun2socksProcess: GuardedProcess = _ var tun2socksProcess: GuardedProcess = _
override def onBind(intent: Intent): IBinder = { override def onBind(intent: Intent): IBinder = {
...@@ -94,17 +93,13 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -94,17 +93,13 @@ class ShadowsocksVpnService extends VpnService with BaseService {
sslocalProcess.destroy() sslocalProcess.destroy()
sslocalProcess = null sslocalProcess = null
} }
if (sstunnelProcess != null) {
sstunnelProcess.destroy()
sstunnelProcess = null
}
if (tun2socksProcess != null) { if (tun2socksProcess != null) {
tun2socksProcess.destroy() tun2socksProcess.destroy()
tun2socksProcess = null tun2socksProcess = null
} }
if (pdnsdProcess != null) { if (overtureProcess != null) {
pdnsdProcess.destroy() overtureProcess.destroy()
pdnsdProcess = null overtureProcess = null
} }
} }
...@@ -149,15 +144,14 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -149,15 +144,14 @@ class ShadowsocksVpnService extends VpnService with BaseService {
/** Called when the activity is first created. */ /** Called when the activity is first created. */
def handleConnection() { def handleConnection() {
val fd = startVpn()
if (!sendFd(fd)) throw new Exception("sendFd failed")
startShadowsocksDaemon() startShadowsocksDaemon()
if (!profile.udpdns) { if (!profile.udpdns) {
startDnsDaemon() startDnsDaemon()
startDnsTunnel()
} }
val fd = startVpn()
if (!sendFd(fd)) throw new Exception("sendFd failed")
} }
override protected def buildPluginCommandLine(): ArrayBuffer[String] = super.buildPluginCommandLine() += "-V" override protected def buildPluginCommandLine(): ArrayBuffer[String] = super.buildPluginCommandLine() += "-V"
...@@ -165,13 +159,12 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -165,13 +159,12 @@ class ShadowsocksVpnService extends VpnService with BaseService {
def startShadowsocksDaemon() { def startShadowsocksDaemon() {
val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so",
"-V", "-V",
"-u",
"-b", "127.0.0.1", "-b", "127.0.0.1",
"-l", profile.localPort.toString, "-l", profile.localPort.toString,
"-t", "600", "-t", "600",
"-c", buildShadowsocksConfig("ss-local-vpn.conf")) "-c", buildShadowsocksConfig("ss-local-vpn.conf"))
if (profile.udpdns) cmd += "-u"
if (profile.route != Acl.ALL) { if (profile.route != Acl.ALL) {
cmd += "--acl" cmd += "--acl"
cmd += Acl.getFile(profile.route).getAbsolutePath cmd += Acl.getFile(profile.route).getAbsolutePath
...@@ -182,33 +175,10 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -182,33 +175,10 @@ class ShadowsocksVpnService extends VpnService with BaseService {
sslocalProcess = new GuardedProcess(cmd: _*).start() sslocalProcess = new GuardedProcess(cmd: _*).start()
} }
def startDnsTunnel(): Unit =
sstunnelProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so",
"-V",
"-t", "10",
"-b", "127.0.0.1",
"-l", (profile.localPort + 63).toString,
"-L" , profile.remoteDns.trim + ":53",
"-c", buildShadowsocksConfig("ss-tunnel-vpn.conf")).start()
def startDnsDaemon() { def startDnsDaemon() {
val reject = if (profile.ipv6) "224.0.0.0/3" else "224.0.0.0/3, ::/0" overtureProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/liboverture.so",
IOUtils.writeString(new File(getFilesDir, "pdnsd-vpn.conf"), profile.route match { "-c", buildOvertureConfig("overture-vpn.conf"), "-V")
case Acl.BYPASS_CHN | Acl.BYPASS_LAN_CHN | Acl.GFWLIST | Acl.CUSTOM_RULES => .start()
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "protect = \"protect_path\";", getCacheDir.getAbsolutePath,
"0.0.0.0", profile.localPort + 53, "114.114.114.114, 223.5.5.5, 1.2.4.8",
getBlackList, reject, profile.localPort + 63, reject)
case Acl.CHINALIST =>
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "protect = \"protect_path\";", getCacheDir.getAbsolutePath,
"0.0.0.0", profile.localPort + 53, "8.8.8.8, 8.8.4.4, 208.67.222.222",
"", reject, profile.localPort + 63, reject)
case _ =>
ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, "protect = \"protect_path\";", getCacheDir.getAbsolutePath,
"0.0.0.0", profile.localPort + 53, profile.localPort + 63, reject)
})
val cmd = Array(getApplicationInfo.nativeLibraryDir + "/libpdnsd.so", "-c", "pdnsd-vpn.conf")
pdnsdProcess = new GuardedProcess(cmd: _*).start()
} }
def startVpn(): Int = { def startVpn(): Int = {
...@@ -271,9 +241,9 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -271,9 +241,9 @@ class ShadowsocksVpnService extends VpnService with BaseService {
if (profile.ipv6) if (profile.ipv6)
cmd += ("--netif-ip6addr", PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "2")) cmd += ("--netif-ip6addr", PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "2"))
if (profile.udpdns)
cmd += "--enable-udprelay" cmd += "--enable-udprelay"
else
if (!profile.udpdns)
cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"),
profile.localPort + 53)) profile.localPort + 53))
......
...@@ -98,7 +98,8 @@ class Profile { ...@@ -98,7 +98,8 @@ class Profile {
.scheme("ss") .scheme("ss")
.encodedAuthority("%s@%s:%d".formatLocal(Locale.ENGLISH, .encodedAuthority("%s@%s:%d".formatLocal(Locale.ENGLISH,
Base64.encodeToString("%s:%s".formatLocal(Locale.ENGLISH, method, password).getBytes, Base64.encodeToString("%s:%s".formatLocal(Locale.ENGLISH, method, password).getBytes,
Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE), host, remotePort)) Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE),
if (host.contains(':')) '[' + host + ']' else host, remotePort))
val configuration = new PluginConfiguration(plugin) val configuration = new PluginConfiguration(plugin)
if (configuration.selected.nonEmpty) if (configuration.selected.nonEmpty)
builder.appendQueryParameter(Key.plugin, configuration.selectedOptions.toString(false)) builder.appendQueryParameter(Key.plugin, configuration.selectedOptions.toString(false))
......
...@@ -45,86 +45,6 @@ object ConfigUtils { ...@@ -45,86 +45,6 @@ object ConfigUtils {
" port = %d;\n" + " port = %d;\n" +
" type = socks5;\n" + " type = socks5;\n" +
"}\n" "}\n"
val PDNSD_LOCAL =
"""
|global {
| perm_cache = 2048;
| %s
| cache_dir = "%s";
| server_ip = %s;
| server_port = %d;
| query_method = tcp_only;
| min_ttl = 15m;
| max_ttl = 1w;
| timeout = 10;
| daemon = off;
|}
|
|server {
| label = "local";
| ip = 127.0.0.1;
| port = %d;
| reject = %s;
| reject_policy = negate;
| reject_recursively = on;
|}
|
|rr {
| name=localhost;
| reverse=on;
| a=127.0.0.1;
| owner=localhost;
| soa=localhost,root.localhost,42,86400,900,86400,86400;
|}
""".stripMargin
val PDNSD_DIRECT =
"""
|global {
| perm_cache = 2048;
| %s
| cache_dir = "%s";
| server_ip = %s;
| server_port = %d;
| query_method = udp_only;
| min_ttl = 15m;
| max_ttl = 1w;
| timeout = 10;
| daemon = off;
| par_queries = 4;
|}
|
|server {
| label = "remote-servers";
| ip = %s;
| timeout = 3;
| query_method = udp_only;
| %s
| policy = included;
| reject = %s;
| reject_policy = fail;
| reject_recursively = on;
|}
|
|server {
| label = "local-server";
| ip = 127.0.0.1;
| query_method = tcp_only;
| port = %d;
| reject = %s;
| reject_policy = negate;
| reject_recursively = on;
|}
|
|rr {
| name=localhost;
| reverse=on;
| a=127.0.0.1;
| owner=localhost;
| soa=localhost,root.localhost,42,86400,900,86400,86400;
|}
""".stripMargin
} }
object Key { object Key {
......
...@@ -20,13 +20,15 @@ ...@@ -20,13 +20,15 @@
package com.github.shadowsocks.utils package com.github.shadowsocks.utils
import java.net.URI
import android.net.Uri import android.net.Uri
import android.util.{Base64, Log} import android.util.{Base64, Log}
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
object Parser { object Parser {
val TAG = "ShadowParser" val TAG = "ShadowParser"
private val pattern = "(?i)ss://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]".r private val pattern = "(?i)ss://[-a-zA-Z0-9+&@#/%?=~_|!:,.;\\[\\]]*[-a-zA-Z0-9+&@#/%=~_|\\[\\]]".r
private val userInfoPattern = "^(.+?):(.*)$".r private val userInfoPattern = "^(.+?):(.*)$".r
private val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)$".r private val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)$".r
...@@ -54,8 +56,12 @@ object Parser { ...@@ -54,8 +56,12 @@ object Parser {
val profile = new Profile val profile = new Profile
profile.method = method profile.method = method
profile.password = password profile.password = password
profile.host = uri.getHost // bug in Android: https://code.google.com/p/android/issues/detail?id=192855
profile.remotePort = uri.getPort val javaURI = new URI(m.matched)
profile.host = javaURI.getHost
if (profile.host.headOption.contains('[') && profile.host.lastOption.contains(']'))
profile.host = profile.host.substring(1, profile.host.length - 1)
profile.remotePort = javaURI.getPort
profile.plugin = uri.getQueryParameter(Key.plugin) profile.plugin = uri.getQueryParameter(Key.plugin)
profile.name = uri.getFragment profile.name = uri.getFragment
profile profile
......
Subproject commit 862ce347e19483fdd6f0a5c84f173c2efdc7d920
* 0.0.3:
* Update support library version to 25.2.0.
* 0.0.2: * 0.0.2:
* Add `getOrDefault` to `PluginOptions`; * Add `getOrDefault` to `PluginOptions`;
* Update support library version to 25.1.1. * Update support library version to 25.1.1.
......
...@@ -2,7 +2,7 @@ enablePlugins(AndroidLib) ...@@ -2,7 +2,7 @@ enablePlugins(AndroidLib)
android.useSupportVectors android.useSupportVectors
name := "plugin" name := "plugin"
version := "0.0.2" version := "0.0.3"
pomExtra in Global := { pomExtra in Global := {
<url>https://github.com/shadowsocks/shadowsocks-android</url> <url>https://github.com/shadowsocks/shadowsocks-android</url>
......
addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.5") addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.6")
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10")
......
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