Compare commits

...
53 Commits
Author SHA1 Message Date
Max Lv 11d827c6ba Bump version 2017-02-13 09:29:11 +08:00
Max Lv dc46cd971a Merge branch 'beta' 2017-02-13 09:21:18 +08:00
Max Lv 876e92f275 Bump version 2017-02-13 09:17:59 +08:00
Max Lv 41747c976b Update shadowsocks-libev 2017-02-12 20:24:56 +08:00
Max Lv 8277b07d24 Bump version 2017-02-10 10:16:27 +08:00
Max Lv b982867130 Merge branch 'beta' 2017-02-10 10:05:18 +08:00
Max Lv 491710679a Fix #1111 2017-02-09 10:50:44 +08:00
Max Lv 24c6aca85c Add remote DNS to route 2017-02-09 10:02:02 +08:00
Mygod 293e9b5dff Fix equals missing in Subnet 2017-02-08 18:47:53 +08:00
Mygod 3ac77e1f8d Fix crash on back pressed after MainActivity recreated 2017-02-08 18:47:23 +08:00
Mygod fd2c67d0b8 Add missing file 2017-02-08 17:19:22 +08:00
Mygod 684698c8e4 Refine URLs 2017-02-08 17:18:04 +08:00
Mygod 6a02d4b985 Add change log to plugin library 2017-02-08 17:13:17 +08:00
Mygod 7219ce429c Bump version 2017-02-08 17:07:56 +08:00
Max Lv 9975b135d0 Remove chacha20-poly1305 2017-02-08 12:19:51 +08:00
Max Lv 5d46ef45e3 Bump version 2017-02-08 12:03:16 +08:00
Max Lv 018aa781d3 Merge branch 'develop' into beta 2017-02-08 12:01:05 +08:00
Max Lv f7e2f8694d Add missing rules of base64.c 2017-02-08 11:58:15 +08:00
Max Lv 2d86122297 Update shadowsocks-libev 2017-02-08 11:58:15 +08:00
Max Lv ad0578d073 Fix UDP relay issues 2017-02-08 11:58:15 +08:00
Max Lv b671bc2c50 Update shadowsocks-libev 2017-02-08 11:58:15 +08:00
Mygod be5036183d Fix issues about SIP002 2017-02-08 11:58:15 +08:00
Mygod 212c3f666b Update shadowsocks-libev 2017-02-08 11:58:15 +08:00
Mygod deba1028c0 Remove unused sources in libsodium 2017-02-08 11:58:15 +08:00
Mygod 5559295beb Remove useless files in libsodium makefile 2017-02-08 11:58:15 +08:00
Mygod 41eec3e351 Use stable branch of libsodium 2017-02-08 11:58:15 +08:00
Mygod 56709385b1 Add missing AEAD ciphers 2017-02-08 11:58:15 +08:00
Mygod dc13249a1e Implement SIP002
https://github.com/shadowsocks/shadowsocks-org/issues/27
2017-02-08 11:58:15 +08:00
Mygod d579660682 Remove obsolete ciphers 2017-02-08 11:58:15 +08:00
Mygod 1e96a13ab1 Implement SIP004
https://github.com/shadowsocks/shadowsocks-org/issues/30
2017-02-08 11:58:15 +08:00
Mygod b71b27fbdf Use official repo of libsodium 2017-02-08 11:58:15 +08:00
Mygod 3fa8a0c90c Refine roundings for TrafficMonitor 2017-02-04 11:46:13 +08:00
Mygod f121ea2549 Prevent crashing when no market is installed 2017-02-02 21:51:52 +08:00
Mygod df1e40d60b Refine Subnet 2017-02-02 21:51:13 +08:00
Mygod ee08f02660 Use remote DNS for VPN 2017-02-02 21:49:31 +08:00
Mygod 7af26d1a4b Increase minimum sync time to let shadowsocks set up 2017-02-02 21:49:24 +08:00
Mygod 88319ecac7 Suggest to use fully qualified name for authorities in doc 2017-02-02 21:43:01 +08:00
Mygod b397ab2bbd Support pasting multiple lines 2017-02-02 21:27:35 +08:00
Mygod 33c087f13f Refine FAQ 2017-02-02 20:58:30 +08:00
Mygod 4996d4fc52 Add a known issue for Huawei's buggy ROM 2017-02-02 17:22:27 +08:00
Mygod 14fd86c716 Update android support library 2017-01-31 20:15:25 +08:00
Mygod 3b9a35d3e6 Merge branch 'beta' into develop 2017-01-31 19:30:37 +08:00
Mygod 3b93302539 Update dependencies 2017-01-31 01:37:25 +08:00
Max Lv 8de1dc1f7a Add signature 2017-01-24 22:59:26 +08:00
Mygod 55667c9e76 Add getOrDefault method for API 23- in plugin lib 2017-01-24 22:53:24 +08:00
Mygod e81c4f257e Bump plugin lib version 2017-01-24 22:52:54 +08:00
Mygod 61a926ff5e Update plugin doc 2017-01-24 22:42:24 +08:00
Mygod 8d1f2da1f0 Implement SIGTERM in jni-helper 2017-01-24 18:44:31 +08:00
Mygod 30c8c7e6eb Fix wrong thread error in binderDied 2017-01-24 16:58:55 +08:00
Mygod dadeffa903 Rename System to JniHelper to rename naming conflicts with
`java.lang.System`
2017-01-24 16:58:20 +08:00
Max Lv caf5731ff6 Update GFWList 2017-01-21 16:48:50 +08:00
Max Lv 437d8c3026 Revert "Revert "Speed up ndk-build from sbt-android""
This reverts commit bcbd860457.
2017-01-17 21:30:54 +08:00
Max Lv 44b47e6cd3 Update background for #1082 2017-01-16 16:18:18 +08:00
39 changed files with 310 additions and 316 deletions
+2 -1
View File
@@ -29,7 +29,8 @@ The exclamation mark in the Wi-Fi/cellular icon appears because the system fails
1. Broken VPNService implementation, especially for IPv6; 1. Broken VPNService implementation, especially for IPv6;
2. Aggressive (or called broken) background service killing policy. 2. Aggressive (or called broken) background service killing policy.
Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772) * Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772)
* Fixes for Huawei: [#1091 (comment)](https://github.com/shadowsocks/shadowsocks-android/issues/1091#issuecomment-276949836)
### How to pause Shadowsocks service? ### How to pause Shadowsocks service?
+2 -1
View File
@@ -29,7 +29,8 @@
url = https://android.googlesource.com/platform/external/pcre url = https://android.googlesource.com/platform/external/pcre
[submodule "mobile/src/main/jni/libsodium"] [submodule "mobile/src/main/jni/libsodium"]
path = mobile/src/main/jni/libsodium path = mobile/src/main/jni/libsodium
url = https://github.com/shadowsocks/libsodium.git url = https://github.com/jedisct1/libsodium.git
branch = stable
[submodule "mobile/src/main/jni/libev"] [submodule "mobile/src/main/jni/libev"]
path = mobile/src/main/jni/libev path = mobile/src/main/jni/libev
url = https://github.com/shadowsocks/libev.git url = https://github.com/shadowsocks/libev.git
+1 -1
View File
@@ -18,7 +18,7 @@ lazy val commonSettings = Seq(
typedResources := false typedResources := false
) )
val supportLibsVersion = "25.1.0" val supportLibsVersion = "25.1.1"
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)
+7 -7
View File
@@ -2,11 +2,11 @@ enablePlugins(AndroidApp)
android.useSupportVectors android.useSupportVectors
name := "shadowsocks" name := "shadowsocks"
version := "4.0.0" version := "4.0.3"
versionCode := Some(175) versionCode := Some(178)
proguardOptions ++= proguardOptions ++=
"-keep class com.github.shadowsocks.System { *; }" :: "-keep class com.github.shadowsocks.JniHelper { *; }" ::
"-dontwarn com.google.android.gms.internal.**" :: "-dontwarn com.google.android.gms.internal.**" ::
"-dontwarn com.j256.ormlite.**" :: "-dontwarn com.j256.ormlite.**" ::
"-dontwarn okio.**" :: "-dontwarn okio.**" ::
@@ -19,20 +19,20 @@ val playServicesVersion = "10.0.1"
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.4" :: "com.evernote" % "android-job" % "1.1.5" ::
"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 ::
"com.google.android.gms" % "play-services-gcm" % playServicesVersion :: "com.google.android.gms" % "play-services-gcm" % playServicesVersion ::
"com.j256.ormlite" % "ormlite-android" % "5.0" :: "com.j256.ormlite" % "ormlite-android" % "5.0" ::
"com.mikepenz" % "crossfader" % "1.5.0" :: "com.mikepenz" % "crossfader" % "1.5.0" ::
"com.mikepenz" % "fastadapter" % "2.1.5" :: "com.mikepenz" % "fastadapter" % "2.1.6" ::
"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.1" ::
"com.mikepenz" % "materialize" % "1.0.0" :: "com.mikepenz" % "materialize" % "1.0.0" ::
"com.squareup.okhttp3" % "okhttp" % "3.5.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" ::
"dnsjava" % "dnsjava" % "2.1.7" :: "dnsjava" % "dnsjava" % "2.1.8" ::
"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
@@ -38,12 +38,23 @@
package com.github.shadowsocks; package com.github.shadowsocks;
public class System { import android.os.Build;
static { import android.system.ErrnoException;
java.lang.System.loadLibrary("system");
}
public static native int exec(String cmd); public class JniHelper {
public static native int sendfd(int fd, String path); static {
public static native void jniclose(int fd); System.loadLibrary("jni-helper");
}
@Deprecated // Use Process.destroy() since API 24
public static void sigtermCompat(Process process) throws Exception {
if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!");
int errno = sigterm(process);
if (errno != 0) throw Build.VERSION.SDK_INT >= 21
? new ErrnoException("kill", errno) : new Exception("kill failed: " + errno);
}
private static native int sigterm(Process process);
public static native int sendFd(int fd, String path);
public static native void close(int fd);
} }
+22 -99
View File
@@ -26,115 +26,32 @@ include $(CLEAR_VARS)
SODIUM_SOURCE := \ SODIUM_SOURCE := \
crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c \ crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c \
crypto_auth/crypto_auth.c \
crypto_auth/hmacsha256/auth_hmacsha256_api.c \
crypto_auth/hmacsha256/cp/hmac_hmacsha256.c \
crypto_auth/hmacsha256/cp/verify_hmacsha256.c \
crypto_auth/hmacsha512/auth_hmacsha512_api.c \
crypto_auth/hmacsha512/cp/hmac_hmacsha512.c \
crypto_auth/hmacsha512/cp/verify_hmacsha512.c \
crypto_auth/hmacsha512256/auth_hmacsha512256_api.c \
crypto_auth/hmacsha512256/cp/hmac_hmacsha512256.c \
crypto_auth/hmacsha512256/cp/verify_hmacsha512256.c \
crypto_box/crypto_box.c \
crypto_box/crypto_box_easy.c \
crypto_box/crypto_box_seal.c \
crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305_api.c \
crypto_box/curve25519xsalsa20poly1305/ref/after_curve25519xsalsa20poly1305.c \
crypto_box/curve25519xsalsa20poly1305/ref/before_curve25519xsalsa20poly1305.c \
crypto_box/curve25519xsalsa20poly1305/ref/box_curve25519xsalsa20poly1305.c \
crypto_box/curve25519xsalsa20poly1305/ref/keypair_curve25519xsalsa20poly1305.c \
crypto_core/hsalsa20/ref2/core_hsalsa20.c \
crypto_core/hsalsa20/core_hsalsa20_api.c \
crypto_core/salsa20/ref/core_salsa20.c \ crypto_core/salsa20/ref/core_salsa20.c \
crypto_core/salsa20/core_salsa20_api.c \
crypto_generichash/crypto_generichash.c \
crypto_generichash/blake2/generichash_blake2_api.c \
crypto_generichash/blake2/ref/blake2-impl.h \
crypto_generichash/blake2/ref/blake2.h \
crypto_generichash/blake2/ref/blake2b-compress-ref.c \ crypto_generichash/blake2/ref/blake2b-compress-ref.c \
crypto_generichash/blake2/ref/blake2b-load-sse2.h \
crypto_generichash/blake2/ref/blake2b-load-sse41.h \
crypto_generichash/blake2/ref/blake2b-ref.c \ crypto_generichash/blake2/ref/blake2b-ref.c \
crypto_generichash/blake2/ref/blake2b-round.h \
crypto_generichash/blake2/ref/generichash_blake2b.c \ crypto_generichash/blake2/ref/generichash_blake2b.c \
crypto_hash/crypto_hash.c \
crypto_hash/sha256/hash_sha256_api.c \
crypto_hash/sha256/cp/hash_sha256.c \
crypto_hash/sha512/hash_sha512_api.c \
crypto_hash/sha512/cp/hash_sha512.c \
crypto_onetimeauth/crypto_onetimeauth.c \
crypto_onetimeauth/poly1305/onetimeauth_poly1305.c \ crypto_onetimeauth/poly1305/onetimeauth_poly1305.c \
crypto_onetimeauth/poly1305/onetimeauth_poly1305.h \
crypto_onetimeauth/poly1305/donna/poly1305_donna.h \
crypto_onetimeauth/poly1305/donna/poly1305_donna32.h \
crypto_onetimeauth/poly1305/donna/poly1305_donna64.h \
crypto_onetimeauth/poly1305/donna/poly1305_donna.c \ crypto_onetimeauth/poly1305/donna/poly1305_donna.c \
crypto_pwhash/scryptsalsa208sha256/crypto_scrypt-common.c \ crypto_pwhash/crypto_pwhash.c \
crypto_pwhash/scryptsalsa208sha256/crypto_scrypt.h \ crypto_pwhash/argon2/argon2-core.c \
crypto_pwhash/scryptsalsa208sha256/scrypt_platform.c \ crypto_pwhash/argon2/argon2.c \
crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.c \ crypto_pwhash/argon2/argon2-encoding.c \
crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.h \ crypto_pwhash/argon2/argon2-fill-block-ref.c \
crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c \ crypto_pwhash/argon2/blake2b-long.c \
crypto_pwhash/scryptsalsa208sha256/sysendian.h \ crypto_pwhash/argon2/pwhash_argon2i.c \
crypto_pwhash/scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c \
crypto_scalarmult/crypto_scalarmult.c \
crypto_scalarmult/curve25519/scalarmult_curve25519.c \ crypto_scalarmult/curve25519/scalarmult_curve25519.c \
crypto_scalarmult/curve25519/scalarmult_curve25519.h \
crypto_secretbox/crypto_secretbox.c \
crypto_secretbox/crypto_secretbox_easy.c \
crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305_api.c \
crypto_shorthash/crypto_shorthash.c \
crypto_shorthash/siphash24/shorthash_siphash24_api.c \
crypto_shorthash/siphash24/ref/shorthash_siphash24.c \
crypto_sign/crypto_sign.c \
crypto_sign/ed25519/ref10/base.h \
crypto_sign/ed25519/ref10/base2.h \
crypto_sign/ed25519/sign_ed25519_api.c \
crypto_sign/ed25519/ref10/keypair.c \
crypto_sign/ed25519/ref10/open.c \
crypto_sign/ed25519/ref10/obsolete.c \
crypto_sign/ed25519/ref10/sign.c \
crypto_stream/crypto_stream.c \
crypto_stream/chacha20/stream_chacha20.c \ crypto_stream/chacha20/stream_chacha20.c \
crypto_stream/chacha20/stream_chacha20.h \
crypto_stream/chacha20/ref/stream_chacha20_ref.h \
crypto_stream/chacha20/ref/stream_chacha20_ref.c \ crypto_stream/chacha20/ref/stream_chacha20_ref.c \
crypto_stream/salsa20/stream_salsa20_api.c \ crypto_stream/salsa20/ref/stream_salsa20_ref.c \
crypto_stream/xsalsa20/stream_xsalsa20_api.c \ crypto_stream/salsa20/ref/xor_salsa20_ref.c \
crypto_stream/xsalsa20/ref/stream_xsalsa20.c \
crypto_stream/xsalsa20/ref/xor_xsalsa20.c \
crypto_verify/16/verify_16_api.c \
crypto_verify/16/ref/verify_16.c \ crypto_verify/16/ref/verify_16.c \
crypto_verify/32/verify_32_api.c \
crypto_verify/32/ref/verify_32.c \
crypto_verify/64/verify_64_api.c \
crypto_verify/64/ref/verify_64.c \
randombytes/randombytes.c \ randombytes/randombytes.c \
randombytes/sysrandom/randombytes_sysrandom.c \
sodium/core.c \ sodium/core.c \
sodium/runtime.c \ sodium/runtime.c \
sodium/utils.c \ sodium/utils.c \
sodium/version.c sodium/version.c
SODIUM_SOURCE += \
crypto_scalarmult/curve25519/ref10/x25519_ref10.c
SODIUM_SOURCE += \
crypto_pwhash/argon2/argon2-core.c \
crypto_pwhash/argon2/argon2.c \
crypto_pwhash/argon2/argon2-encoding.c \
crypto_pwhash/argon2/blake2b-long.c \
crypto_pwhash/argon2/argon2-fill-block-ref.c \
crypto_pwhash/argon2/pwhash_argon2i.c \
crypto_pwhash/argon2/argon2-fill-block-ssse3.c
SODIUM_SOURCE += \
crypto_stream/salsa20/ref/stream_salsa20_ref.c \
crypto_stream/salsa20/ref/xor_salsa20_ref.c
SODIUM_SOURCE += \
randombytes/sysrandom/randombytes_sysrandom.c
LOCAL_MODULE := sodium LOCAL_MODULE := sodium
LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/libsodium/src/libsodium/include \ LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/libsodium/src/libsodium/include \
-I$(LOCAL_PATH)/include \ -I$(LOCAL_PATH)/include \
@@ -329,8 +246,10 @@ include $(BUILD_SHARED_EXECUTABLE)
include $(CLEAR_VARS) include $(CLEAR_VARS)
SHADOWSOCKS_SOURCES := local.c cache.c udprelay.c encrypt.c \ SHADOWSOCKS_SOURCES := local.c \
utils.c netutils.c json.c jconf.c acl.c http.c tls.c rule.c \ cache.c udprelay.c utils.c netutils.c json.c jconf.c \
acl.c http.c tls.c rule.c \
crypto.c aead.c stream.c base64.c \
plugin.c \ plugin.c \
android.c android.c
@@ -364,7 +283,11 @@ include $(BUILD_SHARED_EXECUTABLE)
include $(CLEAR_VARS) include $(CLEAR_VARS)
SHADOWSOCKS_SOURCES := tunnel.c cache.c udprelay.c encrypt.c utils.c netutils.c json.c jconf.c android.c plugin.c SHADOWSOCKS_SOURCES := tunnel.c \
cache.c udprelay.c utils.c netutils.c json.c jconf.c \
crypto.c aead.c stream.c base64.c \
plugin.c \
android.c
LOCAL_MODULE := ss-tunnel LOCAL_MODULE := ss-tunnel
LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES)) LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES))
@@ -388,16 +311,16 @@ LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_EXECUTABLE) include $(BUILD_SHARED_EXECUTABLE)
######################################################## ########################################################
## system ## jni-helper
######################################################## ########################################################
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE:= system LOCAL_MODULE:= jni-helper
LOCAL_C_INCLUDES:= $(LOCAL_PATH)/libancillary LOCAL_C_INCLUDES:= $(LOCAL_PATH)/libancillary
LOCAL_SRC_FILES:= system.cpp LOCAL_SRC_FILES:= jni-helper.cpp
LOCAL_LDLIBS := -ldl -llog LOCAL_LDLIBS := -ldl -llog
@@ -1,11 +1,14 @@
#define LOG_TAG "Shadowsocks" #define LOG_TAG "JniHelper"
#include "jni.h" #include "jni.h"
#include <android/log.h> #include <android/log.h>
#include <sys/system_properties.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include <signal.h>
#include <sys/un.h> #include <sys/un.h>
#include <sys/stat.h> #include <sys/stat.h>
@@ -16,35 +19,33 @@
#define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0) #define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0)
#define LOGW(...) do { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); } while(0) #define LOGW(...) do { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); } while(0)
#define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0) #define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0)
#define THROW(env, clazz, msg) do { env->ThrowNew(env->FindClass(clazz), msg); } while (0)
jint Java_com_github_shadowsocks_system_exec(JNIEnv *env, jobject thiz, jstring cmd) { static jclass ProcessImpl;
const char *cmd_str = env->GetStringUTFChars(cmd, 0); static jfieldID ProcessImpl_pid;
pid_t pid; static int sdk_version() {
char version[PROP_VALUE_MAX + 1];
/* Fork off the parent process */ __system_property_get("ro.build.version.sdk", version);
pid = fork(); return atoi(version);
if (pid < 0) {
env->ReleaseStringUTFChars(cmd, cmd_str);
return -1;
}
if (pid > 0) {
env->ReleaseStringUTFChars(cmd, cmd_str);
return pid;
}
execl("/system/bin/sh", "sh", "-c", cmd_str, NULL);
env->ReleaseStringUTFChars(cmd, cmd_str);
return 1;
} }
void Java_com_github_shadowsocks_system_jniclose(JNIEnv *env, jobject thiz, jint fd) { jint Java_com_github_shadowsocks_jnihelper_sigterm(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 -1;
}
jint pid = env->GetIntField(process, ProcessImpl_pid);
// Suppress "No such process" errors. We just want the process killed. It's fine if it's already killed.
return kill(pid, SIGTERM) == -1 && errno != ESRCH ? errno : 0;
}
void Java_com_github_shadowsocks_jnihelper_close(JNIEnv *env, jobject thiz, jint fd) {
close(fd); close(fd);
} }
jint Java_com_github_shadowsocks_system_sendfd(JNIEnv *env, jobject thiz, jint tun_fd, jstring path) { jint Java_com_github_shadowsocks_jnihelper_sendfd(JNIEnv *env, jobject thiz, jint tun_fd, jstring path) {
int fd; int fd;
struct sockaddr_un addr; struct sockaddr_un addr;
const char *sock_str = env->GetStringUTFChars(path, 0); const char *sock_str = env->GetStringUTFChars(path, 0);
@@ -75,15 +76,15 @@ jint Java_com_github_shadowsocks_system_sendfd(JNIEnv *env, jobject thiz, jint t
return 0; return 0;
} }
static const char *classPathName = "com/github/shadowsocks/System"; static const char *classPathName = "com/github/shadowsocks/JniHelper";
static JNINativeMethod method_table[] = { static JNINativeMethod method_table[] = {
{ "jniclose", "(I)V", { "close", "(I)V",
(void*) Java_com_github_shadowsocks_system_jniclose }, (void*) Java_com_github_shadowsocks_jnihelper_close },
{ "sendfd", "(ILjava/lang/String;)I", { "sendFd", "(ILjava/lang/String;)I",
(void*) Java_com_github_shadowsocks_system_sendfd }, (void*) Java_com_github_shadowsocks_jnihelper_sendfd },
{ "exec", "(Ljava/lang/String;)I", { "sigterm", "(Ljava/lang/Process;)I",
(void*) Java_com_github_shadowsocks_system_exec } (void*) Java_com_github_shadowsocks_jnihelper_sigterm }
}; };
@@ -139,20 +140,30 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
jint result = -1; jint result = -1;
JNIEnv* env = NULL; JNIEnv* env = NULL;
LOGI("JNI_OnLoad"); if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK) {
THROW(env, "java/lang/RuntimeException", "GetEnv failed");
if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed");
goto bail; goto bail;
} }
env = uenv.env; env = uenv.env;
if (registerNatives(env) != JNI_TRUE) { if (registerNatives(env) != JNI_TRUE) {
LOGE("ERROR: registerNatives failed"); THROW(env, "java/lang/RuntimeException", "registerNativeMethods failed");
goto bail; goto bail;
} }
result = JNI_VERSION_1_4; if (sdk_version() < 24) {
if (!(ProcessImpl = env->FindClass("java/lang/ProcessManager$ProcessImpl"))) {
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl not found");
goto bail;
}
ProcessImpl = (jclass) env->NewGlobalRef((jobject) ProcessImpl);
if (!(ProcessImpl_pid = env->GetFieldID(ProcessImpl, "pid", "I"))) {
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.pid not found");
goto bail;
}
}
result = JNI_VERSION_1_6;
bail: bail:
return result; return result;
@@ -10,6 +10,5 @@
<EditText android:id="@+id/content" <EditText android:id="@+id/content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="textNoSuggestions" android:inputType="textNoSuggestions|textMultiLine"/>
android:lines="1"/>
</LinearLayout> </LinearLayout>
+8 -4
View File
@@ -1,8 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string-array name="enc_method_entry"> <string-array name="enc_method_entry">
<item>TABLE</item>
<item>RC4</item>
<item>RC4-MD5</item> <item>RC4-MD5</item>
<item>AES-128-CFB</item> <item>AES-128-CFB</item>
<item>AES-192-CFB</item> <item>AES-192-CFB</item>
@@ -17,11 +15,13 @@
<item>SALSA20</item> <item>SALSA20</item>
<item>CHACHA20</item> <item>CHACHA20</item>
<item>CHACHA20-IETF</item> <item>CHACHA20-IETF</item>
<item>AES-128-GCM</item>
<item>AES-192-GCM</item>
<item>AES-256-GCM</item>
<item>CHACHA20-IETF-POLY1305</item>
</string-array> </string-array>
<string-array name="enc_method_value"> <string-array name="enc_method_value">
<item>table</item>
<item>rc4</item>
<item>rc4-md5</item> <item>rc4-md5</item>
<item>aes-128-cfb</item> <item>aes-128-cfb</item>
<item>aes-192-cfb</item> <item>aes-192-cfb</item>
@@ -36,6 +36,10 @@
<item>salsa20</item> <item>salsa20</item>
<item>chacha20</item> <item>chacha20</item>
<item>chacha20-ietf</item> <item>chacha20-ietf</item>
<item>aes-128-gcm</item>
<item>aes-192-gcm</item>
<item>aes-256-gcm</item>
<item>chacha20-ietf-poly1305</item>
</string-array> </string-array>
<string-array name="route_entry"> <string-array name="route_entry">
-3
View File
@@ -36,8 +36,6 @@
<!-- feature category --> <!-- feature category -->
<string name="ipv6">IPv6 Route</string> <string name="ipv6">IPv6 Route</string>
<string name="ipv6_summary">Redirect IPv6 traffic to remote</string> <string name="ipv6_summary">Redirect IPv6 traffic to remote</string>
<string name="onetime_auth">One-time Authentication</string>
<string name="onetime_auth_summary">Enable one-time authentication</string>
<string name="route_list">Route</string> <string name="route_list">Route</string>
<string name="route_entry_all">All</string> <string name="route_entry_all">All</string>
<string name="route_entry_bypass_lan">Bypass LAN</string> <string name="route_entry_bypass_lan">Bypass LAN</string>
@@ -62,7 +60,6 @@
<string name="forward_success">Shadowsocks started.</string> <string name="forward_success">Shadowsocks started.</string>
<string name="invalid_server">Invalid server name</string> <string name="invalid_server">Invalid server name</string>
<string name="service_failed">Failed to connect the remote server</string> <string name="service_failed">Failed to connect the remote server</string>
<string name="method_unsafe">WARNING: The current encryption method is unsafe</string>
<string name="nat_deprecated">WARNING: NAT mode has been deprecated since Android 5.0</string> <string name="nat_deprecated">WARNING: NAT mode has been deprecated since Android 5.0</string>
<string name="nat_no_root">NAT mode requires ROOT permission</string> <string name="nat_no_root">NAT mode requires ROOT permission</string>
<string name="switch_to_vpn">Switch to VPN mode</string> <string name="switch_to_vpn">Switch to VPN mode</string>
+5 -8
View File
@@ -2,13 +2,14 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks"> xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks">
<be.mygod.preference.EditTextPreference
android:key="profileName"
android:summary="%s"
android:title="@string/profile_name"/>
<be.mygod.preference.PreferenceCategory <be.mygod.preference.PreferenceCategory
android:title="@string/proxy_cat"> android:title="@string/proxy_cat">
<be.mygod.preference.EditTextPreference
android:key="profileName"
android:summary="%s"
android:title="@string/profile_name"/>
<be.mygod.preference.EditTextPreference <be.mygod.preference.EditTextPreference
android:key="proxy" android:key="proxy"
android:summary="%s" android:summary="%s"
@@ -36,10 +37,6 @@
android:entryValues="@array/enc_method_value" android:entryValues="@array/enc_method_value"
android:summary="%s" android:summary="%s"
android:title="@string/enc_method"/> android:title="@string/enc_method"/>
<SwitchPreference
android:key="isAuth"
android:summary="@string/onetime_auth_summary"
android:title="@string/onetime_auth"/>
</be.mygod.preference.PreferenceCategory> </be.mygod.preference.PreferenceCategory>
@@ -183,9 +183,6 @@ trait BaseService extends Service {
changeState(State.CONNECTING) changeState(State.CONNECTING)
if (profile.isMethodUnsafe)
handler.post(() => Toast.makeText(this, R.string.method_unsafe, Toast.LENGTH_LONG).show())
Utils.ThrowableFuture(try connect() catch { Utils.ThrowableFuture(try connect() catch {
case _: NameNotResolvedException => stopRunner(stopService = true, getString(R.string.invalid_server)) case _: NameNotResolvedException => stopRunner(stopService = true, getString(R.string.invalid_server))
case _: NullConnectionException => stopRunner(stopService = true, getString(R.string.reboot_required)) case _: NullConnectionException => stopRunner(stopService = true, getString(R.string.reboot_required))
@@ -325,7 +322,6 @@ trait BaseService extends Service {
.put("server_port", profile.remotePort) .put("server_port", profile.remotePort)
.put("password", profile.password) .put("password", profile.password)
.put("method", profile.method) .put("method", profile.method)
if (profile.auth) config.put("auth", true)
if (pluginPath != null) config if (pluginPath != null) config
.put("plugin", Commandline.toString(buildPluginCommandLine())) .put("plugin", Commandline.toString(buildPluginCommandLine()))
.put("plugin_opts", plugin.toString) .put("plugin_opts", plugin.toString)
@@ -24,6 +24,7 @@ import java.io._
import java.lang.System.currentTimeMillis import java.lang.System.currentTimeMillis
import java.util.concurrent.Semaphore import java.util.concurrent.Semaphore
import android.os.Build
import android.util.Log import android.util.Log
import com.github.shadowsocks.utils.CloseUtils._ import com.github.shadowsocks.utils.CloseUtils._
import com.github.shadowsocks.utils.Commandline import com.github.shadowsocks.utils.Commandline
@@ -89,7 +90,7 @@ class GuardedProcess(cmd: String*) {
} catch { } catch {
case _: InterruptedException => case _: InterruptedException =>
if (BuildConfig.DEBUG) Log.d(TAG, "thread interrupt, destroy process: " + Commandline.toString(cmd)) if (BuildConfig.DEBUG) Log.d(TAG, "thread interrupt, destroy process: " + Commandline.toString(cmd))
process.destroy() destroyProcess()
case e: IOException => ioException = e case e: IOException => ioException = e
} finally semaphore.release() } finally semaphore.release()
}, "GuardThread-" + cmd.head) }, "GuardThread-" + cmd.head)
@@ -105,16 +106,24 @@ class GuardedProcess(cmd: String*) {
def destroy() { def destroy() {
isDestroyed = true isDestroyed = true
guardThread.interrupt() guardThread.interrupt()
process.destroy() destroyProcess()
try guardThread.join() catch { try guardThread.join() catch {
case _: InterruptedException => case _: InterruptedException =>
} }
} }
private def destroyProcess() {
if (Build.VERSION.SDK_INT < 24) {
JniHelper.sigtermCompat(process)
process.waitFor()
}
process.destroy()
}
def restart() { def restart() {
this.synchronized { this.synchronized {
isRestart = true isRestart = true
process.destroy() destroyProcess()
} }
} }
@@ -84,11 +84,6 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
private var txRateText: TextView = _ private var txRateText: TextView = _
private var rxRateText: TextView = _ private var rxRateText: TextView = _
private var currentFragment: ToolbarFragment = _
private lazy val profilesFragment = new ProfilesFragment()
private lazy val customRulesFragment = new CustomRulesFragment()
private lazy val globalSettingsFragment = new GlobalSettingsFragment()
private lazy val aboutFragment = new AboutFragment()
private lazy val customTabsIntent = new CustomTabsIntent.Builder() private lazy val customTabsIntent = new CustomTabsIntent.Builder()
.setToolbarColor(ContextCompat.getColor(this, R.color.material_primary_500)) .setToolbarColor(ContextCompat.getColor(this, R.color.material_primary_500))
.build() .build()
@@ -172,11 +167,11 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
private def addDisableNatToSnackbar(snackbar: Snackbar) = snackbar.setAction(R.string.switch_to_vpn, (_ => private def addDisableNatToSnackbar(snackbar: Snackbar) = snackbar.setAction(R.string.switch_to_vpn, (_ =>
if (state == State.STOPPED) app.editor.putBoolean(Key.isNAT, false).apply()): View.OnClickListener) if (state == State.STOPPED) app.editor.putBoolean(Key.isNAT, false).apply()): View.OnClickListener)
override def binderDied() { override def binderDied(): Unit = handler.post(() => {
detachService() detachService()
app.crashRecovery() app.crashRecovery()
attachService(callback) attachService(callback)
} })
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = resultCode match { override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = resultCode match {
case Activity.RESULT_OK => bgService.use(app.profileId) case Activity.RESULT_OK => bgService.use(app.profileId)
@@ -255,7 +250,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
val tf = Typefaces.get(this, "fonts/Iceland.ttf") val tf = Typefaces.get(this, "fonts/Iceland.ttf")
if (tf != null) title.setTypeface(tf) if (tf != null) title.setTypeface(tf)
if (savedInstanceState == null) displayFragment(profilesFragment) if (savedInstanceState == null) displayFragment(new ProfilesFragment)
statusText = findViewById(R.id.status).asInstanceOf[TextView] statusText = findViewById(R.id.status).asInstanceOf[TextView]
txText = findViewById(R.id.tx).asInstanceOf[TextView] txText = findViewById(R.id.tx).asInstanceOf[TextView]
txRateText = findViewById(R.id.txRate).asInstanceOf[TextView] txRateText = findViewById(R.id.txRate).asInstanceOf[TextView]
@@ -364,14 +359,13 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
} }
private def displayFragment(fragment: ToolbarFragment) { private def displayFragment(fragment: ToolbarFragment) {
currentFragment = fragment
getFragmentManager.beginTransaction().replace(R.id.fragment_holder, fragment).commitAllowingStateLoss() getFragmentManager.beginTransaction().replace(R.id.fragment_holder, fragment).commitAllowingStateLoss()
drawer.closeDrawer() drawer.closeDrawer()
} }
override def onItemClick(view: View, position: Int, drawerItem: IDrawerItem[_, _ <: ViewHolder]): Boolean = { override def onItemClick(view: View, position: Int, drawerItem: IDrawerItem[_, _ <: ViewHolder]): Boolean = {
drawerItem.getIdentifier match { drawerItem.getIdentifier match {
case DRAWER_PROFILES => displayFragment(profilesFragment) case DRAWER_PROFILES => displayFragment(new ProfilesFragment)
case DRAWER_RECOVERY => case DRAWER_RECOVERY =>
app.track("GlobalConfigFragment", "reset") app.track("GlobalConfigFragment", "reset")
if (bgService != null) bgService.use(-1) if (bgService != null) bgService.use(-1)
@@ -384,12 +378,12 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
app.copyAssets() app.copyAssets()
handler.sendEmptyMessage(0) handler.sendEmptyMessage(0)
} }
case DRAWER_GLOBAL_SETTINGS => displayFragment(globalSettingsFragment) case DRAWER_GLOBAL_SETTINGS => displayFragment(new GlobalSettingsFragment)
case DRAWER_ABOUT => case DRAWER_ABOUT =>
app.track(TAG, "about") app.track(TAG, "about")
displayFragment(aboutFragment) displayFragment(new AboutFragment)
case DRAWER_FAQ => launchUrl(getString(R.string.faq_url)) case DRAWER_FAQ => launchUrl(getString(R.string.faq_url))
case DRAWER_CUSTOM_RULES => displayFragment(customRulesFragment) case DRAWER_CUSTOM_RULES => displayFragment(new CustomRulesFragment)
} }
true // unexpected cases will throw exception true // unexpected cases will throw exception
} }
@@ -408,12 +402,14 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
setListeningForBandwidth(true) setListeningForBandwidth(true)
} }
override def onBackPressed(): Unit = override def onBackPressed(): Unit = if (drawer.isDrawerOpen) drawer.closeDrawer() else {
if (drawer.isDrawerOpen) drawer.closeDrawer() else if (!currentFragment.onBackPressed()) val currentFragment = getFragmentManager.findFragmentById(R.id.fragment_holder).asInstanceOf[ToolbarFragment]
if (currentFragment != profilesFragment) { if (!currentFragment.onBackPressed())
displayFragment(profilesFragment) if (currentFragment.isInstanceOf[ProfilesFragment]) super.onBackPressed() else {
displayFragment(new ProfilesFragment)
drawer.setSelection(DRAWER_PROFILES) drawer.setSelection(DRAWER_PROFILES)
} else super.onBackPressed() }
}
override def onStop() { override def onStop() {
setListeningForBandwidth(false) setListeningForBandwidth(false)
@@ -358,7 +358,9 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
REQUEST_SCAN_QR_CODE) catch { REQUEST_SCAN_QR_CODE) catch {
case _: ActivityNotFoundException => case _: ActivityNotFoundException =>
Toast.makeText(getActivity, R.string.add_profile_scanner_not_installed, Toast.LENGTH_LONG).show() Toast.makeText(getActivity, R.string.add_profile_scanner_not_installed, Toast.LENGTH_LONG).show()
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=tw.com.quickmark"))) try startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=tw.com.quickmark"))) catch {
case exc: ActivityNotFoundException => exc.printStackTrace()
}
} }
true true
case R.id.action_import => case R.id.action_import =>
@@ -74,7 +74,7 @@ class ShadowsocksNatService extends BaseService {
"-t", "10", "-t", "10",
"-b", "127.0.0.1", "-b", "127.0.0.1",
"-l", (profile.localPort + 63).toString, "-l", (profile.localPort + 63).toString,
"-L", if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53", "-L", profile.remoteDns + ":53",
"-c", buildShadowsocksConfig("ss-tunnel-nat.conf")) "-c", buildShadowsocksConfig("ss-tunnel-nat.conf"))
if (profile.udpdns) cmd.append("-u") if (profile.udpdns) cmd.append("-u")
@@ -23,14 +23,13 @@ package com.github.shadowsocks
import java.io.File import java.io.File
import java.util.Locale import java.util.Locale
import android.annotation.SuppressLint
import android.content._ import android.content._
import android.content.pm.PackageManager.NameNotFoundException import android.content.pm.PackageManager.NameNotFoundException
import android.net.VpnService import android.net.VpnService
import android.os._ import android.os._
import android.util.Log import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.{Acl, AclSyncJob} import com.github.shadowsocks.acl.{Acl, AclSyncJob, Subnet}
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
@@ -189,7 +188,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
"-t", "10", "-t", "10",
"-b", "127.0.0.1", "-b", "127.0.0.1",
"-l", (profile.localPort + 63).toString, "-l", (profile.localPort + 63).toString,
"-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53", "-L" , profile.remoteDns + ":53",
"-c", buildShadowsocksConfig("ss-tunnel-vpn.conf")).start() "-c", buildShadowsocksConfig("ss-tunnel-vpn.conf")).start()
def startDnsDaemon() { def startDnsDaemon() {
@@ -212,7 +211,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
pdnsdProcess = new GuardedProcess(cmd: _*).start() pdnsdProcess = new GuardedProcess(cmd: _*).start()
} }
@SuppressLint(Array("NewApi"))
def startVpn(): Int = { def startVpn(): Int = {
val builder = new Builder() val builder = new Builder()
@@ -221,7 +219,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
.setMtu(VPN_MTU) .setMtu(VPN_MTU)
.addAddress(PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), 24) .addAddress(PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), 24)
builder.addDnsServer("8.8.8.8") builder.addDnsServer(profile.remoteDns)
if (profile.ipv6) { if (profile.ipv6) {
builder.addAddress(PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "1"), 126) builder.addAddress(PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "1"), 126)
@@ -249,15 +247,13 @@ class ShadowsocksVpnService extends VpnService with BaseService {
if (profile.route == Acl.ALL || profile.route == Acl.BYPASS_CHN) { if (profile.route == Acl.ALL || profile.route == Acl.BYPASS_CHN) {
builder.addRoute("0.0.0.0", 0) builder.addRoute("0.0.0.0", 0)
} else { } else {
val privateList = getResources.getStringArray(R.array.bypass_private_route) getResources.getStringArray(R.array.bypass_private_route).foreach(cidr => {
privateList.foreach(cidr => { val subnet = Subnet.fromString(cidr)
val addr = cidr.split('/') builder.addRoute(subnet.address.getHostAddress, subnet.prefixSize)
builder.addRoute(addr(0), addr(1).toInt)
}) })
builder.addRoute(profile.remoteDns, 32)
} }
builder.addRoute("8.8.0.0", 16)
conn = builder.establish() conn = builder.establish()
if (conn == null) throw new NullConnectionException if (conn == null) throw new NullConnectionException
@@ -291,7 +287,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
var tries = 1 var tries = 1
while (tries < 5) { while (tries < 5) {
Thread.sleep(1000 * tries) Thread.sleep(1000 * tries)
if (System.sendfd(fd, new File(getFilesDir, "sock_path").getAbsolutePath) != -1) { if (JniHelper.sendFd(fd, new File(getFilesDir, "sock_path").getAbsolutePath) != -1) {
return true return true
} }
tries += 1 tries += 1
@@ -92,7 +92,7 @@ class ShadowsocksVpnThread(service: ShadowsocksVpnService) extends Thread {
val ret = service.protect(fd) val ret = service.protect(fd)
// Trick to close file decriptor // Trick to close file decriptor
System.jniclose(fd) JniHelper.close(fd)
if (ret) { if (ret) {
output.write(0) output.write(0)
@@ -36,7 +36,7 @@ object AclSyncJob {
final val TAG = "AclSyncJob" final val TAG = "AclSyncJob"
def schedule(route: String): Int = new JobRequest.Builder(AclSyncJob.TAG + ':' + route) def schedule(route: String): Int = new JobRequest.Builder(AclSyncJob.TAG + ':' + route)
.setExecutionWindow(1, TimeUnit.DAYS.toMillis(28)) .setExecutionWindow(TimeUnit.SECONDS.toMillis(10), TimeUnit.DAYS.toMillis(28))
.setRequirementsEnforced(true) .setRequirementsEnforced(true)
.setRequiredNetworkType(JobRequest.NetworkType.UNMETERED) .setRequiredNetworkType(JobRequest.NetworkType.UNMETERED)
.setRequiresCharging(true) .setRequiresCharging(true)
@@ -20,6 +20,7 @@ import com.github.shadowsocks.widget.UndoSnackbarManager
import com.github.shadowsocks.{MainActivity, R, ToolbarFragment} import com.github.shadowsocks.{MainActivity, R, ToolbarFragment}
import scala.collection.mutable import scala.collection.mutable
import scala.io.Source
/** /**
* @author Mygod * @author Mygod
@@ -144,8 +145,12 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi
apply() apply()
index index
} else -1 } else -1
def addToProxy(input: String): Int = try addSubnet(Subnet.fromString(input)) catch { def addToProxy(input: String): Int = {
case _: IllegalArgumentException => addHostname(input) val acl = new Acl().fromSource(Source.fromString(input), defaultBypass = true)
var result = -1
for (hostname <- acl.proxyHostnames) result = addHostname(hostname)
if (acl.bypass) for (subnet <- acl.subnets) result = addSubnet(subnet)
result
} }
def addFromTemplate(template: Int, text: CharSequence): Int = template match { def addFromTemplate(template: Int, text: CharSequence): Int = template match {
case GENERIC => addToProxy(text.toString) case GENERIC => addToProxy(text.toString)
@@ -298,13 +303,7 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi
.create().show() .create().show()
true true
case R.id.action_import => case R.id.action_import =>
try { try if (adapter.addToProxy(clipboard.getPrimaryClip.getItemAt(0).getText.toString) != -1) return true catch {
val items = clipboard.getPrimaryClip.getItemAt(0).getText.toString.split("\n")
if (items.nonEmpty) {
items.foreach(adapter.addToProxy)
return true
}
} catch {
case _: Exception => case _: Exception =>
} }
Snackbar.make(getActivity.findViewById(R.id.snackbar), R.string.action_import_err, Snackbar.LENGTH_LONG).show() Snackbar.make(getActivity.findViewById(R.id.snackbar), R.string.action_import_err, Snackbar.LENGTH_LONG).show()
@@ -1,6 +1,7 @@
package com.github.shadowsocks.acl package com.github.shadowsocks.acl
import java.net.{Inet4Address, Inet6Address, InetAddress} import java.net.InetAddress
import java.util.Objects
import com.github.shadowsocks.utils.Utils import com.github.shadowsocks.utils.Utils
@@ -9,16 +10,11 @@ import com.github.shadowsocks.utils.Utils
*/ */
@throws[IllegalArgumentException] @throws[IllegalArgumentException]
class Subnet(val address: InetAddress, val prefixSize: Int) extends Comparable[Subnet] { class Subnet(val address: InetAddress, val prefixSize: Int) extends Comparable[Subnet] {
if (prefixSize < 0) throw new IllegalArgumentException private def addressLength = address.getAddress.length << 3
address match { if (prefixSize < 0 || prefixSize > addressLength) throw new IllegalArgumentException
case _: Inet4Address => if (prefixSize > 32) throw new IllegalArgumentException
case _: Inet6Address => if (prefixSize > 128) throw new IllegalArgumentException
}
override def toString: String = if (address match { override def toString: String =
case _: Inet4Address => prefixSize == 32 if (prefixSize == addressLength) address.getHostAddress else address.getHostAddress + '/' + prefixSize
case _: Inet6Address => prefixSize == 128
}) address.getHostAddress else address.getHostAddress + '/' + prefixSize
override def compareTo(that: Subnet): Int = { override def compareTo(that: Subnet): Int = {
val addrThis = address.getAddress val addrThis = address.getAddress
@@ -31,19 +27,21 @@ class Subnet(val address: InetAddress, val prefixSize: Int) extends Comparable[S
} }
prefixSize compare that.prefixSize prefixSize compare that.prefixSize
} }
override def equals(other: Any): Boolean = other match {
case that: Subnet => address == that.address && prefixSize == that.prefixSize
case _ => false
}
override def hashCode: Int = Objects.hash(address, prefixSize: Integer)
} }
object Subnet { object Subnet {
@throws[IllegalArgumentException] @throws[IllegalArgumentException]
def fromString(value: String): Subnet = { def fromString(value: String): Subnet = {
val parts = value.split("/") val parts = value.split("/")
if (!Utils.isNumeric(parts(0))) throw new IllegalArgumentException() val addr = Utils.parseNumericAddress(parts(0))
val addr = InetAddress.getByName(parts(0))
parts.length match { parts.length match {
case 1 => new Subnet(addr, addr match { case 1 => new Subnet(addr, addr.getAddress.length << 3)
case _: Inet4Address => 32
case _: Inet6Address => 128
})
case 2 => new Subnet(addr, parts(1).toInt) case 2 => new Subnet(addr, parts(1).toInt)
case _ => throw new IllegalArgumentException() case _ => throw new IllegalArgumentException()
} }
@@ -77,9 +77,6 @@ class DBHelper(val context: Context)
} else if (oldVersion < 19) { } else if (oldVersion < 19) {
profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'all' WHERE route IS NULL;") profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'all' WHERE route IS NULL;")
} }
if (oldVersion < 10) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN auth SMALLINT;")
}
if (oldVersion < 11) { if (oldVersion < 11) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;") profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
} }
@@ -89,9 +86,9 @@ class DBHelper(val context: Context)
TableUtils.createTable(connectionSource, classOf[Profile]) TableUtils.createTable(connectionSource, classOf[Profile])
profileDao.executeRawNoArgs( profileDao.executeRawNoArgs(
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," + "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," +
" udpdns, auth, ipv6, individual) " + " udpdns, ipv6, individual) " +
"SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, auth," + "SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, ipv6," +
" ipv6, individual FROM `tmp`;") " individual FROM `tmp`;")
profileDao.executeRawNoArgs("DROP TABLE `tmp`;") profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
profileDao.executeRawNoArgs("COMMIT;") profileDao.executeRawNoArgs("COMMIT;")
} else if (oldVersion < 13) { } else if (oldVersion < 13) {
@@ -127,10 +124,10 @@ class DBHelper(val context: Context)
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;") profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
TableUtils.createTable(connectionSource, classOf[Profile]) TableUtils.createTable(connectionSource, classOf[Profile])
profileDao.executeRawNoArgs( profileDao.executeRawNoArgs(
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, remoteDns, proxyApps," + "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, remoteDns, " +
" bypass, udpdns, auth, ipv6, individual, tx, rx, date, userOrder, plugin) " + "proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, plugin) " +
"SELECT id, name, host, localPort, CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, " + "SELECT id, name, host, localPort, CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, " +
"route, remoteDns, proxyApps, bypass, udpdns, auth, ipv6, individual, tx, rx, date, userOrder, " + "route, remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " +
"CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;") "CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;")
profileDao.executeRawNoArgs("DROP TABLE `tmp`;") profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
profileDao.executeRawNoArgs("COMMIT;") profileDao.executeRawNoArgs("COMMIT;")
@@ -20,13 +20,13 @@
package com.github.shadowsocks.database package com.github.shadowsocks.database
import java.net.URLEncoder
import java.util.Locale import java.util.Locale
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri
import android.os.Binder import android.os.Binder
import android.text.TextUtils
import android.util.Base64 import android.util.Base64
import com.github.shadowsocks.plugin.PluginConfiguration
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.j256.ormlite.field.{DataType, DatabaseField} import com.j256.ormlite.field.{DataType, DatabaseField}
@@ -68,9 +68,6 @@ class Profile {
@DatabaseField @DatabaseField
var udpdns: Boolean = _ var udpdns: Boolean = _
@DatabaseField
var auth: Boolean = _
@DatabaseField @DatabaseField
var ipv6: Boolean = _ var ipv6: Boolean = _
@@ -96,11 +93,19 @@ class Profile {
def nameIsEmpty: Boolean = name == null || name.isEmpty def nameIsEmpty: Boolean = name == null || name.isEmpty
def getName: String = if (nameIsEmpty) formattedAddress else name def getName: String = if (nameIsEmpty) formattedAddress else name
override def toString: String = "ss://" + Base64.encodeToString("%s%s:%s@%s:%d".formatLocal(Locale.ENGLISH, def toUri: Uri = {
method, if (auth) "-auth" else "", password, host, remotePort).getBytes, Base64.NO_PADDING | Base64.NO_WRAP) + val builder = new Uri.Builder()
(if (TextUtils.isEmpty(name)) "" else '#' + URLEncoder.encode(name, "utf-8")) .scheme("ss")
.encodedAuthority("%s@%s:%d".formatLocal(Locale.ENGLISH,
def isMethodUnsafe: Boolean = "table".equalsIgnoreCase(method) || "rc4".equalsIgnoreCase(method) Base64.encodeToString("%s:%s".formatLocal(Locale.ENGLISH, method, password).getBytes,
Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE), host, remotePort))
val configuration = new PluginConfiguration(plugin)
if (configuration.selected.nonEmpty)
builder.appendQueryParameter(Key.plugin, configuration.selectedOptions.toString(false))
if (!nameIsEmpty) builder.fragment(name)
builder.build()
}
override def toString: String = toUri.toString
def serialize(editor: SharedPreferences.Editor): SharedPreferences.Editor = editor def serialize(editor: SharedPreferences.Editor): SharedPreferences.Editor = editor
.putString(Key.name, name) .putString(Key.name, name)
@@ -114,7 +119,6 @@ class Profile {
.putBoolean(Key.proxyApps, proxyApps) .putBoolean(Key.proxyApps, proxyApps)
.putBoolean(Key.bypass, bypass) .putBoolean(Key.bypass, bypass)
.putBoolean(Key.udpdns, udpdns) .putBoolean(Key.udpdns, udpdns)
.putBoolean(Key.auth, auth)
.putBoolean(Key.ipv6, ipv6) .putBoolean(Key.ipv6, ipv6)
.putString(Key.individual, individual) .putString(Key.individual, individual)
.putString(Key.plugin, plugin) .putString(Key.plugin, plugin)
@@ -132,7 +136,6 @@ class Profile {
proxyApps = pref.getBoolean(Key.proxyApps, false) proxyApps = pref.getBoolean(Key.proxyApps, false)
bypass = pref.getBoolean(Key.bypass, false) bypass = pref.getBoolean(Key.bypass, false)
udpdns = pref.getBoolean(Key.udpdns, false) udpdns = pref.getBoolean(Key.udpdns, false)
auth = pref.getBoolean(Key.auth, false)
ipv6 = pref.getBoolean(Key.ipv6, false) ipv6 = pref.getBoolean(Key.ipv6, false)
individual = pref.getString(Key.individual, null) individual = pref.getString(Key.individual, null)
plugin = pref.getString(Key.plugin, null) plugin = pref.getString(Key.plugin, null)
@@ -50,6 +50,20 @@ object PluginManager {
|RSo6EooQ7+NBejOXysqIF1q0BJs8Y5s/CaTOmgbL7uPCkzArB6SS/hzXgDk5gw6v |RSo6EooQ7+NBejOXysqIF1q0BJs8Y5s/CaTOmgbL7uPCkzArB6SS/hzXgDk5gw6v
|wkGeOtzcj1DlbUTvt1s5GlnwBTGUmkbLx+YUje+n+IBgMbohLUDYBtUHylRVgMsc |wkGeOtzcj1DlbUTvt1s5GlnwBTGUmkbLx+YUje+n+IBgMbohLUDYBtUHylRVgMsc
|1WS67kDqeJiiQZvrxvyW6CZZ/MIGI+uAkkj3DqJpaZirkwPgvpcOIrjZy0uFvQM= |1WS67kDqeJiiQZvrxvyW6CZZ/MIGI+uAkkj3DqJpaZirkwPgvpcOIrjZy0uFvQM=
""", Base64.DEFAULT)) +
new Signature(Base64.decode( // @madeye
"""
|MIICQzCCAaygAwIBAgIETV9OhjANBgkqhkiG9w0BAQUFADBmMQswCQYDVQQGEwJjbjERMA8GA1UE
|CBMIU2hhbmdoYWkxDzANBgNVBAcTBlB1ZG9uZzEUMBIGA1UEChMLRnVkYW4gVW5pdi4xDDAKBgNV
|BAsTA1BQSTEPMA0GA1UEAxMGTWF4IEx2MB4XDTExMDIxOTA1MDA1NFoXDTM2MDIxMzA1MDA1NFow
|ZjELMAkGA1UEBhMCY24xETAPBgNVBAgTCFNoYW5naGFpMQ8wDQYDVQQHEwZQdWRvbmcxFDASBgNV
|BAoTC0Z1ZGFuIFVuaXYuMQwwCgYDVQQLEwNQUEkxDzANBgNVBAMTBk1heCBMdjCBnzANBgkqhkiG
|9w0BAQEFAAOBjQAwgYkCgYEAq6lA8LqdeEI+es9SDX85aIcx8LoL3cc//iRRi+2mFIWvzvZ+bLKr
|4Wd0rhu/iU7OeMm2GvySFyw/GdMh1bqh5nNPLiRxAlZxpaZxLOdRcxuvh5Nc5yzjM+QBv8ECmuvu
|AOvvT3UDmA0AMQjZqSCmxWIxc/cClZ/0DubreBo2st0CAwEAATANBgkqhkiG9w0BAQUFAAOBgQAQ
|Iqonxpwk2ay+Dm5RhFfZyG9SatM/JNFx2OdErU16WzuK1ItotXGVJaxCZv3u/tTwM5aaMACGED5n
|AvHaDGCWynY74oDAopM4liF/yLe1wmZDu6Zo/7fXrH+T03LBgj2fcIkUfN1AA4dvnBo8XWAm9VrI
|1iNuLIssdhDz3IL9Yg==
""", Base64.DEFAULT)) """, Base64.DEFAULT))
private var receiver: BroadcastReceiver = _ private var receiver: BroadcastReceiver = _
@@ -141,7 +141,6 @@ object Key {
val proxyApps = "isProxyApps" val proxyApps = "isProxyApps"
val bypass = "isBypassApps" val bypass = "isBypassApps"
val udpdns = "isUdpDns" val udpdns = "isUdpDns"
val auth = "isAuth"
val ipv6 = "isIpv6" val ipv6 = "isIpv6"
val host = "proxy" val host = "proxy"
@@ -20,32 +20,49 @@
package com.github.shadowsocks.utils package com.github.shadowsocks.utils
import java.net.URLDecoder 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+-/=_]+)(#(.+))?".r private val pattern = "(?i)^ss://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]$".r
private val decodedPattern = "(?i)^((.+?)(-auth)??:(.*)@(.+?):(\\d+?))$".r private val userInfoPattern = "^(.+?):(.*)$".r
private val legacyPattern = "^((.+?):(.*)@(.+?):(\\d+?))$".r
def findAll(data: CharSequence): Iterator[Profile] = def findAll(data: CharSequence): Iterator[Profile] =
pattern.findAllMatchIn(if (data == null) "" else data).map(m => try pattern.findAllMatchIn(if (data == null) "" else data).map(m => {
decodedPattern.findFirstMatchIn(new String(Base64.decode(m.group(1), Base64.NO_PADDING), "UTF-8")) match { val uri = Uri.parse(m.matched)
case Some(ss) => uri.getUserInfo match {
val profile = new Profile case null => new String(Base64.decode(uri.getHost, Base64.NO_PADDING), "UTF-8") match {
profile.method = ss.group(2).toLowerCase case legacyPattern(_, method, password, host, port) => // legacy uri
if (ss.group(3) != null) profile.auth = true val profile = new Profile
profile.password = ss.group(4) profile.method = method.toLowerCase
profile.host = ss.group(5) profile.password = password
profile.remotePort = ss.group(6).toInt profile.host = host
if (m.group(2) != null) profile.name = URLDecoder.decode(m.group(3), "utf-8") profile.remotePort = port.toInt
profile profile.plugin = uri.getQueryParameter(Key.plugin)
case _ => null profile.name = uri.getFragment
} catch { profile
case ex: Exception => case _ =>
Log.e(TAG, "parser error: " + m.source, ex)// Ignore Log.e(TAG, "Unrecognized URI: " + m.matched)
null null
}).filter(_ != null) }
case userInfo =>
new String(Base64.decode(userInfo, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE), "UTF-8") match {
case userInfoPattern(method, password) =>
val profile = new Profile
profile.method = method
profile.password = password
profile.host = uri.getHost
profile.remotePort = uri.getPort
profile.plugin = uri.getQueryParameter(Key.plugin)
profile.name = uri.getFragment
profile
case _ =>
Log.e(TAG, "Unknown user info: " + m.matched)
null
}
}
}).filter(_ != null)
} }
@@ -45,7 +45,7 @@ object TrafficMonitor {
def formatTraffic(size: Long): String = { def formatTraffic(size: Long): String = {
var n: Double = size var n: Double = size
var i = -1 var i = -1
while (n >= 1000) { while (n >= 999.5) {
n /= 1024 n /= 1024
i = i + 1 i = i + 1
} }
@@ -20,6 +20,7 @@
package com.github.shadowsocks.utils package com.github.shadowsocks.utils
import java.lang.reflect.InvocationTargetException
import java.net._ import java.net._
import java.security.MessageDigest import java.security.MessageDigest
@@ -108,7 +109,15 @@ object Utils {
.orElse(resolve(host)) .orElse(resolve(host))
private lazy val isNumericMethod = classOf[InetAddress].getMethod("isNumeric", classOf[String]) private lazy val isNumericMethod = classOf[InetAddress].getMethod("isNumeric", classOf[String])
private lazy val parseNumericAddressMethod = classOf[InetAddress].getMethod("parseNumericAddress", classOf[String])
def isNumeric(address: String): Boolean = isNumericMethod.invoke(null, address).asInstanceOf[Boolean] def isNumeric(address: String): Boolean = isNumericMethod.invoke(null, address).asInstanceOf[Boolean]
def parseNumericAddress(address: String): InetAddress =
try parseNumericAddressMethod.invoke(null, address).asInstanceOf[InetAddress] catch {
case exc: InvocationTargetException => throw exc.getCause match {
case null => exc
case cause => cause
}
}
/** /**
* If there exists a valid IPv6 interface * If there exists a valid IPv6 interface
+4
View File
@@ -0,0 +1,4 @@
* 0.0.2:
* Add `getOrDefault` to `PluginOptions`;
* Update support library version to 25.1.1.
* 0.0.1: Initial release.
+4 -5
View File
@@ -1,5 +1,7 @@
# shadowsocks-android plugin framework # shadowsocks-android plugin framework
[Documentation](doc.md) | [Change log](CHANGES.md)
Support library for easier development on [shadowsocks Support library for easier development on [shadowsocks
plugin](https://github.com/shadowsocks/shadowsocks-org/issues/28) for Android. Also includes some plugin](https://github.com/shadowsocks/shadowsocks-org/issues/28) for Android. Also includes some
useful resources to easily get consistent styling with the main app. useful resources to easily get consistent styling with the main app.
@@ -19,9 +21,6 @@ This library is designed with Java interoperability in mind so theoretically you
library with other languages and/or build tools but there isn't documentation for that yet. This library with other languages and/or build tools but there isn't documentation for that yet. This
guide is written for Scala + SBT. Contributions are welcome. guide is written for Scala + SBT. Contributions are welcome.
The following is just a simple guide to get you started, for more technical details, see
[doc.md](https://github.com/shadowsocks/shadowsocks-android/blob/develop/plugin/doc.md).
### Package name ### Package name
There are no arbitrary restrictions/requirements on package name, component name and content There are no arbitrary restrictions/requirements on package name, component name and content
@@ -37,7 +36,7 @@ First you need to add this library to your dependencies. This library is written
and it's most convenient to use it with SBT: and it's most convenient to use it with SBT:
```scala ```scala
libraryDependencies += "com.github.shadowsocks" %% "plugin" % "0.0.1-SNAPSHOT" libraryDependencies += "com.github.shadowsocks" %% "plugin" % "0.0.2"
``` ```
### Native binary configuration ### Native binary configuration
@@ -88,7 +87,7 @@ Then add it to your manifest:
... ...
<provider android:name=".BinaryProvider" <provider android:name=".BinaryProvider"
android:exported="true" android:exported="true"
android:authorities="com.github.shadowsocks.plugin.$PLUGIN_ID"> android:authorities="$FULLY_QUALIFIED_NAME_OF_YOUR_CONTENTPROVIDER">
<intent-filter> <intent-filter>
<action android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"/> <action android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"/>
</intent-filter> </intent-filter>
+28 -1
View File
@@ -2,4 +2,31 @@ enablePlugins(AndroidLib)
android.useSupportVectors android.useSupportVectors
name := "plugin" name := "plugin"
version := "0.0.1" version := "0.0.2"
pomExtra in Global := {
<url>https://github.com/shadowsocks/shadowsocks-android</url>
<licenses>
<license>
<name>GPLv3</name>
<url>https://www.gnu.org/licenses/gpl-3.0.html</url>
</license>
</licenses>
<scm>
<connection>scm:git:git://github.com/shadowsocks/shadowsocks-android.git</connection>
<developerConnection>scm:git:git@github.com:shadowsocks/shadowsocks-android.git</developerConnection>
<url>github.com/shadowsocks/shadowsocks-android</url>
</scm>
<developers>
<developer>
<id>madeye</id>
<name>Max Lv</name>
<url>https://github.com/madeye</url>
</developer>
<developer>
<id>Mygod</id>
<name>Mygod Studio</name>
<url>https://github.com/Mygod</url>
</developer>
</developers>
}
+3 -3
View File
@@ -153,7 +153,7 @@ This corresponds to `com.github.shadowsocks.plugin.NativePluginProvider` in the
... ...
<provider android:name=".BinaryProvider" <provider android:name=".BinaryProvider"
android:exported="true" android:exported="true"
android:authorities="com.github.shadowsocks.plugin.$PLUGIN_ID"> android:authorities="$FULLY_QUALIFIED_NAME_OF_YOUR_CONTENTPROVIDER">
<intent-filter> <intent-filter>
<action android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"/> <action android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"/>
</intent-filter> </intent-filter>
@@ -196,7 +196,7 @@ This feature hasn't been implemented yet.
Plugins are certified using package signatures and shadowsocks-android will consider these Plugins are certified using package signatures and shadowsocks-android will consider these
signatures as trusted: signatures as trusted:
* Signatures by [trusted sources](https://github.com/shadowsocks/shadowsocks-android/blob/develop/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala#L22) * Signatures by [trusted sources](/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala#L22)
which includes: which includes:
- @madeye, i.e. the signer of the main repo; - @madeye, i.e. the signer of the main repo;
- The main repo doesn't contain any other trusted signatures. Third-party forks should add their - The main repo doesn't contain any other trusted signatures. Third-party forks should add their
@@ -226,5 +226,5 @@ Plugin app must include this in their application tag: (which should be automati
``` ```
<meta-data android:name="com.github.shadowsocks.plugin.version" <meta-data android:name="com.github.shadowsocks.plugin.version"
android:value="0.0.1"/> android:value="0.0.2"/>
``` ```
+1 -1
View File
@@ -6,6 +6,6 @@
<application <application
android:theme="@style/Theme.Shadowsocks"> android:theme="@style/Theme.Shadowsocks">
<meta-data android:name="com.github.shadowsocks.plugin.version" <meta-data android:name="com.github.shadowsocks.plugin.version"
android:value="0.0.1"/> android:value="0.0.2"/>
</application> </application>
</manifest> </manifest>
@@ -24,6 +24,14 @@ public final class PluginOptions extends HashMap<String, String> {
super(initialCapacity, loadFactor); super(initialCapacity, loadFactor);
} }
// TODO: this method is not needed since API 24
public String getOrDefault(Object key, String defaultValue) {
String v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
private PluginOptions(String options, boolean parseId) { private PluginOptions(String options, boolean parseId) {
this(); this();
if (TextUtils.isEmpty(options)) return; if (TextUtils.isEmpty(options)) return;
@@ -15,7 +15,7 @@ import android.os.{Bundle, ParcelFileDescriptor}
* &lt;application&gt; * &lt;application&gt;
* ... * ...
* &lt;provider android:name="com.github.shadowsocks.$PLUGIN_ID.BinaryProvider" * &lt;provider android:name="com.github.shadowsocks.$PLUGIN_ID.BinaryProvider"
* android:authorities="com.github.shadowsocks.plugin.$PLUGIN_ID"&gt; * android:authorities="com.github.shadowsocks.plugin.$PLUGIN_ID.BinaryProvider"&gt;
* &lt;intent-filter&gt; * &lt;intent-filter&gt;
* &lt;category android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" /&gt; * &lt;category android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" /&gt;
* &lt;/intent-filter&gt; * &lt;/intent-filter&gt;
+1 -1
View File
@@ -1,4 +1,4 @@
addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.3") addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.4")
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10")
-23
View File
@@ -1,23 +0,0 @@
sonatypeProfileName := "com.github.shadowsocks"
pomExtra in Global := {
<url>https://github.com/shadowsocks/shadowsocks-android</url>
<licenses>
<license>
<name>GPLv3</name>
<url>https://www.gnu.org/licenses/gpl-3.0.html</url>
</license>
</licenses>
<scm>
<connection>scm:git:git://github.com/shadowsocks/shadowsocks-android.git</connection>
<developerConnection>scm:git:git@github.com:shadowsocks/shadowsocks-android.git</developerConnection>
<url>github.com/shadowsocks/shadowsocks-android</url>
</scm>
<developers>
<developer>
<id>madeye</id>
<name>Max Lv</name>
<url>https://github.com/madeye/</url>
</developer>
</developers>
}