diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..206bda08
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,21 @@
+classes
+bin
+gen
+obj
+target
+local.properties
+.classpath
+.project
+.settings
+tests/bin
+tests/gen
+tests/local.properties
+NUL
+libs
+
+#Intellij IDEA
+*.iml
+*.ipr
+*.iws
+.idea/
+out/
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100755
index 00000000..1f75f8a2
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/iptables b/assets/iptables
new file mode 100644
index 00000000..7268216c
Binary files /dev/null and b/assets/iptables differ
diff --git a/assets/pdnsd b/assets/pdnsd
new file mode 100755
index 00000000..43b80fd8
Binary files /dev/null and b/assets/pdnsd differ
diff --git a/assets/pdnsd.conf b/assets/pdnsd.conf
new file mode 100644
index 00000000..93ca5de8
--- /dev/null
+++ b/assets/pdnsd.conf
@@ -0,0 +1,43 @@
+global {
+ perm_cache = 2048;
+ cache_dir = "/data/data/com.github.shadowsocks";
+ server_ip = 127.0.0.1;
+ server_port = 8053;
+ status_ctl = on;
+ paranoid = on;
+ query_method = tcp_only;
+ min_ttl = 15m;
+ max_ttl = 1w;
+ timeout = 10;
+ daemon = on;
+ pid_file = "/data/data/com.github.shadowsocks/pdnsd.pid";
+}
+
+server {
+ label = "root-servers";
+ root_server = on;
+
+ ip = 8.8.8.8
+ , 8.8.4.4
+ , 208.67.222.222
+ , 208.67.220.220
+ ;
+
+ timeout = 5;
+
+ uptest = query;
+ interval = 30m; // Test every half hour.
+ ping_timeout = 300; // 30 seconds.
+ purge_cache = off;
+ exclude = .localdomain;
+ policy = included;
+ preset = off;
+}
+
+rr {
+ name=localhost;
+ reverse=on;
+ a=127.0.0.1;
+ owner=localhost;
+ soa=localhost,root.localhost,42,86400,900,86400,86400;
+}
diff --git a/assets/redsocks b/assets/redsocks
new file mode 100755
index 00000000..0454bf77
Binary files /dev/null and b/assets/redsocks differ
diff --git a/assets/shadowsocks b/assets/shadowsocks
new file mode 100755
index 00000000..453daa2c
Binary files /dev/null and b/assets/shadowsocks differ
diff --git a/jni/Android.mk b/jni/Android.mk
index 91464352..4ff57c15 100755
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -24,18 +24,15 @@ LOCAL_SRC_FILES := \
include $(BUILD_STATIC_LIBRARY)
-#include $(CLEAR_VARS)
+include $(CLEAR_VARS)
-#PDNSD_SOURCES := $(wildcard $(LOCAL_PATH)/pdnsd/*.c)
+PDNSD_SOURCES := $(wildcard $(LOCAL_PATH)/pdnsd/*.c)
-#LOCAL_MODULE := pdnsd
-#LOCAL_SRC_FILES := $(PDNSD_SOURCES:$(LOCAL_PATH)%=%)
+LOCAL_MODULE := pdnsd
+LOCAL_SRC_FILES := $(PDNSD_SOURCES:$(LOCAL_PATH)%=%)
+LOCAL_CFLAGS := -Wall -O2 -I$(LOCAL_PATH)/pdnsd
-#LOCAL_CFLAGS := -O3
-
-#LOCAL_STATIC_LIBRARIES :=
-
-#include $(BUILD_STATIC_LIBRARY)
+include $(BUILD_EXECUTABLE)
include $(CLEAR_VARS)
@@ -47,6 +44,17 @@ LOCAL_STATIC_LIBRARIES := libev libcrypto
LOCAL_LDLIBS := -llog
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE:= libexec
+
+LOCAL_SRC_FILES:= \
+ termExec.cpp
+
+LOCAL_LDLIBS := -ldl -llog
+
include $(BUILD_SHARED_LIBRARY)
subdirs := $(addprefix $(LOCAL_PATH)/openssl/,$(addsuffix /Android.mk, \
diff --git a/jni/pdnsd/conff.c b/jni/pdnsd/conff.c
index 83b2f686..32f4e885 100644
--- a/jni/pdnsd/conff.c
+++ b/jni/pdnsd/conff.c
@@ -166,35 +166,6 @@ int read_config_file(const char *nm, globparm_t *global, servparm_array *servers
*errstr=NULL;
goto close_file;
}
- else if (sb.st_uid!=init_uid) {
- /* Note by Paul Rombouts:
- Perhaps we should use getpwuid_r() instead of getpwuid(), which is not necessarily thread safe.
- As long as getpwuid() is only used by only one thread, it should be OK,
- but it is something to keep in mind.
- */
- struct passwd *pws;
- char owner[24],user[24];
- if((pws=getpwuid(sb.st_uid)))
- strncp(owner,pws->pw_name,sizeof(owner));
- else
- sprintf(owner,"%i",sb.st_uid);
- if((pws=getpwuid(init_uid)))
- strncp(user,pws->pw_name,sizeof(user));
- else
- sprintf(user,"%i",init_uid);
- if(asprintf(errstr,
- "Error: %s file %s is owned by '%s', but pdnsd was started as user '%s'.",
- conftype,nm,owner,user)<0)
- *errstr=NULL;
- goto close_file;
- }
- else if ((sb.st_mode&(S_IWGRP|S_IWOTH))) {
- if(asprintf(errstr,
- "Error: Bad %s file permissions: file %s must be only writeable by the user.",
- conftype,nm)<0)
- *errstr=NULL;
- goto close_file;
- }
}
retval=confparse(in,NULL,global,servers,includedepth,errstr);
diff --git a/jni/pdnsd/config.h b/jni/pdnsd/config.h
index 7d0a69ab..601afae7 100644
--- a/jni/pdnsd/config.h
+++ b/jni/pdnsd/config.h
@@ -40,7 +40,7 @@
* In normal operation, you will currently only need IPv4. */
#define ENABLE_IPV4 1
#define DEFAULT_IPV4 1
-/* #undef ENABLE_IPV6 */
+#undef ENABLE_IPV6
/* In all pdnsd versions before 1.0.6, DNS queries were always done over
* TCP. Now, you have the choice. You can control that behaviour using
@@ -55,7 +55,7 @@
* error or times out, the query is retried using UDP.
* UDP_TCP: UDP, then TCP. If the UDP reply is truncated (i.e. the tc flag is set),
* the query is retried using TCP. */
-#define M_PRESET UDP_ONLY
+#define M_PRESET TCP_ONLY
/* In addition to choosing the presets, you may also completely disable
* one of the protocols (TCP for preset UDP_ONLY and UDP for preset TCP_ONLY).
@@ -104,7 +104,7 @@
* default: native; others: gdbm */
#define CACHE_DBM DBM_NATIVE
-#define CACHEDIR "/var/cache/pdnsd"
+#define CACHEDIR "/data/data/com.github.shadowsocks"
#define TEMPDIR "/tmp";
@@ -214,10 +214,10 @@
#define HAVE_FCNTL_H 1
/* Define to 1 if you have the `getline' function. */
-#define HAVE_GETLINE 1
+//#define HAVE_GETLINE 1
/* Define to 1 if you have the `getpwnam_r' function. */
-#define HAVE_GETPWNAM_R 1
+//#define HAVE_GETPWNAM_R 1
/* Define to 1 if you have the `gettimeofday' function. */
#define HAVE_GETTIMEOFDAY 1
@@ -241,7 +241,7 @@
#define HAVE_MEMORY_H 1
/* Define to 1 if you have the `mempcpy' function. */
-#define HAVE_MEMPCPY 1
+//#define HAVE_MEMPCPY 1
/* Define to 1 if you have the `mkfifo' function. */
#define HAVE_MKFIFO 1
@@ -277,10 +277,10 @@
#define HAVE_STDLIB_H 1
/* Define to 1 if you have the `stpcpy' function. */
-#define HAVE_STPCPY 1
+//#define HAVE_STPCPY 1
/* Define to 1 if you have the `stpncpy' function. */
-#define HAVE_STPNCPY 1
+//#define HAVE_STPNCPY 1
/* Define to 1 if you have the `strdup' function. */
#define HAVE_STRDUP 1
diff --git a/jni/pdnsd/main.c b/jni/pdnsd/main.c
index 1190b77c..c9568765 100644
--- a/jni/pdnsd/main.c
+++ b/jni/pdnsd/main.c
@@ -67,7 +67,7 @@ FILE *dbg_file=NULL;
volatile int tcp_socket=-1;
volatile int udp_socket=-1;
sigset_t sigs_msk;
-char *conf_file=CONFDIR"/pdnsd.conf";
+char *conf_file="pdnsd.conf";
/* version and licensing information */
@@ -140,7 +140,7 @@ static const char help_message[] =
"\n"
"-c\t\t--or--\n"
"--config-file\tspecifies the file the configuration is read from.\n"
- "\t\tDefault is " CONFDIR "/pdnsd.conf\n"
+ "\t\tDefault is " "pdnsd.conf\n"
#ifdef ENABLE_IPV4
"-4\t\tswitches to IPv4 mode.\n"
"\t\t"
diff --git a/jni/shadowsocks/local.c b/jni/shadowsocks/local.c
index 52dfc14b..cc89c84f 100644
--- a/jni/shadowsocks/local.c
+++ b/jni/shadowsocks/local.c
@@ -18,7 +18,6 @@
#include
#include
#include
-#include
#include "local.h"
#include "encrypt.h"
@@ -38,440 +37,469 @@ static char *_server;
static char *_remote_port;
struct client_ctx {
- ev_io io;
- int fd;
+ ev_io io;
+ int fd;
};
int setnonblocking(int fd) {
- int flags;
- if (-1 ==(flags = fcntl(fd, F_GETFL, 0)))
- flags = 0;
- return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ int flags;
+ if (-1 ==(flags = fcntl(fd, F_GETFL, 0)))
+ flags = 0;
+ return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
int create_and_bind(const char *port) {
- struct addrinfo hints;
- struct addrinfo *result, *rp;
- int s, listen_sock;
+ struct addrinfo hints;
+ struct addrinfo *result, *rp;
+ int s, listen_sock;
- memset(&hints, 0, sizeof(struct addrinfo));
- hints.ai_family = AF_UNSPEC; /* Return IPv4 and IPv6 choices */
- hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
- hints.ai_flags = AI_PASSIVE; /* All interfaces */
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC; /* Return IPv4 and IPv6 choices */
+ hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
+ hints.ai_flags = AI_PASSIVE; /* All interfaces */
- s = getaddrinfo("0.0.0.0", port, &hints, &result);
- if (s != 0) {
- fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
- return -1;
- }
+ s = getaddrinfo("0.0.0.0", port, &hints, &result);
+ if (s != 0) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
+ return -1;
+ }
- for (rp = result; rp != NULL; rp = rp->ai_next) {
- listen_sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
- int opt = 1;
- setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
- if (listen_sock == -1)
- continue;
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ listen_sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ int opt = 1;
+ setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+ if (listen_sock == -1)
+ continue;
- s = bind(listen_sock, rp->ai_addr, rp->ai_addrlen);
- if (s == 0) {
- /* We managed to bind successfully! */
- break;
- } else {
- perror("bind");
- }
+ s = bind(listen_sock, rp->ai_addr, rp->ai_addrlen);
+ if (s == 0) {
+ /* We managed to bind successfully! */
+ break;
+ } else {
+ perror("bind");
+ }
- close(listen_sock);
- }
+ close(listen_sock);
+ }
- if (rp == NULL) {
- fprintf(stderr, "Could not bind\n");
- return -1;
- }
+ if (rp == NULL) {
+ fprintf(stderr, "Could not bind\n");
+ return -1;
+ }
- freeaddrinfo(result);
+ freeaddrinfo(result);
- return listen_sock;
+ return listen_sock;
}
static void server_recv_cb (EV_P_ ev_io *w, int revents) {
- struct server_ctx *server_recv_ctx = (struct server_ctx *)w;
- struct server *server = server_recv_ctx->server;
- struct remote *remote = server->remote;
- if (remote == NULL) {
- close_and_free_server(EV_A_ server);
- return;
- }
- while (1) {
- ssize_t r = recv(server->fd, remote->buf, BUF_SIZE, 0);
- if (r == 0) {
- // connection closed
- remote->buf_len = 0;
- close_and_free_server(EV_A_ server);
- if (remote != NULL) {
- ev_io_start(EV_A_ &remote->send_ctx->io);
- }
- return;
- } else if(r < 0) {
- perror("recv");
- if (errno == EAGAIN) {
- // no data
- // continue to wait for recv
- break;
- } else {
- close_and_free_server(EV_A_ server);
- close_and_free_remote(EV_A_ remote);
- return;
- }
- }
- encrypt(remote->buf, r);
- int w = send(remote->fd, remote->buf, r, MSG_NOSIGNAL);
- if(w == -1) {
- perror("send");
- if (errno == EAGAIN) {
- // no data, wait for send
- ev_io_stop(EV_A_ &server_recv_ctx->io);
- ev_io_start(EV_A_ &remote->send_ctx->io);
- break;
- } else {
- close_and_free_server(EV_A_ server);
- close_and_free_remote(EV_A_ remote);
- return;
- }
- } else if(w < r) {
- char *pt;
- for (pt = remote->buf; pt < pt + min(w, BUF_SIZE); pt++) {
- *pt = *(pt + w);
- }
- remote->buf_len = r - w;
- ev_io_stop(EV_A_ &server_recv_ctx->io);
- ev_io_start(EV_A_ &remote->send_ctx->io);
- break;
- }
- }
+ struct server_ctx *server_recv_ctx = (struct server_ctx *)w;
+ struct server *server = server_recv_ctx->server;
+ struct remote *remote = server->remote;
+ if (remote == NULL) {
+ close_and_free_server(EV_A_ server);
+ return;
+ }
+ while (1) {
+ ssize_t r = recv(server->fd, remote->buf, BUF_SIZE, 0);
+ if (r == 0) {
+ // connection closed
+ remote->buf_len = 0;
+ close_and_free_server(EV_A_ server);
+ if (remote != NULL) {
+ ev_io_start(EV_A_ &remote->send_ctx->io);
+ }
+ return;
+ } else if(r < 0) {
+ perror("recv");
+ if (errno == EAGAIN) {
+ // no data
+ // continue to wait for recv
+ break;
+ } else {
+ close_and_free_server(EV_A_ server);
+ close_and_free_remote(EV_A_ remote);
+ return;
+ }
+ }
+ encrypt(remote->buf, r);
+ int w = send(remote->fd, remote->buf, r, MSG_NOSIGNAL);
+ if(w == -1) {
+ perror("send");
+ if (errno == EAGAIN) {
+ // no data, wait for send
+ ev_io_stop(EV_A_ &server_recv_ctx->io);
+ ev_io_start(EV_A_ &remote->send_ctx->io);
+ break;
+ } else {
+ close_and_free_server(EV_A_ server);
+ close_and_free_remote(EV_A_ remote);
+ return;
+ }
+ } else if(w < r) {
+ char *pt;
+ for (pt = remote->buf; pt < pt + min(w, BUF_SIZE); pt++) {
+ *pt = *(pt + w);
+ }
+ remote->buf_len = r - w;
+ ev_io_stop(EV_A_ &server_recv_ctx->io);
+ ev_io_start(EV_A_ &remote->send_ctx->io);
+ break;
+ }
+ }
}
static void server_send_cb (EV_P_ ev_io *w, int revents) {
- struct server_ctx *server_send_ctx = (struct server_ctx *)w;
- struct server *server = server_send_ctx->server;
- struct remote *remote = server->remote;
- if (server->buf_len == 0) {
- // close and free
- close_and_free_server(EV_A_ server);
- close_and_free_remote(EV_A_ remote);
- return;
- } else {
- // has data to send
- ssize_t r = send(server->fd, server->buf,
- server->buf_len, 0);
- if (r < 0) {
- perror("send");
- if (errno != EAGAIN) {
- close_and_free_server(EV_A_ server);
- close_and_free_remote(EV_A_ remote);
- return;
- }
- return;
- }
- if (r < server->buf_len) {
- // printf("r=%d\n", r);
- // printf("server->buf_len=%d\n", server->buf_len);
- // partly sent, move memory, wait for the next time to send
- char *pt;
- for (pt = server->buf; pt < pt + min(r, BUF_SIZE); pt++) {
- *pt = *(pt + r);
- }
- server->buf_len -= r;
- return;
- } else {
- // all sent out, wait for reading
- ev_io_stop(EV_A_ &server_send_ctx->io);
- if (remote != NULL) {
- ev_io_start(EV_A_ &remote->recv_ctx->io);
- } else {
- close_and_free_server(EV_A_ server);
- close_and_free_remote(EV_A_ remote);
- return;
- }
- }
- }
+ struct server_ctx *server_send_ctx = (struct server_ctx *)w;
+ struct server *server = server_send_ctx->server;
+ struct remote *remote = server->remote;
+ if (server->buf_len == 0) {
+ // close and free
+ close_and_free_server(EV_A_ server);
+ close_and_free_remote(EV_A_ remote);
+ return;
+ } else {
+ // has data to send
+ ssize_t r = send(server->fd, server->buf,
+ server->buf_len, 0);
+ if (r < 0) {
+ perror("send");
+ if (errno != EAGAIN) {
+ close_and_free_server(EV_A_ server);
+ close_and_free_remote(EV_A_ remote);
+ return;
+ }
+ return;
+ }
+ if (r < server->buf_len) {
+ // printf("r=%d\n", r);
+ // printf("server->buf_len=%d\n", server->buf_len);
+ // partly sent, move memory, wait for the next time to send
+ char *pt;
+ for (pt = server->buf; pt < pt + min(r, BUF_SIZE); pt++) {
+ *pt = *(pt + r);
+ }
+ server->buf_len -= r;
+ return;
+ } else {
+ // all sent out, wait for reading
+ ev_io_stop(EV_A_ &server_send_ctx->io);
+ if (remote != NULL) {
+ ev_io_start(EV_A_ &remote->recv_ctx->io);
+ } else {
+ close_and_free_server(EV_A_ server);
+ close_and_free_remote(EV_A_ remote);
+ return;
+ }
+ }
+ }
}
static void remote_recv_cb (EV_P_ ev_io *w, int revents) {
- struct remote_ctx *remote_recv_ctx = (struct remote_ctx *)w;
- struct remote *remote = remote_recv_ctx->remote;
- struct server *server = remote->server;
- if (server == NULL) {
- close_and_free_remote(EV_A_ remote);
- return;
- }
- while (1) {
- ssize_t r = recv(remote->fd, server->buf, BUF_SIZE, 0);
- // printf("after recv: r=%d\n", r);
- if (r == 0) {
- // connection closed
- server->buf_len = 0;
- close_and_free_remote(EV_A_ remote);
- if (server != NULL) {
- ev_io_start(EV_A_ &server->send_ctx->io);
- }
- return;
- } else if(r < 0) {
- perror("recv");
- if (errno == EAGAIN) {
- // no data
- // continue to wait for recv
- break;
- } else {
- close_and_free_server(EV_A_ server);
- close_and_free_remote(EV_A_ remote);
- return;
- }
- }
- decrypt(server->buf, r);
- int w = send(server->fd, server->buf, r, MSG_NOSIGNAL);
- // printf("after send: w=%d\n", w);
- if(w == -1) {
- perror("send");
- if (errno == EAGAIN) {
- // no data, wait for send
- ev_io_stop(EV_A_ &remote_recv_ctx->io);
- ev_io_start(EV_A_ &server->send_ctx->io);
- break;
- } else {
- close_and_free_server(EV_A_ server);
- close_and_free_remote(EV_A_ remote);
- return;
- }
- } else if(w < r) {
- char *pt;
- for (pt = server->buf; pt < pt + min(w, BUF_SIZE); pt++) {
- *pt = *(pt + w);
- }
- server->buf_len = r - w;
- ev_io_stop(EV_A_ &remote_recv_ctx->io);
- ev_io_start(EV_A_ &server->send_ctx->io);
- break;
- }
- }
+ struct remote_ctx *remote_recv_ctx = (struct remote_ctx *)w;
+ struct remote *remote = remote_recv_ctx->remote;
+ struct server *server = remote->server;
+ if (server == NULL) {
+ close_and_free_remote(EV_A_ remote);
+ return;
+ }
+ while (1) {
+ ssize_t r = recv(remote->fd, server->buf, BUF_SIZE, 0);
+ // printf("after recv: r=%d\n", r);
+ if (r == 0) {
+ // connection closed
+ server->buf_len = 0;
+ close_and_free_remote(EV_A_ remote);
+ if (server != NULL) {
+ ev_io_start(EV_A_ &server->send_ctx->io);
+ }
+ return;
+ } else if(r < 0) {
+ perror("recv");
+ if (errno == EAGAIN) {
+ // no data
+ // continue to wait for recv
+ break;
+ } else {
+ close_and_free_server(EV_A_ server);
+ close_and_free_remote(EV_A_ remote);
+ return;
+ }
+ }
+ decrypt(server->buf, r);
+ int w = send(server->fd, server->buf, r, MSG_NOSIGNAL);
+ // printf("after send: w=%d\n", w);
+ if(w == -1) {
+ perror("send");
+ if (errno == EAGAIN) {
+ // no data, wait for send
+ ev_io_stop(EV_A_ &remote_recv_ctx->io);
+ ev_io_start(EV_A_ &server->send_ctx->io);
+ break;
+ } else {
+ close_and_free_server(EV_A_ server);
+ close_and_free_remote(EV_A_ remote);
+ return;
+ }
+ } else if(w < r) {
+ char *pt;
+ for (pt = server->buf; pt < pt + min(w, BUF_SIZE); pt++) {
+ *pt = *(pt + w);
+ }
+ server->buf_len = r - w;
+ ev_io_stop(EV_A_ &remote_recv_ctx->io);
+ ev_io_start(EV_A_ &server->send_ctx->io);
+ break;
+ }
+ }
}
static void remote_send_cb (EV_P_ ev_io *w, int revents) {
- struct remote_ctx *remote_send_ctx = (struct remote_ctx *)w;
- struct remote *remote = remote_send_ctx->remote;
- struct server *server = remote->server;
- if (!remote_send_ctx->connected) {
- socklen_t len;
- struct sockaddr_storage addr;
- len = sizeof addr;
- int r = getpeername(remote->fd, (struct sockaddr*)&addr, &len);
- if (r == 0) {
- remote_send_ctx->connected = 1;
- ev_io_stop(EV_A_ &remote_send_ctx->io);
- ev_io_start(EV_A_ &server->recv_ctx->io);
- ev_io_start(EV_A_ &remote->recv_ctx->io);
- } else {
- perror("getpeername");
- // not connected
- close_and_free_remote(EV_A_ remote);
- close_and_free_server(EV_A_ server);
- return;
- }
- } else {
- if (remote->buf_len == 0) {
- // close and free
- close_and_free_remote(EV_A_ remote);
- close_and_free_server(EV_A_ server);
- return;
- } else {
- // has data to send
- ssize_t r = send(remote->fd, remote->buf,
- remote->buf_len, 0);
- if (r < 0) {
- perror("send");
- if (errno != EAGAIN) {
- // close and free
- close_and_free_remote(EV_A_ remote);
- close_and_free_server(EV_A_ server);
- return;
- }
- return;
- }
- if (r < remote->buf_len) {
- // partly sent, move memory, wait for the next time to send
- char *pt;
- for (pt = remote->buf; pt < pt + min(r, BUF_SIZE); pt++) {
- *pt = *(pt + r);
- }
- remote->buf_len -= r;
- return;
- } else {
- // all sent out, wait for reading
- ev_io_stop(EV_A_ &remote_send_ctx->io);
- if (server != NULL) {
- ev_io_start(EV_A_ &server->recv_ctx->io);
- } else {
- close_and_free_remote(EV_A_ remote);
- close_and_free_server(EV_A_ server);
- return;
- }
- }
- }
+ struct remote_ctx *remote_send_ctx = (struct remote_ctx *)w;
+ struct remote *remote = remote_send_ctx->remote;
+ struct server *server = remote->server;
+ if (!remote_send_ctx->connected) {
+ socklen_t len;
+ struct sockaddr_storage addr;
+ len = sizeof addr;
+ int r = getpeername(remote->fd, (struct sockaddr*)&addr, &len);
+ if (r == 0) {
+ remote_send_ctx->connected = 1;
+ ev_io_stop(EV_A_ &remote_send_ctx->io);
+ ev_io_start(EV_A_ &server->recv_ctx->io);
+ ev_io_start(EV_A_ &remote->recv_ctx->io);
+ } else {
+ perror("getpeername");
+ // not connected
+ close_and_free_remote(EV_A_ remote);
+ close_and_free_server(EV_A_ server);
+ return;
+ }
+ } else {
+ if (remote->buf_len == 0) {
+ // close and free
+ close_and_free_remote(EV_A_ remote);
+ close_and_free_server(EV_A_ server);
+ return;
+ } else {
+ // has data to send
+ ssize_t r = send(remote->fd, remote->buf,
+ remote->buf_len, 0);
+ if (r < 0) {
+ perror("send");
+ if (errno != EAGAIN) {
+ // close and free
+ close_and_free_remote(EV_A_ remote);
+ close_and_free_server(EV_A_ server);
+ return;
+ }
+ return;
+ }
+ if (r < remote->buf_len) {
+ // partly sent, move memory, wait for the next time to send
+ char *pt;
+ for (pt = remote->buf; pt < pt + min(r, BUF_SIZE); pt++) {
+ *pt = *(pt + r);
+ }
+ remote->buf_len -= r;
+ return;
+ } else {
+ // all sent out, wait for reading
+ ev_io_stop(EV_A_ &remote_send_ctx->io);
+ if (server != NULL) {
+ ev_io_start(EV_A_ &server->recv_ctx->io);
+ } else {
+ close_and_free_remote(EV_A_ remote);
+ close_and_free_server(EV_A_ server);
+ return;
+ }
+ }
+ }
- }
+ }
}
struct remote* new_remote(int fd) {
- struct remote *remote;
- remote = malloc(sizeof(struct remote));
- remote->fd = fd;
- remote->recv_ctx = malloc(sizeof(struct remote_ctx));
- remote->send_ctx = malloc(sizeof(struct remote_ctx));
- ev_io_init(&remote->recv_ctx->io, remote_recv_cb, fd, EV_READ);
- ev_io_init(&remote->send_ctx->io, remote_send_cb, fd, EV_WRITE);
- remote->recv_ctx->remote = remote;
- remote->recv_ctx->connected = 0;
- remote->send_ctx->remote = remote;
- remote->send_ctx->connected = 0;
- fprintf(stderr, "new remote\n");
- return remote;
+ struct remote *remote;
+ remote = malloc(sizeof(struct remote));
+ remote->fd = fd;
+ remote->recv_ctx = malloc(sizeof(struct remote_ctx));
+ remote->send_ctx = malloc(sizeof(struct remote_ctx));
+ ev_io_init(&remote->recv_ctx->io, remote_recv_cb, fd, EV_READ);
+ ev_io_init(&remote->send_ctx->io, remote_send_cb, fd, EV_WRITE);
+ remote->recv_ctx->remote = remote;
+ remote->recv_ctx->connected = 0;
+ remote->send_ctx->remote = remote;
+ remote->send_ctx->connected = 0;
+ fprintf(stderr, "new remote\n");
+ return remote;
}
void free_remote(struct remote *remote) {
- if (remote != NULL) {
- if (remote->server != NULL) {
- remote->server->remote = NULL;
- }
- free(remote->recv_ctx);
- free(remote->send_ctx);
- free(remote);
- fprintf(stderr, "free remote\n");
- }
+ if (remote != NULL) {
+ if (remote->server != NULL) {
+ remote->server->remote = NULL;
+ }
+ free(remote->recv_ctx);
+ free(remote->send_ctx);
+ free(remote);
+ fprintf(stderr, "free remote\n");
+ }
}
void close_and_free_remote(EV_P_ struct remote *remote) {
- if (remote != NULL) {
- ev_io_stop(EV_A_ &remote->send_ctx->io);
- ev_io_stop(EV_A_ &remote->recv_ctx->io);
- close(remote->fd);
- free_remote(remote);
- }
+ if (remote != NULL) {
+ ev_io_stop(EV_A_ &remote->send_ctx->io);
+ ev_io_stop(EV_A_ &remote->recv_ctx->io);
+ close(remote->fd);
+ free_remote(remote);
+ }
}
struct server* new_server(int fd) {
- struct server *server;
- server = malloc(sizeof(struct server));
- server->fd = fd;
- server->recv_ctx = malloc(sizeof(struct server_ctx));
- server->send_ctx = malloc(sizeof(struct server_ctx));
- ev_io_init(&server->recv_ctx->io, server_recv_cb, fd, EV_READ);
- ev_io_init(&server->send_ctx->io, server_send_cb, fd, EV_WRITE);
- server->recv_ctx->server = server;
- server->recv_ctx->connected = 0;
- server->send_ctx->server = server;
- server->send_ctx->connected = 0;
- fprintf(stderr, "new server\n");
- return server;
+ struct server *server;
+ server = malloc(sizeof(struct server));
+ server->fd = fd;
+ server->recv_ctx = malloc(sizeof(struct server_ctx));
+ server->send_ctx = malloc(sizeof(struct server_ctx));
+ ev_io_init(&server->recv_ctx->io, server_recv_cb, fd, EV_READ);
+ ev_io_init(&server->send_ctx->io, server_send_cb, fd, EV_WRITE);
+ server->recv_ctx->server = server;
+ server->recv_ctx->connected = 0;
+ server->send_ctx->server = server;
+ server->send_ctx->connected = 0;
+ fprintf(stderr, "new server\n");
+ return server;
}
void free_server(struct server *server) {
- if (server != NULL) {
- if (server->remote != NULL) {
- server->remote->server = NULL;
- }
- free(server->recv_ctx);
- free(server->send_ctx);
- free(server);
- fprintf(stderr, "free server\n");
- }
+ if (server != NULL) {
+ if (server->remote != NULL) {
+ server->remote->server = NULL;
+ }
+ free(server->recv_ctx);
+ free(server->send_ctx);
+ free(server);
+ fprintf(stderr, "free server\n");
+ }
}
void close_and_free_server(EV_P_ struct server *server) {
- if (server != NULL) {
- ev_io_stop(EV_A_ &server->send_ctx->io);
- ev_io_stop(EV_A_ &server->recv_ctx->io);
- close(server->fd);
- free_server(server);
- }
+ if (server != NULL) {
+ ev_io_stop(EV_A_ &server->send_ctx->io);
+ ev_io_stop(EV_A_ &server->recv_ctx->io);
+ close(server->fd);
+ free_server(server);
+ }
}
static void accept_cb (EV_P_ ev_io *w, int revents)
{
- struct listen_ctx *listener = (struct listen_ctx *)w;
- int serverfd;
- while (1) {
- serverfd = accept(listener->fd, NULL, NULL);
- if (serverfd == -1) {
- perror("accept");
- break;
- }
- setnonblocking(serverfd);
- struct server *server = new_server(serverfd);
- struct addrinfo hints, *res;
- int sockfd;
- memset(&hints, 0, sizeof hints);
- hints.ai_family = AF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
- getaddrinfo(_server, _remote_port, &hints, &res);
- sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
- if (sockfd < 0) {
- perror("socket");
- close(sockfd);
- free_server(server);
- continue;
- }
- setnonblocking(sockfd);
- struct remote *remote = new_remote(sockfd);
- server->remote = remote;
- remote->server = server;
- connect(sockfd, res->ai_addr, res->ai_addrlen);
- freeaddrinfo(res);
- // listen to remote connected event
- ev_io_start(EV_A_ &remote->send_ctx->io);
- break;
- }
+ struct listen_ctx *listener = (struct listen_ctx *)w;
+ int serverfd;
+ while (1) {
+ serverfd = accept(listener->fd, NULL, NULL);
+ if (serverfd == -1) {
+ perror("accept");
+ break;
+ }
+ setnonblocking(serverfd);
+ struct server *server = new_server(serverfd);
+ struct addrinfo hints, *res;
+ int sockfd;
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ getaddrinfo(_server, _remote_port, &hints, &res);
+ sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (sockfd < 0) {
+ perror("socket");
+ close(sockfd);
+ free_server(server);
+ continue;
+ }
+ setnonblocking(sockfd);
+ struct remote *remote = new_remote(sockfd);
+ server->remote = remote;
+ remote->server = server;
+ connect(sockfd, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ // listen to remote connected event
+ ev_io_start(EV_A_ &remote->send_ctx->io);
+ break;
+ }
}
-int start_local (const char *server, const char *remote_port,
- const char *port, const char *key)
+int main (int argc, char **argv)
{
+ if (argc < 5)
+ return -1;
+
+ const char *server = argv[1];
+ const char *remote_port = argv[2];
+ const char *port = argv[3];
+ const char *key = argv[4];
+
+ /* Our process ID and Session ID */
+ pid_t pid, sid;
+
+ /* Fork off the parent process */
+ pid = fork();
+ if (pid < 0) {
+ exit(EXIT_FAILURE);
+ }
+ /* If we got a good PID, then
+ we can exit the parent process. */
+ if (pid > 0) {
+ FILE *file = fopen("/data/data/com.github.shadowsocks/shadowsocks.pid", "w");
+ fprintf(file, "%d", pid);
+ fclose(file);
+ exit(EXIT_SUCCESS);
+ }
+
+ /* Change the file mode mask */
+ umask(0);
+
+ /* Open any logs here */
+
+ /* Create a new SID for the child process */
+ sid = setsid();
+ if (sid < 0) {
+ /* Log the failure */
+ exit(EXIT_FAILURE);
+ }
+
+
+ /* Change the current working directory */
+ if ((chdir("/")) < 0) {
+ /* Log the failure */
+ exit(EXIT_FAILURE);
+ }
+
+ /* Close out the standard file descriptors */
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+
_server = strdup(server);
_remote_port = strdup(remote_port);
- fprintf(stderr, "calculating ciphers\n");
- get_table(key);
+ fprintf(stderr, "calculating ciphers\n");
+ get_table(key);
- int listenfd;
- listenfd = create_and_bind(port);
- if (listenfd < 0) {
- LOGE("bind() error..");
- return 1;
- }
- if (listen(listenfd, SOMAXCONN) == -1) {
- LOGE("listen() error.");
- return 1;
- }
- LOGD("server listening at port %s\n", port);
-
- setnonblocking(listenfd);
- struct listen_ctx listen_ctx;
- listen_ctx.fd = listenfd;
- struct ev_loop *loop = EV_DEFAULT;
- ev_io_init (&listen_ctx.io, accept_cb, listenfd, EV_READ);
- ev_io_start (loop, &listen_ctx.io);
- ev_run (loop, 0);
- return 0;
-}
-
-jint Java_com_github_shadowsocks_Local_main(JNIEnv *env, jobject thiz, jstring server_str,
- jstring remote_port_str, jstring port_str, jstring key_str) {
-
- const char *server = (*env)->GetStringUTFChars(env, server_str, 0);
- const char *remote_port = (*env)->GetStringUTFChars(env, remote_port_str, 0);
- const char *port = (*env)->GetStringUTFChars(env, port_str, 0);
- const char *key = (*env)->GetStringUTFChars(env, key_str, 0);
-
- int ret = start_local(server, remote_port, port, key);
-
- (*env)->ReleaseStringUTFChars(env, server_str, server);
- (*env)->ReleaseStringUTFChars(env, remote_port_str, remote_port);
- (*env)->ReleaseStringUTFChars(env, port_str, port);
- (*env)->ReleaseStringUTFChars(env, key_str, key);
-
- return ret;
+ int listenfd;
+ listenfd = create_and_bind(port);
+ if (listenfd < 0) {
+ LOGE("bind() error..");
+ return 1;
+ }
+ if (listen(listenfd, SOMAXCONN) == -1) {
+ LOGE("listen() error.");
+ return 1;
+ }
+ LOGD("server listening at port %s\n", port);
+ setnonblocking(listenfd);
+ struct listen_ctx listen_ctx;
+ listen_ctx.fd = listenfd;
+ struct ev_loop *loop = EV_DEFAULT;
+ ev_io_init (&listen_ctx.io, accept_cb, listenfd, EV_READ);
+ ev_io_start (loop, &listen_ctx.io);
+ ev_run (loop, 0);
+ return 0;
}
diff --git a/jni/termExec.cpp b/jni/termExec.cpp
new file mode 100644
index 00000000..e2c83d2b
--- /dev/null
+++ b/jni/termExec.cpp
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Exec"
+
+#include "jni.h"
+#include
+
+#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 LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0)
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static jclass class_fileDescriptor;
+static jfieldID field_fileDescriptor_descriptor;
+static jmethodID method_fileDescriptor_init;
+
+typedef unsigned short char16_t;
+
+class String8 {
+public:
+ String8() {
+ mString = 0;
+ }
+
+ ~String8() {
+ if (mString) {
+ free(mString);
+ }
+ }
+
+ void set(const char16_t* o, size_t numChars) {
+ if (mString) {
+ free(mString);
+ }
+ mString = (char*) malloc(numChars + 1);
+ if (!mString) {
+ return;
+ }
+ for (size_t i = 0; i < numChars; i++) {
+ mString[i] = (char) o[i];
+ }
+ mString[numChars] = '\0';
+ }
+
+ const char* string() {
+ return mString;
+ }
+private:
+ char* mString;
+};
+
+static int throwOutOfMemoryError(JNIEnv *env, const char *message)
+{
+ jclass exClass;
+ const char *className = "java/lang/OutOfMemoryError";
+
+ exClass = env->FindClass(className);
+ return env->ThrowNew(exClass, message);
+}
+
+static int create_subprocess(const char *cmd,
+ char *const argv[], char *const envp[], int* pProcessId)
+{
+ char filename[] = "/data/data/com.github.shadowsocks/defout";
+ char script[] = "/data/data/com.github.shadowsocks/script";
+ pid_t pid;
+
+ if (chmod(script, 0755) < 0) {
+ LOGE("error to chmod\n");
+ exit(-1);
+ }
+
+ int defout = open(filename, O_RDWR | O_CREAT | O_TRUNC, 0600);
+
+ if(defout < 0) {
+ LOGE("open file error\n");
+ exit(-1);
+ }
+
+ pid = fork();
+
+ if(pid < 0) {
+ LOGE("- fork failed: %s -\n", strerror(errno));
+ return -1;
+ }
+
+ if(pid == 0){
+
+ //setsid();
+
+ dup2(defout, 1);
+ dup2(defout, 2);
+
+ if (envp) {
+ for (; *envp; ++envp) {
+ putenv(*envp);
+ }
+ }
+
+ execv(cmd, argv);
+ exit(-1);
+ } else {
+ *pProcessId = (int) pid;
+ return defout;
+ }
+}
+
+
+static jobject android_os_Exec_createSubProcess(JNIEnv *env, jobject clazz,
+ jstring cmd, jobjectArray args, jobjectArray envVars,
+ jintArray processIdArray)
+{
+ const jchar* str = cmd ? env->GetStringCritical(cmd, 0) : 0;
+ String8 cmd_8;
+ if (str) {
+ cmd_8.set(str, env->GetStringLength(cmd));
+ env->ReleaseStringCritical(cmd, str);
+ }
+
+ jsize size = args ? env->GetArrayLength(args) : 0;
+ char **argv = NULL;
+ String8 tmp_8;
+ if (size > 0) {
+ argv = (char **)malloc((size+1)*sizeof(char *));
+ if (!argv) {
+ throwOutOfMemoryError(env, "Couldn't allocate argv array");
+ return NULL;
+ }
+ for (int i = 0; i < size; ++i) {
+ jstring arg = reinterpret_cast(env->GetObjectArrayElement(args, i));
+ str = env->GetStringCritical(arg, 0);
+ if (!str) {
+ throwOutOfMemoryError(env, "Couldn't get argument from array");
+ return NULL;
+ }
+ tmp_8.set(str, env->GetStringLength(arg));
+ env->ReleaseStringCritical(arg, str);
+ argv[i] = strdup(tmp_8.string());
+ }
+ argv[size] = NULL;
+ }
+
+ size = envVars ? env->GetArrayLength(envVars) : 0;
+ char **envp = NULL;
+ if (size > 0) {
+ envp = (char **)malloc((size+1)*sizeof(char *));
+ if (!envp) {
+ throwOutOfMemoryError(env, "Couldn't allocate envp array");
+ return NULL;
+ }
+ for (int i = 0; i < size; ++i) {
+ jstring var = reinterpret_cast(env->GetObjectArrayElement(envVars, i));
+ str = env->GetStringCritical(var, 0);
+ if (!str) {
+ throwOutOfMemoryError(env, "Couldn't get env var from array");
+ return NULL;
+ }
+ tmp_8.set(str, env->GetStringLength(var));
+ env->ReleaseStringCritical(var, str);
+ envp[i] = strdup(tmp_8.string());
+ }
+ envp[size] = NULL;
+ }
+
+ int procId;
+ int ptm = create_subprocess(cmd_8.string(), argv, envp, &procId);
+
+ if (argv) {
+ for (char **tmp = argv; *tmp; ++tmp) {
+ free(*tmp);
+ }
+ free(argv);
+ }
+ if (envp) {
+ for (char **tmp = envp; *tmp; ++tmp) {
+ free(*tmp);
+ }
+ free(envp);
+ }
+
+ if (processIdArray) {
+ int procIdLen = env->GetArrayLength(processIdArray);
+ if (procIdLen > 0) {
+ jboolean isCopy;
+
+ int* pProcId = (int*) env->GetPrimitiveArrayCritical(processIdArray, &isCopy);
+ if (pProcId) {
+ *pProcId = procId;
+ env->ReleasePrimitiveArrayCritical(processIdArray, pProcId, 0);
+ }
+ }
+ }
+
+ jobject result = env->NewObject(class_fileDescriptor, method_fileDescriptor_init);
+
+ if (!result) {
+ LOGE("Couldn't create a FileDescriptor.");
+ }
+ else {
+ env->SetIntField(result, field_fileDescriptor_descriptor, ptm);
+ }
+
+ return result;
+}
+
+
+static int android_os_Exec_waitFor(JNIEnv *env, jobject clazz,
+ jint procId) {
+ int status;
+ waitpid(procId, &status, 0);
+ int result = 0;
+ if (WIFEXITED(status)) {
+ result = WEXITSTATUS(status);
+ }
+ return result;
+}
+
+static void android_os_Exec_close(JNIEnv *env, jobject clazz, jobject fileDescriptor)
+{
+ int fd;
+
+ fd = env->GetIntField(fileDescriptor, field_fileDescriptor_descriptor);
+
+ if (env->ExceptionOccurred() != NULL) {
+ return;
+ }
+
+ close(fd);
+}
+
+static void android_os_Exec_hangupProcessGroup(JNIEnv *env, jobject clazz,
+ jint procId) {
+ kill(-procId, SIGHUP);
+}
+
+
+static int register_FileDescriptor(JNIEnv *env)
+{
+ jclass localRef_class_fileDescriptor = env->FindClass("java/io/FileDescriptor");
+
+ if (localRef_class_fileDescriptor == NULL) {
+ LOGE("Can't find class java/io/FileDescriptor");
+ return -1;
+ }
+
+ class_fileDescriptor = (jclass) env->NewGlobalRef(localRef_class_fileDescriptor);
+
+ env->DeleteLocalRef(localRef_class_fileDescriptor);
+
+ if (class_fileDescriptor == NULL) {
+ LOGE("Can't get global ref to class java/io/FileDescriptor");
+ return -1;
+ }
+
+ field_fileDescriptor_descriptor = env->GetFieldID(class_fileDescriptor, "descriptor", "I");
+
+ if (field_fileDescriptor_descriptor == NULL) {
+ LOGE("Can't find FileDescriptor.descriptor");
+ return -1;
+ }
+
+ method_fileDescriptor_init = env->GetMethodID(class_fileDescriptor, "", "()V");
+ if (method_fileDescriptor_init == NULL) {
+ LOGE("Can't find FileDescriptor.init");
+ return -1;
+ }
+ return 0;
+}
+
+
+static const char *classPathName = "com/github/shadowsocks/Exec";
+
+static JNINativeMethod method_table[] = {
+ { "createSubprocess", "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;[I)Ljava/io/FileDescriptor;",
+ (void*) android_os_Exec_createSubProcess },
+ { "waitFor", "(I)I",
+ (void*) android_os_Exec_waitFor},
+ { "close", "(Ljava/io/FileDescriptor;)V",
+ (void*) android_os_Exec_close},
+ { "hangupProcessGroup", "(I)V",
+ (void*) android_os_Exec_hangupProcessGroup}
+};
+
+/*
+ * Register several native methods for one class.
+ */
+static int registerNativeMethods(JNIEnv* env, const char* className,
+ JNINativeMethod* gMethods, int numMethods)
+{
+ jclass clazz;
+
+ clazz = env->FindClass(className);
+ if (clazz == NULL) {
+ LOGE("Native registration unable to find class '%s'", className);
+ return JNI_FALSE;
+ }
+ if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
+ LOGE("RegisterNatives failed for '%s'", className);
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+/*
+ * Register native methods for all classes we know about.
+ *
+ * returns JNI_TRUE on success.
+ */
+static int registerNatives(JNIEnv* env)
+{
+ if (!registerNativeMethods(env, classPathName, method_table,
+ sizeof(method_table) / sizeof(method_table[0]))) {
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+
+// ----------------------------------------------------------------------------
+
+/*
+ * This is called by the VM when the shared library is first loaded.
+ */
+
+typedef union {
+ JNIEnv* env;
+ void* venv;
+} UnionJNIEnvToVoid;
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ UnionJNIEnvToVoid uenv;
+ uenv.venv = NULL;
+ jint result = -1;
+ JNIEnv* env = NULL;
+
+ LOGI("JNI_OnLoad");
+
+ if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
+ LOGE("ERROR: GetEnv failed");
+ goto bail;
+ }
+ env = uenv.env;
+
+ if ((result = register_FileDescriptor(env)) < 0) {
+ LOGE("ERROR: registerFileDescriptor failed");
+ goto bail;
+ }
+
+ if (registerNatives(env) != JNI_TRUE) {
+ LOGE("ERROR: registerNatives failed");
+ goto bail;
+ }
+
+ result = JNI_VERSION_1_4;
+
+bail:
+ return result;
+}
diff --git a/pom.xml b/pom.xml
new file mode 100755
index 00000000..a412dca7
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,222 @@
+
+
+ 4.0.0
+ com.github.shadowsocks
+ shadowsocks
+ 1.0
+ apk
+ Shadowsocks
+
+
+
+ madeye-maven-repository
+ http://madeye-maven-repository.googlecode.com/git
+
+ true
+
+
+ false
+
+
+
+
+
+
+ UTF-8
+
+
+
+
+
+ com.google.android
+ android
+ 4.1.1.4
+ provided
+
+
+
+ com.google.android.analytics
+ analytics
+ V2
+
+
+
+ commons-codec
+ commons-codec
+ 1.5
+
+
+
+
+ ${project.artifactId}
+ src
+
+
+
+ maven-jarsigner-plugin
+ 1.2
+
+
+ com.jayway.maven.plugins.android.generation2
+ android-maven-plugin
+ 3.4.1
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.12.4
+
+ true
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 1.5
+
+
+ org.apache.maven.plugins
+ maven-jarsigner-plugin
+
+
+ signing
+
+ sign
+
+ package
+ true
+
+
+
+ ${project.build.directory}/${project.artifactId}.apk
+
+ ${sign.keystore}
+ ${sign.alias}
+ ${sign.storepass}
+ ${sign.keypass}
+ true
+
+ -sigalgMD5withRSA
+ -digestalgSHA1
+
+
+
+
+
+
+ com.jayway.maven.plugins.android.generation2
+ android-maven-plugin
+
+
+ alignApk
+ install
+
+ zipalign
+
+
+
+ true
+
+ true
+
+ false
+
+
+ true
+ false
+ ${project.build.directory}/${project.artifactId}.apk
+ ${project.build.directory}/${project.artifactId}-${project.version}.apk
+
+
+
+ 16
+
+
+
+
+
+
+
+ debug-sign
+
+
+ ${basedir}/local.properties
+
+
+
+ ${basedir}/travis.keystore
+ travis
+ travis
+ travis
+
+
+
+ release-sign
+
+
+ ${basedir}/local.properties
+
+
+
+
+
+ org.codehaus.mojo
+ properties-maven-plugin
+ 1.0-alpha-2
+
+
+ initialize
+
+ read-project-properties
+
+
+
+ ${basedir}/local.properties
+
+
+
+
+
+
+
+
+
+ travis
+
+
+ TRAVIS
+ true
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+ deploy-to-sae
+ install
+
+ exec
+
+
+
+
+ ${basedir}/deploy
+
+ ${project.build.directory}
+ ${project.artifactId}-${project.version}.apk
+
+
+
+
+
+
+
+
diff --git a/res/drawable-hdpi-v11/ic_stat_gaeproxy.png b/res/drawable-hdpi-v11/ic_stat_gaeproxy.png
new file mode 100755
index 00000000..87d80079
Binary files /dev/null and b/res/drawable-hdpi-v11/ic_stat_gaeproxy.png differ
diff --git a/res/drawable-hdpi-v9/ic_stat_gaeproxy.png b/res/drawable-hdpi-v9/ic_stat_gaeproxy.png
new file mode 100755
index 00000000..ff7dd684
Binary files /dev/null and b/res/drawable-hdpi-v9/ic_stat_gaeproxy.png differ
diff --git a/res/drawable-hdpi/ic_stat_gaeproxy.png b/res/drawable-hdpi/ic_stat_gaeproxy.png
new file mode 100755
index 00000000..7915f888
Binary files /dev/null and b/res/drawable-hdpi/ic_stat_gaeproxy.png differ
diff --git a/res/drawable-hdpi/icon.png b/res/drawable-hdpi/icon.png
new file mode 100644
index 00000000..80cdaa7d
Binary files /dev/null and b/res/drawable-hdpi/icon.png differ
diff --git a/res/drawable-hdpi/ing.png b/res/drawable-hdpi/ing.png
new file mode 100644
index 00000000..42ff8f38
Binary files /dev/null and b/res/drawable-hdpi/ing.png differ
diff --git a/res/drawable-hdpi/off.png b/res/drawable-hdpi/off.png
new file mode 100644
index 00000000..14670b7f
Binary files /dev/null and b/res/drawable-hdpi/off.png differ
diff --git a/res/drawable-hdpi/on.png b/res/drawable-hdpi/on.png
new file mode 100644
index 00000000..53e623fb
Binary files /dev/null and b/res/drawable-hdpi/on.png differ
diff --git a/res/drawable-ldpi-v11/ic_stat_gaeproxy.png b/res/drawable-ldpi-v11/ic_stat_gaeproxy.png
new file mode 100755
index 00000000..0682f157
Binary files /dev/null and b/res/drawable-ldpi-v11/ic_stat_gaeproxy.png differ
diff --git a/res/drawable-ldpi-v9/ic_stat_gaeproxy.png b/res/drawable-ldpi-v9/ic_stat_gaeproxy.png
new file mode 100755
index 00000000..acb9f873
Binary files /dev/null and b/res/drawable-ldpi-v9/ic_stat_gaeproxy.png differ
diff --git a/res/drawable-ldpi/ic_stat_gaeproxy.png b/res/drawable-ldpi/ic_stat_gaeproxy.png
new file mode 100755
index 00000000..5d24f04f
Binary files /dev/null and b/res/drawable-ldpi/ic_stat_gaeproxy.png differ
diff --git a/res/drawable-ldpi/icon.png b/res/drawable-ldpi/icon.png
new file mode 100644
index 00000000..566cd440
Binary files /dev/null and b/res/drawable-ldpi/icon.png differ
diff --git a/res/drawable-ldpi/ing.png b/res/drawable-ldpi/ing.png
new file mode 100644
index 00000000..7305012b
Binary files /dev/null and b/res/drawable-ldpi/ing.png differ
diff --git a/res/drawable-ldpi/off.png b/res/drawable-ldpi/off.png
new file mode 100644
index 00000000..f6c5098f
Binary files /dev/null and b/res/drawable-ldpi/off.png differ
diff --git a/res/drawable-ldpi/on.png b/res/drawable-ldpi/on.png
new file mode 100644
index 00000000..65ad4c5b
Binary files /dev/null and b/res/drawable-ldpi/on.png differ
diff --git a/res/drawable-mdpi-v11/ic_stat_gaeproxy.png b/res/drawable-mdpi-v11/ic_stat_gaeproxy.png
new file mode 100755
index 00000000..1e36c79b
Binary files /dev/null and b/res/drawable-mdpi-v11/ic_stat_gaeproxy.png differ
diff --git a/res/drawable-mdpi-v9/ic_stat_gaeproxy.png b/res/drawable-mdpi-v9/ic_stat_gaeproxy.png
new file mode 100755
index 00000000..46fd0ad8
Binary files /dev/null and b/res/drawable-mdpi-v9/ic_stat_gaeproxy.png differ
diff --git a/res/drawable-mdpi/ic_stat_gaeproxy.png b/res/drawable-mdpi/ic_stat_gaeproxy.png
new file mode 100755
index 00000000..aaba1b9c
Binary files /dev/null and b/res/drawable-mdpi/ic_stat_gaeproxy.png differ
diff --git a/res/drawable-mdpi/icon.png b/res/drawable-mdpi/icon.png
new file mode 100644
index 00000000..e329c02b
Binary files /dev/null and b/res/drawable-mdpi/icon.png differ
diff --git a/res/drawable-mdpi/ing.png b/res/drawable-mdpi/ing.png
new file mode 100644
index 00000000..e18492d2
Binary files /dev/null and b/res/drawable-mdpi/ing.png differ
diff --git a/res/drawable-mdpi/off.png b/res/drawable-mdpi/off.png
new file mode 100644
index 00000000..f5053f88
Binary files /dev/null and b/res/drawable-mdpi/off.png differ
diff --git a/res/drawable-mdpi/on.png b/res/drawable-mdpi/on.png
new file mode 100644
index 00000000..83326e95
Binary files /dev/null and b/res/drawable-mdpi/on.png differ
diff --git a/res/drawable-xhdpi-v11/ic_stat_gaeproxy.png b/res/drawable-xhdpi-v11/ic_stat_gaeproxy.png
new file mode 100755
index 00000000..e319e267
Binary files /dev/null and b/res/drawable-xhdpi-v11/ic_stat_gaeproxy.png differ
diff --git a/res/drawable-xhdpi-v9/ic_stat_gaeproxy.png b/res/drawable-xhdpi-v9/ic_stat_gaeproxy.png
new file mode 100755
index 00000000..de3e66a2
Binary files /dev/null and b/res/drawable-xhdpi-v9/ic_stat_gaeproxy.png differ
diff --git a/res/drawable-xhdpi/ic_stat_gaeproxy.png b/res/drawable-xhdpi/ic_stat_gaeproxy.png
new file mode 100755
index 00000000..e68bccfd
Binary files /dev/null and b/res/drawable-xhdpi/ic_stat_gaeproxy.png differ
diff --git a/res/layout/gaeproxy_appwidget.xml b/res/layout/gaeproxy_appwidget.xml
new file mode 100644
index 00000000..75d19fea
--- /dev/null
+++ b/res/layout/gaeproxy_appwidget.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/res/layout/layout_apps.xml b/res/layout/layout_apps.xml
new file mode 100644
index 00000000..e9ce741c
--- /dev/null
+++ b/res/layout/layout_apps.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/layout_apps_item.xml b/res/layout/layout_apps_item.xml
new file mode 100644
index 00000000..ad821a9e
--- /dev/null
+++ b/res/layout/layout_apps_item.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/layout_progress_bar.xml b/res/layout/layout_progress_bar.xml
new file mode 100644
index 00000000..195e7f62
--- /dev/null
+++ b/res/layout/layout_progress_bar.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
diff --git a/res/layout/main.xml b/res/layout/main.xml
new file mode 100755
index 00000000..3c196e63
--- /dev/null
+++ b/res/layout/main.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/overlay.xml b/res/layout/overlay.xml
new file mode 100644
index 00000000..49c1b45f
--- /dev/null
+++ b/res/layout/overlay.xml
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/res/values-zh/strings.xml b/res/values-zh/strings.xml
new file mode 100755
index 00000000..298afbad
--- /dev/null
+++ b/res/values-zh/strings.xml
@@ -0,0 +1,537 @@
+
+
+
+ 正通过Shadowsocks连接到互联网
+ Shadowsocks
+ Shadowsocks启用成功
+ Shadowsocks启用失败
+ Shadowsocks服务已停止
+ 恢复连接中
+ 恢复连接成功
+ 服务已关闭
+ 服务已启动
+ Shadowsocks连接已关闭
+ 请检查你的网络状况和账户信息
+ 恢复连接失败
+ 我知道了
+ 代理地址
+ 本地端口
+ 密钥
+ 自动连接
+ 自动恢复连接
+ 全局代理
+ 关闭服务
+ 启动服务
+ 反馈 @ofmax
+ Shadowsocks设置
+ 功能设置
+ 后台服务
+ 代理开关
+ 开启 / 关闭后台服务
+ Shadowsocks服务地址
+ 本地代理服务器监听端口,任意大于1024的数字
+ 加密密钥
+ 端口必需是大于1024的数字
+ 启动时自动打开代理服务
+ 配置全局代理,需要 ROOT 权限以及 IPTABLES 的支持
+ 端口不能为空
+ 代理地址不能为空
+ 模块管理
+ 安装/卸载
+ 安装/卸载依赖模块,请确认SD卡已插入,并连接到互联网
+ 下载中...
+ 下载中...(100%处请耐心等待)
+ 请先安装依赖模块,滑动到底部,选择安装
+ 请确认SD卡已插入
+ 重置
+ 解压缩中...
+ 分应用代理
+ 为应用单独设置代理,需要 ROOT 权限以及 IPTABLES 的支持
+ 白名单模式
+ 选择的应用将不通过代理
+ 选择使用Shadowsocks的应用:
+ 检测到一次非正常退出,状态已重置
+ Shadowsocks是一款开源软件,依照GPLv3协议发布。\n\n如果您有任何问题,请前往项目网站进行反馈。
+ \n\n(gaeproxy.googlecode.com)
+ 关于
+ 代理类型
+ 正在连接...
+ 正在初始化...
+ 正在重置...
+ 请修改你的代理地址,以 "https://" 开头
+ HTTPS代理
+ 重定向HTTPS流量,若遇到反复尝试无法连接的情况,请关闭此选项
+
+
+ 通知设置
+ 铃声
+ 选择通知的铃声
+ 震动
+ 在连接状态改变时震动
+ 正在加载应用列表...
+ 电子市场代理
+ 帮助大陆用户从电子市场中获得程序更新(需要重启)
+ 国内路由
+ 访问国内站点时忽略本地代理(实验性)
+ Shadowsocks正在启动
+ Shadowsocks正在重新启动
+ 内置浏览器
+ 启动内置代理浏览器, 只针对没有ROOT权限或IPTABLES支持的用户
+ 您正在使用默认的代理地址,此地址每天只有10G的带宽配额,也就说您将随时无法正常使用本应用\n\n因此,请尽快部署自己的服务器端,具体步骤和说明见:http://code.google.com/p/goagent/
+ 警告
+
+ 系统代理
+ 设置系统代理,无需ROOT权限,仅工作在Android 4.0及以上系统,并仅对当前Wifi连接生效
+
+
+
+ Zirco Browser
+ Zirco Browser - %s
+ 使用Zirco Browser让你更好的使用互联网
+ 关于
+ 版权:
+ http://code.google.com/p/zirco-browser/
+ GPL v3
+ 添加书签
+ 书签
+ 历史
+ 下载
+ 配置
+ 打开
+ 在新页中打开
+ 下载
+ 下载开始
+ 下载结束
+ 下载错误: %s
+ 页 %s
+ 页 %1$s: %2$s
+ 不能打开网址
+ 本程序不支持这个网址: %s.
+ 书签
+ 排序方式
+ 增加书签
+ 没有书签
+ 打开新页
+ 编辑书签
+ 删除书签
+ 导入书签
+ 导出书签
+ 清空所有书签
+ 正在导入书签中...
+ 重在导出书签中...
+ 正在清空书签中...
+ 顺序
+ 倒序
+ 选择导入源
+ Android浏览器的书签
+ 选择导出目标
+ 编辑书签
+ 增加书签
+ 显示名称:
+ 地址Location:
+ 历史
+ 打开新页
+ 删除
+ Zirco Browser-下载
+ 没有下载
+ %s (取消)
+ %s (完成)
+ Zirco Browser - Ad 白名单
+ 白名单是空的.
+ 添加至白名单
+ 清空白名单
+ 通用设置
+ 主页设定
+ 定义你的默认主页
+ 是否全屏幕
+ 在全屏幕模式下显示 (需要重新启动程序).
+ 页切换时是否显示对话框
+ 如果选中,也切换时会有提示
+ 浏览器设定
+ 历史记录大小
+ 每天历史记录大小
+ 是否支持JavaScript
+ 如果不支持JavaScript,很多网站效果不能打开,建议打开.
+ 是否加载图像
+ 使用或者不使用图像
+ 隐私设定
+ 管理你的上网痕迹,确保隐私
+ 保存表单数据
+ 可保存数据,减少你下次输入时间
+ 保存密码
+ 可保存密码,让你下次访问更加方便
+ 允许cookie
+ 是否允许Cookie
+ 清空历史
+ 清空表单数据
+ 清空缓存
+ 清空cookies
+ 工具条显示时间
+ 显示工具条多长时间自动隐藏
+ 1 秒
+ 2 秒
+ 3 秒
+ 4 秒
+ 5 秒
+
+
+ - @string/PreferencesActivity.BarDuration1sec
+ - @string/PreferencesActivity.BarDuration2sec
+ - @string/PreferencesActivity.BarDuration3sec
+ - @string/PreferencesActivity.BarDuration4sec
+ - @string/PreferencesActivity.BarDuration5sec
+
+
+ - 1000
+ - 2000
+ - 3000
+ - 4000
+ - 5000
+
+
+ 工具条图标
+ 工具条图标位置
+ 右
+ 左
+ 两边都有
+
+
+ - @string/PreferencesActivity.BubbleRight
+ - @string/PreferencesActivity.BubbleLeft
+ - @string/PreferencesActivity.BubbleBoth
+
+
+ - right
+ - left
+ - both
+
+
+ Ad广告设定
+ 是否允许 Ad-blocker
+ Ad广告必须要求JavaScript能使用
+ 白名单
+ 管理 Ad广告白名单.
+ 确定
+ 取消
+ 请等待
+ 是
+ 否
+ 操作没有完成,是否继续?
+ 今天 (%s)
+ 昨天 (%s)
+ %1$s 以前 (%2$s)
+ JavaScript对话框
+ 清空历史记录
+ 正在清空历史记录...
+ 清空表单数据
+ 正在清空表单数据...
+ 清空缓存
+ 正在清空缓存...
+ 清空Cookies
+ 正在清空缓存...
+ SD卡不能使用
+ 找不到SD卡
+ SD卡不能使用
+ 关闭
+ yyyy-MM-dd\'T\'HH:mm:ss.SSS
+
+
+ 关于
+ 关于
+ 本程序信息.
+
+
+ 更多..
+ 默认缩放级别
+ 设定默认缩放
+ 关闭
+ 大
+ 非常大
+
+
+ - @string/PreferencesActivity.DefaultZoomClose
+ - @string/PreferencesActivity.DefaultZoomMedium
+ - @string/PreferencesActivity.DefaultZoomFar
+
+
+ - CLOSE
+ - MEDIUM
+ - FAR
+
+
+ 欢迎!
+ 使用最多的书签
+ 历史记录
+ 更新历史
+ 更新日志
+ 本应用程序更新日志
+ 主页
+ 主页
+ 开始页
+ 空白页
+ 自定义
+
+
+ - @string/PreferencesActivity.HomepageStart
+ - @string/PreferencesActivity.HomepageBlank
+ - @string/PreferencesActivity.HomepageCustom
+
+
+
+ http://www.google.com/m?hl=en&gl=us&client=ms-null&source=android-browser-key&q=%s
+ http://www.baidu.com/s?wd=%s
+ 搜索 URL
+ 自定义搜索URL
+ 搜索
+ 搜索
+ 谷歌
+ 百度
+ 自定义
+
+
+ - @string/PreferencesActivity.SearchUrlGoogle
+ - @string/PreferencesActivity.SearchUrlWikipedia
+ - @string/PreferencesActivity.SearchUrlCustom
+
+
+ 搜索
+ 搜
+ 开始页设定
+ 自定义开始页.
+ 是否显示搜索栏
+ 如果选中,首页显示搜索栏
+ 显示最常用书签
+ 如果选中,首页显示最常用书签
+ 是否显示历史
+ 如果选中,显示浏览历史
+ 书签个数
+ 首页中最常用的书签显示
+ 当前历史记录数
+ 在首页中显示的历史记录个数
+ User agent(UA)
+ 定义UA,网站根据UA显示给适合你浏览器的网页
+ User Agent
+ User agent
+ Android默认
+ 桌面级
+ 自定义
+
+
+ - @string/PreferencesActivity.UserAgentDefault
+ - @string/PreferencesActivity.UserAgentDesktop
+ - @string/PreferencesActivity.UserAgentCustom
+
+
+ Android浏览器的书签
+ SD卡
+ 导出完成
+ 你的所有书签已经保存在[%s]中
+ 导出失败
+ 错误信息: %s.
+ 复制链接地址
+ 显示图片
+ 复制图片地址
+ 下载图片
+ 发送邮件
+ 复制邮件地址
+ 地址已经复制到粘贴板
+ 下载开始.
+ 正在下载中.
+ 下载完成.
+ 下载已经取消.
+ 复制URL
+ 清空缓存
+ 如果选中,当程序退出的时候清空缓存.
+ 自定义首页
+ 自定义首页界面
+ 退出
+ 需要重启
+ 效果改变后需要重新启动程序,是否马上重启
+
+
+ 可以使用插件
+ 关闭
+ 需要的时候提示
+ 打开
+
+
+ - @string/PreferencesActivity.PluginsAlwaysOff
+ - @string/PreferencesActivity.PluginsOnDemand
+ - @string/PreferencesActivity.PluginsAlwaysOn
+
+
+ - OFF
+ - ON_DEMAND
+ - ON
+
+
+ 继续
+ 安全警告
+ 网站的安全证书存在问题
+ 网站证书没有可信任的发行者
+ 站点名和证书不匹配
+ 证书已经过期
+ 证书没有验证
+ Zirco Browser-通过网关访问的网站列表
+ 列表为空
+ 添加一个网站
+ 清空所有站点
+ 移动网站列表
+ 管理网站
+ 通过google移动平台访问网站,会减少流量和加快速度.
+ 增加
+ 清空
+ 删除
+
+
+ 声音键
+ 选择声音键的功能
+ 默认不改变
+ 放大
+ 选择不同页
+ 前进/后退
+
+
+ - @string/PreferencesActivity.VolumeKeysActionDefault
+ - @string/PreferencesActivity.VolumeKeysActionZoom
+ - @string/PreferencesActivity.VolumeKeysActionSwitchTabs
+ - @string/PreferencesActivity.VolumeKeysActionHistory
+
+
+ - DEFAULT
+ - ZOOM
+ - SWITCH_TABS
+ - HISTORY
+
+
+
+ Share page
+ Share via
+ Share link url
+ Share image url
+ Share email address
+
+
+ Hide title bars
+ If checked, windows title bars will not be
+ displayed (need application restart).
+ Use wide viewport
+ If checked, browser will use a viewport similar to
+ desktop browsers.
+ Load pages with overview
+ If checked, pages will load zoomed out to show an
+ overview of the page.
+ Switch tabs method
+ Define how you can switch between open tabs.
+ Buttons
+ Fling
+ Both
+
+
+ - @string/PreferencesActivity.SwitchTabButtons
+ - @string/PreferencesActivity.SwitchTabFling
+ - @string/PreferencesActivity.SwitchTabBoth
+
+
+ - buttons
+ - fling
+ - both
+
+
+
+ Today
+ Yesterday
+ Last 7 days
+ Last month
+ Older
+ 主页
+ 菜单
+ 分享
+ 选择文本
+ 移动网关
+ This application include code from the GreenDroid
+ project (https://github.com/cyrilmottier/GreenDroid/), licensed under
+ the Apache license, version 2.
+
+
+ Firefox bookmarks synchronization
+ Manage the settings of the bookmarks
+ synchronization with Firefox Sync.
+ Use Firefox Sync
+ If checked, you can retrieve your bookmarks from
+ Firefox Sync.
+ Server
+ Set the Firefox Sync server.
+ User name
+ Enter your Firefox Sync user name.
+ Password
+ Enter your Firefox Sync password.
+ Key
+ Enter your Firefox Sync key.
+ Firefox bookmarks
+ Synchronize
+ Clear
+ Places
+ Firefox bookmarks are not synchronized. Setup your
+ account informations and do a synchronization.
+ No bookmarks in this folder.
+ Setup
+ Synchronize
+ Synchronization
+ Connecting to server...
+ Getting data...
+ Reading data: %1$s / %2$s
+ Writting data...
+ Synchronization failed.
+ Synchronization failed: %s
+ Invalid authentication token. Check your settings.
+ Server
+ Server
+ Firefox Sync server
+ Custom server
+
+
+ - @string/WeaveServerPreferenceActivity.DefaultServer
+ - @string/WeaveServerPreferenceActivity.CustomServer
+
+
+ Choose file for upload
+ Export history and bookmarks
+ Export done
+ Your history and bookmarks have been saved to %s.
+ Export failed
+ Error message: %s.
+ Import failed
+ This operation can take some times. Do you wish to
+ proceed?
+ Tools
+ Manage bookmarks and history
+ Import, export, clear history and bookmarks.
+ Export bookmarks and history
+ Export bookmarks and history to the SD card.
+ Import bookmarks and history
+ Import bookmarks and history from the SD card.
+ Clear history and/or bookmarks
+ Use with caution, this operation cannot be undone.
+ Clearing history and/or bookmarks...
+ Clear history and/or bookmarks
+ Bookmarks
+ History
+ All
+ Exporting history and bookmarks...
+ Importing history and bookmarks...
+ Choose the import source
+ 正在清空书签中...
+ Remove completed downloads
+ Find on page
+ Find on page
+
+
diff --git a/res/values/analytics.xml b/res/values/analytics.xml
new file mode 100755
index 00000000..6de05dfe
--- /dev/null
+++ b/res/values/analytics.xml
@@ -0,0 +1,11 @@
+
+
+
+ UA-33136904-1
+
+
+ true
+
+
+ true
+
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
new file mode 100644
index 00000000..f3cb82f5
--- /dev/null
+++ b/res/values/colors.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+ #ff5e5e5e
+ #17170443
+ #000000
+ #646464
+ #c8c8c8
+ #323232
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100755
index 00000000..309d28e5
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,169 @@
+
+
+
+ Running in the background.
+ Shadowsocks
+ Service started.
+ Service failed to start.
+ Shadowsocks service stopped.
+ Auto Reconnected
+ Reconnected successfully.
+ Service has been stopped already.
+ Service has been started already.
+ Proxy Service Stopped
+ Please check your network and login information
+ Cannot reconnect
+ OK, I know
+ Address
+ Local Port
+ Site Key
+ Auto Connect
+ Auto reconnect
+ Global Proxy
+ Disconnect
+ Connect
+ Feedback @ofmax
+ Shadowsocks Settings
+ Feature Settings
+ Proxy Switch
+ Service Controller
+ Enable / Disable Proxy
+ Shadowsocks URL
+ Listening port
+ Site key for GoAgent
+ The port number should be greater than 1024
+ Start Shadowsocks at the startup
+ Set up the global proxy (needs ROOT permission and
+ IPTABLES support)
+
+ Port should not be empty
+ Proxy should not be empty
+ Package configure
+ Install / Uninstall
+ Downloading
+ Downloading, Patient Please...
+ Install / Uninstall extra packages (need SD card)
+ Please install dependent module first! (Scroll to
+ the bottom, and select to install)
+
+ Please make sure you have a SD CARD!
+ Reset
+ Unzipping...
+ Individual Proxy
+ Set individual proxy for apps (needs ROOT permission and IPTABLES support)
+
+ Bypass Mode
+ Enable this option to bypass selected apps
+ Select apps to use with Shadowsocks:
+ An unexpected exit detected, context has been
+ recovered
+
+ Shadowsocks is an open source project
+ published under
+ the GPLv3. \n\nIf you have any questions, please visit
+ our project page on
+ googlecode. \n\n(gaeproxy.googlecode.com)
+
+ About
+ Proxy Type
+
+ - GAE
+ - PaaS
+
+
+
+ - 209.0.0.0/8
+ - 72.0.0.0/8
+ - 74.0.0.0/8
+ - 68.0.0.0/8
+ - 69.0.0.0/8
+ - 66.0.0.0/8
+ - 64.0.0.0/8
+ - 199.0.0.0/8
+ - 168.0.0.0/8
+ - 67.0.0.0/8
+ - 173.0.0.0/8
+ - 202.248.0.0/16
+
+
+ - 1.0.0.0/8
+ - 14.0.0.0/8
+ - 27.0.0.0/8
+ - 36.0.0.0/8
+ - 39.0.0.0/8
+ - 42.0.0.0/8
+ - 49.0.0.0/8
+ - 58.0.0.0/7
+ - 60.0.0.0/7
+ - 96.0.0.0/3
+ - 128.0.0.0/4
+ - 171.0.0.0/8
+ - 175.0.0.0/8
+ - 180.0.0.0/8
+ - 182.0.0.0/8
+ - 183.0.0.0/8
+ - 202.0.0.0/8
+ - 203.0.0.0/8
+ - 210.0.0.0/8
+ - 211.0.0.0/8
+ - 216.0.0.0/5
+ - 192.168.0.0/16
+ - 10.0.0.0/8
+ - 172.16.0.0/12
+
+
+
+ d3dxZ3R4eHByb3h5LTF8d3dxZ3R4eHByb3h5LTJ8d3dxZ3R4eHByb3h5LTN8d3dxZ3R4eHByb3h5LTR8d3dxZ3R4eHByb3h5LTV8d3dxZ3R4eHByb3h5LTZ8d3dxZ3R4eHByb3h5LTd8d3dxZ3R4eHByb3h5LTh8d3dxZ3R4eHByb3h5LTl8d3dxZ3R4eHByb3h5LTEwfHd3cWd0eHhwcm94eTEtMXx3d3FndHh4cHJveHkxLTJ8d3dxZ3R4eHByb3h5MS0zfHd3cWd0eHhwcm94eTEtNHx3d3FndHh4cHJveHkxLTV8d3dxZ3R4eHByb3h5MS02fHd3cWd0eHhwcm94eTEtN3x3d3FndHh4cHJveHkxLTh8d3dxZ3R4eHByb3h5MS05fHd3cWd0eHhwcm94eTEtMTB8d3dxZ3R4eHByb3h5Mi0xfHd3cWd0eHhwcm94eTItMnx3d3FndHh4cHJveHkyLTN8d3dxZ3R4eHByb3h5Mi00fHd3cWd0eHhwcm94eTItNXx3d3FndHh4cHJveHkyLTZ8d3dxZ3R4eHByb3h5Mi03fHd3cWd0eHhwcm94eTItOHx3d3FndHh4cHJveHkyLTl8d3dxZ3R4eHByb3h5Mi0xMA==
+
+ 2
+ wwqgtxx-goagent
+
+ Connecting…
+ Initializing…
+ Reseting…
+ Please modify your proxy host to start with
+ "https://"
+
+ HTTPS Proxy
+ Proxy HTTPS through GAE, not compatible with most
+ apps
+
+
+
+ Notification Settings
+ Ringtone
+ Select the notification\'s ringtone
+ Vibrate
+ Also vibrate when connection status changes
+ Loading app list…
+ Market Proxy
+ Only for users in China, help to update app from
+ Android Market (need reboot)
+
+ CHN Route
+ Bypass all sites located in China (needs ROOT
+ permission and
+ IPTABLES support)
+
+ Connecting to Shadowsocks
+ Reconnecting to Shadowsocks
+ GAE Browser
+ Start a browser through GAE proxy, for users
+ without root permission or iptables support
+
+ You are using the default proxy, which has a quota
+ of only 10G bandwidth every day. Please deploy your own
+ servers to get
+ more free bandwidth quota with Google App Engine.\n\nYou can find more
+ information here: https://github.com/phus/goagent
+
+ Warning
+
+ System Proxy
+ Enable system wide proxy automatically without ROOT permission,
+ only works for Wifi on Android 4.0 and above.
+
+ Remote Port
+ Server port of shadowsocks
+
+
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100755
index 00000000..d4d1aa2e
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/xml/shadowsocks_preference.xml b/res/xml/shadowsocks_preference.xml
new file mode 100755
index 00000000..d8bfb8df
--- /dev/null
+++ b/res/xml/shadowsocks_preference.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/com/github/shadowsocks/AppManager.java b/src/com/github/shadowsocks/AppManager.java
new file mode 100755
index 00000000..f331864c
--- /dev/null
+++ b/src/com/github/shadowsocks/AppManager.java
@@ -0,0 +1,416 @@
+/* Copyright (c) 2009, Nathan Freitas, Orbot / The Guardian Project - http://openideals.com/guardian */
+/* See LICENSE for licensing information */
+
+package com.github.shadowsocks;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.PreferenceManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.widget.*;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+
+import java.util.*;
+
+public class AppManager extends Activity implements OnCheckedChangeListener,
+ OnClickListener {
+
+ private static class ListEntry {
+ private CheckBox box;
+ private TextView text;
+ private ImageView icon;
+ }
+
+ public static ProxyedApp[] getProxyedApps(Context context) {
+
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(context);
+
+ String tordAppString = prefs.getString(PREFS_KEY_PROXYED, "");
+ String[] tordApps;
+
+ StringTokenizer st = new StringTokenizer(tordAppString, "|");
+ tordApps = new String[st.countTokens()];
+ int tordIdx = 0;
+ while (st.hasMoreTokens()) {
+ tordApps[tordIdx++] = st.nextToken();
+ }
+
+ Arrays.sort(tordApps);
+
+ // else load the apps up
+ PackageManager pMgr = context.getPackageManager();
+
+ List lAppInfo = pMgr.getInstalledApplications(0);
+
+ Iterator itAppInfo = lAppInfo.iterator();
+
+ Vector vectorApps = new Vector();
+
+ ApplicationInfo aInfo = null;
+
+ while (itAppInfo.hasNext()) {
+ aInfo = itAppInfo.next();
+
+ // ignore system apps
+ if (aInfo.uid < 10000)
+ continue;
+
+ ProxyedApp app = new ProxyedApp();
+
+ app.setUid(aInfo.uid);
+
+ app.setUsername(pMgr.getNameForUid(app.getUid()));
+
+ // check if this application is allowed
+ if (aInfo.packageName != null
+ && aInfo.packageName.equals("com.github.shadowsocks")) {
+ app.setProxyed(true);
+ } else if (Arrays.binarySearch(tordApps, app.getUsername()) >= 0) {
+ app.setProxyed(true);
+ } else {
+ app.setProxyed(false);
+ }
+
+ vectorApps.add(app);
+
+ }
+
+ ProxyedApp[] apps = new ProxyedApp[vectorApps.size()];
+ vectorApps.toArray(apps);
+ return apps;
+ }
+
+ private ProxyedApp[] apps = null;
+
+ private ListView listApps;
+
+ private AppManager mAppManager;
+ private TextView overlay;
+
+ private ProgressDialog pd = null;
+ private ListAdapter adapter;
+
+ private ImageLoader dm;
+
+ private static final int MSG_LOAD_START = 1;
+
+ private static final int MSG_LOAD_FINISH = 2;
+
+ public final static String PREFS_KEY_PROXYED = "Proxyed";
+
+ private boolean appsLoaded = false;
+
+ final Handler handler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_LOAD_START:
+ pd = ProgressDialog.show(AppManager.this, "",
+ getString(R.string.loading), true, true);
+ break;
+ case MSG_LOAD_FINISH:
+
+ listApps.setAdapter(adapter);
+
+ listApps.setOnScrollListener(new OnScrollListener() {
+
+ boolean visible;
+
+ @Override
+ public void onScroll(AbsListView view,
+ int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ if (visible) {
+ String name = apps[firstVisibleItem].getName();
+ if (name != null && name.length() > 1)
+ overlay.setText(apps[firstVisibleItem]
+ .getName().substring(0, 1));
+ else
+ overlay.setText("*");
+ overlay.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view,
+ int scrollState) {
+ visible = true;
+ if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
+ overlay.setVisibility(View.INVISIBLE);
+ }
+ }
+ });
+
+ if (pd != null) {
+ pd.dismiss();
+ pd = null;
+ }
+ break;
+ }
+ super.handleMessage(msg);
+ }
+ };
+
+ public void getApps(Context context) {
+
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(context);
+
+ String tordAppString = prefs.getString(PREFS_KEY_PROXYED, "");
+ String[] tordApps;
+
+ StringTokenizer st = new StringTokenizer(tordAppString, "|");
+ tordApps = new String[st.countTokens()];
+ int tordIdx = 0;
+ while (st.hasMoreTokens()) {
+ tordApps[tordIdx++] = st.nextToken();
+ }
+
+ Arrays.sort(tordApps);
+
+ Vector vectorApps = new Vector();
+
+ // else load the apps up
+ PackageManager pMgr = context.getPackageManager();
+
+ List lAppInfo = pMgr.getInstalledApplications(0);
+
+ Iterator itAppInfo = lAppInfo.iterator();
+
+ ApplicationInfo aInfo = null;
+
+ while (itAppInfo.hasNext()) {
+ aInfo = itAppInfo.next();
+
+ // ignore system apps
+ if (aInfo.uid < 10000)
+ continue;
+
+ if (aInfo.processName == null)
+ continue;
+ if (pMgr.getApplicationLabel(aInfo) == null
+ || pMgr.getApplicationLabel(aInfo).toString().equals(""))
+ continue;
+ if (pMgr.getApplicationIcon(aInfo) == null)
+ continue;
+
+ ProxyedApp tApp = new ProxyedApp();
+
+ tApp.setEnabled(aInfo.enabled);
+ tApp.setUid(aInfo.uid);
+ tApp.setUsername(pMgr.getNameForUid(tApp.getUid()));
+ tApp.setProcname(aInfo.processName);
+ tApp.setName(pMgr.getApplicationLabel(aInfo).toString());
+
+ // check if this application is allowed
+ if (Arrays.binarySearch(tordApps, tApp.getUsername()) >= 0) {
+ tApp.setProxyed(true);
+ } else {
+ tApp.setProxyed(false);
+ }
+
+ vectorApps.add(tApp);
+ }
+
+ apps = new ProxyedApp[vectorApps.size()];
+ vectorApps.toArray(apps);
+
+ }
+
+ private void loadApps() {
+ getApps(this);
+
+ Arrays.sort(apps, new Comparator() {
+ @Override
+ public int compare(ProxyedApp o1, ProxyedApp o2) {
+ if (o1 == null || o2 == null || o1.getName() == null
+ || o2.getName() == null)
+ return 1;
+ if (o1.isProxyed() == o2.isProxyed())
+ return o1.getName().compareTo(o2.getName());
+ if (o1.isProxyed())
+ return -1;
+ return 1;
+ }
+ });
+
+ final LayoutInflater inflater = getLayoutInflater();
+
+ adapter = new ArrayAdapter(this, R.layout.layout_apps_item,
+ R.id.itemtext, apps) {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ListEntry entry;
+ if (convertView == null) {
+ // Inflate a new view
+ convertView = inflater.inflate(R.layout.layout_apps_item,
+ parent, false);
+ entry = new ListEntry();
+ entry.icon = (ImageView) convertView
+ .findViewById(R.id.itemicon);
+ entry.box = (CheckBox) convertView
+ .findViewById(R.id.itemcheck);
+ entry.text = (TextView) convertView
+ .findViewById(R.id.itemtext);
+
+ entry.text.setOnClickListener(mAppManager);
+ entry.text.setOnClickListener(mAppManager);
+
+ convertView.setTag(entry);
+
+ entry.box.setOnCheckedChangeListener(mAppManager);
+ } else {
+ // Convert an existing view
+ entry = (ListEntry) convertView.getTag();
+ }
+
+ final ProxyedApp app = apps[position];
+
+ entry.icon.setTag(app.getUid());
+
+ dm.DisplayImage(app.getUid(),
+ (Activity) convertView.getContext(), entry.icon);
+
+ entry.text.setText(app.getName());
+
+ final CheckBox box = entry.box;
+ box.setTag(app);
+ box.setChecked(app.isProxyed());
+
+ entry.text.setTag(box);
+
+ return convertView;
+ }
+ };
+
+ appsLoaded = true;
+
+ }
+
+ /**
+ * Called an application is check/unchecked
+ */
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ final ProxyedApp app = (ProxyedApp) buttonView.getTag();
+ if (app != null) {
+ app.setProxyed(isChecked);
+ }
+
+ saveAppSettings(this);
+
+ }
+
+ @Override
+ public void onClick(View v) {
+
+ CheckBox cbox = (CheckBox) v.getTag();
+
+ final ProxyedApp app = (ProxyedApp) cbox.getTag();
+ if (app != null) {
+ app.setProxyed(!app.isProxyed());
+ cbox.setChecked(app.isProxyed());
+ }
+
+ saveAppSettings(this);
+
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ this.setContentView(R.layout.layout_apps);
+
+ this.dm = ImageLoaderFactory.getImageLoader(this);
+
+ this.overlay = (TextView) View.inflate(this, R.layout.overlay, null);
+ getWindowManager()
+ .addView(
+ overlay,
+ new WindowManager.LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
+ PixelFormat.TRANSLUCENT));
+
+ mAppManager = this;
+
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ new Thread() {
+
+ @Override
+ public void run() {
+ handler.sendEmptyMessage(MSG_LOAD_START);
+
+ listApps = (ListView) findViewById(R.id.applistview);
+
+ if (!appsLoaded)
+ loadApps();
+ handler.sendEmptyMessage(MSG_LOAD_FINISH);
+ }
+ }.start();
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.app.Activity#onStop()
+ */
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ // Log.d(getClass().getName(),"Exiting Preferences");
+ }
+
+ public void saveAppSettings(Context context) {
+ if (apps == null)
+ return;
+
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(this);
+
+ // final SharedPreferences prefs =
+ // context.getSharedPreferences(PREFS_KEY, 0);
+
+ StringBuilder tordApps = new StringBuilder();
+
+ for (int i = 0; i < apps.length; i++) {
+ if (apps[i].isProxyed()) {
+ tordApps.append(apps[i].getUsername());
+ tordApps.append("|");
+ }
+ }
+
+ Editor edit = prefs.edit();
+ edit.putString(PREFS_KEY_PROXYED, tordApps.toString());
+ edit.commit();
+
+ }
+
+}
diff --git a/src/com/github/shadowsocks/Exec.java b/src/com/github/shadowsocks/Exec.java
new file mode 100755
index 00000000..090723ad
--- /dev/null
+++ b/src/com/github/shadowsocks/Exec.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.github.shadowsocks;
+
+import java.io.FileDescriptor;
+
+/**
+ * Utility methods for creating and managing a subprocess.
+ *
+ * Note: The native methods access a package-private java.io.FileDescriptor
+ * field to get and set the raw Linux file descriptor. This might break if the
+ * implementation of java.io.FileDescriptor is changed.
+ */
+
+public class Exec {
+ static {
+ System.loadLibrary("exec");
+ }
+
+ /**
+ * Close a given file descriptor.
+ */
+ public static native void close(FileDescriptor fd);
+
+ /**
+ * Create a subprocess. Differs from java.lang.ProcessBuilder in that a pty
+ * is used to communicate with the subprocess.
+ *
+ * Callers are responsible for calling Exec.close() on the returned file
+ * descriptor.
+ *
+ * @param cmd The command to execute
+ * @param args An array of arguments to the command
+ * @param envVars An array of strings of the form "VAR=value" to be added to the
+ * environment of the process
+ * @param processId A one-element array to which the process ID of the started
+ * process will be written.
+ * @return the file descriptor of the started process.
+ */
+ public static native FileDescriptor createSubprocess(String cmd,
+ String[] args, String[] envVars, int[] processId);
+
+ /**
+ * Send SIGHUP to a process group.
+ */
+ public static native void hangupProcessGroup(int processId);
+
+ /**
+ * Causes the calling thread to wait for the process associated with the
+ * receiver to finish executing.
+ *
+ * @return The exit value of the Process being waited on
+ */
+ public static native int waitFor(int processId);
+}
diff --git a/src/com/github/shadowsocks/ImageLoader.java b/src/com/github/shadowsocks/ImageLoader.java
new file mode 100755
index 00000000..6d4378d1
--- /dev/null
+++ b/src/com/github/shadowsocks/ImageLoader.java
@@ -0,0 +1,215 @@
+package com.github.shadowsocks;
+
+import android.R;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.widget.ImageView;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.util.HashMap;
+import java.util.Stack;
+
+public class ImageLoader {
+
+ // Used to display bitmap in the UI thread
+ class BitmapDisplayer implements Runnable {
+ Bitmap bitmap;
+ ImageView imageView;
+
+ public BitmapDisplayer(Bitmap b, ImageView i) {
+ bitmap = b;
+ imageView = i;
+ }
+
+ @Override
+ public void run() {
+ if (bitmap != null)
+ imageView.setImageBitmap(bitmap);
+ else
+ imageView.setImageResource(stub_id);
+ }
+ }
+
+ class PhotosLoader extends Thread {
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ // thread waits until there are any images to load in the
+ // queue
+ if (photosQueue.photosToLoad.size() == 0)
+ synchronized (photosQueue.photosToLoad) {
+ photosQueue.photosToLoad.wait();
+ }
+ if (photosQueue.photosToLoad.size() != 0) {
+ PhotoToLoad photoToLoad;
+ synchronized (photosQueue.photosToLoad) {
+ photoToLoad = photosQueue.photosToLoad.pop();
+ }
+ Bitmap bmp = getBitmap(photoToLoad.uid);
+ cache.put(photoToLoad.uid, bmp);
+ Object tag = photoToLoad.imageView.getTag();
+ if (tag != null && ((Integer) tag) == photoToLoad.uid) {
+ BitmapDisplayer bd = new BitmapDisplayer(bmp,
+ photoToLoad.imageView);
+ Activity a = (Activity) photoToLoad.imageView
+ .getContext();
+ a.runOnUiThread(bd);
+ }
+ }
+ if (Thread.interrupted())
+ break;
+ }
+ } catch (InterruptedException e) {
+ // allow thread to exit
+ }
+ }
+ }
+
+ // stores list of photos to download
+ class PhotosQueue {
+ private final Stack photosToLoad = new Stack();
+
+ // removes all instances of this ImageView
+ public void Clean(ImageView image) {
+ synchronized (photosToLoad) {
+ for (int j = 0; j < photosToLoad.size(); ) {
+ if (photosToLoad.get(j).imageView == image) photosToLoad.remove(j);
+ else ++j;
+ }
+ }
+ }
+ }
+
+ // Task for the queue
+ private class PhotoToLoad {
+ public int uid;
+ public ImageView imageView;
+
+ public PhotoToLoad(int u, ImageView i) {
+ uid = u;
+ imageView = i;
+ }
+ }
+
+ // the simplest in-memory cache implementation. This should be replaced with
+ // something like SoftReference or BitmapOptions.inPurgeable(since 1.6)
+ private HashMap cache = new HashMap();
+
+ private File cacheDir;
+
+ private Context context;
+
+ final int stub_id = R.drawable.sym_def_app_icon;
+
+ PhotosQueue photosQueue = new PhotosQueue();
+
+ PhotosLoader photoLoaderThread = new PhotosLoader();
+
+ public ImageLoader(Context c) {
+ // Make the background thead low priority. This way it will not affect
+ // the UI performance
+ photoLoaderThread.setPriority(Thread.NORM_PRIORITY - 1);
+
+ context = c;
+
+ // Find the dir to save cached images
+ cacheDir = context.getCacheDir();
+
+ }
+
+ public void clearCache() {
+ // clear memory cache
+ cache.clear();
+
+ // clear SD cache
+ File[] files = cacheDir.listFiles();
+ for (File f : files)
+ f.delete();
+ }
+
+ // decodes image and scales it to reduce memory consumption
+ private Bitmap decodeFile(File f) {
+ try {
+ // decode image size
+ BitmapFactory.Options o = new BitmapFactory.Options();
+ o.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(new FileInputStream(f), null, o);
+
+ // Find the correct scale value. It should be the power of 2.
+ final int REQUIRED_SIZE = 70;
+ int width_tmp = o.outWidth, height_tmp = o.outHeight;
+ int scale = 1;
+ while (true) {
+ if (width_tmp / 2 < REQUIRED_SIZE
+ || height_tmp / 2 < REQUIRED_SIZE)
+ break;
+ width_tmp /= 2;
+ height_tmp /= 2;
+ scale *= 2;
+ }
+
+ // decode with inSampleSize
+ BitmapFactory.Options o2 = new BitmapFactory.Options();
+ o2.inSampleSize = scale;
+ return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
+ } catch (FileNotFoundException e) {
+ }
+ return null;
+ }
+
+ public void DisplayImage(int uid, Activity activity, ImageView imageView) {
+ if (cache.containsKey(uid))
+ imageView.setImageBitmap(cache.get(uid));
+ else {
+ queuePhoto(uid, activity, imageView);
+ imageView.setImageResource(stub_id);
+ }
+ }
+
+ private Bitmap getBitmap(int uid) {
+ // I identify images by hashcode. Not a perfect solution, good for the
+ // demo.
+ String filename = String.valueOf(uid);
+ File f = new File(cacheDir, filename);
+
+ // from SD cache
+ Bitmap b = decodeFile(f);
+ if (b != null)
+ return b;
+
+ // from web
+ try {
+ BitmapDrawable icon = (BitmapDrawable) Utils.getAppIcon(context,
+ uid);
+ return icon.getBitmap();
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
+ private void queuePhoto(int uid, Activity activity, ImageView imageView) {
+ // This ImageView may be used for other images before. So there may be
+ // some old tasks in the queue. We need to discard them.
+ photosQueue.Clean(imageView);
+ PhotoToLoad p = new PhotoToLoad(uid, imageView);
+ synchronized (photosQueue.photosToLoad) {
+ photosQueue.photosToLoad.push(p);
+ photosQueue.photosToLoad.notifyAll();
+ }
+
+ // start thread if it's not started yet
+ if (photoLoaderThread.getState() == Thread.State.NEW)
+ photoLoaderThread.start();
+ }
+
+ public void stopThread() {
+ photoLoaderThread.interrupt();
+ }
+
+}
diff --git a/src/com/github/shadowsocks/ImageLoaderFactory.java b/src/com/github/shadowsocks/ImageLoaderFactory.java
new file mode 100755
index 00000000..b858ad58
--- /dev/null
+++ b/src/com/github/shadowsocks/ImageLoaderFactory.java
@@ -0,0 +1,50 @@
+// dbartists - Douban artists client for Android
+// Copyright (C) 2011 Max Lv
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+//
+//
+// ___====-_ _-====___
+// _--^^^#####// \\#####^^^--_
+// _-^##########// ( ) \\##########^-_
+// -############// |\^^/| \\############-
+// _/############// (@::@) \\############\_
+// /#############(( \\// ))#############\
+// -###############\\ (oo) //###############-
+// -#################\\ / VV \ //#################-
+// -###################\\/ \//###################-
+// _#/|##########/\######( /\ )######/\##########|\#_
+// |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
+// ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
+// ` ` ` ` / | | | | \ ' ' ' '
+// ( | | | | )
+// __\ | | | | /__
+// (vvv(VVV)(VVV)vvv)
+//
+// HERE BE DRAGONS
+
+package com.github.shadowsocks;
+
+import android.content.Context;
+
+public class ImageLoaderFactory {
+
+ private static ImageLoader il = null;
+
+ public static ImageLoader getImageLoader(Context context) {
+ if (il == null) {
+ il = new ImageLoader(context);
+ }
+ return il;
+ }
+}
diff --git a/src/com/github/shadowsocks/ProxyedApp.java b/src/com/github/shadowsocks/ProxyedApp.java
new file mode 100755
index 00000000..7e747c25
--- /dev/null
+++ b/src/com/github/shadowsocks/ProxyedApp.java
@@ -0,0 +1,96 @@
+package com.github.shadowsocks;
+
+public class ProxyedApp {
+
+ private boolean enabled;
+ private int uid;
+ private String username;
+ private String procname;
+ private String name;
+
+ private boolean proxyed = false;
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @return the procname
+ */
+ public String getProcname() {
+ return procname;
+ }
+
+ /**
+ * @return the uid
+ */
+ public int getUid() {
+ return uid;
+ }
+
+ /**
+ * @return the username
+ */
+ public String getUsername() {
+ return username;
+ }
+
+ /**
+ * @return the enabled
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * @return the proxyed
+ */
+ public boolean isProxyed() {
+ return proxyed;
+ }
+
+ /**
+ * @param enabled the enabled to set
+ */
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ /**
+ * @param name the name to set
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @param procname the procname to set
+ */
+ public void setProcname(String procname) {
+ this.procname = procname;
+ }
+
+ /**
+ * @param proxyed the proxyed to set
+ */
+ public void setProxyed(boolean proxyed) {
+ this.proxyed = proxyed;
+ }
+
+ /**
+ * @param uid the uid to set
+ */
+ public void setUid(int uid) {
+ this.uid = uid;
+ }
+
+ /**
+ * @param username the username to set
+ */
+ public void setUsername(String username) {
+ this.username = username;
+ }
+}
\ No newline at end of file
diff --git a/src/com/github/shadowsocks/Shadowsocks.java b/src/com/github/shadowsocks/Shadowsocks.java
new file mode 100644
index 00000000..584a565c
--- /dev/null
+++ b/src/com/github/shadowsocks/Shadowsocks.java
@@ -0,0 +1,639 @@
+/* Shadowsocks - GoAgent / WallProxy client App for Android
+ * Copyright (C) 2012
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * ___====-_ _-====___
+ * _--^^^#####// \\#####^^^--_
+ * _-^##########// ( ) \\##########^-_
+ * -############// |\^^/| \\############-
+ * _/############// (@::@) \\############\_
+ * /#############(( \\// ))#############\
+ * -###############\\ (oo) //###############-
+ * -#################\\ / VV \ //#################-
+ * -###################\\/ \//###################-
+ * _#/|##########/\######( /\ )######/\##########|\#_
+ * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
+ * ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
+ * ` ` ` ` / | | | | \ ' ' ' '
+ * ( | | | | )
+ * __\ | | | | /__
+ * (vvv(VVV)(VVV)vvv)
+ *
+ * HERE BE DRAGONS
+ *
+ */
+
+package com.github.shadowsocks;
+
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.AssetManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.*;
+import android.text.SpannableString;
+import android.text.method.LinkMovementMethod;
+import android.text.util.Linkify;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.google.analytics.tracking.android.EasyTracker;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class Shadowsocks extends PreferenceActivity implements
+ OnSharedPreferenceChangeListener {
+
+ private static final String TAG = "Shadowsocks";
+
+ public static final String PREFS_NAME = "Shadowsocks";
+
+ private static final int MSG_CRASH_RECOVER = 1;
+ private static final int MSG_INITIAL_FINISH = 2;
+
+ final Handler handler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ SharedPreferences settings = PreferenceManager
+ .getDefaultSharedPreferences(Shadowsocks.this);
+ Editor ed = settings.edit();
+ switch (msg.what) {
+ case MSG_CRASH_RECOVER:
+ Toast.makeText(Shadowsocks.this, R.string.crash_alert,
+ Toast.LENGTH_LONG).show();
+ ed.putBoolean("isRunning", false);
+ break;
+ case MSG_INITIAL_FINISH:
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ mProgressDialog = null;
+ }
+ break;
+ }
+ ed.commit();
+ super.handleMessage(msg);
+ }
+ };
+
+ private static ProgressDialog mProgressDialog = null;
+
+ private CheckBoxPreference isAutoConnectCheck;
+ private CheckBoxPreference isGlobalProxyCheck;
+ private EditTextPreference proxyText;
+ private EditTextPreference portText;
+ private EditTextPreference remotePortText;
+ private EditTextPreference sitekeyText;
+ private CheckBoxPreference isGFWListCheck;
+ private CheckBoxPreference isRunningCheck;
+ private Preference proxyedApps;
+ private CheckBoxPreference isBypassAppsCheck;
+
+ private void copyAssets(String path) {
+
+ AssetManager assetManager = getAssets();
+ String[] files = null;
+ try {
+ files = assetManager.list(path);
+ } catch (IOException e) {
+ Log.e(TAG, e.getMessage());
+ }
+ if (files != null) {
+ for (String file : files) {
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ in = assetManager.open(file);
+ out = new FileOutputStream("/data/data/com.github.shadowsocks/"
+ + file);
+ copyFile(in, out);
+ in.close();
+ in = null;
+ out.flush();
+ out.close();
+ out = null;
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage());
+ }
+ }
+ }
+ }
+
+ private void copyFile(InputStream in, OutputStream out) throws IOException {
+ byte[] buffer = new byte[1024];
+ int read;
+ while ((read = in.read(buffer)) != -1) {
+ out.write(buffer, 0, read);
+ }
+ }
+
+ private void crash_recovery() {
+
+ Utils.runRootCommand(Utils.getIptables() + " -t nat -F OUTPUT");
+
+ Utils.runCommand(ShadowsocksService.BASE + "proxy.sh stop");
+
+ }
+
+ private void disableAll() {
+ proxyText.setEnabled(false);
+ portText.setEnabled(false);
+ remotePortText.setEnabled(false);
+ sitekeyText.setEnabled(false);
+ proxyedApps.setEnabled(false);
+ isGFWListCheck.setEnabled(false);
+ isBypassAppsCheck.setEnabled(false);
+
+ isAutoConnectCheck.setEnabled(false);
+ isGlobalProxyCheck.setEnabled(false);
+ }
+
+ private void enableAll() {
+ proxyText.setEnabled(true);
+ portText.setEnabled(true);
+ remotePortText.setEnabled(true);
+ sitekeyText.setEnabled(true);
+ isGlobalProxyCheck.setEnabled(true);
+ isGFWListCheck.setEnabled(true);
+ if (!isGlobalProxyCheck.isChecked()) {
+ proxyedApps.setEnabled(true);
+ isBypassAppsCheck.setEnabled(true);
+ }
+
+ isAutoConnectCheck.setEnabled(true);
+ }
+
+ private boolean isTextEmpty(String s, String msg) {
+ if (s == null || s.length() <= 0) {
+ showAToast(msg);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Called when the activity is first created.
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.main);
+ addPreferencesFromResource(R.xml.shadowsocks_preference);
+
+ proxyText = (EditTextPreference) findPreference("proxy");
+ portText = (EditTextPreference) findPreference("port");
+ remotePortText = (EditTextPreference) findPreference("remotePort");
+ sitekeyText = (EditTextPreference) findPreference("sitekey");
+ proxyedApps = findPreference("proxyedApps");
+
+ isRunningCheck = (CheckBoxPreference) findPreference("isRunning");
+ isAutoConnectCheck = (CheckBoxPreference) findPreference("isAutoConnect");
+ isGlobalProxyCheck = (CheckBoxPreference) findPreference("isGlobalProxy");
+ isGFWListCheck = (CheckBoxPreference) findPreference("isGFWList");
+ isBypassAppsCheck = (CheckBoxPreference) findPreference("isBypassApps");
+
+ if (mProgressDialog == null)
+ mProgressDialog = ProgressDialog.show(this, "",
+ getString(R.string.initializing), true, true);
+
+ final SharedPreferences settings = PreferenceManager
+ .getDefaultSharedPreferences(this);
+
+ new Thread() {
+ @Override
+ public void run() {
+
+ Utils.isRoot();
+
+ String versionName;
+ try {
+ versionName = getPackageManager().getPackageInfo(
+ getPackageName(), 0).versionName;
+ } catch (NameNotFoundException e) {
+ versionName = "NONE";
+ }
+
+ if (!settings.getBoolean(versionName, false)) {
+
+ Editor edit = settings.edit();
+ edit.putBoolean(versionName, true);
+ edit.commit();
+
+ reset();
+
+ }
+
+ handler.sendEmptyMessage(MSG_INITIAL_FINISH);
+ }
+ }.start();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(Menu.NONE, Menu.FIRST + 1, 1, getString(R.string.recovery))
+ .setIcon(android.R.drawable.ic_menu_delete);
+ menu.add(Menu.NONE, Menu.FIRST + 2, 2, getString(R.string.about))
+ .setIcon(android.R.drawable.ic_menu_info_details);
+ return true;
+
+ }
+
+ /**
+ * Called when the activity is closed.
+ */
+ @Override
+ public void onDestroy() {
+ SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
+
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean("isConnected", ShadowsocksService.isServiceStarted());
+ editor.commit();
+
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ mProgressDialog = null;
+ }
+
+ super.onDestroy();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { // 按下的如果是BACK,同时没有重复
+ try {
+ finish();
+ } catch (Exception ignore) {
+ // Nothing
+ }
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ // 菜单项被选择事件
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case Menu.FIRST + 1:
+ recovery();
+ break;
+ case Menu.FIRST + 2:
+ String versionName = "";
+ try {
+ versionName = getPackageManager().getPackageInfo(
+ getPackageName(), 0).versionName;
+ } catch (NameNotFoundException e) {
+ versionName = "";
+ }
+ showAToast(getString(R.string.about) + " (" + versionName + ")\n\n"
+ + getString(R.string.copy_rights));
+ break;
+ }
+
+ return true;
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ // Unregister the listener whenever a key changes
+ getPreferenceScreen().getSharedPreferences()
+ .unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+ Preference preference) {
+ SharedPreferences settings = PreferenceManager
+ .getDefaultSharedPreferences(this);
+
+ if (preference.getKey() != null
+ && preference.getKey().equals("proxyedApps")) {
+ Intent intent = new Intent(this, AppManager.class);
+ startActivity(intent);
+ } else if (preference.getKey() != null
+ && preference.getKey().equals("isRunning")) {
+ if (!serviceStart()) {
+ Editor edit = settings.edit();
+ edit.putBoolean("isRunning", false);
+ edit.commit();
+ }
+ }
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ SharedPreferences settings = PreferenceManager
+ .getDefaultSharedPreferences(this);
+
+ if (settings.getBoolean("isSystemProxy", false)) {
+ isGlobalProxyCheck.setEnabled(false);
+ isGFWListCheck.setEnabled(false);
+ proxyedApps.setEnabled(false);
+ isBypassAppsCheck.setEnabled(false);
+ } else {
+ if (settings.getBoolean("isGlobalProxy", false)) {
+ proxyedApps.setEnabled(false);
+ isBypassAppsCheck.setEnabled(false);
+ } else {
+ proxyedApps.setEnabled(true);
+ isBypassAppsCheck.setEnabled(true);
+ }
+ }
+
+ sitekeyText.setEnabled(true);
+
+ Editor edit = settings.edit();
+
+ if (ShadowsocksService.isServiceStarted()) {
+ edit.putBoolean("isRunning", true);
+ } else {
+ if (settings.getBoolean("isRunning", false)) {
+ new Thread() {
+ @Override
+ public void run() {
+ crash_recovery();
+ handler.sendEmptyMessage(MSG_CRASH_RECOVER);
+ }
+ }.start();
+ }
+ edit.putBoolean("isRunning", false);
+ }
+
+ edit.commit();
+
+ if (settings.getBoolean("isRunning", false)) {
+ isRunningCheck.setChecked(true);
+ disableAll();
+ } else {
+ isRunningCheck.setChecked(false);
+ enableAll();
+ }
+
+ // Setup the initial values
+
+ if (!settings.getString("sitekey", "").equals(""))
+ sitekeyText.setSummary(settings.getString("sitekey", ""));
+
+ if (!settings.getString("port", "").equals(""))
+ portText.setSummary(settings.getString("port",
+ getString(R.string.port_summary)));
+
+ if (!settings.getString("remotePort", "").equals(""))
+ portText.setSummary(settings.getString("remotePort",
+ getString(R.string.port_summary)));
+
+ if (!settings.getString("proxy", "").equals(""))
+ proxyText.setSummary(settings.getString("proxy",
+ getString(R.string.proxy_summary)));
+
+ // Set up a listener whenever a key changes
+ getPreferenceScreen().getSharedPreferences()
+ .registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+ String key) {
+ // Let's do something a preference value changes
+ SharedPreferences settings = PreferenceManager
+ .getDefaultSharedPreferences(this);
+
+ if (key.equals("isConnecting")) {
+ if (settings.getBoolean("isConnecting", false)) {
+ Log.d(TAG, "Connecting start");
+ if (mProgressDialog == null)
+ mProgressDialog = ProgressDialog.show(this, "",
+ getString(R.string.connecting), true, true);
+ } else {
+ Log.d(TAG, "Connecting finish");
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ mProgressDialog = null;
+ }
+ }
+ }
+
+ if (key.equals("isGlobalProxy")) {
+ if (settings.getBoolean("isGlobalProxy", false)) {
+ proxyedApps.setEnabled(false);
+ isBypassAppsCheck.setEnabled(false);
+ } else {
+ proxyedApps.setEnabled(true);
+ isBypassAppsCheck.setEnabled(true);
+ }
+ }
+
+ if (key.equals("isRunning")) {
+ if (settings.getBoolean("isRunning", false)) {
+ disableAll();
+ isRunningCheck.setChecked(true);
+ } else {
+ isRunningCheck.setChecked(false);
+ enableAll();
+ }
+ }
+
+ if (key.equals("remotePort"))
+ if (settings.getString("remotePort", "").equals(""))
+ remotePortText.setSummary(getString(R.string.remote_port_summary));
+ else
+ remotePortText.setSummary(settings.getString("remotePort", ""));
+ else if (key.equals("port"))
+ if (settings.getString("port", "").equals(""))
+ portText.setSummary(getString(R.string.port_summary));
+ else
+ portText.setSummary(settings.getString("port", ""));
+ else if (key.equals("sitekey"))
+ if (settings.getString("sitekey", "").equals(""))
+ sitekeyText.setSummary(getString(R.string.sitekey_summary));
+ else
+ sitekeyText.setSummary(settings.getString("sitekey", ""));
+ else if (key.equals("proxy"))
+ if (settings.getString("proxy", "").equals("")) {
+ proxyText.setSummary(getString(R.string.proxy_summary));
+ } else {
+ proxyText.setSummary(settings.getString("proxy", ""));
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ EasyTracker.getInstance().activityStart(this);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ EasyTracker.getInstance().activityStop(this);
+ }
+
+ public void reset() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(Utils.getIptables()).append(" -t nat -F OUTPUT").append("\n");
+ sb.append("kill -9 `cat /data/data/com.github.shadowsocks/pdnsd.pid`").append("\n");
+ sb.append("kill -9 `cat /data/data/com.github.shadowsocks/redsocks.pid`").append("\n");
+ sb.append("kill -9 `cat /data/data/com.github.shadowsocks/shadowsocks.pid`").append("\n");
+ sb.append("killall pdnsd").append("\n");
+ sb.append("killall redsocks").append("\n");
+ sb.append("killall shadowsocks").append("\n");
+ Utils.runRootCommand(sb.toString());
+
+ copyAssets("");
+
+ Utils.runCommand("chmod 755 /data/data/com.github.shadowsocks/iptables\n"
+ + "chmod 755 /data/data/com.github.shadowsocks/redsocks\n"
+ + "chmod 755 /data/data/com.github.shadowsocks/pdnsd\n"
+ + "chmod 755 /data/data/com.github.shadowsocks/shadowsocks\n"
+ );
+ }
+
+ private void recovery() {
+
+ if (mProgressDialog == null)
+ mProgressDialog = ProgressDialog.show(this, "", getString(R.string.recovering),
+ true, true);
+
+ final Handler h = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ mProgressDialog = null;
+ }
+ }
+ };
+
+ try {
+ stopService(new Intent(this, ShadowsocksService.class));
+ } catch (Exception e) {
+ // Nothing
+ }
+
+ new Thread() {
+ @Override
+ public void run() {
+
+ reset();
+
+ h.sendEmptyMessage(0);
+ }
+ }.start();
+
+ }
+
+ /**
+ * Called when connect button is clicked.
+ */
+ public boolean serviceStart() {
+
+ if (ShadowsocksService.isServiceStarted()) {
+ try {
+ stopService(new Intent(this, ShadowsocksService.class));
+ } catch (Exception e) {
+ // Nothing
+ }
+ return false;
+ }
+
+ SharedPreferences settings = PreferenceManager
+ .getDefaultSharedPreferences(this);
+
+ final String proxy = settings.getString("proxy", "");
+ if (isTextEmpty(proxy, getString(R.string.proxy_empty)))
+ return false;
+
+ if (proxy.contains("proxyofmax.appspot.com")) {
+ final TextView message = new TextView(this);
+ message.setPadding(10, 5, 10, 5);
+ final SpannableString s = new SpannableString(
+ getText(R.string.default_proxy_alert));
+ Linkify.addLinks(s, Linkify.WEB_URLS);
+ message.setText(s);
+ message.setMovementMethod(LinkMovementMethod.getInstance());
+
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.warning)
+ .setCancelable(false)
+ .setIcon(android.R.drawable.ic_dialog_info)
+ .setNegativeButton(getString(R.string.ok_iknow),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog,
+ int id) {
+ dialog.cancel();
+ }
+ }).setView(message).create().show();
+ }
+
+ String portText = settings.getString("port", "");
+ if (isTextEmpty(portText, getString(R.string.port_empty)))
+ return false;
+ try {
+ int port = Integer.valueOf(portText);
+ if (port <= 1024) {
+ this.showAToast(getString(R.string.port_alert));
+ return false;
+ }
+ } catch (Exception e) {
+ this.showAToast(getString(R.string.port_alert));
+ return false;
+ }
+
+ try {
+ Intent it = new Intent(this, ShadowsocksService.class);
+ startService(it);
+ } catch (Exception e) {
+ // Nothing
+ return false;
+ }
+
+ return true;
+ }
+
+ private void showAToast(String msg) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage(msg)
+ .setCancelable(false)
+ .setNegativeButton(getString(R.string.ok_iknow),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ AlertDialog alert = builder.create();
+ alert.show();
+ }
+
+}
diff --git a/src/com/github/shadowsocks/ShadowsocksApplication.java b/src/com/github/shadowsocks/ShadowsocksApplication.java
new file mode 100755
index 00000000..b0ac76c6
--- /dev/null
+++ b/src/com/github/shadowsocks/ShadowsocksApplication.java
@@ -0,0 +1,13 @@
+package com.github.shadowsocks;
+
+import android.app.Application;
+import com.google.analytics.tracking.android.EasyTracker;
+
+public class ShadowsocksApplication extends Application {
+
+ @Override
+ public void onCreate() {
+ EasyTracker.getInstance().setContext(this);
+ }
+
+}
diff --git a/src/com/github/shadowsocks/ShadowsocksService.java b/src/com/github/shadowsocks/ShadowsocksService.java
new file mode 100755
index 00000000..32ae7fbe
--- /dev/null
+++ b/src/com/github/shadowsocks/ShadowsocksService.java
@@ -0,0 +1,575 @@
+/* gaeproxy - GAppProxy / WallProxy client App for Android
+ * Copyright (C) 2011
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * ___====-_ _-====___
+ * _--^^^#####// \\#####^^^--_
+ * _-^##########// ( ) \\##########^-_
+ * -############// |\^^/| \\############-
+ * _/############// (@::@) \\############\_
+ * /#############(( \\// ))#############\
+ * -###############\\ (oo) //###############-
+ * -#################\\ / VV \ //#################-
+ * -###################\\/ \//###################-
+ * _#/|##########/\######( /\ )######/\##########|\#_
+ * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
+ * ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
+ * ` ` ` ` / | | | | \ ' ' ' '
+ * ( | | | | )
+ * __\ | | | | /__
+ * (vvv(VVV)(VVV)vvv)
+ *
+ * HERE BE DRAGONS
+ *
+ */
+
+package com.github.shadowsocks;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import com.google.analytics.tracking.android.EasyTracker;
+
+import java.io.DataOutputStream;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+
+public class ShadowsocksService extends Service {
+
+ private Notification notification;
+ private NotificationManager notificationManager;
+ private PendingIntent pendIntent;
+ private PowerManager.WakeLock mWakeLock;
+
+ public static final String BASE = "/data/data/com.github.shadowsocks/";
+ private static final int MSG_CONNECT_START = 0;
+ private static final int MSG_CONNECT_FINISH = 1;
+ private static final int MSG_CONNECT_SUCCESS = 2;
+ private static final int MSG_CONNECT_FAIL = 3;
+ private static final int MSG_HOST_CHANGE = 4;
+ private static final int MSG_STOP_SELF = 5;
+
+ final static String CMD_IPTABLES_RETURN = " -t nat -A OUTPUT -p tcp -d 0.0.0.0 -j RETURN\n";
+
+ final static String CMD_IPTABLES_REDIRECT_ADD_HTTP = " -t nat -A OUTPUT -p tcp "
+ + "--dport 80 -j REDIRECT --to 8123\n";
+ final static String CMD_IPTABLES_REDIRECT_ADD_HTTPS = " -t nat -A OUTPUT -p tcp "
+ + "--dport 443 -j REDIRECT --to 8124\n";
+
+ final static String CMD_IPTABLES_DNAT_ADD_HTTP = " -t nat -A OUTPUT -p tcp "
+ + "--dport 80 -j DNAT --to-destination 127.0.0.1:8123\n";
+ final static String CMD_IPTABLES_DNAT_ADD_HTTPS = " -t nat -A OUTPUT -p tcp "
+ + "--dport 443 -j DNAT --to-destination 127.0.0.1:8124\n";
+
+ private static final String TAG = "ShadowsocksService";
+ private static final String DEFAULT_HOST = "74.125.128.18";
+ private final static int DNS_PORT = 8053;
+
+ private Process httpProcess = null;
+ private DataOutputStream httpOS = null;
+
+ private String appHost;
+ private int remotePort;
+ private int port;
+ private String sitekey;
+
+ private SharedPreferences settings = null;
+
+ private boolean hasRedirectSupport = true;
+ private boolean isGlobalProxy = false;
+ private boolean isGFWList = false;
+ private boolean isBypassApps = false;
+
+ private ProxyedApp apps[];
+
+ private static final Class>[] mStartForegroundSignature = new Class[]{
+ int.class, Notification.class};
+ private static final Class>[] mStopForegroundSignature = new Class[]{boolean.class};
+ private static final Class>[] mSetForegroundSignature = new Class[]{boolean.class};
+
+ private Method mSetForeground;
+ private Method mStartForeground;
+ private Method mStopForeground;
+
+ private Object[] mSetForegroundArgs = new Object[1];
+ private Object[] mStartForegroundArgs = new Object[2];
+ private Object[] mStopForegroundArgs = new Object[1];
+
+ /*
+ * This is a hack see
+ * http://www.mail-archive.com/android-developers@googlegroups
+ * .com/msg18298.html we are not really able to decide if the service was
+ * started. So we remember a week reference to it. We set it if we are
+ * running and clear it if we are stopped. If anything goes wrong, the
+ * reference will hopefully vanish
+ */
+ private static WeakReference sRunningInstance = null;
+
+ public static boolean isServiceStarted() {
+ final boolean isServiceStarted;
+ if (sRunningInstance == null) {
+ isServiceStarted = false;
+ } else if (sRunningInstance.get() == null) {
+ isServiceStarted = false;
+ sRunningInstance = null;
+ } else {
+ isServiceStarted = true;
+ }
+ return isServiceStarted;
+ }
+
+ final Handler handler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ Editor ed = settings.edit();
+ switch (msg.what) {
+ case MSG_CONNECT_START:
+ ed.putBoolean("isConnecting", true);
+
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK
+ | PowerManager.ON_AFTER_RELEASE, "GAEProxy");
+
+ mWakeLock.acquire();
+
+ break;
+ case MSG_CONNECT_FINISH:
+ ed.putBoolean("isConnecting", false);
+
+ if (mWakeLock != null && mWakeLock.isHeld())
+ mWakeLock.release();
+
+ break;
+ case MSG_CONNECT_SUCCESS:
+ ed.putBoolean("isRunning", true);
+ break;
+ case MSG_CONNECT_FAIL:
+ ed.putBoolean("isRunning", false);
+ break;
+ case MSG_HOST_CHANGE:
+ ed.putString("appHost", appHost);
+ break;
+ case MSG_STOP_SELF:
+ stopSelf();
+ break;
+ }
+ ed.commit();
+ super.handleMessage(msg);
+ }
+ };
+
+ public void startShadowsocksDaemon() {
+ final String cmd = String.format(BASE
+ + "shadowsocks \"%s\" \"%d\" \"%d\" \"%s\"",
+ appHost, remotePort, port, sitekey);
+ Utils.runRootCommand(cmd);
+ }
+
+ public void startDnsDaemon() {
+ final String cmd = BASE + "pdnsd -c " + BASE + "pdnsd.conf";
+ Utils.runRootCommand(cmd);
+ }
+
+ private String getVersionName() {
+ String version;
+ try {
+ PackageInfo pi = getPackageManager().getPackageInfo(
+ getPackageName(), 0);
+ version = pi.versionName;
+ } catch (PackageManager.NameNotFoundException e) {
+ version = "Package name not found";
+ }
+ return version;
+ }
+
+ public void handleCommand(Intent intent) {
+
+ if (intent == null) {
+ stopSelf();
+ return;
+ }
+
+ appHost = settings.getString("proxy", "127.0.0.1");
+ sitekey = settings.getString("sitekey", "default");
+ try {
+ remotePort = Integer.valueOf(settings.getString("remotePort", "1984"));
+ } catch (NumberFormatException ex) {
+ remotePort = 1984;
+ }
+ try {
+ port = Integer.valueOf(settings.getString("port", "1984"));
+ } catch (NumberFormatException ex) {
+ port = 1984;
+ }
+
+ isGlobalProxy = settings.getBoolean("isGlobalProxy", false);
+ isGFWList = settings.getBoolean("isGFWList", false);
+ isBypassApps = settings.getBoolean("isBypassApps", false);
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+
+ handler.sendEmptyMessage(MSG_CONNECT_START);
+
+ Log.d(TAG, "IPTABLES: " + Utils.getIptables());
+
+ // Test for Redirect Support
+ hasRedirectSupport = Utils.getHasRedirectSupport();
+
+ if (handleConnection()) {
+ // Connection and forward successful
+ notifyAlert(getString(R.string.forward_success),
+ getString(R.string.service_running));
+
+ handler.sendEmptyMessageDelayed(MSG_CONNECT_SUCCESS, 500);
+
+
+ } else {
+ // Connection or forward unsuccessful
+ notifyAlert(getString(R.string.forward_fail),
+ getString(R.string.service_failed));
+
+ stopSelf();
+ handler.sendEmptyMessageDelayed(MSG_CONNECT_FAIL, 500);
+ }
+
+ handler.sendEmptyMessageDelayed(MSG_CONNECT_FINISH, 500);
+
+ }
+ }).start();
+ markServiceStarted();
+ }
+
+ /**
+ * Called when the activity is first created.
+ */
+ public boolean handleConnection() {
+
+ startShadowsocksDaemon();
+ startDnsDaemon();
+ setupIptables();
+
+ return true;
+ }
+
+ private void initSoundVibrateLights(Notification notification) {
+ notification.sound = null;
+ notification.defaults |= Notification.DEFAULT_LIGHTS;
+ }
+
+ void invokeMethod(Method method, Object[] args) {
+ try {
+ method.invoke(this, mStartForegroundArgs);
+ } catch (InvocationTargetException e) {
+ // Should not happen.
+ Log.w(TAG, "Unable to invoke method", e);
+ } catch (IllegalAccessException e) {
+ // Should not happen.
+ Log.w(TAG, "Unable to invoke method", e);
+ }
+ }
+
+ private void markServiceStarted() {
+ sRunningInstance = new WeakReference(this);
+ }
+
+ private void markServiceStopped() {
+ sRunningInstance = null;
+ }
+
+ private void notifyAlert(String title, String info) {
+ notification.icon = R.drawable.ic_stat_gaeproxy;
+ notification.tickerText = title;
+ notification.flags = Notification.FLAG_ONGOING_EVENT;
+ initSoundVibrateLights(notification);
+ // notification.defaults = Notification.DEFAULT_SOUND;
+ notification.setLatestEventInfo(this, getString(R.string.app_name),
+ info, pendIntent);
+ startForegroundCompat(1, notification);
+ }
+
+ private void notifyAlert(String title, String info, int flags) {
+ notification.icon = R.drawable.ic_stat_gaeproxy;
+ notification.tickerText = title;
+ notification.flags = flags;
+ initSoundVibrateLights(notification);
+ notification.setLatestEventInfo(this, getString(R.string.app_name),
+ info, pendIntent);
+ notificationManager.notify(0, notification);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ EasyTracker.getTracker().trackEvent("service", "start",
+ getVersionName(), 0L);
+
+ settings = PreferenceManager.getDefaultSharedPreferences(this);
+ notificationManager = (NotificationManager) this
+ .getSystemService(NOTIFICATION_SERVICE);
+
+ Intent intent = new Intent(this, Shadowsocks.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ pendIntent = PendingIntent.getActivity(this, 0, intent, 0);
+ notification = new Notification();
+
+ try {
+ mStartForeground = getClass().getMethod("startForeground",
+ mStartForegroundSignature);
+ mStopForeground = getClass().getMethod("stopForeground",
+ mStopForegroundSignature);
+ } catch (NoSuchMethodException e) {
+ // Running on an older platform.
+ mStartForeground = mStopForeground = null;
+ }
+
+ try {
+ mSetForeground = getClass().getMethod("setForeground",
+ mSetForegroundSignature);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalStateException(
+ "OS doesn't have Service.startForeground OR Service.setForeground!");
+ }
+ }
+
+ /**
+ * Called when the activity is closed.
+ */
+ @Override
+ public void onDestroy() {
+
+ EasyTracker.getTracker().trackEvent("service", "stop",
+ getVersionName(), 0L);
+
+ stopForegroundCompat(1);
+
+ notifyAlert(getString(R.string.forward_stop),
+ getString(R.string.service_stopped),
+ Notification.FLAG_AUTO_CANCEL);
+
+ try {
+ if (httpOS != null) {
+ httpOS.close();
+ httpOS = null;
+ }
+ if (httpProcess != null) {
+ httpProcess.destroy();
+ httpProcess = null;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "HTTP Server close unexpected");
+ }
+
+ new Thread() {
+ @Override
+ public void run() {
+
+ // Make sure the connection is closed, important here
+ onDisconnect();
+
+ }
+ }.start();
+
+ Editor ed = settings.edit();
+ ed.putBoolean("isRunning", false);
+ ed.putBoolean("isConnecting", false);
+ ed.commit();
+
+ try {
+ notificationManager.cancel(0);
+ } catch (Exception ignore) {
+ // Nothing
+ }
+
+ super.onDestroy();
+
+ markServiceStopped();
+ }
+
+ private void onDisconnect() {
+ Utils.runRootCommand(Utils.getIptables() + " -t nat -F OUTPUT");
+ StringBuilder sb = new StringBuilder();
+ sb.append("kill -9 `cat /data/data/com.github.shadowsocks/pdnsd.pid`").append("\n");
+ sb.append("kill -9 `cat /data/data/com.github.shadowsocks/redsocks.pid`").append("\n");
+ sb.append("kill -9 `cat /data/data/com.github.shadowsocks/shadowsocks.pid`").append("\n");
+ Utils.runRootCommand(sb.toString());
+ }
+
+ // This is the old onStart method that will be called on the pre-2.0
+ // platform. On 2.0 or later we override onStartCommand() so this
+ // method will not be called.
+ @Override
+ public void onStart(Intent intent, int startId) {
+
+ handleCommand(intent);
+
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ handleCommand(intent);
+ // We want this service to continue running until it is explicitly
+ // stopped, so return sticky.
+ return START_STICKY;
+ }
+
+ private boolean setupIptables() {
+
+ StringBuilder init_sb = new StringBuilder();
+
+ StringBuilder http_sb = new StringBuilder();
+
+ StringBuilder https_sb = new StringBuilder();
+
+ init_sb.append(Utils.getIptables()).append(" -t nat -F OUTPUT\n");
+
+ if (hasRedirectSupport) {
+ init_sb.append(Utils.getIptables()).append(" -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to ").append(DNS_PORT).append("\n");
+ } else {
+ init_sb.append(Utils.getIptables()).append(" -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:").append(DNS_PORT).append("\n");
+ }
+
+ String cmd_bypass = Utils.getIptables() + CMD_IPTABLES_RETURN;
+
+ init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "--dport " + remotePort));
+
+ init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner "
+ + getApplicationInfo().uid));
+
+ if (isGFWList) {
+ String[] chn_list = getResources().getStringArray(R.array.chn_list);
+
+ for (String item : chn_list) {
+ init_sb.append(cmd_bypass.replace("0.0.0.0", item));
+ }
+ }
+ if (isGlobalProxy || isBypassApps) {
+ http_sb.append(hasRedirectSupport ? Utils.getIptables()
+ + CMD_IPTABLES_REDIRECT_ADD_HTTP : Utils.getIptables()
+ + CMD_IPTABLES_DNAT_ADD_HTTP);
+ https_sb.append(hasRedirectSupport ? Utils.getIptables()
+ + CMD_IPTABLES_REDIRECT_ADD_HTTPS : Utils.getIptables()
+ + CMD_IPTABLES_DNAT_ADD_HTTPS);
+ }
+ if (!isGlobalProxy) {
+ // for proxy specified apps
+ if (apps == null || apps.length <= 0)
+ apps = AppManager.getProxyedApps(this);
+
+ HashSet uidSet = new HashSet();
+ for (ProxyedApp app : apps) {
+ if (app.isProxyed()) {
+ uidSet.add(app.getUid());
+ }
+ }
+ for (int uid : uidSet) {
+ if (!isBypassApps) {
+ http_sb.append((hasRedirectSupport ? Utils.getIptables()
+ + CMD_IPTABLES_REDIRECT_ADD_HTTP : Utils.getIptables()
+ + CMD_IPTABLES_DNAT_ADD_HTTP).replace("-t nat",
+ "-t nat -m owner --uid-owner " + uid));
+ https_sb.append((hasRedirectSupport ? Utils.getIptables()
+ + CMD_IPTABLES_REDIRECT_ADD_HTTPS : Utils.getIptables()
+ + CMD_IPTABLES_DNAT_ADD_HTTPS).replace("-t nat",
+ "-t nat -m owner --uid-owner " + uid));
+ } else {
+ init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + uid));
+ }
+ }
+ }
+
+ String init_rules = init_sb.toString();
+ Utils.runRootCommand(init_rules, 30 * 1000);
+
+ String redt_rules = http_sb.toString();
+
+ redt_rules += https_sb.toString();
+
+ Utils.runRootCommand(redt_rules);
+
+ return true;
+
+ }
+
+ /**
+ * This is a wrapper around the new startForeground method, using the older
+ * APIs if it is not available.
+ */
+ void startForegroundCompat(int id, Notification notification) {
+ // If we have the new startForeground API, then use it.
+ if (mStartForeground != null) {
+ mStartForegroundArgs[0] = id;
+ mStartForegroundArgs[1] = notification;
+ invokeMethod(mStartForeground, mStartForegroundArgs);
+ return;
+ }
+
+ // Fall back on the old API.
+ mSetForegroundArgs[0] = Boolean.TRUE;
+ invokeMethod(mSetForeground, mSetForegroundArgs);
+ notificationManager.notify(id, notification);
+ }
+
+ /**
+ * This is a wrapper around the new stopForeground method, using the older
+ * APIs if it is not available.
+ */
+ void stopForegroundCompat(int id) {
+ // If we have the new stopForeground API, then use it.
+ if (mStopForeground != null) {
+ mStopForegroundArgs[0] = Boolean.TRUE;
+ try {
+ mStopForeground.invoke(this, mStopForegroundArgs);
+ } catch (InvocationTargetException e) {
+ // Should not happen.
+ Log.w(TAG, "Unable to invoke stopForeground", e);
+ } catch (IllegalAccessException e) {
+ // Should not happen.
+ Log.w(TAG, "Unable to invoke stopForeground", e);
+ }
+ return;
+ }
+
+ // Fall back on the old API. Note to cancel BEFORE changing the
+ // foreground state, since we could be killed at that point.
+ notificationManager.cancel(id);
+ mSetForegroundArgs[0] = Boolean.FALSE;
+ invokeMethod(mSetForeground, mSetForegroundArgs);
+ }
+
+}
diff --git a/src/com/github/shadowsocks/Utils.java b/src/com/github/shadowsocks/Utils.java
new file mode 100755
index 00000000..5470ce6a
--- /dev/null
+++ b/src/com/github/shadowsocks/Utils.java
@@ -0,0 +1,427 @@
+package com.github.shadowsocks;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.*;
+import java.util.ArrayList;
+
+public class Utils {
+
+ /**
+ * Internal thread used to execute scripts (as root or not).
+ */
+ private static final class ScriptRunner extends Thread {
+ private final File file;
+ private final String script;
+ private final StringBuilder res;
+ private final boolean asroot;
+ public int exitcode = -1;
+ // private Process exec;
+ private int mProcId;
+ private FileDescriptor mTermFd;
+
+ /**
+ * Creates a new script runner.
+ *
+ * @param file temporary script file
+ * @param script script to run
+ * @param res response output
+ * @param asroot if true, executes the script as root
+ */
+ public ScriptRunner(File file, String script, StringBuilder res,
+ boolean asroot) {
+ this.file = file;
+ this.script = script;
+ this.res = res;
+ this.asroot = asroot;
+ }
+
+ private int createSubprocess(int[] processId, String cmd) {
+ ArrayList argList = parse(cmd);
+ String arg0 = argList.get(0);
+ String[] args = argList.toArray(new String[1]);
+
+ mTermFd = Exec.createSubprocess(arg0, args, null, processId);
+ return processId[0];
+ }
+
+ /**
+ * Destroy this script runner
+ */
+ @Override
+ public synchronized void destroy() {
+ try {
+ Exec.hangupProcessGroup(mProcId);
+ Exec.close(mTermFd);
+ } catch (NoClassDefFoundError ignore) {
+ // Nothing
+ }
+ }
+
+ private ArrayList parse(String cmd) {
+ final int PLAIN = 0;
+ final int WHITESPACE = 1;
+ final int INQUOTE = 2;
+ int state = WHITESPACE;
+ ArrayList result = new ArrayList();
+ int cmdLen = cmd.length();
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < cmdLen; i++) {
+ char c = cmd.charAt(i);
+ if (state == PLAIN) {
+ if (Character.isWhitespace(c)) {
+ result.add(builder.toString());
+ builder.delete(0, builder.length());
+ state = WHITESPACE;
+ } else if (c == '"') {
+ state = INQUOTE;
+ } else {
+ builder.append(c);
+ }
+ } else if (state == WHITESPACE) {
+ if (Character.isWhitespace(c)) {
+ // do nothing
+ } else if (c == '"') {
+ state = INQUOTE;
+ } else {
+ state = PLAIN;
+ builder.append(c);
+ }
+ } else if (state == INQUOTE) {
+ if (c == '\\') {
+ if (i + 1 < cmdLen) {
+ i += 1;
+ builder.append(cmd.charAt(i));
+ }
+ } else if (c == '"') {
+ state = PLAIN;
+ } else {
+ builder.append(c);
+ }
+ }
+ }
+ if (builder.length() > 0) {
+ result.add(builder.toString());
+ }
+ return result;
+ }
+
+ @Override
+ public void run() {
+ try {
+ new File(DEFOUT_FILE).createNewFile();
+ file.createNewFile();
+ final String abspath = file.getAbsolutePath();
+
+ // TODO: Rewrite this line
+ // make sure we have execution permission on the script file
+ // Runtime.getRuntime().exec("chmod 755 " + abspath).waitFor();
+
+ // Write the script to be executed
+ final OutputStreamWriter out = new OutputStreamWriter(
+ new FileOutputStream(file));
+ out.write("#!/system/bin/sh\n");
+ out.write(script);
+ if (!script.endsWith("\n"))
+ out.write("\n");
+ out.write("exit\n");
+ out.flush();
+ out.close();
+
+ if (this.asroot) {
+ // Create the "su" request to run the script
+ // exec = Runtime.getRuntime().exec(
+ // root_shell + " -c " + abspath);
+
+ int pid[] = new int[1];
+ mProcId = createSubprocess(pid, root_shell + " -c "
+ + abspath);
+ } else {
+ // Create the "sh" request to run the script
+ // exec = Runtime.getRuntime().exec(getShell() + " " +
+ // abspath);
+
+ int pid[] = new int[1];
+ mProcId = createSubprocess(pid, getShell() + " " + abspath);
+ }
+
+ final InputStream stdout = new FileInputStream(DEFOUT_FILE);
+ final byte buf[] = new byte[8192];
+ int read = 0;
+
+ exitcode = Exec.waitFor(mProcId);
+
+ // Read stdout
+ while (stdout.available() > 0) {
+ read = stdout.read(buf);
+ if (res != null)
+ res.append(new String(buf, 0, read));
+ }
+
+ } catch (Exception ex) {
+ if (res != null)
+ res.append("\n" + ex);
+ } finally {
+ destroy();
+ }
+ }
+ }
+
+ public final static String TAG = "Shadowsocks";
+
+ public final static String DEFAULT_SHELL = "/system/bin/sh";
+ public final static String DEFAULT_ROOT = "/system/bin/su";
+
+ public final static String ALTERNATIVE_ROOT = "/system/xbin/su";
+ public final static String DEFAULT_IPTABLES = "/data/data/com.github.shadowsocks/iptables";
+ public final static String ALTERNATIVE_IPTABLES = "/system/bin/iptables";
+ public final static String SCRIPT_FILE = "/data/data/com.github.shadowsocks/script";
+ public final static String DEFOUT_FILE = "/data/data/com.github.shadowsocks/defout";
+
+ public final static int TIME_OUT = -99;
+ private static boolean initialized = false;
+ private static int hasRedirectSupport = -1;
+
+ private static int isRoot = -1;
+ private static String shell = null;
+ private static String root_shell = null;
+ private static String iptables = null;
+
+ private static String data_path = null;
+
+ private static void checkIptables() {
+
+ if (!isRoot()) {
+ iptables = DEFAULT_IPTABLES;
+ return;
+ }
+
+ // Check iptables binary
+ iptables = DEFAULT_IPTABLES;
+
+ String lines = null;
+
+ boolean compatible = false;
+ boolean version = false;
+
+ StringBuilder sb = new StringBuilder();
+ String command = iptables + " --version\n" + iptables
+ + " -L -t nat -n\n" + "exit\n";
+
+ int exitcode = runScript(command, sb, 10 * 1000, true);
+
+ if (exitcode == TIME_OUT)
+ return;
+
+ lines = sb.toString();
+
+ if (lines.contains("OUTPUT")) {
+ compatible = true;
+ }
+ if (lines.contains("v1.4.")) {
+ version = true;
+ }
+
+ if (!compatible || !version) {
+ iptables = ALTERNATIVE_IPTABLES;
+ if (!new File(iptables).exists())
+ iptables = "iptables";
+ }
+
+ }
+
+ public static Drawable getAppIcon(Context c, int uid) {
+ PackageManager pm = c.getPackageManager();
+ Drawable appIcon = c.getResources().getDrawable(
+ android.R.drawable.sym_def_app_icon);
+ String[] packages = pm.getPackagesForUid(uid);
+
+ if (packages != null) {
+ if (packages.length == 1) {
+ try {
+ ApplicationInfo appInfo = pm.getApplicationInfo(
+ packages[0], 0);
+ appIcon = pm.getApplicationIcon(appInfo);
+ } catch (NameNotFoundException e) {
+ Log.e(c.getPackageName(),
+ "No package found matching with the uid " + uid);
+ }
+ }
+ } else {
+ Log.e(c.getPackageName(), "Package not found for uid " + uid);
+ }
+
+ return appIcon;
+ }
+
+ public static String getDataPath(Context ctx) {
+
+ if (data_path == null) {
+
+ if (Environment.MEDIA_MOUNTED.equals(Environment
+ .getExternalStorageState())) {
+ data_path = Environment.getExternalStorageDirectory()
+ .getAbsolutePath();
+ } else {
+ data_path = ctx.getFilesDir().getAbsolutePath();
+ }
+
+ Log.d(TAG, "Python Data Path: " + data_path);
+ }
+
+ return data_path;
+ }
+
+ public static boolean getHasRedirectSupport() {
+ if (hasRedirectSupport == -1)
+ initHasRedirectSupported();
+ return hasRedirectSupport == 1 ? true : false;
+ }
+
+ public static String getIptables() {
+ if (iptables == null)
+ checkIptables();
+ return iptables;
+ }
+
+ private static String getShell() {
+ if (shell == null) {
+ shell = DEFAULT_SHELL;
+ if (!new File(shell).exists())
+ shell = "sh";
+ }
+ return shell;
+ }
+
+ public static void initHasRedirectSupported() {
+
+ if (!Utils.isRoot())
+ return;
+
+ StringBuilder sb = new StringBuilder();
+ String command = Utils.getIptables()
+ + " -t nat -A OUTPUT -p udp --dport 54 -j REDIRECT --to 8154";
+
+ int exitcode = runScript(command, sb, 10 * 1000, true);
+
+ String lines = sb.toString();
+
+ hasRedirectSupport = 1;
+
+ // flush the check command
+ Utils.runRootCommand(command.replace("-A", "-D"));
+
+ if (exitcode == TIME_OUT)
+ return;
+
+ if (lines.contains("No chain/target/match")) {
+ hasRedirectSupport = 0;
+ }
+ }
+
+ public static boolean isInitialized() {
+ if (initialized)
+ return true;
+ else {
+ initialized = true;
+ return false;
+ }
+
+ }
+
+ public static boolean isRoot() {
+
+ if (isRoot != -1)
+ return isRoot == 1 ? true : false;
+
+ // switch between binaries
+ if (new File(DEFAULT_ROOT).exists()) {
+ root_shell = DEFAULT_ROOT;
+ } else if (new File(ALTERNATIVE_ROOT).exists()) {
+ root_shell = ALTERNATIVE_ROOT;
+ } else {
+ root_shell = "su";
+ }
+
+ String lines = null;
+
+ StringBuilder sb = new StringBuilder();
+ String command = "ls /\n" + "exit\n";
+
+ int exitcode = runScript(command, sb, 10 * 1000, true);
+
+ if (exitcode == TIME_OUT) {
+ return false;
+ }
+
+ lines = sb.toString();
+
+ if (lines.contains("system")) {
+ isRoot = 1;
+ }
+
+ return isRoot == 1 ? true : false;
+ }
+
+ public static boolean runCommand(String command) {
+
+ return runCommand(command, 10 * 1000);
+
+ }
+
+ public static boolean runCommand(String command, int timeout) {
+
+ Log.d(TAG, command);
+
+ runScript(command, null, timeout, false);
+
+ return true;
+ }
+
+ public static boolean runRootCommand(String command) {
+
+ return runRootCommand(command, 10 * 1000);
+
+ }
+
+ public static boolean runRootCommand(String command, int timeout) {
+
+ if (!isRoot())
+ return false;
+
+ Log.d(TAG, command);
+
+ runScript(command, null, timeout, true);
+
+ return true;
+ }
+
+ private synchronized static int runScript(String script, StringBuilder res,
+ long timeout, boolean asroot) {
+ final File file = new File(SCRIPT_FILE);
+ final ScriptRunner runner = new ScriptRunner(file, script, res, asroot);
+ runner.start();
+ try {
+ if (timeout > 0) {
+ runner.join(timeout);
+ } else {
+ runner.join();
+ }
+ if (runner.isAlive()) {
+ // Timed-out
+ runner.destroy();
+ runner.join(1000);
+ return TIME_OUT;
+ }
+ } catch (InterruptedException ex) {
+ return TIME_OUT;
+ }
+ return runner.exitcode;
+ }
+}
diff --git a/travis.keystore b/travis.keystore
new file mode 100755
index 00000000..fb6d8520
Binary files /dev/null and b/travis.keystore differ