Commit 2e0a6fc6 authored by Max Lv's avatar Max Lv

almost works

parent 9c1a28b1
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/
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.shadowsocks"
android:installLocation="auto"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false"/>
<uses-sdk
android:minSdkVersion="7"
android:targetSdkVersion="17"/>
<application
android:name=".ShadowsocksApplication"
android:hardwareAccelerated="true"
android:icon="@drawable/icon"
android:label="@string/app_name">
<activity
android:name=".Shadowsocks"
android:label="@string/app_name"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".AppManager"
android:label="@string/app_name"/>
<service
android:name=".ShadowsocksService"
android:enabled="true"/>
<receiver android:name=".ShadowsocksReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>
File added
File added
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;
}
File added
......@@ -24,18 +24,15 @@ LOCAL_SRC_FILES := \
include $(BUILD_STATIC_LIBRARY)
#include $(CLEAR_VARS)
#PDNSD_SOURCES := $(wildcard $(LOCAL_PATH)/pdnsd/*.c)
#LOCAL_MODULE := pdnsd
#LOCAL_SRC_FILES := $(PDNSD_SOURCES:$(LOCAL_PATH)%=%)
include $(CLEAR_VARS)
#LOCAL_CFLAGS := -O3
PDNSD_SOURCES := $(wildcard $(LOCAL_PATH)/pdnsd/*.c)
#LOCAL_STATIC_LIBRARIES :=
LOCAL_MODULE := pdnsd
LOCAL_SRC_FILES := $(PDNSD_SOURCES:$(LOCAL_PATH)%=%)
LOCAL_CFLAGS := -Wall -O2 -I$(LOCAL_PATH)/pdnsd
#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, \
......
......@@ -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);
......
......@@ -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
......
......@@ -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"
......
......@@ -18,7 +18,6 @@
#include <unistd.h>
#include <linux/limits.h>
#include <android/log.h>
#include <jni.h>
#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;
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;
}
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");
}
close(listen_sock);
}
if (rp == NULL) {
fprintf(stderr, "Could not bind\n");
return -1;
}
freeaddrinfo(result);
return 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 */
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;
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);
}
if (rp == NULL) {
fprintf(stderr, "Could not bind\n");
return -1;
}
freeaddrinfo(result);
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);
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;
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;
}
/*
* 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 <android/log.h>
#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 <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <signal.h>
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<jstring>(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<jstring>(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, "<init>", "()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;
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.shadowsocks</groupId>
<artifactId>shadowsocks</artifactId>
<version>1.0</version>
<packaging>apk</packaging>
<name>Shadowsocks</name>
<repositories>
<repository>
<id>madeye-maven-repository</id>
<url>http://madeye-maven-repository.googlecode.com/git</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.google.android</groupId>
<artifactId>android</artifactId>
<version>4.1.1.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.android.analytics</groupId>
<artifactId>analytics</artifactId>
<version>V2</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.5</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<sourceDirectory>src</sourceDirectory>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-jarsigner-plugin</artifactId>
<version>1.2</version>
</plugin>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
<version>3.4.1</version>
<extensions>true</extensions>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.12.4</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jarsigner-plugin</artifactId>
<executions>
<execution>
<id>signing</id>
<goals>
<goal>sign</goal>
</goals>
<phase>package</phase>
<inherited>true</inherited>
<configuration>
<archiveDirectory/>
<includes>
<include>${project.build.directory}/${project.artifactId}.apk</include>
</includes>
<keystore>${sign.keystore}</keystore>
<alias>${sign.alias}</alias>
<storepass>${sign.storepass}</storepass>
<keypass>${sign.keypass}</keypass>
<verbose>true</verbose>
<arguments>
<argument>-sigalg</argument><argument>MD5withRSA</argument>
<argument>-digestalg</argument><argument>SHA1</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>android-maven-plugin</artifactId>
<executions>
<execution>
<id>alignApk</id>
<phase>install</phase>
<goals>
<goal>zipalign</goal>
</goals>
</execution>
</executions>
<extensions>true</extensions>
<configuration>
<release>true</release>
<sign>
<debug>false</debug>
</sign>
<zipalign>
<verbose>true</verbose>
<skip>false</skip>
<inputApk>${project.build.directory}/${project.artifactId}.apk</inputApk>
<outputApk>${project.build.directory}/${project.artifactId}-${project.version}.apk
</outputApk>
</zipalign>
<sdk>
<platform>16</platform>
</sdk>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>debug-sign</id>
<activation>
<file>
<missing>${basedir}/local.properties</missing>
</file>
</activation>
<properties>
<sign.keystore>${basedir}/travis.keystore</sign.keystore>
<sign.alias>travis</sign.alias>
<sign.keypass>travis</sign.keypass>
<sign.storepass>travis</sign.storepass>
</properties>
</profile>
<profile>
<id>release-sign</id>
<activation>
<file>
<exists>${basedir}/local.properties</exists>
</file>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>properties-maven-plugin</artifactId>
<version>1.0-alpha-2</version>
<executions>
<execution>
<phase>initialize</phase>
<goals>
<goal>read-project-properties</goal>
</goals>
<configuration>
<files>
<file>${basedir}/local.properties</file>
</files>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>travis</id>
<activation>
<property>
<name>TRAVIS</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>deploy-to-sae</id>
<phase>install</phase>
<goals>
<goal>exec</goal>
</goals>
</execution>
</executions>
<configuration>
<executable>${basedir}/deploy</executable>
<arguments>
<argument>${project.build.directory}</argument>
<argument>${project.artifactId}-${project.version}.apk</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:gravity="center">
<ImageView android:layout_height="wrap_content"
android:layout_width="wrap_content" android:id="@+id/serviceToggle"
android:src="@drawable/off" android:layout_gravity="center"></ImageView>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="fill_parent"
android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:duplicateParentState="false">
<TextView android:text="@string/proxyed_help"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:textSize="16sp" android:padding="3px" />
<ListView android:layout_width="fill_parent"
android:layout_height="wrap_content" android:id="@+id/applistview"></ListView>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:stretchColumns="1">
<TableRow>
<ImageView android:id="@+id/itemicon" android:layout_width="48sp"
android:layout_height="48sp" android:scaleType="fitCenter"
android:padding="3dip"></ImageView>
<TextView android:layout_height="wrap_content" android:id="@+id/itemtext"
android:text="uid:packages" android:maxLength="25" android:textSize="18sp" android:padding="3dip"></TextView>
<CheckBox android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/itemcheck"></CheckBox>
</TableRow>
</TableLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ImageView android:id="@+id/image" android:layout_width="wrap_content"
android:layout_height="fill_parent" android:layout_marginLeft="10dp"
android:layout_marginRight="10dp" android:src="@drawable/icon" />
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical">
<TextView android:id="@+id/down_tv" android:layout_width="wrap_content"
android:layout_height="fill_parent" android:textSize="14sp"
android:paddingBottom="4dp" android:text="@string/downloading" />
<ProgressBar android:id="@+id/pb" android:layout_width="200dp"
android:layout_height="wrap_content" style="?android:attr/progressBarStyleHorizontal" />
</LinearLayout>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:myapp="http://schemas.android.com/apk/res/v.sched.quite"
android:id="@+id/mainLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:drawSelectorOnTop="false"
android:scrollbarAlwaysDrawVerticalTrack="true" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="80dp" android:maxWidth="80dp" android:gravity="center"
android:textSize="70sp" android:textColor="#ffffffff"
android:background="#99000088" android:padding="10dp"
android:visibility="invisible" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="service_running">正通过Shadowsocks连接到互联网</string>
<string name="app_name">Shadowsocks</string>
<string name="forward_success">Shadowsocks启用成功</string>
<string name="forward_fail">Shadowsocks启用失败</string>
<string name="service_stopped">Shadowsocks服务已停止</string>
<string name="auto_reconnected">恢复连接中</string>
<string name="reconnect_success">恢复连接成功</string>
<string name="already_stopped">服务已关闭</string>
<string name="already_running">服务已启动</string>
<string name="forward_stop">Shadowsocks连接已关闭</string>
<string name="service_failed">请检查你的网络状况和账户信息</string>
<string name="reconnect_fail">恢复连接失败</string>
<string name="ok_iknow">我知道了</string>
<string name="proxy">代理地址</string>
<string name="port">本地端口</string>
<string name="sitekey">密钥</string>
<string name="auto_connect">自动连接</string>
<string name="auto_reconnect">自动恢复连接</string>
<string name="auto_set_proxy">全局代理</string>
<string name="disconnect">关闭服务</string>
<string name="connect">启动服务</string>
<string name="feedback">反馈 @ofmax</string>
<string name="proxy_cat">Shadowsocks设置</string>
<string name="fearute_cat">功能设置</string>
<string name="function_cat">后台服务</string>
<string name="service_controller">代理开关</string>
<string name="service_summary">开启 / 关闭后台服务</string>
<string name="proxy_summary">Shadowsocks服务地址</string>
<string name="port_summary">本地代理服务器监听端口,任意大于1024的数字</string>
<string name="sitekey_summary">加密密钥</string>
<string name="port_alert">端口必需是大于1024的数字</string>
<string name="auto_connect_summary">启动时自动打开代理服务</string>
<string name="auto_set_proxy_summary">配置全局代理,需要 ROOT 权限以及 IPTABLES 的支持</string>
<string name="port_empty">端口不能为空</string>
<string name="proxy_empty">代理地址不能为空</string>
<string name="install_cat">模块管理</string>
<string name="isInstalled">安装/卸载</string>
<string name="isInstalled_summary">安装/卸载依赖模块,请确认SD卡已插入,并连接到互联网</string>
<string name="download">下载中...</string>
<string name="downloading">下载中...(100%处请耐心等待)</string>
<string name="install_alert">请先安装依赖模块,滑动到底部,选择安装</string>
<string name="sdcard_alert">请确认SD卡已插入</string>
<string name="recovery">重置</string>
<string name="unzip">解压缩中...</string>
<string name="proxyed_apps">分应用代理</string>
<string name="proxyed_apps_summary">为应用单独设置代理,需要 ROOT 权限以及 IPTABLES 的支持</string>
<string name="bypass_apps">白名单模式</string>
<string name="bypass_apps_summary">选择的应用将不通过代理</string>
<string name="proxyed_help">选择使用Shadowsocks的应用:</string>
<string name="crash_alert">检测到一次非正常退出,状态已重置</string>
<string name="copy_rights">Shadowsocks是一款开源软件,依照GPLv3协议发布。\n\n如果您有任何问题,请前往项目网站进行反馈。
\n\n(gaeproxy.googlecode.com)</string>
<string name="about">关于</string>
<string name="proxy_type">代理类型</string>
<string name="connecting">正在连接...</string>
<string name="initializing">正在初始化...</string>
<string name="recovering">正在重置...</string>
<string name="https_alert">请修改你的代理地址,以 "https://" 开头</string>
<string name="https_proxy">HTTPS代理</string>
<string name="https_proxy_summary">重定向HTTPS流量,若遇到反复尝试无法连接的情况,请关闭此选项</string>
<!-- settings notification category -->
<string name="notif_cat">通知设置</string>
<string name="notif_ringtone_title">铃声</string>
<string name="notif_ringtone_summary">选择通知的铃声</string>
<string name="notif_vibrate_title">震动</string>
<string name="notif_vibrate_summary">在连接状态改变时震动</string>
<string name="loading">正在加载应用列表...</string>
<string name="enable_market">电子市场代理</string>
<string name="enable_market_summary">帮助大陆用户从电子市场中获得程序更新(需要重启)</string>
<string name="auto_set_gfwlist">国内路由</string>
<string name="auto_set_gfwlist_summary">访问国内站点时忽略本地代理(实验性)</string>
<string name="toast_start">Shadowsocks正在启动</string>
<string name="toast_restart">Shadowsocks正在重新启动</string>
<string name="browser">内置浏览器</string>
<string name="browser_summary">启动内置代理浏览器, 只针对没有ROOT权限或IPTABLES支持的用户</string>
<string name="default_proxy_alert">您正在使用默认的代理地址,此地址每天只有10G的带宽配额,也就说您将随时无法正常使用本应用\n\n因此,请尽快部署自己的服务器端,具体步骤和说明见:http://code.google.com/p/goagent/</string>
<string name="warning">警告</string>
<string name="system_proxy">系统代理</string>
<string name="system_proxy_summary">设置系统代理,无需ROOT权限,仅工作在Android 4.0及以上系统,并仅对当前Wifi连接生效</string>
<!--
Zirco Browser for Android Copyright (C) 2010 - 2011 J. Devauchelle
and contributors. This program is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License version
3 as published by the Free Software Foundation. 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.
-->
<string name="ApplicationName">Zirco Browser</string>
<string name="ApplicationNameUrl">Zirco Browser - %s</string>
<string name="ApplicationDescription">使用Zirco Browser让你更好的使用互联网</string>
<string name="AboutActivity.Title">关于</string>
<string name="AboutActivity.LicenseText">版权:</string>
<string name="AboutActivity.UrlTextValue">http://code.google.com/p/zirco-browser/</string>
<string name="AboutActivity.LicenseTextValue">GPL v3</string>
<string name="Main.MenuAddBookmark">添加书签</string>
<string name="Main.MenuShowBookmarks">书签</string>
<string name="Main.MenuShowHistory">历史</string>
<string name="Main.MenuShowDownloads">下载</string>
<string name="Main.MenuPreferences">配置</string>
<string name="Main.MenuOpen">打开</string>
<string name="Main.MenuOpenNewTab">在新页中打开</string>
<string name="Main.MenuDownload">下载</string>
<string name="Main.DownloadStartedMsg">下载开始</string>
<string name="Main.DownloadFinishedMsg">下载结束</string>
<string name="Main.DownloadErrorMsg">下载错误: %s</string>
<string name="Main.ToastTabSwitchMessage">页 %s</string>
<string name="Main.ToastTabSwitchFullMessage">页 %1$s: %2$s</string>
<string name="Main.VndErrorTitle">不能打开网址</string>
<string name="Main.VndErrorMessage">本程序不支持这个网址: %s.</string>
<string name="BookmarksListActivity.Title">书签</string>
<string name="BookmarksListActivity.MenuSortMode">排序方式</string>
<string name="BookmarksListActivity.MenuAddBookmark">增加书签</string>
<string name="BookmarksListActivity.Empty">没有书签</string>
<string name="BookmarksListActivity.MenuOpenInTab">打开新页</string>
<string name="BookmarksListActivity.MenuEditBookmark">编辑书签</string>
<string name="BookmarksListActivity.MenuDeleteBookmark">删除书签</string>
<string name="BookmarksListActivity.ImportBookmarks">导入书签</string>
<string name="BookmarksListActivity.ExportBookmarks">导出书签</string>
<string name="BookmarksListActivity.ClearBookmarks">清空所有书签</string>
<string name="BookmarksListActivity.ImportingBookmarks">正在导入书签中...</string>
<string name="BookmarksListActivity.ExportingBookmarks">重在导出书签中...</string>
<string name="BookmarksListActivity.ClearingBookmarks">正在清空书签中...</string>
<string name="BookmarksListActivity.AlphaSortMode">顺序</string>
<string name="BookmarksListActivity.RecentSortMode">倒序</string>
<string name="BookmarksListActivity.ImportSource">选择导入源</string>
<string name="BookmarksListActivity.AndroidImportSource">Android浏览器的书签</string>
<string name="BookmarksListActivity.ExportDialogTitle">选择导出目标</string>
<string name="EditBookmarkActivity.Title">编辑书签</string>
<string name="EditBookmarkActivity.TitleAdd">增加书签</string>
<string name="EditBookmarkActivity.TitleLabel">显示名称:</string>
<string name="EditBookmarkActivity.UrlLabel">地址Location:</string>
<string name="HistoryListActivity.Title">历史</string>
<string name="HistoryListActivity.MenuOpenInTab">打开新页</string>
<string name="HistoryListActivity.MenuDelete">删除</string>
<string name="DownloadListActivity.Title">Zirco Browser-下载</string>
<string name="DownloadListActivity.Empty">没有下载</string>
<string name="DownloadListActivity.Aborted">%s (取消)</string>
<string name="DownloadListActivity.Finished">%s (完成)</string>
<string name="AdBlockerWhiteListActivity.Title">Zirco Browser - Ad 白名单</string>
<string name="AdBlockerWhiteListActivity.Empty">白名单是空的.</string>
<string name="AdBlockerWhiteListActivity.AddMessage">添加至白名单</string>
<string name="AdBlockerWhiteListActivity.ClearMessage">清空白名单</string>
<string name="PreferencesActivity.GeneralSettingsCategoryTitle">通用设置</string>
<string name="PreferencesActivity.HomePagePreferenceTitle">主页设定</string>
<string name="PreferencesActivity.HomePagePreferenceSummary">定义你的默认主页</string>
<string name="PreferencesActivity.FullScreenPreferenceTitle">是否全屏幕</string>
<string name="PreferencesActivity.FullScreenPreferenceSummary">在全屏幕模式下显示 (需要重新启动程序).</string>
<string name="PreferencesActivity.ShowToastOnTabSwitchPreferenceTitle">页切换时是否显示对话框</string>
<string name="PreferencesActivity.ShowToastOnTabSwitchPreferenceSummary">如果选中,也切换时会有提示</string>
<string name="PreferencesActivity.BrowserSettingsCategoryTitle">浏览器设定</string>
<string name="PreferencesActivity.HistorySizePreferenceTitle">历史记录大小</string>
<string name="PreferencesActivity.HistorySizePreferenceSummary">每天历史记录大小</string>
<string name="PreferencesActivity.EnableJavascriptPreferenceTitle">是否支持JavaScript</string>
<string name="PreferencesActivity.EnableJavascriptPreferenceSummary">如果不支持JavaScript,很多网站效果不能打开,建议打开.</string>
<string name="PreferencesActivity.LoadImagesPreferenceTitle">是否加载图像</string>
<string name="PreferencesActivity.LoadImagesPreferenceSummary">使用或者不使用图像</string>
<string name="PreferencesActivity.PrivacyPreferenceTitle">隐私设定</string>
<string name="PreferencesActivity.PrivacyPreferenceSummary">管理你的上网痕迹,确保隐私</string>
<string name="PreferencesActivity.EnableFormDataPreferenceTitle">保存表单数据</string>
<string name="PreferencesActivity.EnableFormDataPreferenceSummary">可保存数据,减少你下次输入时间</string>
<string name="PreferencesActivity.EnablePasswordsPreferenceTitle">保存密码</string>
<string name="PreferencesActivity.EnablePasswordsPreferenceSummary">可保存密码,让你下次访问更加方便</string>
<string name="PreferencesActivity.EnableCookiesPreferenceTitle">允许cookie</string>
<string name="PreferencesActivity.EnableCookiesPreferenceSummary">是否允许Cookie</string>
<string name="PreferencesActivity.PrivacyClearHistoryTitle">清空历史</string>
<string name="PreferencesActivity.PrivacyClearFormDataTitle">清空表单数据</string>
<string name="PreferencesActivity.PrivacyClearCacheTitle">清空缓存</string>
<string name="PreferencesActivity.PrivacyClearCookiesTitle">清空cookies</string>
<string name="PreferencesActivity.BarDurationPreferenceTitle">工具条显示时间</string>
<string name="PreferencesActivity.BarDurationPreferenceSummary">显示工具条多长时间自动隐藏</string>
<string name="PreferencesActivity.BarDuration1sec">1 秒</string>
<string name="PreferencesActivity.BarDuration2sec">2 秒</string>
<string name="PreferencesActivity.BarDuration3sec">3 秒</string>
<string name="PreferencesActivity.BarDuration4sec">4 秒</string>
<string name="PreferencesActivity.BarDuration5sec">5 秒</string>
<string-array name="BarDurationEntries">
<item>@string/PreferencesActivity.BarDuration1sec</item>
<item>@string/PreferencesActivity.BarDuration2sec</item>
<item>@string/PreferencesActivity.BarDuration3sec</item>
<item>@string/PreferencesActivity.BarDuration4sec</item>
<item>@string/PreferencesActivity.BarDuration5sec</item>
</string-array>
<string-array name="BarDurationValues">
<item>1000</item>
<item>2000</item>
<item>3000</item>
<item>4000</item>
<item>5000</item>
</string-array>
<string name="PreferencesActivity.BubblePositionPreferenceTitle">工具条图标</string>
<string name="PreferencesActivity.BubblePositionPreferenceSummary">工具条图标位置</string>
<string name="PreferencesActivity.BubbleRight"></string>
<string name="PreferencesActivity.BubbleLeft"></string>
<string name="PreferencesActivity.BubbleBoth">两边都有</string>
<string-array name="BubblePositionEntries">
<item>@string/PreferencesActivity.BubbleRight</item>
<item>@string/PreferencesActivity.BubbleLeft</item>
<item>@string/PreferencesActivity.BubbleBoth</item>
</string-array>
<string-array name="BubblePositionValues">
<item>right</item>
<item>left</item>
<item>both</item>
</string-array>
<string name="PreferencesActivity.AdBlockerSettingsCategoryTitle">Ad广告设定</string>
<string name="PreferencesActivity.EnableAdBlockerPreferenceTitle">是否允许 Ad-blocker</string>
<string name="PreferencesActivity.EnableAdBlockerPreferenceSummary">Ad广告必须要求JavaScript能使用</string>
<string name="PreferencesActivity.AdBlockerWhiteListPreferenceTitle">白名单</string>
<string name="PreferencesActivity.AdBlockerWhiteListPreferenceSummary">管理 Ad广告白名单.</string>
<string name="Commons.Ok">确定</string>
<string name="Commons.Cancel">取消</string>
<string name="Commons.PleaseWait">请等待</string>
<string name="Commons.Yes"></string>
<string name="Commons.No"></string>
<string name="Commons.NoUndoMessage">操作没有完成,是否继续?</string>
<string name="Commons.Today">今天 (%s)</string>
<string name="Commons.Yesterday">昨天 (%s)</string>
<string name="Commons.DaysAgo">%1$s 以前 (%2$s)</string>
<string name="Commons.JavaScriptDialog">JavaScript对话框</string>
<string name="Commons.ClearHistory">清空历史记录</string>
<string name="Commons.ClearingHistory">正在清空历史记录...</string>
<string name="Commons.ClearFormData">清空表单数据</string>
<string name="Commons.ClearingFormData">正在清空表单数据...</string>
<string name="Commons.ClearCache">清空缓存</string>
<string name="Commons.ClearingCache">正在清空缓存...</string>
<string name="Commons.ClearCookies">清空Cookies</string>
<string name="Commons.ClearingCookies">正在清空缓存...</string>
<string name="Commons.SDCardErrorTitle">SD卡不能使用</string>
<string name="Commons.SDCardErrorNoSDMsg">找不到SD卡</string>
<string name="Commons.SDCardErrorSDUnavailable">SD卡不能使用</string>
<string name="Commons.Close">关闭</string>
<string name="DATE_FORMAT_ISO8601">yyyy-MM-dd\'T\'HH:mm:ss.SSS</string>
<!-- 0.3.1 -->
<string name="PreferencesActivity.AboutCategoryTitle">关于</string>
<string name="PreferencesActivity.AboutPreferenceTitle">关于</string>
<string name="PreferencesActivity.AboutPreferenceSummary">本程序信息.</string>
<!-- 0.3.2 -->
<string name="BookmarksListActivity.MostUsedSortMode">更多..</string>
<string name="PreferencesActivity.DefaultZoomPreferenceTitle">默认缩放级别</string>
<string name="PreferencesActivity.DefaultZoomPreferenceSummary">设定默认缩放</string>
<string name="PreferencesActivity.DefaultZoomClose">关闭</string>
<string name="PreferencesActivity.DefaultZoomMedium"></string>
<string name="PreferencesActivity.DefaultZoomFar">非常大</string>
<string-array name="DefaultZoomEntries">
<item>@string/PreferencesActivity.DefaultZoomClose</item>
<item>@string/PreferencesActivity.DefaultZoomMedium</item>
<item>@string/PreferencesActivity.DefaultZoomFar</item>
</string-array>
<string-array name="DefaultZoomValues">
<item>CLOSE</item>
<item>MEDIUM</item>
<item>FAR</item>
</string-array>
<string name="StartPage.Welcome">欢迎!</string>
<string name="StartPage.Bookmarks">使用最多的书签</string>
<string name="StartPage.History">历史记录</string>
<string name="ChangelogActivity.Title">更新历史</string>
<string name="PreferencesActivity.ChangelogPreferenceTitle">更新日志</string>
<string name="PreferencesActivity.ChangelogPreferenceSummary">本应用程序更新日志</string>
<string name="HomepagePreferenceActivity.Title">主页</string>
<string name="HomepagePreferenceActivity.Prompt">主页</string>
<string name="PreferencesActivity.HomepageStart">开始页</string>
<string name="PreferencesActivity.HomepageBlank">空白页</string>
<string name="PreferencesActivity.HomepageCustom">自定义</string>
<string-array name="HomepageValues">
<item>@string/PreferencesActivity.HomepageStart</item>
<item>@string/PreferencesActivity.HomepageBlank</item>
<item>@string/PreferencesActivity.HomepageCustom</item>
</string-array>
<!-- 0.3.3 -->
<string name="Constants.SearchUrlGoogle">http://www.google.com/m?hl=en&amp;gl=us&amp;client=ms-null&amp;source=android-browser-key&amp;q=%s</string>
<string name="Constants.SearchUrlWikipedia">http://www.baidu.com/s?wd=%s</string>
<string name="PreferencesActivity.SearchUrlPreferenceTitle">搜索 URL</string>
<string name="PreferencesActivity.SearchUrlPreferenceSummary">自定义搜索URL</string>
<string name="SearchUrlPreferenceActivity.Title">搜索</string>
<string name="SearchUrlPreferenceActivity.Prompt">搜索</string>
<string name="PreferencesActivity.SearchUrlGoogle">谷歌</string>
<string name="PreferencesActivity.SearchUrlWikipedia">百度</string>
<string name="PreferencesActivity.SearchUrlCustom">自定义</string>
<string-array name="SearchUrlValues">
<item>@string/PreferencesActivity.SearchUrlGoogle</item>
<item>@string/PreferencesActivity.SearchUrlWikipedia</item>
<item>@string/PreferencesActivity.SearchUrlCustom</item>
</string-array>
<string name="StartPage.Search">搜索</string>
<string name="StartPage.SearchButton"></string>
<string name="PreferencesActivity.StartPageCustomizationPreferenceTitle">开始页设定</string>
<string name="PreferencesActivity.StartPageCustomizationPreferenceSummary">自定义开始页.</string>
<string name="PreferencesActivity.StartPageEnableSearchPreferenceTitle">是否显示搜索栏</string>
<string name="PreferencesActivity.StartPageEnableSearchPreferenceSummary">如果选中,首页显示搜索栏</string>
<string name="PreferencesActivity.StartPageEnableBookmarksPreferenceTitle">显示最常用书签</string>
<string name="PreferencesActivity.StartPageEnableBookmarksPreferenceSummary">如果选中,首页显示最常用书签</string>
<string name="PreferencesActivity.StartPageEnableHistoryPreferenceTitle">是否显示历史</string>
<string name="PreferencesActivity.StartPageEnableHistoryPreferenceSummary">如果选中,显示浏览历史</string>
<string name="PreferencesActivity.StartPageBookmarksLimitPreferenceTitle">书签个数</string>
<string name="PreferencesActivity.StartPageBookmarksLimitPreferenceSummary">首页中最常用的书签显示</string>
<string name="PreferencesActivity.StartPageHistoryLimitPreferenceTitle">当前历史记录数</string>
<string name="PreferencesActivity.StartPageHistoryLimitPreferenceSummary">在首页中显示的历史记录个数</string>
<string name="PreferencesActivity.UserAgentPreferenceTitle">User agent(UA)</string>
<string name="PreferencesActivity.UserAgentPreferenceSummary">定义UA,网站根据UA显示给适合你浏览器的网页</string>
<string name="UserAgentPreferenceActivity.Title">User Agent</string>
<string name="UserAgentPreferenceActivity.Prompt">User agent</string>
<string name="PreferencesActivity.UserAgentDefault">Android默认</string>
<string name="PreferencesActivity.UserAgentDesktop">桌面级</string>
<string name="PreferencesActivity.UserAgentCustom">自定义</string>
<string-array name="UserAgentValues">
<item>@string/PreferencesActivity.UserAgentDefault</item>
<item>@string/PreferencesActivity.UserAgentDesktop</item>
<item>@string/PreferencesActivity.UserAgentCustom</item>
</string-array>
<string name="BookmarksListActivity.AndroidExportTarget">Android浏览器的书签</string>
<string name="BookmarksListActivity.SDCardExportTarget">SD卡</string>
<string name="BookmarksListActivity.BookmarksExportSDCardDoneTitle">导出完成</string>
<string name="BookmarksListActivity.BookmarksExportSDCardDoneMessage">你的所有书签已经保存在[%s]中</string>
<string name="BookmarksListActivity.BookmarksExportSDCardFailedTitle">导出失败</string>
<string name="BookmarksListActivity.BookmarksExportSDCardFailedMessage">错误信息: %s.</string>
<string name="Main.MenuCopyLinkUrl">复制链接地址</string>
<string name="Main.MenuViewImage">显示图片</string>
<string name="Main.MenuCopyImageUrl">复制图片地址</string>
<string name="Main.MenuDownloadImage">下载图片</string>
<string name="Main.MenuSendEmail">发送邮件</string>
<string name="Main.MenuCopyEmailUrl">复制邮件地址</string>
<string name="Commons.UrlCopyToastMessage">地址已经复制到粘贴板</string>
<string name="DownloadNotification.DownloadStart">下载开始.</string>
<string name="DownloadNotification.DownloadInProgress">正在下载中.</string>
<string name="DownloadNotification.DownloadComplete">下载完成.</string>
<string name="DownloadNotification.DownloadCanceled">下载已经取消.</string>
<string name="BookmarksHistoryActivity.MenuCopyLinkUrl">复制URL</string>
<string name="PreferencesActivity.ClearCacheOnExitPreferenceTitle">清空缓存</string>
<string name="PreferencesActivity.ClearCacheOnExitPreferenceSummary">如果选中,当程序退出的时候清空缓存.</string>
<string name="PreferencesActivity.UIPreferenceTitle">自定义首页</string>
<string name="PreferencesActivity.UIPreferenceSummary">自定义首页界面</string>
<string name="Main.MenuExit">退出</string>
<string name="PreferencesActivity.RestartDialogTitle">需要重启</string>
<string name="PreferencesActivity.RestartDialogMessage">效果改变后需要重新启动程序,是否马上重启</string>
<!-- 0.3.5 -->
<string name="PreferencesActivity.EnablePluginsTitle">可以使用插件</string>
<string name="PreferencesActivity.PluginsAlwaysOff">关闭</string>
<string name="PreferencesActivity.PluginsOnDemand">需要的时候提示</string>
<string name="PreferencesActivity.PluginsAlwaysOn">打开</string>
<string-array name="PluginsEntries">
<item>@string/PreferencesActivity.PluginsAlwaysOff</item>
<item>@string/PreferencesActivity.PluginsOnDemand</item>
<item>@string/PreferencesActivity.PluginsAlwaysOn</item>
</string-array>
<string-array name="PluginsValues">
<item>OFF</item>
<item>ON_DEMAND</item>
<item>ON</item>
</string-array>
<string name="Commons.Continue">继续</string>
<string name="Commons.SslWarning">安全警告</string>
<string name="Commons.SslWarningsHeader">网站的安全证书存在问题</string>
<string name="Commons.SslUntrusted">网站证书没有可信任的发行者</string>
<string name="Commons.SslIDMismatch">站点名和证书不匹配</string>
<string name="Commons.SslExpired">证书已经过期</string>
<string name="Commons.SslNotYetValid">证书没有验证</string>
<string name="MobileViewListActivity.Title">Zirco Browser-通过网关访问的网站列表</string>
<string name="MobileViewListActivity.ListEmpty">列表为空</string>
<string name="MobileViewListActivity.AddMessage">添加一个网站</string>
<string name="MobileViewListActivity.ClearMessage">清空所有站点</string>
<string name="PreferencesActivity.MobileViewCategoryTitle">移动网站列表</string>
<string name="PreferencesActivity.MobileViewListPreferenceTitle">管理网站</string>
<string name="PreferencesActivity.MobileViewListPreferenceSummary">通过google移动平台访问网站,会减少流量和加快速度.</string>
<string name="Commons.Add">增加</string>
<string name="Commons.Clear">清空</string>
<string name="Commons.Delete">删除</string>
<!-- 0.3.6 -->
<string name="PreferencesActivity.VolumeKeysBehaviourPreferenceTitle">声音键</string>
<string name="PreferencesActivity.VolumeKeysBehaviourPreferenceSummary">选择声音键的功能</string>
<string name="PreferencesActivity.VolumeKeysActionDefault">默认不改变</string>
<string name="PreferencesActivity.VolumeKeysActionZoom">放大</string>
<string name="PreferencesActivity.VolumeKeysActionSwitchTabs">选择不同页</string>
<string name="PreferencesActivity.VolumeKeysActionHistory">前进/后退</string>
<string-array name="VolumeKeysActionEntries">
<item>@string/PreferencesActivity.VolumeKeysActionDefault</item>
<item>@string/PreferencesActivity.VolumeKeysActionZoom</item>
<item>@string/PreferencesActivity.VolumeKeysActionSwitchTabs</item>
<item>@string/PreferencesActivity.VolumeKeysActionHistory</item>
</string-array>
<string-array name="VolumeKeysActionValues">
<item>DEFAULT</item>
<item>ZOOM</item>
<item>SWITCH_TABS</item>
<item>HISTORY</item>
</string-array>
<!-- 0.3.7 -->
<string name="Main.MenuSharePage">Share page</string>
<string name="Main.ShareChooserTitle">Share via</string>
<string name="Main.MenuShareLinkUrl">Share link url</string>
<string name="Main.MenuShareImageUrl">Share image url</string>
<string name="Main.MenuShareEmailUrl">Share email address</string>
<!-- 0.3.8 -->
<string name="PreferencesActivity.HideTitleBarPreferenceTitle">Hide title bars</string>
<string name="PreferencesActivity.HideTitleBarPreferenceSummary">If checked, windows title bars will not be
displayed (need application restart).</string>
<string name="PreferencesActivity.UseWideViewPortPreferenceTitle">Use wide viewport</string>
<string name="PreferencesActivity.UseWideViewPortPreferenceSummary">If checked, browser will use a viewport similar to
desktop browsers.</string>
<string name="PreferencesActivity.LoadWithOverviewPreferenceTitle">Load pages with overview</string>
<string name="PreferencesActivity.LoadWithOverviewPreferenceSummary">If checked, pages will load zoomed out to show an
overview of the page.</string>
<string name="PreferencesActivity.SwitchTabsMethodPreferenceTitle">Switch tabs method</string>
<string name="PreferencesActivity.SwitchTabsMethodPreferenceSummary">Define how you can switch between open tabs.</string>
<string name="PreferencesActivity.SwitchTabButtons">Buttons</string>
<string name="PreferencesActivity.SwitchTabFling">Fling</string>
<string name="PreferencesActivity.SwitchTabBoth">Both</string>
<string-array name="SwitchTabsMethodEntries">
<item>@string/PreferencesActivity.SwitchTabButtons</item>
<item>@string/PreferencesActivity.SwitchTabFling</item>
<item>@string/PreferencesActivity.SwitchTabBoth</item>
</string-array>
<string-array name="SwitchTabsMethodValues">
<item>buttons</item>
<item>fling</item>
<item>both</item>
</string-array>
<!-- 0.3.9 -->
<string name="HistoryListActivity.Today">Today</string>
<string name="HistoryListActivity.Yesterday">Yesterday</string>
<string name="HistoryListActivity.LastSevenDays">Last 7 days</string>
<string name="HistoryListActivity.LastMonth">Last month</string>
<string name="HistoryListActivity.Older">Older</string>
<string name="QuickAction.Home">主页</string>
<string name="QuickAction.Menu">菜单</string>
<string name="QuickAction.Share">分享</string>
<string name="QuickAction.SelectText">选择文本</string>
<string name="QuickAction.MobileView">移动网关</string>
<string name="AboutActivity.OtherLicenseText">This application include code from the GreenDroid
project (https://github.com/cyrilmottier/GreenDroid/), licensed under
the Apache license, version 2.</string>
<!-- 0.4.0 -->
<string name="PreferencesActivity.WeavePreferenceTitle">Firefox bookmarks synchronization</string>
<string name="PreferencesActivity.WeavePreferenceSummary">Manage the settings of the bookmarks
synchronization with Firefox Sync.</string>
<string name="PreferencesActivity.UseWeavePreferenceTitle">Use Firefox Sync</string>
<string name="PreferencesActivity.UseWeavePreferenceSummary">If checked, you can retrieve your bookmarks from
Firefox Sync.</string>
<string name="PreferencesActivity.WeaveServerPreferenceTitle">Server</string>
<string name="PreferencesActivity.WeaveServerPreferenceSummary">Set the Firefox Sync server.</string>
<string name="PreferencesActivity.WeaveUsernameTitle">User name</string>
<string name="PreferencesActivity.WeaveUsernameSummary">Enter your Firefox Sync user name.</string>
<string name="PreferencesActivity.WeavePasswordTitle">Password</string>
<string name="PreferencesActivity.WeavePasswordSummary">Enter your Firefox Sync password.</string>
<string name="PreferencesActivity.WeaveKeyTitle">Key</string>
<string name="PreferencesActivity.WeaveKeySummary">Enter your Firefox Sync key.</string>
<string name="WeaveBookmarksListActivity.Title">Firefox bookmarks</string>
<string name="WeaveBookmarksListActivity.MenuSync">Synchronize</string>
<string name="WeaveBookmarksListActivity.MenuClear">Clear</string>
<string name="WeaveBookmarksListActivity.WeaveRootFolder">Places</string>
<string name="WeaveBookmarksListActivity.EmptyText">Firefox bookmarks are not synchronized. Setup your
account informations and do a synchronization.</string>
<string name="WeaveBookmarksListActivity.EmptyFolderText">No bookmarks in this folder.</string>
<string name="WeaveBookmarksListActivity.SetupButton">Setup</string>
<string name="WeaveBookmarksListActivity.SyncButton">Synchronize</string>
<string name="WeaveSync.SyncTitle">Synchronization</string>
<string name="WeaveSync.Connecting">Connecting to server...</string>
<string name="WeaveSync.GettingData">Getting data...</string>
<string name="WeaveSync.ReadingData">Reading data: %1$s / %2$s</string>
<string name="WeaveSync.WrittingData">Writting data...</string>
<string name="Errors.WeaveSyncFailedTitle">Synchronization failed.</string>
<string name="Errors.WeaveSyncFailedMessage">Synchronization failed: %s</string>
<string name="Errors.WeaveAuthFailedMessage">Invalid authentication token. Check your settings.</string>
<string name="WeaveServerPreferenceActivity.Prompt">Server</string>
<string name="WeaveServerPreferenceActivity.Title">Server</string>
<string name="WeaveServerPreferenceActivity.DefaultServer">Firefox Sync server</string>
<string name="WeaveServerPreferenceActivity.CustomServer">Custom server</string>
<string-array name="WeaveServerValues">
<item>@string/WeaveServerPreferenceActivity.DefaultServer</item>
<item>@string/WeaveServerPreferenceActivity.CustomServer</item>
</string-array>
<string name="Main.FileChooserPrompt">Choose file for upload</string>
<string name="Commons.HistoryBookmarksExportSDCardConfirmation">Export history and bookmarks</string>
<string name="Commons.HistoryBookmarksExportSDCardDoneTitle">Export done</string>
<string name="Commons.HistoryBookmarksExportSDCardDoneMessage">Your history and bookmarks have been saved to %s.</string>
<string name="Commons.HistoryBookmarksExportSDCardFailedTitle">Export failed</string>
<string name="Commons.HistoryBookmarksFailedMessage">Error message: %s.</string>
<string name="Commons.HistoryBookmarksImportSDCardFailedTitle">Import failed</string>
<string name="Commons.OperationCanBeLongMessage">This operation can take some times. Do you wish to
proceed?</string>
<string name="PreferencesActivity.ToolsCategoryTitle">Tools</string>
<string name="PreferencesActivity.ToolsHistoryBookmarksPreferenceTitle">Manage bookmarks and history</string>
<string name="PreferencesActivity.ToolsHistoryBookmarksPreferenceSummary">Import, export, clear history and bookmarks.</string>
<string name="PreferencesActivity.ExportHistoryBookmarksPreferenceTitle">Export bookmarks and history</string>
<string name="PreferencesActivity.ExportHistoryBookmarksPreferenceSummary">Export bookmarks and history to the SD card.</string>
<string name="PreferencesActivity.ImportHistoryBookmarksPreferenceTitle">Import bookmarks and history</string>
<string name="PreferencesActivity.ImportHistoryBookmarksPreferenceSummary">Import bookmarks and history from the SD card.</string>
<string name="PreferencesActivity.PrivacyClearHistoryBookmarksTitle">Clear history and/or bookmarks</string>
<string name="PreferencesActivity.SummaryCannotBeUndone">Use with caution, this operation cannot be undone.</string>
<string name="Commons.ClearingHistoryBookmarks">Clearing history and/or bookmarks...</string>
<string name="Commons.ClearHistoryBookmarks">Clear history and/or bookmarks</string>
<string name="Commons.Bookmarks">Bookmarks</string>
<string name="Commons.History">History</string>
<string name="Commons.All">All</string>
<string name="Commons.ExportingHistoryBookmarks">Exporting history and bookmarks...</string>
<string name="Commons.ImportingHistoryBookmarks">Importing history and bookmarks...</string>
<string name="Commons.ImportHistoryBookmarksSource">Choose the import source</string>
<string name="Commons.ClearingBookmarks">正在清空书签中...</string>
<string name="DownloadListActivity.RemoveCompletedDownloads">Remove completed downloads</string>
<string name="QuickAction.Find">Find on page</string>
<string name="SearchDialog.Hint">Find on page</string>
</resources>
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<!--Replace placeholder ID with your tracking ID-->
<string name="ga_trackingId">UA-33136904-1</string>
<!--Enable Activity tracking-->
<bool name="ga_autoActivityTracking">true</bool>
<!--Enable automatic exception tracking-->
<bool name="ga_reportUncaughtExceptions">true</bool>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!--
Zirco Browser for Android
Copyright (C) 2010 J. Devauchelle and contributors.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 3 as published by the Free Software Foundation.
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.
-->
<resources>
<color name="title_background">#ff5e5e5e</color>
<color name="dialog_title_background">#17170443</color>
<color name="black">#000000</color>
<color name="grey1">#646464</color>
<color name="grey2">#c8c8c8</color>
<color name="grey3">#323232</color>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="service_running">Running in the background.</string>
<string name="app_name">Shadowsocks</string>
<string name="forward_success">Service started.</string>
<string name="forward_fail">Service failed to start.</string>
<string name="service_stopped">Shadowsocks service stopped.</string>
<string name="auto_reconnected">Auto Reconnected</string>
<string name="reconnect_success">Reconnected successfully.</string>
<string name="already_stopped">Service has been stopped already.</string>
<string name="already_running">Service has been started already.</string>
<string name="forward_stop">Proxy Service Stopped</string>
<string name="service_failed">Please check your network and login information</string>
<string name="reconnect_fail">Cannot reconnect</string>
<string name="ok_iknow">OK, I know</string>
<string name="proxy">Address</string>
<string name="port">Local Port</string>
<string name="sitekey">Site Key</string>
<string name="auto_connect">Auto Connect</string>
<string name="auto_reconnect">Auto reconnect</string>
<string name="auto_set_proxy">Global Proxy</string>
<string name="disconnect">Disconnect</string>
<string name="connect">Connect</string>
<string name="feedback">Feedback @ofmax</string>
<string name="proxy_cat">Shadowsocks Settings</string>
<string name="fearute_cat">Feature Settings</string>
<string name="service_controller">Proxy Switch</string>
<string name="function_cat">Service Controller</string>
<string name="service_summary">Enable / Disable Proxy</string>
<string name="proxy_summary">Shadowsocks URL</string>
<string name="port_summary">Listening port</string>
<string name="sitekey_summary">Site key for GoAgent</string>
<string name="port_alert">The port number should be greater than 1024</string>
<string name="auto_connect_summary">Start Shadowsocks at the startup</string>
<string name="auto_set_proxy_summary">Set up the global proxy (needs ROOT permission and
IPTABLES support)
</string>
<string name="port_empty">Port should not be empty</string>
<string name="proxy_empty">Proxy should not be empty</string>
<string name="install_cat">Package configure</string>
<string name="isInstalled">Install / Uninstall</string>
<string name="download">Downloading</string>
<string name="downloading">Downloading, Patient Please...</string>
<string name="isInstalled_summary">Install / Uninstall extra packages (need SD card)</string>
<string name="install_alert">Please install dependent module first! (Scroll to
the bottom, and select to install)
</string>
<string name="sdcard_alert">Please make sure you have a SD CARD!</string>
<string name="recovery">Reset</string>
<string name="unzip">Unzipping...</string>
<string name="proxyed_apps">Individual Proxy</string>
<string name="proxyed_apps_summary">Set individual proxy for apps (needs ROOT permission and IPTABLES support)
</string>
<string name="bypass_apps">Bypass Mode</string>
<string name="bypass_apps_summary">Enable this option to bypass selected apps</string>
<string name="proxyed_help">Select apps to use with Shadowsocks:</string>
<string name="crash_alert">An unexpected exit detected, context has been
recovered
</string>
<string name="copy_rights">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)
</string>
<string name="about">About</string>
<string name="proxy_type">Proxy Type</string>
<array name="proxy_type_entry">
<item>GAE</item>
<item>PaaS</item>
</array>
<string-array name="gfw_list">
<item>209.0.0.0/8</item>
<item>72.0.0.0/8</item>
<item>74.0.0.0/8</item>
<item>68.0.0.0/8</item>
<item>69.0.0.0/8</item>
<item>66.0.0.0/8</item>
<item>64.0.0.0/8</item>
<item>199.0.0.0/8</item>
<item>168.0.0.0/8</item>
<item>67.0.0.0/8</item>
<item>173.0.0.0/8</item>
<item>202.248.0.0/16</item>
</string-array>
<string-array name="chn_list">
<item>1.0.0.0/8</item>
<item>14.0.0.0/8</item>
<item>27.0.0.0/8</item>
<item>36.0.0.0/8</item>
<item>39.0.0.0/8</item>
<item>42.0.0.0/8</item>
<item>49.0.0.0/8</item>
<item>58.0.0.0/7</item>
<item>60.0.0.0/7</item>
<item>96.0.0.0/3</item>
<item>128.0.0.0/4</item>
<item>171.0.0.0/8</item>
<item>175.0.0.0/8</item>
<item>180.0.0.0/8</item>
<item>182.0.0.0/8</item>
<item>183.0.0.0/8</item>
<item>202.0.0.0/8</item>
<item>203.0.0.0/8</item>
<item>210.0.0.0/8</item>
<item>211.0.0.0/8</item>
<item>216.0.0.0/5</item>
<item>192.168.0.0/16</item>
<item>10.0.0.0/8</item>
<item>172.16.0.0/12</item>
</string-array>
<string name="mirror_list">
d3dxZ3R4eHByb3h5LTF8d3dxZ3R4eHByb3h5LTJ8d3dxZ3R4eHByb3h5LTN8d3dxZ3R4eHByb3h5LTR8d3dxZ3R4eHByb3h5LTV8d3dxZ3R4eHByb3h5LTZ8d3dxZ3R4eHByb3h5LTd8d3dxZ3R4eHByb3h5LTh8d3dxZ3R4eHByb3h5LTl8d3dxZ3R4eHByb3h5LTEwfHd3cWd0eHhwcm94eTEtMXx3d3FndHh4cHJveHkxLTJ8d3dxZ3R4eHByb3h5MS0zfHd3cWd0eHhwcm94eTEtNHx3d3FndHh4cHJveHkxLTV8d3dxZ3R4eHByb3h5MS02fHd3cWd0eHhwcm94eTEtN3x3d3FndHh4cHJveHkxLTh8d3dxZ3R4eHByb3h5MS05fHd3cWd0eHhwcm94eTEtMTB8d3dxZ3R4eHByb3h5Mi0xfHd3cWd0eHhwcm94eTItMnx3d3FndHh4cHJveHkyLTN8d3dxZ3R4eHByb3h5Mi00fHd3cWd0eHhwcm94eTItNXx3d3FndHh4cHJveHkyLTZ8d3dxZ3R4eHByb3h5Mi03fHd3cWd0eHhwcm94eTItOHx3d3FndHh4cHJveHkyLTl8d3dxZ3R4eHByb3h5Mi0xMA==
</string>
<string name="mirror_path">2</string>
<string name="mirror_sitekey">wwqgtxx-goagent</string>
<string name="connecting">Connecting…</string>
<string name="initializing">Initializing…</string>
<string name="recovering">Reseting…</string>
<string name="https_alert">Please modify your proxy host to start with
"https://"
</string>
<string name="https_proxy">HTTPS Proxy</string>
<string name="https_proxy_summary">Proxy HTTPS through GAE, not compatible with most
apps
</string>
<!-- settings notification category -->
<string name="notif_cat">Notification Settings</string>
<string name="notif_ringtone_title">Ringtone</string>
<string name="notif_ringtone_summary">Select the notification\'s ringtone</string>
<string name="notif_vibrate_title">Vibrate</string>
<string name="notif_vibrate_summary">Also vibrate when connection status changes</string>
<string name="loading">Loading app list…</string>
<string name="enable_market">Market Proxy</string>
<string name="enable_market_summary">Only for users in China, help to update app from
Android Market (need reboot)
</string>
<string name="auto_set_gfwlist">CHN Route</string>
<string name="auto_set_gfwlist_summary">Bypass all sites located in China (needs ROOT
permission and
IPTABLES support)
</string>
<string name="toast_start">Connecting to Shadowsocks</string>
<string name="toast_restart">Reconnecting to Shadowsocks</string>
<string name="browser">GAE Browser</string>
<string name="browser_summary">Start a browser through GAE proxy, for users
without root permission or iptables support
</string>
<string name="default_proxy_alert">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
</string>
<string name="warning">Warning</string>
<string name="system_proxy">System Proxy</string>
<string name="system_proxy_summary">Enable system wide proxy automatically without ROOT permission,
only works for Wifi on Android 4.0 and above.</string>
<string name="remote_port">Remote Port</string>
<string name="remote_port_summary">Server port of shadowsocks</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<!--
Zirco Browser for Android
Copyright (C) 2010 - 2011 J. Devauchelle and contributors.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 3 as published by the Free Software Foundation.
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.
-->
<resources>
<!-- Base application theme is the default theme -->
<style name="Theme" parent="android:Theme" />
<!-- Variation on the Light theme that turns off the title -->
<style name="Theme.GAEProxy" parent="@android:style/Theme">
<item name="android:windowNoTitle">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
<style name="Bookmarks"></style>
<style name="Bookmarks.Title">
<item name="android:textSize">16sp</item>
<item name="android:textColor">@android:color/white</item>
</style>
<style name="History"></style>
<style name="History.Title">
<item name="android:textSize">16sp</item>
<item name="android:textColor">@android:color/white</item>
</style>
<style name="History.Url">
<item name="android:textSize">12sp</item>
<item name="android:textColor">#888888</item>
</style>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="@string/function_cat">
<CheckBoxPreference
android:key="isRunning"
android:summary="@string/service_summary"
android:title="@string/service_controller">
</CheckBoxPreference>
</PreferenceCategory>
<PreferenceCategory android:title="@string/proxy_cat">
<EditTextPreference
android:defaultValue="127.0.0.1"
android:key="proxy"
android:summary="@string/proxy_summary"
android:title="@string/proxy">
</EditTextPreference>
<EditTextPreference
android:defaultValue="1984"
android:key="remotePort"
android:summary="@string/remote_port_summary"
android:title="@string/remote_port">
</EditTextPreference>
<EditTextPreference
android:defaultValue="1080"
android:key="port"
android:summary="@string/port_summary"
android:title="@string/port">
</EditTextPreference>
<EditTextPreference
android:defaultValue=""
android:key="sitekey"
android:summary="@string/sitekey_summary"
android:title="@string/sitekey">
</EditTextPreference>
</PreferenceCategory>
<PreferenceCategory android:title="@string/fearute_cat">
<CheckBoxPreference
android:key="isAutoConnect"
android:summary="@string/auto_connect_summary"
android:title="@string/auto_connect">
</CheckBoxPreference>
<CheckBoxPreference
android:defaultValue="true"
android:key="isGFWList"
android:summary="@string/auto_set_gfwlist_summary"
android:title="@string/auto_set_gfwlist">
</CheckBoxPreference>
<CheckBoxPreference
android:defaultValue="true"
android:key="isGlobalProxy"
android:summary="@string/auto_set_proxy_summary"
android:title="@string/auto_set_proxy">
</CheckBoxPreference>
<CheckBoxPreference
android:defaultValue="false"
android:key="isBypassApps"
android:summary="@string/bypass_apps_summary"
android:title="@string/bypass_apps">
</CheckBoxPreference>
<Preference
android:key="proxyedApps"
android:summary="@string/proxyed_apps_summary"
android:title="@string/proxyed_apps">
</Preference>
</PreferenceCategory>
</PreferenceScreen>
/* 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<ApplicationInfo> lAppInfo = pMgr.getInstalledApplications(0);
Iterator<ApplicationInfo> itAppInfo = lAppInfo.iterator();
Vector<ProxyedApp> vectorApps = new Vector<ProxyedApp>();
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<ProxyedApp> vectorApps = new Vector<ProxyedApp>();
// else load the apps up
PackageManager pMgr = context.getPackageManager();
List<ApplicationInfo> lAppInfo = pMgr.getInstalledApplications(0);
Iterator<ApplicationInfo> 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<ProxyedApp>() {
@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<ProxyedApp>(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();
}
}
/*
* 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.
* <p/>
* 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.
* <p/>
* 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);
}
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<PhotoToLoad> photosToLoad = new Stack<PhotoToLoad>();
// 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<Integer, Bitmap> cache = new HashMap<Integer, Bitmap>();
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();
}
}
// dbartists - Douban artists client for Android
// Copyright (C) 2011 Max Lv <max.c.lv@gmail.com>
//
// 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;
}
}
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
/* Shadowsocks - GoAgent / WallProxy client App for Android
* Copyright (C) 2012 <max.c.lv@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* ___====-_ _-====___
* _--^^^#####// \\#####^^^--_
* _-^##########// ( ) \\##########^-_
* -############// |\^^/| \\############-
* _/############// (@::@) \\############\_
* /#############(( \\// ))#############\
* -###############\\ (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();
}
}
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);
}
}
/* gaeproxy - GAppProxy / WallProxy client App for Android
* Copyright (C) 2011 <max.c.lv@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* ___====-_ _-====___
* _--^^^#####// \\#####^^^--_
* _-^##########// ( ) \\##########^-_
* -############// |\^^/| \\############-
* _/############// (@::@) \\############\_
* /#############(( \\// ))#############\
* -###############\\ (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<ShadowsocksService> 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<ShadowsocksService>(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<Integer> uidSet = new HashSet<Integer>();
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);
}
}
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<String> 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<String> parse(String cmd) {
final int PLAIN = 0;
final int WHITESPACE = 1;
final int INQUOTE = 2;
int state = WHITESPACE;
ArrayList<String> result = new ArrayList<String>();
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;
}
}
File added
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment