Compare commits

...
14 Commits
Author SHA1 Message Date
Max Lv 8e2fc2b228 Merge pull request #572 from Mygod/master
Refine undo
2016-02-11 23:12:36 +08:00
Mygod d794632616 Refine undo 2016-02-11 21:09:50 +08:00
Max Lv 529b1a32d3 Merge pull request #571 from Mygod/master
Add option: bypass China only
2016-02-08 16:43:03 +08:00
Mygod 396b49dab6 Fix broken links 2016-02-08 16:06:51 +08:00
Mygod 06deb4bbcb Add option: bypass China only
Fix #569.

It could have worked without upgrading database by using an option called `bypass-china-only`. But I think we will get better readability this way.
2016-02-08 15:44:10 +08:00
Max Lv 8127b9f8e2 Merge pull request #570 from Mygod/master
General enhancements
2016-02-08 15:13:14 +08:00
Mygod 3556eebfeb Tasker: scroll to selected profile 2016-02-08 15:06:53 +08:00
Mygod 10e3b211e3 Ask user to refrain from further using NAT mode 2016-02-08 14:40:49 +08:00
Mygod 05981fb117 Add a prompt: per-app proxy requires NAT mode under Android 4.x 2016-02-08 14:25:14 +08:00
Max Lv 4258e95f1e Merge pull request #568 from Mygod/layout
Refine layout of StatusPreference
2016-02-08 13:53:15 +08:00
Mygod cd5c2ef8ef Fix rate display disappearing in StatusPreference
It turns out that `onCreateView` gets called every time `setSummary` is invoked, and thus restoring texts for `TextView` is mandatory.
2016-02-08 13:47:30 +08:00
Mygod 5a84854881 Refine layout using GridView 2016-02-08 12:53:21 +08:00
Mygod 0cf1585712 Refine traffic stat layout 2016-02-07 18:41:50 +08:00
Mygod b8d97dde03 Remove obsolete xml files 2016-02-07 16:18:53 +08:00
25 changed files with 252 additions and 247 deletions
+1 -1
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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>'
+5 -5
View File
@@ -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="&#x6d;&#97;&#x69;&#108;&#116;&#111;&#x3a;&#x6d;&#97;x&#46;&#99;.&#108;&#118;&#x40;&#103;&#109;&#97;&#x69;&#108;&#46;&#x63;&#x6f;&#109;">&#x6d;&#97;&#x78;.&#x63;&#x2e;&#108;v&#64;&#x67;&#109;&#x61;&#x69;&#108;&#x2e;&#99;&#111;&#109;</a></p> <p>Copyright (C) 2016 Max Lv <a href="&#x6d;&#97;&#x69;&#108;&#116;&#111;&#x3a;&#x6d;&#97;x&#46;&#99;.&#108;&#118;&#x40;&#103;&#109;&#97;&#x69;&#108;&#46;&#x63;&#x6f;&#109;">&#x6d;&#97;&#x78;.&#x63;&#x2e;&#108;v&#64;&#x67;&#109;&#x61;&#x69;&#108;&#x2e;&#99;&#111;&#109;</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>
+67 -51
View File
@@ -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>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="proxied_apps_summary">Set proxy for selected apps</string>
</resources>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="proxied_apps_summary">为应用程序分别设置代理</string>
</resources>
+6 -3
View File
@@ -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>
+2
View File
@@ -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 &amp; China</item> <item>Bypass LAN &amp; 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 -14
View File
@@ -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>
+6 -3
View File
@@ -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>
+1 -1
View File
@@ -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
}