Commit ac2b668a authored by Mygod's avatar Mygod

Implement custom rules

Fix #1009 finally!
parent 8374a8cd
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
<item>
<selector android:enterFadeDuration="@android:integer/config_mediumAnimTime"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_selected="true">
<color android:color="@color/material_primary_100"/>
</item>
</selector>
</item>
</ripple>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:enterFadeDuration="@android:integer/config_mediumAnimTime"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_selected="true">
<color android:color="@color/material_primary_100"/>
</item>
</selector>
<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="#FF000000"
android:pathData="M19,3h-4.18C14.4,1.84 13.3,1 12,1c-1.3,0 -2.4,0.84 -2.82,2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM14,17L7,17v-2h7v2zM17,13L7,13v-2h10v2zM17,9L7,9L7,7h10v2z"/>
</vector>
<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="M14,10L2,10v2h12v-2zM14,6L2,6v2h12L14,6zM18,14v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2,16h8v-2L2,14v2z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Spinner android:id="@+id/template_selector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/acl_rule_templates"/>
<EditText android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lines="1"/>
</LinearLayout>
...@@ -47,6 +47,7 @@ ...@@ -47,6 +47,7 @@
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:id="@+id/applistview" android:id="@+id/applistview"
android:scrollbars="vertical"
android:visibility="gone"/> android:visibility="gone"/>
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
android:orientation="vertical"> android:orientation="vertical">
<include layout="@layout/toolbar_light_dark"/> <include layout="@layout/toolbar_light_dark"/>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" <android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/profilesList" android:id="@+id/list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:paddingBottom="32dp" android:paddingBottom="32dp"
android:scrollbars="vertical"
android:clipChildren="false" android:clipChildren="false"
android:clipToPadding="false"/> android:clipToPadding="false"/>
</LinearLayout> </LinearLayout>
...@@ -15,7 +15,8 @@ ...@@ -15,7 +15,8 @@
android:textSize="18sp" android:textSize="18sp"
android:padding="16dp"/> android:padding="16dp"/>
</FrameLayout> </FrameLayout>
<android.support.v7.widget.RecyclerView android:id="@+id/profilesList" <android.support.v7.widget.RecyclerView android:id="@+id/list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"
android:scrollbars="vertical"/>
</LinearLayout> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/selection"
android:title="@string/action_selection"
android:icon="@drawable/abc_ic_menu_selectall_mtrl_alpha"
android:alphabeticShortcut="s"
android:numericShortcut="0"
android:visible="false"
app:showAsAction="always">
<menu>
<item android:id="@+id/action_select_all"
android:title="@android:string/selectAll"
android:alphabeticShortcut="a"
android:numericShortcut="1"
app:showAsAction="always"/>
<item android:id="@+id/action_cut"
android:title="@android:string/cut"
android:alphabeticShortcut="t"
android:numericShortcut="2"
app:showAsAction="always"/>
<item android:id="@+id/action_copy"
android:title="@android:string/copy"
android:alphabeticShortcut="c"
android:numericShortcut="3"
app:showAsAction="always"/>
<item android:id="@+id/action_delete"
android:title="@string/delete"
android:alphabeticShortcut="d"
android:numericShortcut="4"
app:showAsAction="always"/>
</menu>
</item>
<item android:title="@string/action_add_rule"
android:icon="@drawable/ic_av_playlist_add"
android:alphabeticShortcut="a"
android:numericShortcut="1"
app:showAsAction="always">
<menu>
<item android:id="@+id/action_manual_settings"
android:title="@string/add_profile_methods_manual_settings"
android:alphabeticShortcut="m"
android:numericShortcut="1"/>
<item android:id="@+id/action_import"
android:title="@string/action_import"
android:alphabeticShortcut="i"
android:numericShortcut="2"/>
<item android:id="@+id/action_import_gfwlist"
android:title="@string/route_entry_gfwlist"
android:alphabeticShortcut="g"
android:numericShortcut="3"/>
</menu>
</item>
</menu>
...@@ -88,13 +88,15 @@ ...@@ -88,13 +88,15 @@
<string name="add_profile_methods_scan_qr_code">QRコードを読み取る</string> <string name="add_profile_methods_scan_qr_code">QRコードを読み取る</string>
<string name="add_profile_methods_manual_settings">手動でセットアップ</string> <string name="add_profile_methods_manual_settings">手動でセットアップ</string>
<string name="route_entry_gfwlist">中国本土からアクセス不可なアドレス以外を迂回する</string>
<string-array name="route_entry"> <string-array name="route_entry">
<item>すべて</item> <item>すべて</item>
<item>LANアドレスを迂回する</item> <item>LANアドレスを迂回する</item>
<item>中国本土のアドレスを迂回する</item> <item>中国本土のアドレスを迂回する</item>
<item>LAN及び中国本土のアドレスを迂回する</item> <item>LAN及び中国本土のアドレスを迂回する</item>
<item>中国本土からアクセス不可なアドレス以外を迂回する</item> <item>@string/route_entry_gfwlist</item>
<item>中国本土のアドレス以外を迂回する</item> <item>中国本土のアドレス以外を迂回する</item>
<item>@string/custom_rules</item>
</string-array> </string-array>
<string name="action_apply_all">設定をすべてのプロファイルに適応する</string> <string name="action_apply_all">設定をすべてのプロファイルに適応する</string>
......
...@@ -41,13 +41,15 @@ ...@@ -41,13 +41,15 @@
<string name="auto_set_gfwlist">CHN Маршрут</string> <string name="auto_set_gfwlist">CHN Маршрут</string>
<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="route_entry_gfwlist">Список GFW</string>
<string-array name="route_entry"> <string-array name="route_entry">
<item>Все</item> <item>Все</item>
<item>Обход LAN</item> <item>Обход LAN</item>
<item>Обходной Китай</item> <item>Обходной Китай</item>
<item>Обход LAN и Китай</item> <item>Обход LAN и Китай</item>
<item>Список GFW</item> <item>@string/route_entry_gfwlist</item>
<item>Список Китай</item> <item>Список Китай</item>
<item>@string/custom_rules</item>
</string-array> </string-array>
<string name="proxied_apps">Прокси для выбранных приложений</string> <string name="proxied_apps">Прокси для выбранных приложений</string>
<string name="proxied_apps_summary">Установить прокси для выбранных приложений, требует режим NAT под Android 4.x</string> <string name="proxied_apps_summary">Установить прокси для выбранных приложений, требует режим NAT под Android 4.x</string>
......
...@@ -100,13 +100,15 @@ ...@@ -100,13 +100,15 @@
<string name="add_profile_methods_manual_settings">手动设置</string> <string name="add_profile_methods_manual_settings">手动设置</string>
<string name="add_profile_scanner_not_installed">请安装任意兼容 ZXing 的二维码扫描应用。</string> <string name="add_profile_scanner_not_installed">请安装任意兼容 ZXing 的二维码扫描应用。</string>
<string name="route_entry_gfwlist">仅代理中国大陆无法访问的地址</string>
<string-array name="route_entry"> <string-array name="route_entry">
<item>全局</item> <item>全局</item>
<item>绕过局域网地址</item> <item>绕过局域网地址</item>
<item>绕过中国大陆地址</item> <item>绕过中国大陆地址</item>
<item>绕过局域网及中国大陆地址</item> <item>绕过局域网及中国大陆地址</item>
<item>仅代理中国大陆无法访问的地址</item> <item>@string/route_entry_gfwlist</item>
<item>仅代理中国大陆地址</item> <item>仅代理中国大陆地址</item>
<item>@string/custom_rules</item>
</string-array> </string-array>
<string name="action_apply_all">应用设置到所有配置文件</string> <string name="action_apply_all">应用设置到所有配置文件</string>
...@@ -142,4 +144,14 @@ ...@@ -142,4 +144,14 @@
<string name="kcp_port_summary">%d(远端 KCPTUN 服务器的端口号)</string> <string name="kcp_port_summary">%d(远端 KCPTUN 服务器的端口号)</string>
<string name="kcpcli">KCP 参数</string> <string name="kcpcli">KCP 参数</string>
<!-- acl -->
<string name="custom_rules">自定义规则</string>
<string name="action_selection">选择…</string>
<string name="action_add_rule">添加规则…</string>
<string-array name="acl_rule_templates">
<item>子网/域名 PCRE 正则表达式</item>
<item>域名及其子域名</item>
</string-array>
<string name="edit_rule">编辑规则</string>
</resources> </resources>
...@@ -101,13 +101,15 @@ ...@@ -101,13 +101,15 @@
<string name="add_profile_methods_manual_settings">手動設定</string> <string name="add_profile_methods_manual_settings">手動設定</string>
<string name="add_profile_scanner_not_installed">請安裝任何兼容 ZXing 的 QR 碼掃描應用程式。</string> <string name="add_profile_scanner_not_installed">請安裝任何兼容 ZXing 的 QR 碼掃描應用程式。</string>
<string name="route_entry_gfwlist">GFW List</string>
<string-array name="route_entry"> <string-array name="route_entry">
<item>全域</item> <item>全域</item>
<item>略過區域網路位址</item> <item>略過區域網路位址</item>
<item>略過中國大陸位址</item> <item>略過中國大陸位址</item>
<item>略過區域網路及中國大陸位址</item> <item>略過區域網路及中國大陸位址</item>
<item>僅代理大陸屏蔽位址</item> <item>@string/route_entry_gfwlist</item>
<item>僅代理大陸位址</item> <item>僅代理大陸位址</item>
<item>@string/custom_rules</item>
</string-array> </string-array>
<string name="action_apply_all">應用設置到所有配置文件</string> <string name="action_apply_all">應用設置到所有配置文件</string>
...@@ -143,4 +145,14 @@ ...@@ -143,4 +145,14 @@
<string name="kcp_port_summary">%d(遠端 KCPTUN 伺服器的連接埠號碼)</string> <string name="kcp_port_summary">%d(遠端 KCPTUN 伺服器的連接埠號碼)</string>
<string name="kcpcli">KCP 參數</string> <string name="kcpcli">KCP 參數</string>
<!-- acl -->
<string name="custom_rules">自定義規則</string>
<string name="action_selection">選擇…</string>
<string name="action_add_rule">添加規則…</string>
<string-array name="acl_rule_templates">
<item>子網/主機名 PCRE 正則表達式</item>
<item>域名及其所有子域名</item>
</string-array>
<string name="edit_rule">編輯規則</string>
</resources> </resources>
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
<item>bypass-lan-china</item> <item>bypass-lan-china</item>
<item>gfwlist</item> <item>gfwlist</item>
<item>china-list</item> <item>china-list</item>
<item>custom-rules</item>
</string-array> </string-array>
<string-array name="bypass_private_route"> <string-array name="bypass_private_route">
......
...@@ -4,9 +4,11 @@ ...@@ -4,9 +4,11 @@
<color name="material_green_700">#388E3C</color> <color name="material_green_700">#388E3C</color>
<color name="material_green_a700">#00C853</color> <color name="material_green_a700">#00C853</color>
<color name="material_blue_grey_100">#CFD8DC</color>
<color name="material_blue_grey_300">#90A4AE</color> <color name="material_blue_grey_300">#90A4AE</color>
<color name="material_blue_grey_500">#607D8B</color> <color name="material_blue_grey_500">#607D8B</color>
<color name="material_blue_grey_700">#455A64</color> <color name="material_blue_grey_700">#455A64</color>
<color name="material_primary_100">@color/material_blue_grey_100</color>
<color name="material_primary_300">@color/material_blue_grey_300</color> <color name="material_primary_300">@color/material_blue_grey_300</color>
<color name="material_primary_500">@color/material_blue_grey_500</color> <color name="material_primary_500">@color/material_blue_grey_500</color>
<color name="material_primary_700">@color/material_blue_grey_700</color> <color name="material_primary_700">@color/material_blue_grey_700</color>
......
...@@ -48,13 +48,15 @@ ...@@ -48,13 +48,15 @@
<string name="auto_set_gfwlist_summary">Bypass all sites located in China <string name="auto_set_gfwlist_summary">Bypass all sites located in China
</string> </string>
<string name="route_list">Route</string> <string name="route_list">Route</string>
<string name="route_entry_gfwlist">GFW List</string>
<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 China</item>
<item>Bypass LAN &amp; China</item> <item>Bypass LAN &amp; China</item>
<item>GFW List</item> <item>@string/route_entry_gfwlist</item>
<item>China List</item> <item>China List</item>
<item>@string/custom_rules</item>
</string-array> </string-array>
<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, requires NAT mode under Android 4.x</string> <string name="proxied_apps_summary">Set proxy for selected apps, requires NAT mode under Android 4.x</string>
...@@ -156,4 +158,14 @@ ...@@ -156,4 +158,14 @@
<string name="kcp_port">KCP Port</string> <string name="kcp_port">KCP Port</string>
<string name="kcp_port_summary">%d (port number of the remote KCPTUN server)</string> <string name="kcp_port_summary">%d (port number of the remote KCPTUN server)</string>
<string name="kcpcli">KCP Parameters (CLI)</string> <string name="kcpcli">KCP Parameters (CLI)</string>
<!-- acl -->
<string name="custom_rules">Custom rules</string>
<string name="action_selection">Selection…</string>
<string name="action_add_rule">Add rule(s)…</string>
<string-array name="acl_rule_templates">
<item>Subnet/Hostname PCRE pattern</item>
<item>Domain name and all its subdomain names</item>
</string-array>
<string name="edit_rule">Edit rule</string>
</resources> </resources>
...@@ -221,7 +221,7 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { ...@@ -221,7 +221,7 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
initProxiedApps() initProxiedApps()
loadingView = findViewById(R.id.loading) loadingView = findViewById(R.id.loading)
appListView = findViewById(R.id.applistview).asInstanceOf[RecyclerView] appListView = findViewById(R.id.applistview).asInstanceOf[RecyclerView]
appListView.setLayoutManager(new LinearLayoutManager(this)) appListView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false))
appListView.setItemAnimator(new DefaultItemAnimator) appListView.setItemAnimator(new DefaultItemAnimator)
instance = this instance = this
......
...@@ -34,6 +34,7 @@ import android.text.TextUtils ...@@ -34,6 +34,7 @@ import android.text.TextUtils
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback} import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
...@@ -132,38 +133,39 @@ trait BaseService extends Service { ...@@ -132,38 +133,39 @@ trait BaseService extends Service {
false false
} else true } else true
def connect(): Unit = if (profile.host == "198.199.101.152") { def connect() {
val holder = app.containerHolder if (profile.host == "198.199.101.152") {
val container = holder.getContainer val holder = app.containerHolder
val url = container.getString("proxy_url") val container = holder.getContainer
val sig = Utils.getSignature(this) val url = container.getString("proxy_url")
val sig = Utils.getSignature(this)
val client = new OkHttpClient.Builder()
.dns(hostname => Utils.resolve(hostname, enableIPv6 = false) match { val client = new OkHttpClient.Builder()
case Some(ip) => util.Arrays.asList(InetAddress.getByName(ip)) .dns(hostname => Utils.resolve(hostname, enableIPv6 = false) match {
case _ => Dns.SYSTEM.lookup(hostname) case Some(ip) => util.Arrays.asList(InetAddress.getByName(ip))
}) case _ => Dns.SYSTEM.lookup(hostname)
.connectTimeout(10, TimeUnit.SECONDS) })
.writeTimeout(10, TimeUnit.SECONDS) .connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS)
.build() .readTimeout(30, TimeUnit.SECONDS)
val requestBody = new FormBody.Builder() .build()
.add("sig", sig) val requestBody = new FormBody.Builder()
.build() .add("sig", sig)
val request = new Request.Builder() .build()
.url(url) val request = new Request.Builder()
.post(requestBody) .url(url)
.build() .post(requestBody)
.build()
val resposne = client.newCall(request).execute()
val list = resposne.body.string val proxies = Random.shuffle(client.newCall(request).execute().body.string.split('|').toSeq)
val proxy = proxies.head.split(':')
val proxies = Random.shuffle(list.split('|').toSeq) profile.host = proxy(0).trim
val proxy = proxies.head.split(':') profile.remotePort = proxy(1).trim.toInt
profile.host = proxy(0).trim profile.password = proxy(2).trim
profile.remotePort = proxy(1).trim.toInt profile.method = proxy(3).trim
profile.password = proxy(2).trim }
profile.method = proxy(3).trim if (profile.route == Acl.CUSTOM_RULES) // rationalize custom rules
Acl.save(Acl.CUSTOM_RULES, new Acl().fromId(Acl.CUSTOM_RULES))
} }
def startRunner(profile: Profile) { def startRunner(profile: Profile) {
......
...@@ -43,6 +43,7 @@ import android.view.View ...@@ -43,6 +43,7 @@ import android.view.View
import android.widget.{TextView, Toast} import android.widget.{TextView, Toast}
import com.github.jorgecastilloprz.FABProgressCircle import com.github.jorgecastilloprz.FABProgressCircle
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.CustomRulesFragment
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.utils.CloseUtils.autoDisconnect import com.github.shadowsocks.utils.CloseUtils.autoDisconnect
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
...@@ -62,6 +63,7 @@ object MainActivity { ...@@ -62,6 +63,7 @@ object MainActivity {
private final val DRAWER_RECOVERY = 2L private final val DRAWER_RECOVERY = 2L
private final val DRAWER_ABOUT = 3L private final val DRAWER_ABOUT = 3L
private final val DRAWER_FAQ = 4L private final val DRAWER_FAQ = 4L
private final val DRAWER_CUSTOM_RULES = 5L
} }
class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawerItemClickListener class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawerItemClickListener
...@@ -84,6 +86,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe ...@@ -84,6 +86,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
private var currentFragment: ToolbarFragment = _ private var currentFragment: ToolbarFragment = _
private lazy val profilesFragment = new ProfilesFragment() private lazy val profilesFragment = new ProfilesFragment()
private lazy val customRulesFragment = new CustomRulesFragment()
private lazy val globalSettingsFragment = new GlobalSettingsFragment() private lazy val globalSettingsFragment = new GlobalSettingsFragment()
private lazy val aboutFragment = new AboutFragment() private lazy val aboutFragment = new AboutFragment()
private lazy val customTabsIntent = new CustomTabsIntent.Builder() private lazy val customTabsIntent = new CustomTabsIntent.Builder()
...@@ -193,6 +196,11 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe ...@@ -193,6 +196,11 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
.withName(R.string.profiles) .withName(R.string.profiles)
.withIcon(AppCompatResources.getDrawable(this, R.drawable.ic_action_description)) .withIcon(AppCompatResources.getDrawable(this, R.drawable.ic_action_description))
.withIconTintingEnabled(true), .withIconTintingEnabled(true),
new PrimaryDrawerItem()
.withIdentifier(DRAWER_CUSTOM_RULES)
.withName(R.string.custom_rules)
.withIcon(AppCompatResources.getDrawable(this, R.drawable.ic_action_assignment))
.withIconTintingEnabled(true),
new PrimaryDrawerItem() new PrimaryDrawerItem()
.withIdentifier(DRAWER_GLOBAL_SETTINGS) .withIdentifier(DRAWER_GLOBAL_SETTINGS)
.withName(R.string.settings) .withName(R.string.settings)
...@@ -377,6 +385,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe ...@@ -377,6 +385,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
app.track(TAG, "about") app.track(TAG, "about")
displayFragment(aboutFragment) displayFragment(aboutFragment)
case DRAWER_FAQ => launchUrl(getString(R.string.faq_url)) case DRAWER_FAQ => launchUrl(getString(R.string.faq_url))
case DRAWER_CUSTOM_RULES => displayFragment(customRulesFragment)
} }
true // unexpected cases will throw exception true // unexpected cases will throw exception
} }
......
...@@ -71,7 +71,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic ...@@ -71,7 +71,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
case _ => false case _ => false
} }
final class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) final class ProfileViewHolder(view: View) extends RecyclerView.ViewHolder(view)
with View.OnClickListener with PopupMenu.OnMenuItemClickListener { with View.OnClickListener with PopupMenu.OnMenuItemClickListener {
var item: Profile = _ var item: Profile = _
...@@ -256,7 +256,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic ...@@ -256,7 +256,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
.putExtra(Action.EXTRA_PROFILE_ID, id)) .putExtra(Action.EXTRA_PROFILE_ID, id))
override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View = override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View =
inflater.inflate(R.layout.layout_profiles, container, false) inflater.inflate(R.layout.layout_list, container, false)
override def onViewCreated(view: View, savedInstanceState: Bundle) { override def onViewCreated(view: View, savedInstanceState: Bundle) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
...@@ -265,18 +265,19 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic ...@@ -265,18 +265,19 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
toolbar.setOnMenuItemClickListener(this) toolbar.setOnMenuItemClickListener(this)
if (app.profileManager.getFirstProfile.isEmpty) app.profileId(app.profileManager.createProfile().id) if (app.profileManager.getFirstProfile.isEmpty) app.profileId(app.profileManager.createProfile().id)
val profilesList = view.findViewById(R.id.profilesList).asInstanceOf[RecyclerView] val profilesList = view.findViewById(R.id.list).asInstanceOf[RecyclerView]
val layoutManager = new LinearLayoutManager(getActivity) val layoutManager = new LinearLayoutManager(getActivity, LinearLayoutManager.VERTICAL, false)
profilesList.setLayoutManager(layoutManager) profilesList.setLayoutManager(layoutManager)
layoutManager.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst {
case (profile, i) if profile.id == app.profileId => i
}.getOrElse(-1))
val animator = new DefaultItemAnimator() val animator = new DefaultItemAnimator()
animator.setSupportsChangeAnimations(false) // prevent fading-in/out when rebinding animator.setSupportsChangeAnimations(false) // prevent fading-in/out when rebinding
profilesList.setItemAnimator(animator) profilesList.setItemAnimator(animator)
profilesList.setAdapter(profilesAdapter) profilesList.setAdapter(profilesAdapter)
instance = this instance = this
layoutManager.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst { undoManager = new UndoSnackbarManager[Profile](getActivity.findViewById(R.id.snackbar),
case (profile, i) if profile.id == app.profileId => i profilesAdapter.undo, profilesAdapter.commit)
}.getOrElse(-1))
undoManager = new UndoSnackbarManager[Profile](getActivity.findViewById(R.id.snackbar), profilesAdapter.undo, profilesAdapter.commit)
new ItemTouchHelper(new SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, new ItemTouchHelper(new SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
ItemTouchHelper.START | ItemTouchHelper.END) { ItemTouchHelper.START | ItemTouchHelper.END) {
override def getSwipeDirs(recyclerView: RecyclerView, viewHolder: ViewHolder): Int = override def getSwipeDirs(recyclerView: RecyclerView, viewHolder: ViewHolder): Int =
...@@ -288,7 +289,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic ...@@ -288,7 +289,7 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
def onSwiped(viewHolder: ViewHolder, direction: Int) { def onSwiped(viewHolder: ViewHolder, direction: Int) {
val index = viewHolder.getAdapterPosition val index = viewHolder.getAdapterPosition
profilesAdapter.remove(index) profilesAdapter.remove(index)
undoManager.remove(index, viewHolder.asInstanceOf[ProfileViewHolder].item) undoManager.remove((index, viewHolder.asInstanceOf[ProfileViewHolder].item))
} }
def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder): Boolean = { def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder): Boolean = {
profilesAdapter.move(viewHolder.getAdapterPosition, target.getAdapterPosition) profilesAdapter.move(viewHolder.getAdapterPosition, target.getAdapterPosition)
......
...@@ -30,7 +30,18 @@ import android.widget.{ImageView, LinearLayout} ...@@ -30,7 +30,18 @@ import android.widget.{ImageView, LinearLayout}
import com.github.shadowsocks.utils.Utils import com.github.shadowsocks.utils.Utils
import net.glxn.qrgen.android.QRCode import net.glxn.qrgen.android.QRCode
final class QRCodeDialog(url: String) extends DialogFragment { object QRCodeDialog {
private final val KEY_URL = "com.github.shadowsocks.QRCodeDialog.KEY_URL"
}
final class QRCodeDialog extends DialogFragment {
import QRCodeDialog._
def this(url: String) {
this()
getArguments.putString(KEY_URL, url)
}
private def url = getArguments.getString(KEY_URL)
private lazy val nfcShareItem = url.getBytes(Charset.forName("UTF-8")) private lazy val nfcShareItem = url.getBytes(Charset.forName("UTF-8"))
private var adapter: NfcAdapter = _ private var adapter: NfcAdapter = _
......
...@@ -34,8 +34,8 @@ import android.preference.PreferenceManager ...@@ -34,8 +34,8 @@ import android.preference.PreferenceManager
import android.support.v7.app.AppCompatDelegate import android.support.v7.app.AppCompatDelegate
import android.util.Log import android.util.Log
import com.evernote.android.job.JobManager import com.evernote.android.job.JobManager
import com.github.shadowsocks.acl.DonaldTrump
import com.github.shadowsocks.database.{DBHelper, Profile, ProfileManager} import com.github.shadowsocks.database.{DBHelper, Profile, ProfileManager}
import com.github.shadowsocks.job.DonaldTrump
import com.github.shadowsocks.utils.CloseUtils._ import com.github.shadowsocks.utils.CloseUtils._
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders, StandardExceptionParser, Tracker} import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders, StandardExceptionParser, Tracker}
......
...@@ -28,8 +28,8 @@ import android.content._ ...@@ -28,8 +28,8 @@ import android.content._
import android.os._ import android.os._
import android.util.Log import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.{AclSyncJob, Acl}
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.job.AclSyncJob
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
import eu.chainfire.libsuperuser.Shell import eu.chainfire.libsuperuser.Shell
...@@ -76,9 +76,9 @@ class ShadowsocksNatService extends BaseService { ...@@ -76,9 +76,9 @@ class ShadowsocksNatService extends BaseService {
if (TcpFastOpen.sendEnabled) cmd += "--fast-open" if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
if (profile.route != Route.ALL) { if (profile.route != Acl.ALL) {
cmd += "--acl" cmd += "--acl"
cmd += getApplicationInfo.dataDir + '/' + profile.route + ".acl" cmd += Acl.getPath(profile.route)
} }
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
...@@ -169,11 +169,11 @@ class ShadowsocksNatService extends BaseService { ...@@ -169,11 +169,11 @@ class ShadowsocksNatService extends BaseService {
val reject = if (profile.ipv6) "224.0.0.0/3" else "224.0.0.0/3, ::/0" val reject = if (profile.ipv6) "224.0.0.0/3" else "224.0.0.0/3, ::/0"
val conf = profile.route match { val conf = profile.route match {
case Route.BYPASS_CHN | Route.BYPASS_LAN_CHN | Route.GFWLIST => case Acl.BYPASS_CHN | Acl.BYPASS_LAN_CHN | Acl.GFWLIST | Acl.CUSTOM_RULES =>
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "", getApplicationInfo.dataDir, ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "", getApplicationInfo.dataDir,
"127.0.0.1", profile.localPort + 53, "114.114.114.114, 223.5.5.5, 1.2.4.8", "127.0.0.1", profile.localPort + 53, "114.114.114.114, 223.5.5.5, 1.2.4.8",
getBlackList, reject, profile.localPort + 63, reject) getBlackList, reject, profile.localPort + 63, reject)
case Route.CHINALIST => case Acl.CHINALIST =>
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "", getApplicationInfo.dataDir, ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "", getApplicationInfo.dataDir,
"127.0.0.1", profile.localPort + 53, "8.8.8.8, 8.8.4.4, 208.67.222.222", "127.0.0.1", profile.localPort + 53, "8.8.8.8, 8.8.4.4, 208.67.222.222",
"", reject, profile.localPort + 63, reject) "", reject, profile.localPort + 63, reject)
...@@ -311,7 +311,7 @@ class ShadowsocksNatService extends BaseService { ...@@ -311,7 +311,7 @@ class ShadowsocksNatService extends BaseService {
handleConnection() handleConnection()
if (profile.route != Route.ALL) if (profile.route != Acl.ALL)
AclSyncJob.schedule(profile.route) AclSyncJob.schedule(profile.route)
changeState(State.CONNECTED) changeState(State.CONNECTED)
......
...@@ -30,8 +30,8 @@ import android.net.VpnService ...@@ -30,8 +30,8 @@ import android.net.VpnService
import android.os._ import android.os._
import android.util.Log import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.acl.{AclSyncJob, Acl}
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.job.AclSyncJob
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
...@@ -145,7 +145,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -145,7 +145,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
handleConnection() handleConnection()
changeState(State.CONNECTED) changeState(State.CONNECTED)
if (profile.route != Route.ALL) if (profile.route != Acl.ALL)
AclSyncJob.schedule(profile.route) AclSyncJob.schedule(profile.route)
notification = new ShadowsocksNotification(this, profile.getName) notification = new ShadowsocksNotification(this, profile.getName)
...@@ -241,9 +241,9 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -241,9 +241,9 @@ class ShadowsocksVpnService extends VpnService with BaseService {
if (profile.udpdns && !profile.kcp) cmd += "-u" if (profile.udpdns && !profile.kcp) cmd += "-u"
if (profile.route != Route.ALL) { if (profile.route != Acl.ALL) {
cmd += "--acl" cmd += "--acl"
cmd += getApplicationInfo.dataDir + '/' + profile.route + ".acl" cmd += Acl.getPath(profile.route)
} }
if (TcpFastOpen.sendEnabled) cmd += "--fast-open" if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
...@@ -291,11 +291,11 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -291,11 +291,11 @@ class ShadowsocksVpnService extends VpnService with BaseService {
val reject = if (profile.ipv6) "224.0.0.0/3" else "224.0.0.0/3, ::/0" val reject = if (profile.ipv6) "224.0.0.0/3" else "224.0.0.0/3, ::/0"
val protect = "protect = \"" + protectPath +"\";" val protect = "protect = \"" + protectPath +"\";"
val conf = profile.route match { val conf = profile.route match {
case Route.BYPASS_CHN | Route.BYPASS_LAN_CHN | Route.GFWLIST => case Acl.BYPASS_CHN | Acl.BYPASS_LAN_CHN | Acl.GFWLIST | Acl.CUSTOM_RULES =>
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, protect, getApplicationInfo.dataDir, ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, protect, getApplicationInfo.dataDir,
"0.0.0.0", profile.localPort + 53, "114.114.114.114, 223.5.5.5, 1.2.4.8", "0.0.0.0", profile.localPort + 53, "114.114.114.114, 223.5.5.5, 1.2.4.8",
getBlackList, reject, profile.localPort + 63, reject) getBlackList, reject, profile.localPort + 63, reject)
case Route.CHINALIST => case Acl.CHINALIST =>
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, protect, getApplicationInfo.dataDir, ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, protect, getApplicationInfo.dataDir,
"0.0.0.0", profile.localPort + 53, "8.8.8.8, 8.8.4.4, 208.67.222.222", "0.0.0.0", profile.localPort + 53, "8.8.8.8, 8.8.4.4, 208.67.222.222",
"", reject, profile.localPort + 63, reject) "", reject, profile.localPort + 63, reject)
...@@ -347,7 +347,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -347,7 +347,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
} }
} }
if (profile.route == Route.ALL || profile.route == Route.BYPASS_CHN) { if (profile.route == Acl.ALL || profile.route == Acl.BYPASS_CHN) {
builder.addRoute("0.0.0.0", 0) builder.addRoute("0.0.0.0", 0)
} else { } else {
val privateList = getResources.getStringArray(R.array.bypass_private_route) val privateList = getResources.getStringArray(R.array.bypass_private_route)
......
...@@ -35,7 +35,7 @@ import com.github.shadowsocks.ShadowsocksApplication.app ...@@ -35,7 +35,7 @@ import com.github.shadowsocks.ShadowsocksApplication.app
* @author CzBiX * @author CzBiX
*/ */
class TaskerActivity extends AppCompatActivity { class TaskerActivity extends AppCompatActivity {
private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener { private class ProfileViewHolder(view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener {
{ {
val typedArray = obtainStyledAttributes(Array(android.R.attr.selectableItemBackground)) val typedArray = obtainStyledAttributes(Array(android.R.attr.selectableItemBackground))
view.setBackgroundResource(typedArray.getResourceId(0, 0)) view.setBackgroundResource(typedArray.getResourceId(0, 0))
...@@ -92,8 +92,8 @@ class TaskerActivity extends AppCompatActivity { ...@@ -92,8 +92,8 @@ class TaskerActivity extends AppCompatActivity {
taskerOption = TaskerSettings.fromIntent(getIntent) taskerOption = TaskerSettings.fromIntent(getIntent)
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.list).asInstanceOf[RecyclerView]
val lm = new LinearLayoutManager(this) val lm = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
profilesList.setLayoutManager(lm) profilesList.setLayoutManager(lm)
profilesList.setItemAnimator(new DefaultItemAnimator) profilesList.setItemAnimator(new DefaultItemAnimator)
profilesList.setAdapter(profilesAdapter) profilesList.setAdapter(profilesAdapter)
......
package com.github.shadowsocks.acl
import java.io.FileNotFoundException
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.IOUtils
import com.j256.ormlite.field.DatabaseField
import scala.collection.mutable
import scala.io.Source
/**
* ACL handler compliant with: src/main/jni/shadowsocks-libev/src/acl.c
*
* OrmLite integration is unused for now.
*
* @author Mygod
*/
class Acl {
@DatabaseField(generatedId = true)
var id: Int = _
val bypassHostnames = new mutable.SortedList[String]()
val proxyHostnames = new mutable.SortedList[String]()
val subnets = new mutable.SortedList[Subnet]()
@DatabaseField
var bypass: Boolean = _
def getBypassHostnamesString: String = bypassHostnames.mkString("\n")
def getProxyHostnamesString: String = proxyHostnames.mkString("\n")
def getSubnetsString: String = subnets.mkString("\n")
def setBypassHostnamesString(value: String) {
bypassHostnames.clear()
bypassHostnames ++= value.split("\n")
}
def setProxyHostnamesString(value: String) {
proxyHostnames.clear()
proxyHostnames ++= value.split("\n")
}
def setSubnetsString(value: String) {
subnets.clear()
subnets ++= value.split("\n").map(Subnet.fromString)
}
def fromAcl(other: Acl): Acl = {
bypassHostnames.clear()
bypassHostnames ++= other.bypassHostnames
proxyHostnames.clear()
proxyHostnames ++= other.proxyHostnames
subnets.clear()
subnets ++= other.subnets
bypass = other.bypass
this
}
def fromSource(value: Source): Acl = {
bypassHostnames.clear()
proxyHostnames.clear()
this.subnets.clear()
bypass = false
lazy val bypassSubnets = new mutable.SortedList[Subnet]()
lazy val proxySubnets = new mutable.SortedList[Subnet]()
var hostnames: mutable.SortedList[String] = proxyHostnames
var subnets: mutable.SortedList[Subnet] = proxySubnets
for (line <- value.getLines()) (line.indexOf('#') match {
case -1 => line
case index => line.substring(0, index) // trim comments
}).trim match {
case "[outbound_block_list]" =>
hostnames = null
subnets = null
case "[black_list]" | "[bypass_list]" =>
hostnames = bypassHostnames
subnets = bypassSubnets
case "[white_list]" | "[proxy_list]" =>
hostnames = proxyHostnames
subnets = proxySubnets
case "[reject_all]" | "[bypass_all]" => bypass = true
case "[accept_all]" | "[proxy_all]" => bypass = false
case input if subnets != null && input.nonEmpty => try subnets += Subnet.fromString(input) catch {
case _: IllegalArgumentException => hostnames += input
}
case _ =>
}
this.subnets ++= (if (bypass) bypassSubnets else proxySubnets)
this
}
final def fromId(id: String): Acl = fromSource(Source.fromFile(Acl.getPath(id)))
override def toString: String = {
val result = new StringBuilder()
result.append(if (bypass) "[bypass_all]\n" else "[proxy_all]\n")
var bypassList = bypassHostnames.toStream
var proxyList = proxyHostnames.toStream
if (bypass) bypassList = subnets.toStream.map(_.toString) #::: bypassList
else proxyList = subnets.toStream.map(_.toString) #::: proxyList
if (bypassList.nonEmpty) {
result.append("[bypass_list]\n")
result.append(bypassList.mkString("\n"))
result.append('\n')
}
if (proxyList.nonEmpty) {
result.append("[proxy_list]\n")
result.append(proxyList.mkString("\n"))
result.append('\n')
}
result.toString
}
def isValidCustomRules: Boolean = bypass && bypassHostnames.isEmpty
// Don't change: dummy fields for OrmLite interaction
// noinspection ScalaUnusedSymbol
@DatabaseField(useGetSet = true)
private val bypassHostnamesString: String = null
// noinspection ScalaUnusedSymbol
@DatabaseField(useGetSet = true)
private val proxyHostnamesString: String = null
// noinspection ScalaUnusedSymbol
@DatabaseField(useGetSet = true)
private val subnetsString: String = null
}
object Acl {
final val ALL = "all"
final val BYPASS_LAN = "bypass-lan"
final val BYPASS_CHN = "bypass-china"
final val BYPASS_LAN_CHN = "bypass-lan-china"
final val GFWLIST = "gfwlist"
final val CHINALIST = "china-list"
final val CUSTOM_RULES = "custom-rules"
def getPath(id: String): String = app.getApplicationInfo.dataDir + '/' + id + ".acl"
def customRules: Acl = {
val acl = new Acl()
try acl.fromId(CUSTOM_RULES) catch {
case _: FileNotFoundException =>
}
acl.bypass = true
acl.bypassHostnames.clear() // everything is bypassed
acl
}
def save(id: String, acl: Acl): Unit = IOUtils.writeString(getPath(id), acl.toString)
}
...@@ -18,18 +18,18 @@ ...@@ -18,18 +18,18 @@
/* */ /* */
/*******************************************************************************/ /*******************************************************************************/
package com.github.shadowsocks.job package com.github.shadowsocks.acl
import java.io.IOException import java.io.IOException
import java.net.URL
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import com.evernote.android.job.Job.{Params, Result} import com.evernote.android.job.Job.{Params, Result}
import com.evernote.android.job.{Job, JobRequest} import com.evernote.android.job.{Job, JobRequest}
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.CloseUtils._
import com.github.shadowsocks.utils.IOUtils import com.github.shadowsocks.utils.IOUtils
import scala.io.Source
/** /**
* @author Mygod * @author Mygod
*/ */
...@@ -50,9 +50,8 @@ class AclSyncJob(route: String) extends Job { ...@@ -50,9 +50,8 @@ class AclSyncJob(route: String) extends Job {
val filename = route + ".acl" val filename = route + ".acl"
try { try {
//noinspection JavaAccessorMethodCalledAsEmptyParen //noinspection JavaAccessorMethodCalledAsEmptyParen
IOUtils.writeString(app.getApplicationInfo.dataDir + '/' + filename, autoClose( IOUtils.writeString(app.getApplicationInfo.dataDir + '/' + filename,
new URL("https://shadowsocks.org/acl/android/v1/" + Source.fromURL("https://shadowsocks.org/acl/android/v1/" + filename).mkString)
filename).openConnection().getInputStream())(IOUtils.readString))
Result.SUCCESS Result.SUCCESS
} catch { } catch {
case e: IOException => case e: IOException =>
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
/* */ /* */
/*******************************************************************************/ /*******************************************************************************/
package com.github.shadowsocks.job package com.github.shadowsocks.acl
import android.util.Log import android.util.Log
import com.evernote.android.job.JobCreator import com.evernote.android.job.JobCreator
......
package com.github.shadowsocks.acl
import java.net.{Inet4Address, Inet6Address, InetAddress}
import com.github.shadowsocks.utils.Utils
/**
* @author Mygod
*/
@throws[IllegalArgumentException]
class Subnet(val address: InetAddress, val prefixSize: Int) extends Comparable[Subnet] {
if (prefixSize < 0) throw new IllegalArgumentException
address match {
case _: Inet4Address => if (prefixSize > 32) throw new IllegalArgumentException
case _: Inet6Address => if (prefixSize > 128) throw new IllegalArgumentException
}
override def toString: String = if (address match {
case _: Inet4Address => prefixSize == 32
case _: Inet6Address => prefixSize == 128
}) address.toString else address.toString + '/' + prefixSize
override def compareTo(that: Subnet): Int = {
val addrThis = address.getAddress
val addrThat = that.address.getAddress
var result = addrThis lengthCompare addrThat.length // IPv4 address goes first
if (result != 0) return result
for ((x, y) <- addrThis zip addrThat) {
result = x compare y
if (result != 0) return result
}
prefixSize compare that.prefixSize
}
}
object Subnet {
@throws[IllegalArgumentException]
def fromString(value: String): Subnet = {
val parts = value.split("/")
if (!Utils.isNumeric(parts(0))) throw new IllegalArgumentException()
val addr = InetAddress.getByName(parts(0))
parts.length match {
case 1 => new Subnet(addr, addr match {
case _: Inet4Address => 32
case _: Inet6Address => 128
})
case 2 => new Subnet(addr, parts(1).toInt)
case _ => throw new IllegalArgumentException()
}
}
}
...@@ -179,12 +179,3 @@ object Action { ...@@ -179,12 +179,3 @@ object Action {
final val EXTRA_PROFILE_ID = "com.github.shadowsocks.EXTRA_PROFILE_ID" final val EXTRA_PROFILE_ID = "com.github.shadowsocks.EXTRA_PROFILE_ID"
} }
object Route {
val ALL = "all"
val BYPASS_LAN = "bypass-lan"
val BYPASS_CHN = "bypass-china"
val BYPASS_LAN_CHN = "bypass-lan-china"
val GFWLIST = "gfwlist"
val CHINALIST = "china-list"
}
...@@ -37,16 +37,6 @@ object IOUtils { ...@@ -37,16 +37,6 @@ object IOUtils {
} }
} }
def readString(in: InputStream): String = {
val builder = new StringBuilder()
val buffer = new Array[Byte](BUFFER_SIZE)
while (true) {
val count = in.read(buffer)
if (count >= 0) builder.append(new String(buffer, 0, count)) else return builder.toString()
}
null
}
def writeString(file: String, content: String): Unit = def writeString(file: String, content: String): Unit =
autoClose(new FileWriter(file))(writer => writer.write(content)) autoClose(new FileWriter(file))(writer => writer.write(content))
} }
...@@ -51,8 +51,8 @@ class UndoSnackbarManager[T](view: View, undo: Iterator[(Int, T)] => Unit, ...@@ -51,8 +51,8 @@ class UndoSnackbarManager[T](view: View, undo: Iterator[(Int, T)] => Unit,
} }
private var last: Snackbar = _ private var last: Snackbar = _
def remove(index: Int, item: T) { def remove(items: (Int, T)*) {
recycleBin.append((index, item)) recycleBin.appendAll(items)
val count = recycleBin.length val count = recycleBin.length
last = Snackbar last = Snackbar
.make(view, view.getResources.getQuantityString(R.plurals.removed, count, count: Integer), Snackbar.LENGTH_LONG) .make(view, view.getResources.getQuantityString(R.plurals.removed, count, count: Integer), Snackbar.LENGTH_LONG)
......
package scala.collection.mutable
import scala.collection.SortedSetLike
import scala.collection.generic.MutableSortedSetFactory
import scala.collection.immutable.RedBlackTree.Tree
import scala.collection.immutable.{RedBlackTree => RB}
import scala.runtime.ObjectRef
/**
* @define Coll `mutable.SortedList`
* @define coll mutable sorted list
* @factoryInfo
* Companion object of SortedList providing factory related utilities.
* @author Mygod
*/
//noinspection ReferenceMustBePrefixed,ScalaDocUnknownTag
object SortedList extends MutableSortedSetFactory[SortedList] {
/**
* The empty list of this type
*/
def empty[A](implicit ordering: Ordering[A]) = new SortedList[A]()
}
/**
* A mutable SortedList heavily based on SortedSet which "is not designed to enable meaningful subclassing".
*
* Based on: https://github.com/scala/scala/blob/3cc99d7/src/library/scala/collection/mutable/TreeSet.scala
*
* @author Mygod
*/
//noinspection ReferenceMustBePrefixed
class SortedList[A] private(treeRef: ObjectRef[RB.Tree[A, Null]], from: Option[A], until: Option[A])
(implicit val ordering: Ordering[A])
extends SortedSet[A] with SetLike[A, SortedList[A]] with SortedSetLike[A, SortedList[A]] with Set[A] with Serializable {
def insert(elem: A): Boolean = {
val old = size
this += elem
old < size
}
// AbstractSeq
def apply(i: Int): A = RB.nth(treeRef.elem, i).key // Should i out of range result in NullReferenceException?
def length: Int = size
// Buffer
def +=:(elem: A): SortedList.this.type = +=(elem) // prepending is the same as the list is sorted
def remove(n: Int): A = {
val result = apply(n)
remove(result)
result
}
def update(idx: Int, elem: A) {
val x = apply(idx)
if (!ordering.equiv(x, elem)) {
remove(x)
this.add(elem)
}
}
// Optimized methods
override def clear(): Unit = treeRef.elem = null
def indexOf[B >: A](elem: B, from: Int = 0): Int = lookupIndex(treeRef.elem, elem.asInstanceOf[A]) match {
case result if result < from => -1
case result => result
}
override def last: A = RB.greatest(treeRef.elem).key
// Tree helper methods, TODO: expand to non-recursive form?
private def lookupIndex(tree: Tree[A, Null], x: A): Int =
if (tree eq null) -1 else ordering.compare(x, tree.key) match {
case 0 => RB.count(tree.left)
case cmp if cmp < 0 => lookupIndex(tree.left, x)
case _ => lookupIndex(tree.right, x) match {
case i if i < 0 => i
case i => RB.count(tree.left) + 1 + i
}
}
// The following is copied from TreeSet
if (ordering eq null)
throw new NullPointerException("ordering must not be null")
def this()(implicit ordering: Ordering[A]) = this(new ObjectRef(null), None, None)
override def size: Int = RB.countInRange(treeRef.elem, from, until)
override def stringPrefix = "SortedList"
override def empty: SortedList[A] = SortedList.empty
private def pickBound(comparison: (A, A) => A, oldBound: Option[A], newBound: Option[A]) = (newBound, oldBound) match {
case (Some(newB), Some(oldB)) => Some(comparison(newB, oldB))
case (None, _) => oldBound
case _ => newBound
}
override def rangeImpl(fromArg: Option[A], untilArg: Option[A]): SortedList[A] = {
val newFrom = pickBound(ordering.max, fromArg, from)
val newUntil = pickBound(ordering.min, untilArg, until)
new SortedList(treeRef, newFrom, newUntil)
}
override def -=(elem: A): this.type = {
treeRef.elem = RB.delete(treeRef.elem, elem)
this
}
override def +=(elem: A): this.type = {
treeRef.elem = RB.update(treeRef.elem, elem, null, overwrite = false)
this
}
/**
* Thanks to the immutable nature of the
* underlying Tree, we can share it with
* the clone. So clone complexity in time is O(1).
*
*/
override def clone(): SortedList[A] =
new SortedList[A](new ObjectRef(treeRef.elem), from, until)
private val notProjection = !(from.isDefined || until.isDefined)
override def contains(elem: A): Boolean = {
def leftAcceptable: Boolean = from match {
case Some(lb) => ordering.gteq(elem, lb)
case _ => true
}
def rightAcceptable: Boolean = until match {
case Some(ub) => ordering.lt(elem, ub)
case _ => true
}
(notProjection || (leftAcceptable && rightAcceptable)) &&
RB.contains(treeRef.elem, elem)
}
override def iterator: Iterator[A] = iteratorFrom(None)
override def keysIteratorFrom(start: A): Iterator[A] = iteratorFrom(Some(start))
private def iteratorFrom(start: Option[A]) = {
val it = RB.keysIterator(treeRef.elem, pickBound(ordering.max, from, start))
until match {
case None => it
case Some(ub) => it takeWhile (k => ordering.lt(k, ub))
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment