Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e2fc2b228 | ||
|
|
d794632616 | ||
|
|
529b1a32d3 | ||
|
|
396b49dab6 | ||
|
|
06deb4bbcb | ||
|
|
8127b9f8e2 | ||
|
|
3556eebfeb | ||
|
|
10e3b211e3 | ||
|
|
05981fb117 | ||
|
|
4258e95f1e | ||
|
|
cd5c2ef8ef | ||
|
|
5a84854881 | ||
|
|
0cf1585712 | ||
|
|
b8d97dde03 |
@@ -70,7 +70,7 @@ Help to translate shadowsocks: http://crowdin.net/project/shadowsocks/invite
|
|||||||
|
|
||||||
### LICENSE
|
### LICENSE
|
||||||
|
|
||||||
Copyright (C) 2015 Max Lv <max.c.lv@gmail.com>
|
Copyright (C) 2016 Max Lv <max.c.lv@gmail.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ libraryDependencies ++= Seq(
|
|||||||
"com.google.android.gms" % "play-services-ads" % "8.4.0",
|
"com.google.android.gms" % "play-services-ads" % "8.4.0",
|
||||||
"com.google.android.gms" % "play-services-analytics" % "8.4.0",
|
"com.google.android.gms" % "play-services-analytics" % "8.4.0",
|
||||||
"com.android.support" % "design" % "23.1.1",
|
"com.android.support" % "design" % "23.1.1",
|
||||||
|
"com.android.support" % "gridlayout-v7" % "23.1.1",
|
||||||
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
|
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
|
||||||
"com.j256.ormlite" % "ormlite-core" % "4.48",
|
"com.j256.ormlite" % "ormlite-core" % "4.48",
|
||||||
"com.j256.ormlite" % "ormlite-android" % "4.48",
|
"com.j256.ormlite" % "ormlite-android" % "4.48",
|
||||||
|
|||||||
+1
-36
@@ -13,43 +13,8 @@ def main():
|
|||||||
for line in sys.stdin:
|
for line in sys.stdin:
|
||||||
china_list_set.add(IPy.IP(line))
|
china_list_set.add(IPy.IP(line))
|
||||||
|
|
||||||
# 添加内网地址
|
|
||||||
internal_list = IPy.IPSet(map(IPy.IP, [
|
|
||||||
"0.0.0.0/8",
|
|
||||||
"10.0.0.0/8",
|
|
||||||
"100.64.0.0/10",
|
|
||||||
"112.124.47.0/24",
|
|
||||||
"114.114.114.0/24",
|
|
||||||
"127.0.0.0/8",
|
|
||||||
"169.254.0.0/16",
|
|
||||||
"172.16.0.0/12",
|
|
||||||
"192.0.0.0/29",
|
|
||||||
"192.0.2.0/24",
|
|
||||||
"192.88.99.0/24",
|
|
||||||
"192.168.0.0/16",
|
|
||||||
"198.18.0.0/15",
|
|
||||||
"198.51.100.0/24",
|
|
||||||
"203.0.113.0/24",
|
|
||||||
"224.0.0.0/4",
|
|
||||||
"240.0.0.0/4",
|
|
||||||
]))
|
|
||||||
china_list_set += internal_list
|
|
||||||
|
|
||||||
all = china_list_set
|
|
||||||
|
|
||||||
# 取反
|
|
||||||
# all = IPy.IPSet([IPy.IP("0.0.0.0/0")])
|
|
||||||
# 剔除所有孤立的C段
|
|
||||||
# for ip in china_list_set:
|
|
||||||
# all.discard(ip)
|
|
||||||
|
|
||||||
# filter = itertools.ifilter(lambda x: len(x) <= 65536, all)
|
|
||||||
# for ip in filter:
|
|
||||||
# all.discard(ip)
|
|
||||||
# all.add(IPy.IP(ip.strNormal(0)).make_net('255.255.0.0'))
|
|
||||||
|
|
||||||
# 输出结果
|
# 输出结果
|
||||||
for ip in all:
|
for ip in china_list_set:
|
||||||
print '<item>' + str(ip) + '</item>'
|
print '<item>' + str(ip) + '</item>'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ img {
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<p>A <a href="http://shadowsocks.org">Shadowsocks</a> client for Android, written in Scala.</p>
|
<p>A <a href="http://shadowsocks.org">Shadowsocks</a> client for Android, written in Scala.</p>
|
||||||
<p>Copyright (C) 2014 Max Lv <a href="mailto:max.c.lv@gmail.com">max.c.lv@gmail.com</a></p>
|
<p>Copyright (C) 2016 Max Lv <a href="mailto:max.c.lv@gmail.com">max.c.lv@gmail.com</a></p>
|
||||||
<p>This program is free software: you can redistribute it and/or modify
|
<p>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
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
@@ -289,11 +289,11 @@ along with this program. If not, see <a href="http://www.gnu.org/licenses/">http
|
|||||||
<h3>Open Source Licenses</h3>
|
<h3>Open Source Licenses</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li>shadowsocks-libev: <a href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/LICENSE">GPLv3</a></li>
|
<li>shadowsocks-libev: <a href="https://github.com/shadowsocks/shadowsocks-libev/blob/master/LICENSE">GPLv3</a></li>
|
||||||
<li>tun2socks: <a href="https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/badvpn/COPYING">BSD</a></li>
|
<li>tun2socks: <a href="https://github.com/shadowsocks/badvpn/blob/shadowsocks-android/COPYING">BSD</a></li>
|
||||||
<li>redsocks: <a href="https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/redsocks/README">APL 2.0</a></li>
|
<li>redsocks: <a href="https://github.com/shadowsocks/redsocks/blob/shadowsocks-android/README">APL 2.0</a></li>
|
||||||
<li>OpenSSL: <a href="https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/openssl/NOTICE">OpenSSL</a></li>
|
<li>OpenSSL: <a href="https://github.com/shadowsocks/openssl-android/blob/master/NOTICE">OpenSSL</a></li>
|
||||||
<li>pdnsd: <a href="https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/pdnsd/COPYING">GPLv3</a></li>
|
<li>pdnsd: <a href="https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/pdnsd/COPYING">GPLv3</a></li>
|
||||||
<li>libev: <a href="https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/libev/LICENSE">GPLv2</a></li>
|
<li>libev: <a href="https://github.com/shadowsocks/libevent/blob/shadowsocks-android/LICENSE">GPLv2</a></li>
|
||||||
<li>libevent: <a href="https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/libevent/LICENSE">BSD</a></li>
|
<li>libevent: <a href="https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/libevent/LICENSE">BSD</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24.0"
|
|
||||||
android:viewportHeight="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFFFF"
|
|
||||||
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24.0"
|
|
||||||
android:viewportHeight="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFFFF"
|
|
||||||
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4c-1.48,0 -2.85,0.43 -4.01,1.17l1.46,1.46C10.21,6.23 11.08,6 12,6c3.04,0 5.5,2.46 5.5,5.5v0.5H19c1.66,0 3,1.34 3,3 0,1.13 -0.64,2.11 -1.56,2.62l1.45,1.45C23.16,18.16 24,16.68 24,15c0,-2.64 -2.05,-4.78 -4.65,-4.96zM3,5.27l2.75,2.74C2.56,8.15 0,10.77 0,14c0,3.31 2.69,6 6,6h11.73l2,2L21,20.73 4.27,4 3,5.27zM7.73,10l8,8H6c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4h1.73z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24.0"
|
|
||||||
android:viewportHeight="24.0">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFFFFFFF"
|
|
||||||
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM19,18H6c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4h0.71C7.37,7.69 9.48,6 12,6c3.04,0 5.5,2.46 5.5,5.5v0.5H19c1.66,0 3,1.34 3,3s-1.34,3 -3,3z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -4,33 +4,23 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:paddingEnd="?android:attr/scrollbarSize"
|
android:paddingLeft="15dip"
|
||||||
|
android:paddingRight="15dip"
|
||||||
|
android:paddingTop="6dip"
|
||||||
|
android:paddingBottom="6dip"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:orientation="vertical" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<TableLayout
|
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content">
|
||||||
android:layout_marginLeft="15dip"
|
<TextView
|
||||||
android:layout_marginRight="6dip"
|
|
||||||
android:layout_marginTop="6dip"
|
|
||||||
android:layout_marginBottom="6dip"
|
|
||||||
android:stretchColumns="3"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TableRow
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<TextView
|
|
||||||
android:layout_column="0"
|
|
||||||
android:layout_span="2"
|
|
||||||
android:id="@android:id/title"
|
android:id="@android:id/title"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
android:ellipsize="marquee"
|
android:ellipsize="marquee"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"
|
||||||
<TextView
|
android:layout_weight="1"/>
|
||||||
android:layout_column="3"
|
<TextView
|
||||||
android:id="@android:id/summary"
|
android:id="@android:id/summary"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:textColor="?android:attr/textColorSecondary"
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
@@ -38,71 +28,97 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="end" />
|
android:gravity="end" />
|
||||||
</TableRow>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<android.support.v7.widget.GridLayout
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
app:columnCount="4"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<TableRow
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_column="0"
|
app:layout_column="0"
|
||||||
style="@style/TextAppearance.AppCompat.Body2"
|
style="@style/TextAppearance.AppCompat.Body2"
|
||||||
android:ellipsize="marquee"
|
android:ellipsize="marquee"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginRight="24dp"
|
android:layout_marginRight="24dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:text="@string/sent" />
|
android:text="@string/sent" />
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_column="1"
|
app:layout_column="1"
|
||||||
android:id="@+id/tx"
|
android:id="@+id/tx"
|
||||||
style="@style/TextAppearance.AppCompat.Caption"
|
android:textSize="14sp"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_columnWeight="1"
|
||||||
|
app:layout_gravity="fill_horizontal" />
|
||||||
|
<TextView
|
||||||
|
app:layout_column="2"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
android:ellipsize="marquee"
|
android:ellipsize="marquee"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="end"
|
android:text="▲" />
|
||||||
android:text="0 Byte" />
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_column="3"
|
app:layout_column="3"
|
||||||
android:id="@+id/txRate"
|
android:id="@+id/txRate"
|
||||||
style="@style/TextAppearance.AppCompat.Caption"
|
android:textSize="14sp"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
android:ellipsize="marquee"
|
android:ellipsize="marquee"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="end"
|
app:layout_columnWeight="1"
|
||||||
android:text="▲ 0 B/s" />
|
app:layout_gravity="fill_horizontal"
|
||||||
</TableRow>
|
android:gravity="end" />
|
||||||
|
|
||||||
<TableRow
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_column="0"
|
app:layout_row="1"
|
||||||
|
app:layout_column="0"
|
||||||
style="@style/TextAppearance.AppCompat.Body2"
|
style="@style/TextAppearance.AppCompat.Body2"
|
||||||
android:ellipsize="marquee"
|
android:ellipsize="marquee"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginRight="24dp"
|
android:layout_marginRight="24dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
android:gravity="end"
|
android:gravity="end"
|
||||||
android:text="@string/received"/>
|
android:text="@string/received"/>
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_column="1"
|
app:layout_row="1"
|
||||||
|
app:layout_column="1"
|
||||||
android:id="@+id/rx"
|
android:id="@+id/rx"
|
||||||
style="@style/TextAppearance.AppCompat.Caption"
|
android:textSize="14sp"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_columnWeight="1"
|
||||||
|
app:layout_gravity="fill_horizontal" />
|
||||||
|
<TextView
|
||||||
|
app:layout_row="1"
|
||||||
|
app:layout_column="2"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
android:ellipsize="marquee"
|
android:ellipsize="marquee"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="end"
|
android:text="▼" />
|
||||||
android:text="0 Byte" />
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_column="3"
|
app:layout_row="1"
|
||||||
|
app:layout_column="3"
|
||||||
android:id="@+id/rxRate"
|
android:id="@+id/rxRate"
|
||||||
style="@style/TextAppearance.AppCompat.Caption"
|
android:textSize="14sp"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
android:ellipsize="marquee"
|
android:ellipsize="marquee"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="end"
|
app:layout_columnWeight="1"
|
||||||
android:text="▼ 0 B/s" />
|
app:layout_gravity="fill_horizontal"
|
||||||
</TableRow>
|
android:gravity="end" />
|
||||||
|
|
||||||
</TableLayout>
|
</android.support.v7.widget.GridLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="proxied_apps_summary">Set proxy for selected apps</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="proxied_apps_summary">为应用程序分别设置代理</string>
|
||||||
|
</resources>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
<string name="auto_set_gfwlist_summary">绕过所有位于中国的网站</string>
|
<string name="auto_set_gfwlist_summary">绕过所有位于中国的网站</string>
|
||||||
<string name="route_list">路由</string>
|
<string name="route_list">路由</string>
|
||||||
<string name="proxied_apps">分应用代理</string>
|
<string name="proxied_apps">分应用代理</string>
|
||||||
<string name="proxied_apps_summary">为应用程序分别设置代理</string>
|
<string name="proxied_apps_summary">为应用程序分别设置代理,在 4.x 下需要启用 NAT 模式</string>
|
||||||
<string name="on">开</string>
|
<string name="on">开</string>
|
||||||
<string name="bypass_apps">绕行模式</string>
|
<string name="bypass_apps">绕行模式</string>
|
||||||
<string name="bypass_apps_summary">启用该选项,以使所选应用程序的流量不经过代理</string>
|
<string name="bypass_apps_summary">启用该选项,以使所选应用程序的流量不经过代理</string>
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
<string name="auto_connect_summary">随系统启动后台服务</string>
|
<string name="auto_connect_summary">随系统启动后台服务</string>
|
||||||
<string name="forward_success">后台服务已开始运行。</string>
|
<string name="forward_success">后台服务已开始运行。</string>
|
||||||
<string name="service_failed">无法连接远程服务器</string>
|
<string name="service_failed">无法连接远程服务器</string>
|
||||||
|
<string name="nat_deprecated">警告:在 Android 5.0 及更高版本中不建议使用 NAT 模式</string>
|
||||||
<string name="nat_no_root">NAT 模式需要 ROOT 权限</string>
|
<string name="nat_no_root">NAT 模式需要 ROOT 权限</string>
|
||||||
<string name="switch_to_vpn">切换到 VPN 模式</string>
|
<string name="switch_to_vpn">切换到 VPN 模式</string>
|
||||||
<string name="stop">停止服务</string>
|
<string name="stop">停止服务</string>
|
||||||
@@ -105,7 +106,10 @@
|
|||||||
<string name="action_import_msg">导入成功</string>
|
<string name="action_import_msg">导入成功</string>
|
||||||
<string name="action_import_err">导入失败</string>
|
<string name="action_import_err">导入失败</string>
|
||||||
|
|
||||||
<string name="removed">已删除</string>
|
<plurals name="removed">
|
||||||
|
<item quantity="one">已删除</item>
|
||||||
|
<item quantity="other">已删除 %d 项</item>
|
||||||
|
</plurals>
|
||||||
<string name="undo">撤销</string>
|
<string name="undo">撤销</string>
|
||||||
<string name="toggle_service_state">启动服务</string>
|
<string name="toggle_service_state">启动服务</string>
|
||||||
<string name="start_service_default">连接到当前服务器</string>
|
<string name="start_service_default">连接到当前服务器</string>
|
||||||
@@ -116,6 +120,5 @@
|
|||||||
<!-- status -->
|
<!-- status -->
|
||||||
<string name="sent">发送:</string>
|
<string name="sent">发送:</string>
|
||||||
<string name="received">接收:</string>
|
<string name="received">接收:</string>
|
||||||
<string name="check_connectivity">检查网络连接</string>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
<string-array name="route_entry">
|
<string-array name="route_entry">
|
||||||
<item>All</item>
|
<item>All</item>
|
||||||
<item>Bypass LAN</item>
|
<item>Bypass LAN</item>
|
||||||
|
<item>Bypass China</item>
|
||||||
<item>Bypass LAN & China</item>
|
<item>Bypass LAN & China</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
@@ -52,6 +53,7 @@
|
|||||||
<item>all</item>
|
<item>all</item>
|
||||||
<item>bypass-lan</item>
|
<item>bypass-lan</item>
|
||||||
<item>bypass-china</item>
|
<item>bypass-china</item>
|
||||||
|
<item>bypass-lan-china</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="private_route">
|
<string-array name="private_route">
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string-array name="chn_route_full">
|
<string-array name="chn_route">
|
||||||
<item>0.0.0.0/8</item>
|
|
||||||
<item>1.0.1.0/24</item>
|
<item>1.0.1.0/24</item>
|
||||||
<item>1.0.2.0/23</item>
|
<item>1.0.2.0/23</item>
|
||||||
<item>1.0.8.0/21</item>
|
<item>1.0.8.0/21</item>
|
||||||
@@ -49,7 +48,6 @@
|
|||||||
<item>1.192.0.0/13</item>
|
<item>1.192.0.0/13</item>
|
||||||
<item>1.202.0.0/15</item>
|
<item>1.202.0.0/15</item>
|
||||||
<item>1.204.0.0/14</item>
|
<item>1.204.0.0/14</item>
|
||||||
<item>10.0.0.0/8</item>
|
|
||||||
<item>14.0.0.0/21</item>
|
<item>14.0.0.0/21</item>
|
||||||
<item>14.0.12.0/22</item>
|
<item>14.0.12.0/22</item>
|
||||||
<item>14.1.0.0/22</item>
|
<item>14.1.0.0/22</item>
|
||||||
@@ -771,7 +769,6 @@
|
|||||||
<item>61.236.0.0/15</item>
|
<item>61.236.0.0/15</item>
|
||||||
<item>61.240.0.0/14</item>
|
<item>61.240.0.0/14</item>
|
||||||
<item>91.234.36.0/24</item>
|
<item>91.234.36.0/24</item>
|
||||||
<item>100.64.0.0/10</item>
|
|
||||||
<item>101.0.0.0/22</item>
|
<item>101.0.0.0/22</item>
|
||||||
<item>101.1.0.0/22</item>
|
<item>101.1.0.0/22</item>
|
||||||
<item>101.2.172.0/22</item>
|
<item>101.2.172.0/22</item>
|
||||||
@@ -2270,7 +2267,6 @@
|
|||||||
<item>125.215.0.0/18</item>
|
<item>125.215.0.0/18</item>
|
||||||
<item>125.216.0.0/13</item>
|
<item>125.216.0.0/13</item>
|
||||||
<item>125.254.128.0/17</item>
|
<item>125.254.128.0/17</item>
|
||||||
<item>127.0.0.0/8</item>
|
|
||||||
<item>137.59.88.0/22</item>
|
<item>137.59.88.0/22</item>
|
||||||
<item>139.9.0.0/16</item>
|
<item>139.9.0.0/16</item>
|
||||||
<item>139.129.0.0/16</item>
|
<item>139.129.0.0/16</item>
|
||||||
@@ -2386,7 +2382,6 @@
|
|||||||
<item>167.189.0.0/16</item>
|
<item>167.189.0.0/16</item>
|
||||||
<item>167.220.244.0/22</item>
|
<item>167.220.244.0/22</item>
|
||||||
<item>168.160.0.0/16</item>
|
<item>168.160.0.0/16</item>
|
||||||
<item>169.254.0.0/16</item>
|
|
||||||
<item>171.8.0.0/13</item>
|
<item>171.8.0.0/13</item>
|
||||||
<item>171.34.0.0/15</item>
|
<item>171.34.0.0/15</item>
|
||||||
<item>171.36.0.0/14</item>
|
<item>171.36.0.0/14</item>
|
||||||
@@ -2395,7 +2390,6 @@
|
|||||||
<item>171.104.0.0/13</item>
|
<item>171.104.0.0/13</item>
|
||||||
<item>171.112.0.0/12</item>
|
<item>171.112.0.0/12</item>
|
||||||
<item>171.208.0.0/12</item>
|
<item>171.208.0.0/12</item>
|
||||||
<item>172.16.0.0/12</item>
|
|
||||||
<item>175.0.0.0/12</item>
|
<item>175.0.0.0/12</item>
|
||||||
<item>175.16.0.0/13</item>
|
<item>175.16.0.0/13</item>
|
||||||
<item>175.24.0.0/14</item>
|
<item>175.24.0.0/14</item>
|
||||||
@@ -2507,14 +2501,8 @@
|
|||||||
<item>183.182.0.0/19</item>
|
<item>183.182.0.0/19</item>
|
||||||
<item>183.184.0.0/13</item>
|
<item>183.184.0.0/13</item>
|
||||||
<item>183.192.0.0/10</item>
|
<item>183.192.0.0/10</item>
|
||||||
<item>192.0.0.0/29</item>
|
|
||||||
<item>192.0.2.0/24</item>
|
|
||||||
<item>192.88.99.0/24</item>
|
|
||||||
<item>192.124.154.0/24</item>
|
<item>192.124.154.0/24</item>
|
||||||
<item>192.168.0.0/16</item>
|
|
||||||
<item>192.188.170.0/24</item>
|
<item>192.188.170.0/24</item>
|
||||||
<item>198.18.0.0/15</item>
|
|
||||||
<item>198.51.100.0/24</item>
|
|
||||||
<item>202.0.100.0/23</item>
|
<item>202.0.100.0/23</item>
|
||||||
<item>202.0.122.0/23</item>
|
<item>202.0.122.0/23</item>
|
||||||
<item>202.0.176.0/22</item>
|
<item>202.0.176.0/22</item>
|
||||||
@@ -2910,7 +2898,6 @@
|
|||||||
<item>203.0.90.0/23</item>
|
<item>203.0.90.0/23</item>
|
||||||
<item>203.0.96.0/23</item>
|
<item>203.0.96.0/23</item>
|
||||||
<item>203.0.104.0/21</item>
|
<item>203.0.104.0/21</item>
|
||||||
<item>203.0.113.0/24</item>
|
|
||||||
<item>203.0.114.0/23</item>
|
<item>203.0.114.0/23</item>
|
||||||
<item>203.0.122.0/24</item>
|
<item>203.0.122.0/24</item>
|
||||||
<item>203.0.128.0/24</item>
|
<item>203.0.128.0/24</item>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
</string>
|
</string>
|
||||||
<string name="route_list">Route</string>
|
<string name="route_list">Route</string>
|
||||||
<string name="proxied_apps">Per-App Proxy</string>
|
<string name="proxied_apps">Per-App Proxy</string>
|
||||||
<string name="proxied_apps_summary">Set proxy for selected apps</string>
|
<string name="proxied_apps_summary">Set proxy for selected apps, requires NAT mode under Android 4.x</string>
|
||||||
<string name="on">On</string>
|
<string name="on">On</string>
|
||||||
<string name="bypass_apps">Bypass Mode</string>
|
<string name="bypass_apps">Bypass Mode</string>
|
||||||
<string name="bypass_apps_summary">Enable this option to bypass selected apps</string>
|
<string name="bypass_apps_summary">Enable this option to bypass selected apps</string>
|
||||||
@@ -66,6 +66,7 @@
|
|||||||
<!-- notification category -->
|
<!-- notification category -->
|
||||||
<string name="forward_success">Shadowsocks started.</string>
|
<string name="forward_success">Shadowsocks started.</string>
|
||||||
<string name="service_failed">Failed to connect the remote server</string>
|
<string name="service_failed">Failed to connect the remote server</string>
|
||||||
|
<string name="nat_deprecated">WARNING: NAT mode has been deprecated since Android 5.0</string>
|
||||||
<string name="nat_no_root">NAT mode requires ROOT permission</string>
|
<string name="nat_no_root">NAT mode requires ROOT permission</string>
|
||||||
<string name="switch_to_vpn">Switch to VPN mode</string>
|
<string name="switch_to_vpn">Switch to VPN mode</string>
|
||||||
<string name="stop">Stop the service</string>
|
<string name="stop">Stop the service</string>
|
||||||
@@ -111,7 +112,10 @@
|
|||||||
<string name="add_profile_dialog">Add this Shadowsocks Profile?</string>
|
<string name="add_profile_dialog">Add this Shadowsocks Profile?</string>
|
||||||
<string name="add_profile_methods_scan_qr_code">Scan QR code</string>
|
<string name="add_profile_methods_scan_qr_code">Scan QR code</string>
|
||||||
<string name="add_profile_methods_manual_settings">Manual Settings</string>
|
<string name="add_profile_methods_manual_settings">Manual Settings</string>
|
||||||
<string name="removed">Removed</string>
|
<plurals name="removed">
|
||||||
|
<item quantity="one">Removed</item>
|
||||||
|
<item quantity="other">%d items removed</item>
|
||||||
|
</plurals>
|
||||||
<string name="undo">Undo</string>
|
<string name="undo">Undo</string>
|
||||||
|
|
||||||
<!-- tasker -->
|
<!-- tasker -->
|
||||||
@@ -124,5 +128,4 @@
|
|||||||
<!-- status -->
|
<!-- status -->
|
||||||
<string name="sent">Sent:</string>
|
<string name="sent">Sent:</string>
|
||||||
<string name="received">Received:</string>
|
<string name="received">Received:</string>
|
||||||
<string name="check_connectivity">Check Connectivity</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<com.github.shadowsocks.preferences.StatusPreference
|
<com.github.shadowsocks.preferences.StatusPreference
|
||||||
android:key="stat"
|
android:key="stat"
|
||||||
android:summary="@string/check_connectivity"
|
android:summary="@string/connection_test_pending"
|
||||||
android:title="@string/stat"/>
|
android:title="@string/stat"/>
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.github.shadowsocks
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.{Bundle, Handler}
|
import android.os.{Bundle, Handler}
|
||||||
import android.support.design.widget.Snackbar
|
|
||||||
import android.support.v7.app.{AlertDialog, AppCompatActivity}
|
import android.support.v7.app.{AlertDialog, AppCompatActivity}
|
||||||
import android.support.v7.widget.RecyclerView.ViewHolder
|
import android.support.v7.widget.RecyclerView.ViewHolder
|
||||||
import android.support.v7.widget.Toolbar.OnMenuItemClickListener
|
import android.support.v7.widget.Toolbar.OnMenuItemClickListener
|
||||||
@@ -11,12 +10,12 @@ import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback
|
|||||||
import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar}
|
import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar}
|
||||||
import android.text.style.TextAppearanceSpan
|
import android.text.style.TextAppearanceSpan
|
||||||
import android.text.{SpannableStringBuilder, Spanned}
|
import android.text.{SpannableStringBuilder, Spanned}
|
||||||
import android.view.View.{OnAttachStateChangeListener, OnClickListener}
|
|
||||||
import android.view.{LayoutInflater, MenuItem, View, ViewGroup}
|
import android.view.{LayoutInflater, MenuItem, View, ViewGroup}
|
||||||
import android.widget.{CheckedTextView, ImageView, LinearLayout, Toast}
|
import android.widget.{CheckedTextView, ImageView, LinearLayout, Toast}
|
||||||
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
|
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
|
||||||
import com.github.shadowsocks.database.Profile
|
import com.github.shadowsocks.database.Profile
|
||||||
import com.github.shadowsocks.utils.{Parser, TrafficMonitor, Utils}
|
import com.github.shadowsocks.utils.{Parser, TrafficMonitor, Utils}
|
||||||
|
import com.github.shadowsocks.widget.UndoSnackbarManager
|
||||||
import com.google.zxing.integration.android.IntentIntegrator
|
import com.google.zxing.integration.android.IntentIntegrator
|
||||||
import net.glxn.qrgen.android.QRCode
|
import net.glxn.qrgen.android.QRCode
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
|
|||||||
import ProfileManagerActivity._
|
import ProfileManagerActivity._
|
||||||
|
|
||||||
private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener {
|
private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener {
|
||||||
private var item: Profile = _
|
var item: Profile = _
|
||||||
private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView]
|
private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView]
|
||||||
itemView.setOnClickListener(this)
|
itemView.setOnClickListener(this)
|
||||||
|
|
||||||
@@ -76,12 +75,11 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
|
|||||||
handler.post(() => text.setText(builder))
|
handler.post(() => text.setText(builder))
|
||||||
}
|
}
|
||||||
|
|
||||||
def bind(item: Profile, i: Int) {
|
def bind(item: Profile) {
|
||||||
this.item = item
|
this.item = item
|
||||||
updateText()
|
updateText()
|
||||||
if (item.id == ShadowsocksApplication.profileId) {
|
if (item.id == ShadowsocksApplication.profileId) {
|
||||||
text.setChecked(true)
|
text.setChecked(true)
|
||||||
selectedIndex = i
|
|
||||||
selectedItem = this
|
selectedItem = this
|
||||||
} else {
|
} else {
|
||||||
text.setChecked(false)
|
text.setChecked(false)
|
||||||
@@ -96,26 +94,25 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] {
|
private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] {
|
||||||
private val recycleBin = new ArrayBuffer[(Int, Profile)]
|
|
||||||
var profiles = new ArrayBuffer[Profile]
|
var profiles = new ArrayBuffer[Profile]
|
||||||
profiles ++= ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List.empty[Profile])
|
profiles ++= ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List.empty[Profile])
|
||||||
|
|
||||||
def getItemCount = profiles.length
|
def getItemCount = profiles.length
|
||||||
|
|
||||||
def onBindViewHolder(vh: ProfileViewHolder, i: Int) = vh.bind(profiles(i), i)
|
def onBindViewHolder(vh: ProfileViewHolder, i: Int) = vh.bind(profiles(i))
|
||||||
|
|
||||||
def onCreateViewHolder(vg: ViewGroup, i: Int) =
|
def onCreateViewHolder(vg: ViewGroup, i: Int) =
|
||||||
new ProfileViewHolder(LayoutInflater.from(vg.getContext).inflate(R.layout.layout_profiles_item, vg, false))
|
new ProfileViewHolder(LayoutInflater.from(vg.getContext).inflate(R.layout.layout_profiles_item, vg, false))
|
||||||
|
|
||||||
def add(item: Profile) {
|
def add(item: Profile) {
|
||||||
removedSnackbar.dismiss
|
undoManager.flush
|
||||||
commitRemoves
|
|
||||||
val pos = getItemCount
|
val pos = getItemCount
|
||||||
profiles += item
|
profiles += item
|
||||||
notifyItemInserted(pos)
|
notifyItemInserted(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
def move(from: Int, to: Int) {
|
def move(from: Int, to: Int) {
|
||||||
|
undoManager.flush
|
||||||
val step = if (from < to) 1 else -1
|
val step = if (from < to) 1 else -1
|
||||||
val first = profiles(from)
|
val first = profiles(from)
|
||||||
var previousOrder = profiles(from).userOrder
|
var previousOrder = profiles(from).userOrder
|
||||||
@@ -134,32 +131,24 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
|
|||||||
}
|
}
|
||||||
|
|
||||||
def remove(pos: Int) {
|
def remove(pos: Int) {
|
||||||
recycleBin.append((pos, profiles(pos)))
|
|
||||||
profiles.remove(pos)
|
profiles.remove(pos)
|
||||||
notifyItemRemoved(pos)
|
notifyItemRemoved(pos)
|
||||||
}
|
}
|
||||||
def undoRemoves {
|
def undo(actions: Iterator[(Int, Profile)]) = for ((index, item) <- actions) {
|
||||||
for ((index, item) <- recycleBin.reverseIterator) {
|
profiles.insert(index, item)
|
||||||
profiles.insert(index, item)
|
notifyItemInserted(index)
|
||||||
notifyItemInserted(index)
|
|
||||||
}
|
|
||||||
recycleBin.clear
|
|
||||||
}
|
}
|
||||||
def commitRemoves {
|
def commit(actions: Iterator[(Int, Profile)]) = for ((index, item) <- actions) {
|
||||||
for ((index, item) <- recycleBin) {
|
ShadowsocksApplication.profileManager.delProfile(item.id)
|
||||||
ShadowsocksApplication.profileManager.delProfile(item.id)
|
if (item.id == ShadowsocksApplication.profileId) ShadowsocksApplication.profileId(-1)
|
||||||
if (item.id == ShadowsocksApplication.profileId) ShadowsocksApplication.profileId(-1)
|
|
||||||
}
|
|
||||||
recycleBin.clear
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var selectedIndex = -1
|
|
||||||
private var selectedItem: ProfileViewHolder = _
|
private var selectedItem: ProfileViewHolder = _
|
||||||
private val handler = new Handler
|
private val handler = new Handler
|
||||||
|
|
||||||
private lazy val profilesAdapter = new ProfilesAdapter
|
private lazy val profilesAdapter = new ProfilesAdapter
|
||||||
private var removedSnackbar: Snackbar = _
|
private var undoManager: UndoSnackbarManager[Profile] = _
|
||||||
|
|
||||||
override def onCreate(savedInstanceState: Bundle) {
|
override def onCreate(savedInstanceState: Bundle) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -181,21 +170,16 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
|
|||||||
profilesList.setLayoutManager(layoutManager)
|
profilesList.setLayoutManager(layoutManager)
|
||||||
profilesList.setItemAnimator(new DefaultItemAnimator)
|
profilesList.setItemAnimator(new DefaultItemAnimator)
|
||||||
profilesList.setAdapter(profilesAdapter)
|
profilesList.setAdapter(profilesAdapter)
|
||||||
if (selectedIndex < 0) selectedIndex = profilesAdapter.profiles.zipWithIndex.collectFirst {
|
layoutManager.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst {
|
||||||
case (profile, i) if profile.id == ShadowsocksApplication.profileId => i
|
case (profile, i) if profile.id == ShadowsocksApplication.profileId => i
|
||||||
}.getOrElse(-1)
|
}.getOrElse(-1))
|
||||||
layoutManager.scrollToPosition(selectedIndex)
|
undoManager = new UndoSnackbarManager[Profile](profilesList, profilesAdapter.undo, profilesAdapter.commit)
|
||||||
removedSnackbar = Snackbar.make(profilesList, R.string.removed, Snackbar.LENGTH_LONG)
|
|
||||||
.setAction(R.string.undo, ((v: View) => profilesAdapter.undoRemoves): OnClickListener)
|
|
||||||
removedSnackbar.getView.addOnAttachStateChangeListener(new OnAttachStateChangeListener {
|
|
||||||
def onViewDetachedFromWindow(v: View) = profilesAdapter.commitRemoves
|
|
||||||
def onViewAttachedToWindow(v: View) = ()
|
|
||||||
})
|
|
||||||
new ItemTouchHelper(new SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
|
new ItemTouchHelper(new SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
|
||||||
ItemTouchHelper.START | ItemTouchHelper.END) {
|
ItemTouchHelper.START | ItemTouchHelper.END) {
|
||||||
def onSwiped(viewHolder: ViewHolder, direction: Int) = {
|
def onSwiped(viewHolder: ViewHolder, direction: Int) = {
|
||||||
profilesAdapter.remove(viewHolder.getAdapterPosition)
|
val index = viewHolder.getAdapterPosition
|
||||||
removedSnackbar.show
|
profilesAdapter.remove(index)
|
||||||
|
undoManager.remove(index, viewHolder.asInstanceOf[ProfileViewHolder].item)
|
||||||
}
|
}
|
||||||
def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = {
|
def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = {
|
||||||
profilesAdapter.move(viewHolder.getAdapterPosition, target.getAdapterPosition)
|
profilesAdapter.move(viewHolder.getAdapterPosition, target.getAdapterPosition)
|
||||||
@@ -227,9 +211,9 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
|
|||||||
|
|
||||||
override def onDestroy {
|
override def onDestroy {
|
||||||
deattachService()
|
deattachService()
|
||||||
super.onDestroy
|
undoManager.flush
|
||||||
ShadowsocksApplication.profileManager.setProfileAddedListener(null)
|
ShadowsocksApplication.profileManager.setProfileAddedListener(null)
|
||||||
profilesAdapter.commitRemoves
|
super.onDestroy
|
||||||
}
|
}
|
||||||
|
|
||||||
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
|
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ import android.view.{View, ViewGroup, ViewParent}
|
|||||||
import android.widget._
|
import android.widget._
|
||||||
import com.github.jorgecastilloprz.FABProgressCircle
|
import com.github.jorgecastilloprz.FABProgressCircle
|
||||||
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
|
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
|
||||||
import com.github.shadowsocks.preferences._
|
|
||||||
import com.github.shadowsocks.database._
|
import com.github.shadowsocks.database._
|
||||||
import com.github.shadowsocks.utils._
|
import com.github.shadowsocks.utils._
|
||||||
import com.google.android.gms.ads.{AdRequest, AdSize, AdView}
|
import com.google.android.gms.ads.{AdRequest, AdSize, AdView}
|
||||||
@@ -107,9 +106,6 @@ class Shadowsocks
|
|||||||
var state = State.STOPPED
|
var state = State.STOPPED
|
||||||
var currentProfile = new Profile
|
var currentProfile = new Profile
|
||||||
var vpnEnabled = -1
|
var vpnEnabled = -1
|
||||||
var trafficCache: Array[String] = _
|
|
||||||
var connectionTestResult: String = _
|
|
||||||
var connectionTestSuccess: Boolean = true
|
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
var currentServiceName = classOf[ShadowsocksNatService].getName
|
var currentServiceName = classOf[ShadowsocksNatService].getName
|
||||||
@@ -158,28 +154,13 @@ class Shadowsocks
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
def trafficUpdated(txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
|
def trafficUpdated(txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
|
||||||
trafficCache = Array(
|
handler.post(() => updateTraffic(txRate, rxRate, txTotal, rxTotal))
|
||||||
"▲ " + TrafficMonitor.formatTraffic(txRate) + "/s",
|
|
||||||
"▼ " + TrafficMonitor.formatTraffic(rxRate) + "/s",
|
|
||||||
TrafficMonitor.formatTraffic(txTotal), TrafficMonitor.formatTraffic(rxTotal))
|
|
||||||
handler.post(updateTraffic)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateTraffic(): Unit = if (trafficCache == null) callback.trafficUpdated(0, 0, 0, 0) else {
|
def updateTraffic(txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) = preferences.stat
|
||||||
if (connectionTestResult == null) connectionTestResult = getString(R.string.connection_test_pending)
|
.setRate(TrafficMonitor.formatTraffic(txTotal), TrafficMonitor.formatTraffic(rxTotal),
|
||||||
if (preferences.natSwitch.isChecked) {
|
TrafficMonitor.formatTraffic(txRate) + "/s", TrafficMonitor.formatTraffic(rxRate) + "/s")
|
||||||
preferences.stat.setSummary("")
|
|
||||||
} else {
|
|
||||||
if (connectionTestSuccess) {
|
|
||||||
preferences.stat.setSummary(connectionTestResult)
|
|
||||||
} else {
|
|
||||||
preferences.stat.setSummary(getString(R.string.connection_test_fail))
|
|
||||||
Snackbar.make(findViewById(android.R.id.content), connectionTestResult, Snackbar.LENGTH_LONG).show
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preferences.stat.asInstanceOf[StatusPreference].setRate(trafficCache(2), trafficCache(3), trafficCache(0), trafficCache(1))
|
|
||||||
}
|
|
||||||
|
|
||||||
def attachService: Unit = attachService(callback)
|
def attachService: Unit = attachService(callback)
|
||||||
|
|
||||||
@@ -187,6 +168,11 @@ class Shadowsocks
|
|||||||
// Update the UI
|
// Update the UI
|
||||||
if (fab != null) fab.setEnabled(true)
|
if (fab != null) fab.setEnabled(true)
|
||||||
updateState()
|
updateState()
|
||||||
|
if (Build.VERSION.SDK_INT >= 21 && !ShadowsocksApplication.isVpnEnabled) {
|
||||||
|
val snackbar = Snackbar.make(findViewById(android.R.id.content), R.string.nat_deprecated, Snackbar.LENGTH_LONG)
|
||||||
|
snackbar.setAction(R.string.switch_to_vpn, (_ => preferences.natSwitch.setChecked(false)): View.OnClickListener)
|
||||||
|
snackbar.show
|
||||||
|
}
|
||||||
|
|
||||||
if (!ShadowsocksApplication.settings.getBoolean(ShadowsocksApplication.getVersionName, false)) {
|
if (!ShadowsocksApplication.settings.getBoolean(ShadowsocksApplication.getVersionName, false)) {
|
||||||
ShadowsocksApplication.settings.edit.putBoolean(ShadowsocksApplication.getVersionName, true).apply()
|
ShadowsocksApplication.settings.edit.putBoolean(ShadowsocksApplication.getVersionName, true).apply()
|
||||||
@@ -263,7 +249,7 @@ class Shadowsocks
|
|||||||
} else {
|
} else {
|
||||||
in = assetManager.open(file)
|
in = assetManager.open(file)
|
||||||
}
|
}
|
||||||
out = new FileOutputStream(getApplicationInfo().dataDir + "/" + file)
|
out = new FileOutputStream(getApplicationInfo.dataDir + "/" + file)
|
||||||
copyFile(in, out)
|
copyFile(in, out)
|
||||||
in.close()
|
in.close()
|
||||||
in = null
|
in = null
|
||||||
@@ -293,8 +279,8 @@ class Shadowsocks
|
|||||||
val cmd = new ArrayBuffer[String]()
|
val cmd = new ArrayBuffer[String]()
|
||||||
|
|
||||||
for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks", "tun2socks")) {
|
for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks", "tun2socks")) {
|
||||||
cmd.append("chmod 666 %s/%s-nat.pid".formatLocal(Locale.ENGLISH, getApplicationInfo().dataDir, task))
|
cmd.append("chmod 666 %s/%s-nat.pid".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
|
||||||
cmd.append("chmod 666 %s/%s-vpn.pid".formatLocal(Locale.ENGLISH, getApplicationInfo().dataDir, task))
|
cmd.append("chmod 666 %s/%s-vpn.pid".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ShadowsocksApplication.isVpnEnabled) {
|
if (ShadowsocksApplication.isVpnEnabled) {
|
||||||
@@ -307,8 +293,8 @@ class Shadowsocks
|
|||||||
|
|
||||||
for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks", "tun2socks")) {
|
for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks", "tun2socks")) {
|
||||||
try {
|
try {
|
||||||
val pid_nat = scala.io.Source.fromFile(getApplicationInfo().dataDir + "/" + task + "-nat.pid").mkString.trim.toInt
|
val pid_nat = scala.io.Source.fromFile(getApplicationInfo.dataDir + "/" + task + "-nat.pid").mkString.trim.toInt
|
||||||
val pid_vpn = scala.io.Source.fromFile(getApplicationInfo().dataDir + "/" + task + "-vpn.pid").mkString.trim.toInt
|
val pid_vpn = scala.io.Source.fromFile(getApplicationInfo.dataDir + "/" + task + "-vpn.pid").mkString.trim.toInt
|
||||||
cmd.append("kill -9 %d".formatLocal(Locale.ENGLISH, pid_nat))
|
cmd.append("kill -9 %d".formatLocal(Locale.ENGLISH, pid_nat))
|
||||||
cmd.append("kill -9 %d".formatLocal(Locale.ENGLISH, pid_vpn))
|
cmd.append("kill -9 %d".formatLocal(Locale.ENGLISH, pid_vpn))
|
||||||
Process.killProcess(pid_nat)
|
Process.killProcess(pid_nat)
|
||||||
@@ -316,10 +302,10 @@ class Shadowsocks
|
|||||||
} catch {
|
} catch {
|
||||||
case e: Throwable => // Ignore
|
case e: Throwable => // Ignore
|
||||||
}
|
}
|
||||||
cmd.append("rm -f %s/%s-nat.pid".formatLocal(Locale.ENGLISH, getApplicationInfo().dataDir, task))
|
cmd.append("rm -f %s/%s-nat.pid".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
|
||||||
cmd.append("rm -f %s/%s-nat.conf".formatLocal(Locale.ENGLISH, getApplicationInfo().dataDir, task))
|
cmd.append("rm -f %s/%s-nat.conf".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
|
||||||
cmd.append("rm -f %s/%s-vpn.pid".formatLocal(Locale.ENGLISH, getApplicationInfo().dataDir, task))
|
cmd.append("rm -f %s/%s-vpn.pid".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
|
||||||
cmd.append("rm -f %s/%s-vpn.conf".formatLocal(Locale.ENGLISH, getApplicationInfo().dataDir, task))
|
cmd.append("rm -f %s/%s-vpn.conf".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
|
||||||
}
|
}
|
||||||
if (ShadowsocksApplication.isVpnEnabled) Console.runCommand(cmd.toArray) else {
|
if (ShadowsocksApplication.isVpnEnabled) Console.runCommand(cmd.toArray) else {
|
||||||
Console.runRootCommand(cmd.toArray)
|
Console.runRootCommand(cmd.toArray)
|
||||||
@@ -389,6 +375,7 @@ class Shadowsocks
|
|||||||
Toast.LENGTH_SHORT), fab, getWindow, 0, Utils.dpToPx(this, 8)).show
|
Toast.LENGTH_SHORT), fab, getWindow, 0, Utils.dpToPx(this, 8)).show
|
||||||
true
|
true
|
||||||
})
|
})
|
||||||
|
updateTraffic(0, 0, 0, 0)
|
||||||
|
|
||||||
// Bind to the service
|
// Bind to the service
|
||||||
handler.post(() => attachService)
|
handler.post(() => attachService)
|
||||||
@@ -461,7 +448,6 @@ class Shadowsocks
|
|||||||
// Check if current profile changed
|
// Check if current profile changed
|
||||||
if (ShadowsocksApplication.profileId != currentProfile.id) reloadProfile()
|
if (ShadowsocksApplication.profileId != currentProfile.id) reloadProfile()
|
||||||
|
|
||||||
updateTraffic()
|
|
||||||
updateState()
|
updateState()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,7 +489,7 @@ class Shadowsocks
|
|||||||
|
|
||||||
val ab = new ArrayBuffer[String]
|
val ab = new ArrayBuffer[String]
|
||||||
for (executable <- Shadowsocks.EXECUTABLES) {
|
for (executable <- Shadowsocks.EXECUTABLES) {
|
||||||
ab.append("chmod 755 " + getApplicationInfo().dataDir + "/" + executable)
|
ab.append("chmod 755 " + getApplicationInfo.dataDir + "/" + executable)
|
||||||
}
|
}
|
||||||
Console.runCommand(ab.toArray)
|
Console.runCommand(ab.toArray)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import java.lang.Process
|
|||||||
import java.net.{Inet6Address, InetAddress}
|
import java.net.{Inet6Address, InetAddress}
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
import android.app.Service
|
|
||||||
import android.content._
|
import android.content._
|
||||||
import android.content.pm.{PackageInfo, PackageManager}
|
import android.content.pm.{PackageInfo, PackageManager}
|
||||||
import android.net.{ConnectivityManager, Network}
|
import android.net.{ConnectivityManager, Network}
|
||||||
@@ -164,12 +163,14 @@ class ShadowsocksNatService extends BaseService {
|
|||||||
|
|
||||||
def startShadowsocksDaemon() {
|
def startShadowsocksDaemon() {
|
||||||
if (config.route != Route.ALL) {
|
if (config.route != Route.ALL) {
|
||||||
val acl: Array[String] = config.route match {
|
val acl: Array[Array[String]] = config.route match {
|
||||||
case Route.BYPASS_LAN => getResources.getStringArray(R.array.private_route)
|
case Route.BYPASS_LAN => Array(getResources.getStringArray(R.array.private_route))
|
||||||
case Route.BYPASS_CHN => getResources.getStringArray(R.array.chn_route_full)
|
case Route.BYPASS_CHN => Array(getResources.getStringArray(R.array.chn_route))
|
||||||
|
case Route.BYPASS_LAN_CHN =>
|
||||||
|
Array(getResources.getStringArray(R.array.private_route), getResources.getStringArray(R.array.chn_route))
|
||||||
}
|
}
|
||||||
ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/acl.list"))(p => {
|
ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/acl.list"))(p => {
|
||||||
acl.foreach(item => p.println(item))
|
acl.flatten.foreach(p.println)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ import android.content.{DialogInterface, Intent, SharedPreferences}
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.{Build, Bundle}
|
import android.os.{Build, Bundle}
|
||||||
import android.preference.{Preference, PreferenceFragment, SwitchPreference}
|
import android.preference.{Preference, PreferenceFragment, SwitchPreference}
|
||||||
|
import android.support.design.widget.Snackbar
|
||||||
import android.support.v7.app.AlertDialog
|
import android.support.v7.app.AlertDialog
|
||||||
import android.webkit.{WebView, WebViewClient}
|
import android.webkit.{WebView, WebViewClient}
|
||||||
import com.github.shadowsocks.database.Profile
|
import com.github.shadowsocks.database.Profile
|
||||||
import com.github.shadowsocks.preferences.{DropDownPreference, NumberPickerPreference, PasswordEditTextPreference, SummaryEditTextPreference}
|
import com.github.shadowsocks.preferences._
|
||||||
import com.github.shadowsocks.utils.CloseUtils._
|
import com.github.shadowsocks.utils.CloseUtils._
|
||||||
import com.github.shadowsocks.utils.{Key, Utils}
|
import com.github.shadowsocks.utils.{Key, Utils}
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
|
|||||||
|
|
||||||
private def activity = getActivity.asInstanceOf[Shadowsocks]
|
private def activity = getActivity.asInstanceOf[Shadowsocks]
|
||||||
lazy val natSwitch = findPreference(Key.isNAT).asInstanceOf[SwitchPreference]
|
lazy val natSwitch = findPreference(Key.isNAT).asInstanceOf[SwitchPreference]
|
||||||
var stat: Preference = _
|
var stat: StatusPreference = _
|
||||||
|
|
||||||
private var isProxyApps: SwitchPreference = _
|
private var isProxyApps: SwitchPreference = _
|
||||||
private var testCount: Int = _
|
private var testCount: Int = _
|
||||||
@@ -78,13 +79,11 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
|
|||||||
addPreferencesFromResource(R.xml.pref_all)
|
addPreferencesFromResource(R.xml.pref_all)
|
||||||
getPreferenceManager.getSharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
getPreferenceManager.getSharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||||
|
|
||||||
stat= findPreference(Key.stat)
|
stat = findPreference(Key.stat).asInstanceOf[StatusPreference]
|
||||||
stat.setOnPreferenceClickListener(_ => {
|
stat.setOnPreferenceClickListener(_ => {
|
||||||
val id = synchronized {
|
val id = synchronized {
|
||||||
testCount += 1
|
testCount += 1
|
||||||
activity.connectionTestResult = getString(R.string.connection_test_testing)
|
activity.handler.post(() => stat.setSummary(R.string.connection_test_testing))
|
||||||
activity.connectionTestSuccess = true
|
|
||||||
activity.updateTraffic()
|
|
||||||
testCount
|
testCount
|
||||||
}
|
}
|
||||||
ThrowableFuture {
|
ThrowableFuture {
|
||||||
@@ -97,7 +96,7 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
|
|||||||
conn.setUseCaches(false)
|
conn.setUseCaches(false)
|
||||||
if (testCount == id) {
|
if (testCount == id) {
|
||||||
var result: String = null
|
var result: String = null
|
||||||
var success: Boolean = true
|
var success = true
|
||||||
try {
|
try {
|
||||||
val start = currentTimeMillis
|
val start = currentTimeMillis
|
||||||
conn.getInputStream
|
conn.getInputStream
|
||||||
@@ -107,21 +106,24 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
|
|||||||
result = getString(R.string.connection_test_available, elapsed: java.lang.Long)
|
result = getString(R.string.connection_test_available, elapsed: java.lang.Long)
|
||||||
else throw new Exception(getString(R.string.connection_test_error_status_code, code: Integer))
|
else throw new Exception(getString(R.string.connection_test_error_status_code, code: Integer))
|
||||||
} catch {
|
} catch {
|
||||||
case e: Exception => {
|
case e: Exception =>
|
||||||
success = false
|
success = false
|
||||||
result = getString(R.string.connection_test_error, e.getMessage)
|
result = getString(R.string.connection_test_error, e.getMessage)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
synchronized(if (testCount == id) {
|
synchronized(if (testCount == id) {
|
||||||
activity.connectionTestSuccess = success
|
if (ShadowsocksApplication.isVpnEnabled) activity.handler.post(() => {
|
||||||
activity.connectionTestResult = result
|
if (success) stat.setSummary(result) else {
|
||||||
activity.handler.post(activity.updateTraffic)
|
stat.setSummary(R.string.connection_test_fail)
|
||||||
|
Snackbar.make(activity.findViewById(android.R.id.content), result, Snackbar.LENGTH_LONG).show
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
})
|
})
|
||||||
|
stat.setSummary(if (ShadowsocksApplication.isVpnEnabled) getString(R.string.connection_test_pending) else null)
|
||||||
|
|
||||||
isProxyApps = findPreference(Key.isProxyApps).asInstanceOf[SwitchPreference]
|
isProxyApps = findPreference(Key.isProxyApps).asInstanceOf[SwitchPreference]
|
||||||
isProxyApps.setOnPreferenceClickListener((preference: Preference) => {
|
isProxyApps.setOnPreferenceClickListener((preference: Preference) => {
|
||||||
@@ -182,10 +184,12 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
|
|||||||
}
|
}
|
||||||
|
|
||||||
def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) = key match {
|
def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) = key match {
|
||||||
case Key.isNAT => activity.handler.post(() => {
|
case Key.isNAT =>
|
||||||
activity.deattachService
|
activity.handler.post(() => {
|
||||||
activity.attachService
|
activity.deattachService
|
||||||
})
|
activity.attachService
|
||||||
|
})
|
||||||
|
stat.setSummary(if (ShadowsocksApplication.isVpnEnabled) getString(R.string.connection_test_pending) else null)
|
||||||
case _ =>
|
case _ =>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -273,12 +273,14 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
def startShadowsocksDaemon() {
|
def startShadowsocksDaemon() {
|
||||||
|
|
||||||
if (config.route != Route.ALL) {
|
if (config.route != Route.ALL) {
|
||||||
val acl: Array[String] = config.route match {
|
val acl: Array[Array[String]] = config.route match {
|
||||||
case Route.BYPASS_LAN => getResources.getStringArray(R.array.private_route)
|
case Route.BYPASS_LAN => Array(getResources.getStringArray(R.array.private_route))
|
||||||
case Route.BYPASS_CHN => getResources.getStringArray(R.array.chn_route_full)
|
case Route.BYPASS_CHN => Array(getResources.getStringArray(R.array.chn_route))
|
||||||
|
case Route.BYPASS_LAN_CHN =>
|
||||||
|
Array(getResources.getStringArray(R.array.private_route), getResources.getStringArray(R.array.chn_route))
|
||||||
}
|
}
|
||||||
ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/acl.list"))(p => {
|
ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/acl.list"))(p => {
|
||||||
acl.foreach(item => p.println(item))
|
acl.flatten.foreach(p.println)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class TaskerActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] {
|
private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] {
|
||||||
private val profiles = ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List.empty[Profile])
|
val profiles = ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List.empty[Profile])
|
||||||
def getItemCount = 1 + profiles.length
|
def getItemCount = 1 + profiles.length
|
||||||
def onBindViewHolder(vh: ProfileViewHolder, i: Int) = i match {
|
def onBindViewHolder(vh: ProfileViewHolder, i: Int) = i match {
|
||||||
case 0 => vh.bindDefault
|
case 0 => vh.bindDefault
|
||||||
@@ -110,9 +110,13 @@ class TaskerActivity extends AppCompatActivity {
|
|||||||
switch = findViewById(R.id.serviceSwitch).asInstanceOf[Switch]
|
switch = findViewById(R.id.serviceSwitch).asInstanceOf[Switch]
|
||||||
switch.setChecked(taskerOption.switchOn)
|
switch.setChecked(taskerOption.switchOn)
|
||||||
val profilesList = findViewById(R.id.profilesList).asInstanceOf[RecyclerView]
|
val profilesList = findViewById(R.id.profilesList).asInstanceOf[RecyclerView]
|
||||||
profilesList.setLayoutManager(new LinearLayoutManager(this))
|
val lm = new LinearLayoutManager(this)
|
||||||
|
profilesList.setLayoutManager(lm)
|
||||||
profilesList.setItemAnimator(new DefaultItemAnimator)
|
profilesList.setItemAnimator(new DefaultItemAnimator)
|
||||||
profilesList.setAdapter(profilesAdapter)
|
profilesList.setAdapter(profilesAdapter)
|
||||||
|
if (taskerOption.profileId >= 0) lm.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst {
|
||||||
|
case (profile, i) if profile.id == taskerOption.profileId => i + 1
|
||||||
|
}.getOrElse(0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ object DBHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class DBHelper(val context: Context)
|
class DBHelper(val context: Context)
|
||||||
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 15) {
|
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 16) {
|
||||||
import DBHelper._
|
import DBHelper._
|
||||||
|
|
||||||
lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile])
|
lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile])
|
||||||
@@ -121,6 +121,10 @@ class DBHelper(val context: Context)
|
|||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 16) {
|
||||||
|
profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'bypass-lan-china' WHERE route = 'bypass-china'")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package com.github.shadowsocks.preferences
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.preference.Preference
|
import android.preference.Preference
|
||||||
import android.view.{View, ViewGroup, LayoutInflater}
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.view.{LayoutInflater, View, ViewGroup}
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.github.shadowsocks.R
|
import com.github.shadowsocks.R
|
||||||
|
|
||||||
@@ -12,14 +12,18 @@ import com.github.shadowsocks.R
|
|||||||
*/
|
*/
|
||||||
final class StatusPreference(context: Context, attrs: AttributeSet = null)
|
final class StatusPreference(context: Context, attrs: AttributeSet = null)
|
||||||
extends Preference(context, attrs) {
|
extends Preference(context, attrs) {
|
||||||
var txView: TextView = null
|
var tx: String = _
|
||||||
var rxView: TextView = null
|
var rx: String = _
|
||||||
var txRateView: TextView = null
|
var txRate: String = _
|
||||||
var rxRateView: TextView = null
|
var rxRate: String = _
|
||||||
|
var txView: TextView = _
|
||||||
|
var rxView: TextView = _
|
||||||
|
var txRateView: TextView = _
|
||||||
|
var rxRateView: TextView = _
|
||||||
|
|
||||||
override def onCreateView(parent: ViewGroup): View = {
|
override def onCreateView(parent: ViewGroup): View = {
|
||||||
val li = getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE).asInstanceOf[LayoutInflater]
|
val view = getContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE).asInstanceOf[LayoutInflater]
|
||||||
val view = li.inflate(R.layout.status_pref, parent, false)
|
.inflate(R.layout.status_pref, parent, false)
|
||||||
|
|
||||||
txView = view.findViewById(R.id.tx).asInstanceOf[TextView]
|
txView = view.findViewById(R.id.tx).asInstanceOf[TextView]
|
||||||
rxView = view.findViewById(R.id.rx).asInstanceOf[TextView]
|
rxView = view.findViewById(R.id.rx).asInstanceOf[TextView]
|
||||||
@@ -29,16 +33,28 @@ final class StatusPreference(context: Context, attrs: AttributeSet = null)
|
|||||||
view
|
view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def onBindView(view: View) = {
|
||||||
|
super.onBindView(view)
|
||||||
|
txView.setText(tx)
|
||||||
|
rxView.setText(rx)
|
||||||
|
txRateView.setText(txRate)
|
||||||
|
rxRateView.setText(rxRate)
|
||||||
|
}
|
||||||
|
|
||||||
def setRate(tx: String, rx: String, txRate: String, rxRate: String) {
|
def setRate(tx: String, rx: String, txRate: String, rxRate: String) {
|
||||||
|
this.tx = tx
|
||||||
if (txView != null) {
|
if (txView != null) {
|
||||||
txView.setText(tx)
|
txView.setText(tx)
|
||||||
}
|
}
|
||||||
|
this.rx = rx
|
||||||
if (rxView != null) {
|
if (rxView != null) {
|
||||||
rxView.setText(rx)
|
rxView.setText(rx)
|
||||||
}
|
}
|
||||||
|
this.txRate = txRate
|
||||||
if (txRateView != null) {
|
if (txRateView != null) {
|
||||||
txRateView.setText(txRate)
|
txRateView.setText(txRate)
|
||||||
}
|
}
|
||||||
|
this.rxRate = rxRate
|
||||||
if (rxRateView != null) {
|
if (rxRateView != null) {
|
||||||
rxRateView.setText(rxRate)
|
rxRateView.setText(rxRate)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,4 +110,5 @@ object Route {
|
|||||||
val ALL = "all"
|
val ALL = "all"
|
||||||
val BYPASS_LAN = "bypass-lan"
|
val BYPASS_LAN = "bypass-lan"
|
||||||
val BYPASS_CHN = "bypass-china"
|
val BYPASS_CHN = "bypass-china"
|
||||||
|
val BYPASS_LAN_CHN = "bypass-lan-china"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.github.shadowsocks.widget
|
||||||
|
|
||||||
|
import android.support.design.widget.Snackbar
|
||||||
|
import android.view.View
|
||||||
|
import com.github.shadowsocks.R
|
||||||
|
|
||||||
|
import scala.collection.mutable.ArrayBuffer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Mygod
|
||||||
|
* @param view The view to find a parent from.
|
||||||
|
* @param undo Callback for undoing removals.
|
||||||
|
* @param commit Callback for committing removals.
|
||||||
|
* @tparam T Item type.
|
||||||
|
*/
|
||||||
|
class UndoSnackbarManager[T](view: View, undo: Iterator[(Int, T)] => Unit,
|
||||||
|
commit: Iterator[(Int, T)] => Unit = null) {
|
||||||
|
private val recycleBin = new ArrayBuffer[(Int, T)]
|
||||||
|
private val removedCallback = new Snackbar.Callback {
|
||||||
|
override def onDismissed(snackbar: Snackbar, event: Int) = {
|
||||||
|
event match {
|
||||||
|
case Snackbar.Callback.DISMISS_EVENT_MANUAL | Snackbar.Callback.DISMISS_EVENT_TIMEOUT =>
|
||||||
|
if (commit != null) commit(recycleBin.iterator)
|
||||||
|
recycleBin.clear
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
last = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var last: Snackbar = _
|
||||||
|
|
||||||
|
def remove(index: Int, item: T) = {
|
||||||
|
recycleBin.append((index, item))
|
||||||
|
val count = recycleBin.length
|
||||||
|
last = Snackbar
|
||||||
|
.make(view, view.getResources.getQuantityString(R.plurals.removed, count, count: Integer), Snackbar.LENGTH_LONG)
|
||||||
|
.setCallback(removedCallback).setAction(R.string.undo, (_ => {
|
||||||
|
undo(recycleBin.reverseIterator)
|
||||||
|
recycleBin.clear
|
||||||
|
}): View.OnClickListener)
|
||||||
|
last.show
|
||||||
|
}
|
||||||
|
|
||||||
|
def flush = if (last != null) last.dismiss
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user