Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb960d0664 | ||
|
|
9ed320d33b | ||
|
|
2599e88a81 | ||
|
|
ac0a8c7600 | ||
|
|
63fc69f495 | ||
|
|
5761962754 | ||
|
|
20506eeda8 | ||
|
|
dd63bda2cd | ||
|
|
e6d7a0855d | ||
|
|
4da89cd18e | ||
|
|
66250eb66b | ||
|
|
005c3443ae | ||
|
|
6b60f276b2 | ||
|
|
0edbe01237 | ||
|
|
c87882e5d4 | ||
|
|
8f80fbfd4a |
@@ -15,6 +15,7 @@ gen_*
|
|||||||
*.o
|
*.o
|
||||||
local.sbt
|
local.sbt
|
||||||
.deps
|
.deps
|
||||||
|
/proguard-sbt.txt
|
||||||
|
|
||||||
#Intellij IDEA
|
#Intellij IDEA
|
||||||
*.iml
|
*.iml
|
||||||
|
|||||||
@@ -33,11 +33,12 @@ useSupportVectors
|
|||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"com.android.support" % "design" % "24.2.1",
|
"com.android.support" % "design" % "24.2.1",
|
||||||
"com.android.support" % "gridlayout-v7" % "24.2.1",
|
"com.android.support" % "gridlayout-v7" % "24.2.1",
|
||||||
|
"com.android.support" % "preference-v14" % "24.2.1",
|
||||||
"com.github.clans" % "fab" % "1.6.4",
|
"com.github.clans" % "fab" % "1.6.4",
|
||||||
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
|
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
|
||||||
"com.github.kevinsawicki" % "http-request" % "6.0",
|
"com.github.kevinsawicki" % "http-request" % "6.0",
|
||||||
"com.google.android.gms" % "play-services-ads" % "9.4.0",
|
"com.google.android.gms" % "play-services-ads" % "9.6.0",
|
||||||
"com.google.android.gms" % "play-services-analytics" % "9.4.0",
|
"com.google.android.gms" % "play-services-analytics" % "9.6.0",
|
||||||
"com.j256.ormlite" % "ormlite-android" % "5.0",
|
"com.j256.ormlite" % "ormlite-android" % "5.0",
|
||||||
"com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2",
|
"com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2",
|
||||||
"dnsjava" % "dnsjava" % "2.1.7",
|
"dnsjava" % "dnsjava" % "2.1.7",
|
||||||
|
|||||||
+1
-1
Submodule kcptun/kcptun updated: e44d8c9041...ced94f7820
@@ -1,7 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools" package="com.github.shadowsocks"
|
xmlns:tools="http://schemas.android.com/tools" package="com.github.shadowsocks"
|
||||||
android:versionCode="157"
|
android:versionCode="159"
|
||||||
android:versionName="3.1.5">
|
android:versionName="3.1.7">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
|||||||
|
0.0.0.0/8
|
||||||
|
10.0.0.0/8
|
||||||
|
100.64.0.0/10
|
||||||
|
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/3
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Submodule src/main/jni/shadowsocks-libev updated: 2868e7e7b2...248fdcb610
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
|
||||||
<item>
|
<item>
|
||||||
<color android:color="@color/material_accent_300"/>
|
<color android:color="@color/material_primary_300"/>
|
||||||
</item>
|
</item>
|
||||||
</ripple>
|
</ripple>
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<color xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/material_accent_300"/>
|
<color xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/material_primary_300"/>
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
app:menu_colorNormal="@color/material_accent_500"
|
app:menu_colorNormal="@color/material_primary_500"
|
||||||
app:menu_colorPressed="@color/material_accent_700"
|
app:menu_colorPressed="@color/material_primary_700"
|
||||||
app:fab_colorRipple="@android:color/white"
|
app:fab_colorRipple="@android:color/white"
|
||||||
app:layout_behavior="com.github.shadowsocks.widget.FloatingActionMenuBehavior">
|
app:layout_behavior="com.github.shadowsocks.widget.FloatingActionMenuBehavior">
|
||||||
|
|
||||||
@@ -26,8 +26,8 @@
|
|||||||
android:id="@+id/fab_manual_add"
|
android:id="@+id/fab_manual_add"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:fab_colorNormal="@color/material_accent_500"
|
app:fab_colorNormal="@color/material_primary_500"
|
||||||
app:fab_colorPressed="@color/material_accent_700"
|
app:fab_colorPressed="@color/material_primary_700"
|
||||||
app:fab_colorRipple="@android:color/white"
|
app:fab_colorRipple="@android:color/white"
|
||||||
app:fab_size="mini"
|
app:fab_size="mini"
|
||||||
app:fab_label="@string/add_profile_methods_manual_settings"
|
app:fab_label="@string/add_profile_methods_manual_settings"
|
||||||
@@ -36,8 +36,8 @@
|
|||||||
android:id="@+id/fab_qrcode_add"
|
android:id="@+id/fab_qrcode_add"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:fab_colorNormal="@color/material_accent_500"
|
app:fab_colorNormal="@color/material_primary_500"
|
||||||
app:fab_colorPressed="@color/material_accent_700"
|
app:fab_colorPressed="@color/material_primary_700"
|
||||||
app:fab_colorRipple="@android:color/white"
|
app:fab_colorRipple="@android:color/white"
|
||||||
app:fab_size="mini"
|
app:fab_size="mini"
|
||||||
app:fab_label="@string/add_profile_methods_scan_qr_code"
|
app:fab_label="@string/add_profile_methods_scan_qr_code"
|
||||||
@@ -46,8 +46,8 @@
|
|||||||
android:id="@+id/fab_nfc_add"
|
android:id="@+id/fab_nfc_add"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:fab_colorNormal="@color/material_accent_500"
|
app:fab_colorNormal="@color/material_primary_500"
|
||||||
app:fab_colorPressed="@color/material_accent_700"
|
app:fab_colorPressed="@color/material_primary_700"
|
||||||
app:fab_colorRipple="@android:color/white"
|
app:fab_colorRipple="@android:color/white"
|
||||||
app:fab_size="mini"
|
app:fab_size="mini"
|
||||||
app:fab_label="NFC"
|
app:fab_label="NFC"
|
||||||
@@ -56,8 +56,8 @@
|
|||||||
android:id="@+id/fab_import_add"
|
android:id="@+id/fab_import_add"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:fab_colorNormal="@color/material_accent_500"
|
app:fab_colorNormal="@color/material_primary_500"
|
||||||
app:fab_colorPressed="@color/material_accent_700"
|
app:fab_colorPressed="@color/material_primary_700"
|
||||||
app:fab_colorRipple="@android:color/white"
|
app:fab_colorRipple="@android:color/white"
|
||||||
app:fab_size="mini"
|
app:fab_size="mini"
|
||||||
app:fab_label="@string/action_import"
|
app:fab_label="@string/action_import"
|
||||||
|
|||||||
@@ -24,13 +24,9 @@
|
|||||||
<string name="proxy_cat">Настройки Сервера</string>
|
<string name="proxy_cat">Настройки Сервера</string>
|
||||||
<string name="profile_name">Имя Профиля</string>
|
<string name="profile_name">Имя Профиля</string>
|
||||||
<string name="proxy">Сервер</string>
|
<string name="proxy">Сервер</string>
|
||||||
<string name="proxy_summary">Имя или IP удалённого сервера</string>
|
|
||||||
<string name="remote_port">Удалённый порт</string>
|
<string name="remote_port">Удалённый порт</string>
|
||||||
<string name="remote_port_summary">%d (номер порта удалённого сервера)</string>
|
|
||||||
<string name="port">Локальный порт</string>
|
<string name="port">Локальный порт</string>
|
||||||
<string name="port_summary">%d (номер порта локального сервера)</string>
|
|
||||||
<string name="sitekey">Пароль</string>
|
<string name="sitekey">Пароль</string>
|
||||||
<string name="sitekey_summary">Пароль удалённого сервера</string>
|
|
||||||
<string name="enc_method">Метод Шифрования</string>
|
<string name="enc_method">Метод Шифрования</string>
|
||||||
|
|
||||||
<!-- feature category -->
|
<!-- feature category -->
|
||||||
@@ -52,6 +48,7 @@
|
|||||||
<item>Обходной Китай</item>
|
<item>Обходной Китай</item>
|
||||||
<item>Обход LAN и Китай</item>
|
<item>Обход LAN и Китай</item>
|
||||||
<item>Список GFW</item>
|
<item>Список GFW</item>
|
||||||
|
<item>Список Китай</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>
|
||||||
@@ -148,5 +145,4 @@
|
|||||||
<string name="kcp_port">KCP порт</string>
|
<string name="kcp_port">KCP порт</string>
|
||||||
<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>
|
||||||
<string name="kcpcli_summary">Командная строка KCPTUN</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<style name="Theme.Material" parent="Theme.MaterialBase"/>
|
||||||
|
</resources>
|
||||||
@@ -9,13 +9,9 @@
|
|||||||
<string name="profile">配置文件</string>
|
<string name="profile">配置文件</string>
|
||||||
<string name="profile_summary">新建或切换到其他配置文件</string>
|
<string name="profile_summary">新建或切换到其他配置文件</string>
|
||||||
<string name="proxy">服务器</string>
|
<string name="proxy">服务器</string>
|
||||||
<string name="proxy_summary">远程服务器的地址</string>
|
|
||||||
<string name="remote_port">远程端口</string>
|
<string name="remote_port">远程端口</string>
|
||||||
<string name="remote_port_summary">%d(远程服务器的端口号)</string>
|
|
||||||
<string name="port">本地端口</string>
|
<string name="port">本地端口</string>
|
||||||
<string name="port_summary">%d(本地客户端的端口号)</string>
|
|
||||||
<string name="sitekey">密码</string>
|
<string name="sitekey">密码</string>
|
||||||
<string name="sitekey_summary">远程服务器的密码</string>
|
|
||||||
<string name="enc_method">加密方法</string>
|
<string name="enc_method">加密方法</string>
|
||||||
<string name="feature_cat">功能设置</string>
|
<string name="feature_cat">功能设置</string>
|
||||||
<string name="http_proxy">HTTP 代理服务器</string>
|
<string name="http_proxy">HTTP 代理服务器</string>
|
||||||
@@ -142,6 +138,5 @@
|
|||||||
<string name="kcp_port">KCP 端口</string>
|
<string name="kcp_port">KCP 端口</string>
|
||||||
<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>
|
||||||
<string name="kcpcli_summary">KCPTUN 的命令行参数</string>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -10,13 +10,9 @@
|
|||||||
<string name="profile">設定檔</string>
|
<string name="profile">設定檔</string>
|
||||||
<string name="profile_summary">切換至其他設定檔或新增新設定檔</string>
|
<string name="profile_summary">切換至其他設定檔或新增新設定檔</string>
|
||||||
<string name="proxy">伺服器</string>
|
<string name="proxy">伺服器</string>
|
||||||
<string name="proxy_summary">遠端伺服器的 IP 位址或主機名稱</string>
|
|
||||||
<string name="remote_port">遠端連接埠</string>
|
<string name="remote_port">遠端連接埠</string>
|
||||||
<string name="remote_port_summary">%d(遠端伺服器的連接埠號碼)</string>
|
|
||||||
<string name="port">連接埠號碼</string>
|
<string name="port">連接埠號碼</string>
|
||||||
<string name="port_summary">%d(本機伺服器的連接埠號碼)</string>
|
|
||||||
<string name="sitekey">密碼</string>
|
<string name="sitekey">密碼</string>
|
||||||
<string name="sitekey_summary">遠端伺服器的密碼</string>
|
|
||||||
<string name="enc_method">加密方法</string>
|
<string name="enc_method">加密方法</string>
|
||||||
<string name="feature_cat">功能設定</string>
|
<string name="feature_cat">功能設定</string>
|
||||||
<string name="http_proxy">HTTP Proxy</string>
|
<string name="http_proxy">HTTP Proxy</string>
|
||||||
@@ -144,6 +140,5 @@
|
|||||||
<string name="kcp_port">KCP 連接埠</string>
|
<string name="kcp_port">KCP 連接埠</string>
|
||||||
<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>
|
||||||
<string name="kcpcli_summary">KCPTUN 的命令行參數</string>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<declare-styleable name="DropDownPreference">
|
|
||||||
<attr name="android:entries" />
|
|
||||||
<attr name="android:entryValues" />
|
|
||||||
</declare-styleable>
|
|
||||||
<declare-styleable name="NumberPickerPreference">
|
<declare-styleable name="NumberPickerPreference">
|
||||||
<attr name="max" format="integer" />
|
<attr name="max" format="integer" />
|
||||||
<attr name="min" format="integer" />
|
<attr name="min" format="integer" />
|
||||||
|
|||||||
@@ -6,8 +6,9 @@
|
|||||||
<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_accent_300">@color/material_blue_grey_300</color>
|
<color name="material_primary_300">@color/material_blue_grey_300</color>
|
||||||
<color name="material_accent_500">@color/material_blue_grey_500</color>
|
<color name="material_primary_500">@color/material_blue_grey_500</color>
|
||||||
<color name="material_accent_700">@color/material_blue_grey_700</color>
|
<color name="material_primary_700">@color/material_blue_grey_700</color>
|
||||||
|
<color name="material_accent_200">@color/material_blue_grey_500</color>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -29,13 +29,9 @@
|
|||||||
<string name="proxy_cat">Server Settings</string>
|
<string name="proxy_cat">Server Settings</string>
|
||||||
<string name="profile_name">Profile Name</string>
|
<string name="profile_name">Profile Name</string>
|
||||||
<string name="proxy">Server</string>
|
<string name="proxy">Server</string>
|
||||||
<string name="proxy_summary">Hostname or IP of the remote server</string>
|
|
||||||
<string name="remote_port">Remote Port</string>
|
<string name="remote_port">Remote Port</string>
|
||||||
<string name="remote_port_summary">%d (port number of the remote server)</string>
|
|
||||||
<string name="port">Local Port</string>
|
<string name="port">Local Port</string>
|
||||||
<string name="port_summary">%d (port number of the local server)</string>
|
|
||||||
<string name="sitekey">Password</string>
|
<string name="sitekey">Password</string>
|
||||||
<string name="sitekey_summary">Password of the remote server</string>
|
|
||||||
<string name="enc_method">Encrypt Method</string>
|
<string name="enc_method">Encrypt Method</string>
|
||||||
|
|
||||||
<!-- feature category -->
|
<!-- feature category -->
|
||||||
@@ -155,5 +151,4 @@
|
|||||||
<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>
|
||||||
<string name="kcpcli_summary">The command line of KCPTUN</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -6,20 +6,43 @@
|
|||||||
<item name="android:textSize">24sp</item>
|
<item name="android:textSize">24sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Material" parent="Theme.AppCompat.Light.NoActionBar">
|
<style name="Theme.MaterialBase" parent="Theme.AppCompat.Light.NoActionBar">
|
||||||
<!--suppress NewApi -->
|
<!--suppress NewApi -->
|
||||||
<item name="android:navigationBarColor">@color/material_accent_700</item>
|
<item name="android:navigationBarColor">@color/material_primary_700</item>
|
||||||
<item name="colorAccent">@color/material_accent_500</item>
|
<item name="colorAccent">@color/material_primary_500</item>
|
||||||
<item name="colorButtonNormal">@color/material_accent_500</item>
|
<item name="colorButtonNormal">@color/material_primary_500</item>
|
||||||
<item name="colorPrimary">@color/material_accent_500</item>
|
<item name="colorPrimary">@color/material_primary_500</item>
|
||||||
<item name="colorPrimaryDark">@color/material_accent_700</item>
|
<item name="colorPrimaryDark">@color/material_primary_700</item>
|
||||||
|
<item name="preferenceTheme">@style/Theme.Material.PreferenceThemeOverlay</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Theme.Material.Dialog" parent="Theme.AppCompat.Light.Dialog">
|
<style name="Theme.Material.Dialog" parent="Theme.AppCompat.Light.Dialog">
|
||||||
<item name="colorAccent">@color/material_accent_500</item>
|
<item name="colorAccent">@color/material_primary_500</item>
|
||||||
<item name="colorPrimary">@color/material_accent_500</item>
|
<item name="colorButtonNormal">@color/material_accent_200</item>
|
||||||
<item name="colorPrimaryDark">@color/material_accent_700</item>
|
<item name="colorPrimary">@color/material_primary_500</item>
|
||||||
|
<item name="colorPrimaryDark">@color/material_primary_700</item>
|
||||||
|
<item name="preferenceTheme">@style/Theme.Material.PreferenceThemeOverlay</item>
|
||||||
<item name="windowNoTitle">true</item>
|
<item name="windowNoTitle">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!-- Fix for wrong padding on v17-v20 based on: https://github.com/Gericop/Android-Support-Preference-V7-Fix/blob/a6082cb/preference-v7/src/main/res/values-v17/styles.xml -->
|
||||||
|
<style name="Theme.Material" parent="Theme.MaterialBase">
|
||||||
|
<item name="android:listPreferredItemPaddingStart">16dp</item>
|
||||||
|
<item name="android:listPreferredItemPaddingEnd">16dp</item>
|
||||||
|
</style>
|
||||||
|
<!-- This is needed because the default PreferenceCategory appearance doesn't use bold text style -->
|
||||||
|
<style name="Preference_TextAppearanceMaterialBody2">
|
||||||
|
<item name="android:textSize">14sp</item>
|
||||||
|
<item name="android:fontFamily">sans-serif-medium</item>
|
||||||
|
<item name="android:textStyle">bold</item>
|
||||||
|
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||||
|
</style>
|
||||||
|
<style name="Theme.Material.PreferenceThemeOverlay" parent="@style/PreferenceThemeOverlay.v14.Material">
|
||||||
|
<item name="preferenceFragmentListStyle">@style/Theme.Material.PreferenceFragmentList</item>
|
||||||
|
</style>
|
||||||
|
<style name="Theme.Material.PreferenceFragmentList" parent="@style/PreferenceFragmentList.Material">
|
||||||
|
<item name="android:fadeScrollbars">true</item>
|
||||||
|
<item name="android:scrollbars">vertical</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2,39 +2,40 @@
|
|||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks">
|
xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks">
|
||||||
|
|
||||||
<PreferenceCategory
|
<tk.mygod.preference.PreferenceCategory
|
||||||
android:title="@string/proxy_cat">
|
android:title="@string/proxy_cat">
|
||||||
|
|
||||||
<com.github.shadowsocks.preferences.SummaryEditTextPreference
|
<tk.mygod.preference.EditTextPreference
|
||||||
android:key="profileName"
|
android:key="profileName"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
|
android:summary="%s"
|
||||||
android:title="@string/profile_name"/>
|
android:title="@string/profile_name"/>
|
||||||
<com.github.shadowsocks.preferences.SummaryEditTextPreference
|
<tk.mygod.preference.EditTextPreference
|
||||||
android:key="proxy"
|
android:key="proxy"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:summary="@string/proxy_summary"
|
android:summary="%s"
|
||||||
android:title="@string/proxy"/>
|
android:title="@string/proxy"/>
|
||||||
<com.github.shadowsocks.preferences.NumberPickerPreference
|
<tk.mygod.preference.NumberPickerPreference
|
||||||
app:min="1"
|
app:min="1"
|
||||||
app:max="65535"
|
app:max="65535"
|
||||||
android:key="remotePortNum"
|
android:key="remotePortNum"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:summary="@string/remote_port_summary"
|
android:summary="%d"
|
||||||
android:title="@string/remote_port"/>
|
android:title="@string/remote_port"/>
|
||||||
<com.github.shadowsocks.preferences.NumberPickerPreference
|
<tk.mygod.preference.NumberPickerPreference
|
||||||
app:min="1025"
|
app:min="1025"
|
||||||
app:max="65535"
|
app:max="65535"
|
||||||
android:key="localPortNum"
|
android:key="localPortNum"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:summary="@string/port_summary"
|
android:summary="%d"
|
||||||
android:title="@string/port"/>
|
android:title="@string/port"/>
|
||||||
<com.github.shadowsocks.preferences.PasswordEditTextPreference
|
<tk.mygod.preference.EditTextPreference
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:key="sitekey"
|
android:key="sitekey"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:summary="@string/sitekey_summary"
|
android:summary="%s"
|
||||||
android:title="@string/sitekey"/>
|
android:title="@string/sitekey"/>
|
||||||
<com.github.shadowsocks.preferences.DropDownPreference
|
<android.support.v7.preference.DropDownPreference
|
||||||
android:key="encMethod"
|
android:key="encMethod"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:entries="@array/enc_method_entry"
|
android:entries="@array/enc_method_entry"
|
||||||
@@ -47,12 +48,12 @@
|
|||||||
android:summary="@string/onetime_auth_summary"
|
android:summary="@string/onetime_auth_summary"
|
||||||
android:title="@string/onetime_auth"/>
|
android:title="@string/onetime_auth"/>
|
||||||
|
|
||||||
</PreferenceCategory>
|
</tk.mygod.preference.PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<tk.mygod.preference.PreferenceCategory
|
||||||
android:title="@string/feature_cat">
|
android:title="@string/feature_cat">
|
||||||
|
|
||||||
<com.github.shadowsocks.preferences.DropDownPreference
|
<android.support.v7.preference.DropDownPreference
|
||||||
android:key="route"
|
android:key="route"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:entries="@array/route_entry"
|
android:entries="@array/route_entry"
|
||||||
@@ -75,9 +76,9 @@
|
|||||||
android:summary="@string/udp_dns_summary"
|
android:summary="@string/udp_dns_summary"
|
||||||
android:title="@string/udp_dns"/>
|
android:title="@string/udp_dns"/>
|
||||||
|
|
||||||
</PreferenceCategory>
|
</tk.mygod.preference.PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<tk.mygod.preference.PreferenceCategory
|
||||||
android:title="@string/kcp">
|
android:title="@string/kcp">
|
||||||
|
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
@@ -86,7 +87,7 @@
|
|||||||
android:summary="@string/kcp_summary"
|
android:summary="@string/kcp_summary"
|
||||||
android:title="@string/kcp"/>
|
android:title="@string/kcp"/>
|
||||||
|
|
||||||
<com.github.shadowsocks.preferences.NumberPickerPreference
|
<tk.mygod.preference.NumberPickerPreference
|
||||||
app:min="1"
|
app:min="1"
|
||||||
app:max="65535"
|
app:max="65535"
|
||||||
android:key="kcpPort"
|
android:key="kcpPort"
|
||||||
@@ -94,15 +95,15 @@
|
|||||||
android:summary="@string/kcp_port_summary"
|
android:summary="@string/kcp_port_summary"
|
||||||
android:title="@string/kcp_port"/>
|
android:title="@string/kcp_port"/>
|
||||||
|
|
||||||
<com.github.shadowsocks.preferences.KcpCliPreference
|
<tk.mygod.preference.EditTextPreference
|
||||||
android:key="kcpcli"
|
android:key="kcpcli"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:summary="@string/kcpcli_summary"
|
android:summary="%s"
|
||||||
android:title="@string/kcpcli"/>
|
android:title="@string/kcpcli"/>
|
||||||
|
|
||||||
</PreferenceCategory>
|
</tk.mygod.preference.PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/misc_cat">
|
<tk.mygod.preference.PreferenceCategory android:title="@string/misc_cat">
|
||||||
|
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
android:key="isAutoConnect"
|
android:key="isAutoConnect"
|
||||||
@@ -125,7 +126,7 @@
|
|||||||
android:summary="@string/flush_dnscache_summary"/>
|
android:summary="@string/flush_dnscache_summary"/>
|
||||||
<Preference android:key="about" android:title="@string/about"/>
|
<Preference android:key="about" android:title="@string/about"/>
|
||||||
|
|
||||||
</PreferenceCategory>
|
</tk.mygod.preference.PreferenceCategory>
|
||||||
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ trait BaseService extends Service {
|
|||||||
final val callbacks = new RemoteCallbackList[IShadowsocksServiceCallback]
|
final val callbacks = new RemoteCallbackList[IShadowsocksServiceCallback]
|
||||||
var callbacksCount: Int = _
|
var callbacksCount: Int = _
|
||||||
lazy val handler = new Handler(getMainLooper)
|
lazy val handler = new Handler(getMainLooper)
|
||||||
|
lazy val restartHanlder = new Handler(getMainLooper)
|
||||||
|
|
||||||
private val closeReceiver: BroadcastReceiver = (context: Context, intent: Intent) => {
|
private val closeReceiver: BroadcastReceiver = (context: Context, intent: Intent) => {
|
||||||
Toast.makeText(context, R.string.stopping, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.stopping, Toast.LENGTH_SHORT).show()
|
||||||
@@ -82,7 +83,10 @@ trait BaseService extends Service {
|
|||||||
val activeNetwork = cm.getActiveNetworkInfo()
|
val activeNetwork = cm.getActiveNetworkInfo()
|
||||||
val isConnected = activeNetwork != null && activeNetwork.isConnected()
|
val isConnected = activeNetwork != null && activeNetwork.isConnected()
|
||||||
|
|
||||||
if (isConnected && profile.kcp && kcptunProcess != null) kcptunProcess.restart()
|
if (isConnected && profile.kcp && kcptunProcess != null) {
|
||||||
|
restartHanlder.removeCallbacks(null)
|
||||||
|
restartHanlder.postDelayed(() => kcptunProcess.restart(), 2000)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var networkReceiverRegistered: Boolean = _
|
var networkReceiverRegistered: Boolean = _
|
||||||
|
|
||||||
@@ -294,15 +298,4 @@ trait BaseService extends Service {
|
|||||||
state = s
|
state = s
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
def getBlackList = {
|
|
||||||
val default = getString(R.string.black_list)
|
|
||||||
try {
|
|
||||||
val container = app.containerHolder.getContainer
|
|
||||||
val update = container.getString("black_list")
|
|
||||||
if (update == null || update.isEmpty) default else update
|
|
||||||
} catch {
|
|
||||||
case ex: Exception => default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,5 +53,7 @@ object BootReceiver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class BootReceiver extends BroadcastReceiver {
|
class BootReceiver extends BroadcastReceiver {
|
||||||
def onReceive(context: Context, intent: Intent) = Utils.startSsService(context)
|
def onReceive(context: Context, intent: Intent) = {
|
||||||
|
Utils.startSsService(context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ package com.github.shadowsocks
|
|||||||
import java.io.{FileOutputStream, IOException, InputStream, OutputStream}
|
import java.io.{FileOutputStream, IOException, InputStream, OutputStream}
|
||||||
import java.lang.System.currentTimeMillis
|
import java.lang.System.currentTimeMillis
|
||||||
import java.net.{HttpURLConnection, URL}
|
import java.net.{HttpURLConnection, URL}
|
||||||
import java.util
|
import java.util.Date
|
||||||
|
import java.util.Hashtable
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
import android.app.backup.BackupManager
|
import android.app.backup.BackupManager
|
||||||
@@ -59,15 +60,16 @@ import android.util.Log
|
|||||||
import android.view.{View, ViewGroup}
|
import android.view.{View, ViewGroup}
|
||||||
import android.widget._
|
import android.widget._
|
||||||
import com.github.jorgecastilloprz.FABProgressCircle
|
import com.github.jorgecastilloprz.FABProgressCircle
|
||||||
|
import com.github.shadowsocks.ShadowsocksApplication.app
|
||||||
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
|
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
|
||||||
import com.github.shadowsocks.database._
|
import com.github.shadowsocks.database._
|
||||||
import com.github.shadowsocks.utils.CloseUtils._
|
import com.github.shadowsocks.utils.CloseUtils._
|
||||||
import com.github.shadowsocks.utils._
|
import com.github.shadowsocks.utils._
|
||||||
import com.github.shadowsocks.ShadowsocksApplication.app
|
|
||||||
import com.google.android.gms.ads.{AdRequest, AdSize, AdView}
|
import com.google.android.gms.ads.{AdRequest, AdSize, AdView}
|
||||||
import eu.chainfire.libsuperuser.Shell
|
import eu.chainfire.libsuperuser.Shell
|
||||||
|
|
||||||
import scala.collection.mutable.ArrayBuffer
|
import scala.collection.mutable.ArrayBuffer
|
||||||
|
import scala.util.Random
|
||||||
|
|
||||||
object Typefaces {
|
object Typefaces {
|
||||||
def get(c: Context, assetPath: String): Typeface = {
|
def get(c: Context, assetPath: String): Typeface = {
|
||||||
@@ -87,7 +89,7 @@ object Typefaces {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final val TAG = "Typefaces"
|
private final val TAG = "Typefaces"
|
||||||
private final val cache = new util.Hashtable[String, Typeface]
|
private final val cache = new Hashtable[String, Typeface]
|
||||||
}
|
}
|
||||||
|
|
||||||
object Shadowsocks {
|
object Shadowsocks {
|
||||||
@@ -487,7 +489,18 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
|
|||||||
adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322")
|
adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322")
|
||||||
adView.setAdSize(AdSize.SMART_BANNER)
|
adView.setAdSize(AdSize.SMART_BANNER)
|
||||||
preferences.getView.asInstanceOf[ViewGroup].addView(adView, 1)
|
preferences.getView.asInstanceOf[ViewGroup].addView(adView, 1)
|
||||||
adView.loadAd(new AdRequest.Builder().build())
|
|
||||||
|
// Demographics
|
||||||
|
val random = new Random()
|
||||||
|
val adBuilder = new AdRequest.Builder()
|
||||||
|
adBuilder.setGender(AdRequest.GENDER_MALE)
|
||||||
|
val year = 1975 + random.nextInt(40)
|
||||||
|
val month = 1 + random.nextInt(12)
|
||||||
|
val day = random.nextInt(28)
|
||||||
|
adBuilder.setBirthday(new Date(year, month, day))
|
||||||
|
|
||||||
|
// Load Ad
|
||||||
|
adView.loadAd(adBuilder.build())
|
||||||
} else adView.setVisibility(View.VISIBLE) else if (adView != null) adView.setVisibility(View.GONE)
|
} else adView.setVisibility(View.VISIBLE) else if (adView != null) adView.setVisibility(View.GONE)
|
||||||
|
|
||||||
preferences.setProfile(profile)
|
preferences.setProfile(profile)
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class ShadowsocksApplication extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override def onCreate() {
|
override def onCreate() {
|
||||||
java.lang.System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR")
|
if (!BuildConfig.DEBUG) java.lang.System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR")
|
||||||
app = this
|
app = this
|
||||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
|
||||||
val tm = TagManager.getInstance(this)
|
val tm = TagManager.getInstance(this)
|
||||||
@@ -112,7 +112,7 @@ class ShadowsocksApplication extends Application {
|
|||||||
}
|
}
|
||||||
pending.setResultCallback(callback, 2, TimeUnit.SECONDS)
|
pending.setResultCallback(callback, 2, TimeUnit.SECONDS)
|
||||||
|
|
||||||
TcpFastOpen.enabled(settings.getBoolean(Key.tfo, false))
|
TcpFastOpen.enabled(settings.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
|
||||||
}
|
}
|
||||||
|
|
||||||
def refreshContainerHolder {
|
def refreshContainerHolder {
|
||||||
|
|||||||
@@ -71,20 +71,6 @@ class ShadowsocksNatService extends BaseService {
|
|||||||
var su: Shell.Interactive = _
|
var su: Shell.Interactive = _
|
||||||
|
|
||||||
def startShadowsocksDaemon() {
|
def startShadowsocksDaemon() {
|
||||||
if (profile.route != Route.ALL && profile.route != Route.GFWLIST) {
|
|
||||||
val acl: Array[String] = profile.route match {
|
|
||||||
case Route.BYPASS_LAN => getResources.getStringArray(R.array.private_route)
|
|
||||||
case Route.BYPASS_CHN => getResources.getStringArray(R.array.chn_route)
|
|
||||||
case Route.BYPASS_LAN_CHN =>
|
|
||||||
getResources.getStringArray(R.array.private_route) ++ getResources.getStringArray(R.array.chn_route)
|
|
||||||
case Route.CHINALIST =>
|
|
||||||
Array("[bypass_all]", "[white_list]") ++ getResources.getStringArray(R.array.chn_route)
|
|
||||||
}
|
|
||||||
Utils.printToFile(new File(getApplicationInfo.dataDir + "/acl.list"))(p => {
|
|
||||||
acl.foreach(p.println)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
val conf = if (profile.kcp) {
|
val conf = if (profile.kcp) {
|
||||||
ConfigUtils
|
ConfigUtils
|
||||||
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, "127.0.0.1", profile.localPort + 90, profile.localPort,
|
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, "127.0.0.1", profile.localPort + 90, profile.localPort,
|
||||||
@@ -110,10 +96,13 @@ class ShadowsocksNatService extends BaseService {
|
|||||||
|
|
||||||
if (profile.route != Route.ALL) {
|
if (profile.route != Route.ALL) {
|
||||||
cmd += "--acl"
|
cmd += "--acl"
|
||||||
if (profile.route == Route.GFWLIST)
|
profile.route match {
|
||||||
cmd += (getApplicationInfo.dataDir + "/gfwlist.acl")
|
case Route.BYPASS_LAN => cmd += (getApplicationInfo.dataDir + "/bypass_lan.acl")
|
||||||
else
|
case Route.BYPASS_CHN => cmd += (getApplicationInfo.dataDir + "/bypass_chn.acl")
|
||||||
cmd += (getApplicationInfo.dataDir + "/acl.list")
|
case Route.BYPASS_LAN_CHN => cmd += (getApplicationInfo.dataDir + "/bypass_lan_chn.acl")
|
||||||
|
case Route.GFWLIST => cmd += (getApplicationInfo.dataDir + "/gfwlist.acl")
|
||||||
|
case Route.CHINALIST => cmd += (getApplicationInfo.dataDir + "/chinalist.acl")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||||
@@ -190,17 +179,13 @@ class ShadowsocksNatService extends BaseService {
|
|||||||
def startDnsDaemon() {
|
def startDnsDaemon() {
|
||||||
|
|
||||||
val conf = profile.route match {
|
val conf = profile.route match {
|
||||||
case Route.BYPASS_CHN | Route.BYPASS_LAN_CHN => {
|
case Route.BYPASS_CHN | Route.BYPASS_LAN_CHN | Route.GFWLIST => {
|
||||||
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
|
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
|
||||||
"127.0.0.1", profile.localPort + 53, getBlackList, profile.localPort + 63, "")
|
"127.0.0.1", profile.localPort + 53, "1.2.4.8, 114.114.114.114, 208.67.222.222", "", profile.localPort + 63, "")
|
||||||
}
|
|
||||||
case Route.GFWLIST => {
|
|
||||||
ConfigUtils.PDNSD_UDP.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
|
|
||||||
"127.0.0.1", profile.localPort + 53, "1.2.4.8, 208.67.222.222", profile.localPort + 63, "")
|
|
||||||
}
|
}
|
||||||
case Route.CHINALIST => {
|
case Route.CHINALIST => {
|
||||||
ConfigUtils.PDNSD_UDP.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
|
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
|
||||||
"127.0.0.1", profile.localPort + 53, "8.8.8.8, 208.67.222.222", profile.localPort + 63, "")
|
"127.0.0.1", profile.localPort + 53, "8.8.8.8, 208.67.222.222", "", profile.localPort + 63, "")
|
||||||
}
|
}
|
||||||
case _ => {
|
case _ => {
|
||||||
ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
|
ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
|
|||||||
|
|
||||||
private val builder = new NotificationCompat.Builder(service)
|
private val builder = new NotificationCompat.Builder(service)
|
||||||
.setWhen(0)
|
.setWhen(0)
|
||||||
.setColor(ContextCompat.getColor(service, R.color.material_accent_500))
|
.setColor(ContextCompat.getColor(service, R.color.material_primary_500))
|
||||||
.setTicker(service.getString(R.string.forward_success))
|
.setTicker(service.getString(R.string.forward_success))
|
||||||
.setContentTitle(profileName)
|
.setContentTitle(profileName)
|
||||||
.setContentIntent(PendingIntent.getActivity(service, 0, new Intent(service, classOf[Shadowsocks])
|
.setContentIntent(PendingIntent.getActivity(service, 0, new Intent(service, classOf[Shadowsocks])
|
||||||
|
|||||||
@@ -54,11 +54,11 @@ class ShadowsocksRunnerService extends Service with ServiceBoundContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override def onServiceConnected() {
|
override def onServiceConnected() {
|
||||||
handler.postDelayed(() => if (bgService != null) {
|
if (bgService != null) {
|
||||||
if (app.isNatEnabled) startBackgroundService()
|
if (app.isNatEnabled) startBackgroundService()
|
||||||
else if (VpnService.prepare(ShadowsocksRunnerService.this) == null) startBackgroundService()
|
else if (VpnService.prepare(ShadowsocksRunnerService.this) == null) startBackgroundService()
|
||||||
stopSelf()
|
handler.postDelayed(() => stopSelf(), 10000)
|
||||||
}, 1000)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def startBackgroundService() = bgService.useSync(app.profileId)
|
def startBackgroundService() = bgService.useSync(app.profileId)
|
||||||
|
|||||||
@@ -6,14 +6,16 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
|||||||
import android.content.{Intent, SharedPreferences}
|
import android.content.{Intent, SharedPreferences}
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.preference.{Preference, PreferenceFragment, SwitchPreference}
|
|
||||||
import android.support.design.widget.Snackbar
|
import android.support.design.widget.Snackbar
|
||||||
|
import android.support.v14.preference.SwitchPreference
|
||||||
import android.support.v7.app.AlertDialog
|
import android.support.v7.app.AlertDialog
|
||||||
|
import android.support.v7.preference.{DropDownPreference, Preference}
|
||||||
import android.webkit.{WebView, WebViewClient}
|
import android.webkit.{WebView, WebViewClient}
|
||||||
import com.github.shadowsocks.ShadowsocksApplication.app
|
import com.github.shadowsocks.ShadowsocksApplication.app
|
||||||
import com.github.shadowsocks.database.Profile
|
import com.github.shadowsocks.database.Profile
|
||||||
import com.github.shadowsocks.preferences._
|
import com.github.shadowsocks.preferences.KcpCliPreferenceDialogFragment
|
||||||
import com.github.shadowsocks.utils.{Key, TcpFastOpen, Utils}
|
import com.github.shadowsocks.utils.{Key, TcpFastOpen, Utils}
|
||||||
|
import tk.mygod.preference._
|
||||||
|
|
||||||
object ShadowsocksSettings {
|
object ShadowsocksSettings {
|
||||||
// Constants
|
// Constants
|
||||||
@@ -27,31 +29,25 @@ object ShadowsocksSettings {
|
|||||||
pref.asInstanceOf[DropDownPreference].setValue(value)
|
pref.asInstanceOf[DropDownPreference].setValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
def updatePasswordEditTextPreference(pref: Preference, value: String) {
|
def updateEditTextPreference(pref: Preference, value: String) {
|
||||||
pref.setSummary(value)
|
pref.asInstanceOf[EditTextPreference].setText(value)
|
||||||
pref.asInstanceOf[PasswordEditTextPreference].setText(value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateNumberPickerPreference(pref: Preference, value: Int) {
|
def updateNumberPickerPreference(pref: Preference, value: Int) {
|
||||||
pref.asInstanceOf[NumberPickerPreference].setValue(value)
|
pref.asInstanceOf[NumberPickerPreference].setValue(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateSummaryEditTextPreference(pref: Preference, value: String) {
|
|
||||||
pref.setSummary(value)
|
|
||||||
pref.asInstanceOf[SummaryEditTextPreference].setText(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateSwitchPreference(pref: Preference, value: Boolean) {
|
def updateSwitchPreference(pref: Preference, value: Boolean) {
|
||||||
pref.asInstanceOf[SwitchPreference].setChecked(value)
|
pref.asInstanceOf[SwitchPreference].setChecked(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
def updatePreference(pref: Preference, name: String, profile: Profile) {
|
def updatePreference(pref: Preference, name: String, profile: Profile) {
|
||||||
name match {
|
name match {
|
||||||
case Key.name => updateSummaryEditTextPreference(pref, profile.name)
|
case Key.name => updateEditTextPreference(pref, profile.name)
|
||||||
case Key.host => updateSummaryEditTextPreference(pref, profile.host)
|
case Key.host => updateEditTextPreference(pref, profile.host)
|
||||||
case Key.remotePort => updateNumberPickerPreference(pref, profile.remotePort)
|
case Key.remotePort => updateNumberPickerPreference(pref, profile.remotePort)
|
||||||
case Key.localPort => updateNumberPickerPreference(pref, profile.localPort)
|
case Key.localPort => updateNumberPickerPreference(pref, profile.localPort)
|
||||||
case Key.password => updatePasswordEditTextPreference(pref, profile.password)
|
case Key.password => updateEditTextPreference(pref, profile.password)
|
||||||
case Key.method => updateDropDownPreference(pref, profile.method)
|
case Key.method => updateDropDownPreference(pref, profile.method)
|
||||||
case Key.route => updateDropDownPreference(pref, profile.route)
|
case Key.route => updateDropDownPreference(pref, profile.route)
|
||||||
case Key.proxyApps => updateSwitchPreference(pref, profile.proxyApps)
|
case Key.proxyApps => updateSwitchPreference(pref, profile.proxyApps)
|
||||||
@@ -60,7 +56,7 @@ object ShadowsocksSettings {
|
|||||||
case Key.ipv6 => updateSwitchPreference(pref, profile.ipv6)
|
case Key.ipv6 => updateSwitchPreference(pref, profile.ipv6)
|
||||||
case Key.kcp => updateSwitchPreference(pref, profile.kcp)
|
case Key.kcp => updateSwitchPreference(pref, profile.kcp)
|
||||||
case Key.kcpPort => updateNumberPickerPreference(pref, profile.kcpPort)
|
case Key.kcpPort => updateNumberPickerPreference(pref, profile.kcpPort)
|
||||||
case Key.kcpcli => updateSummaryEditTextPreference(pref, profile.kcpcli)
|
case Key.kcpcli => updateEditTextPreference(pref, profile.kcpcli)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,8 +69,7 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
|
|||||||
|
|
||||||
private var isProxyApps: SwitchPreference = _
|
private var isProxyApps: SwitchPreference = _
|
||||||
|
|
||||||
override def onCreate(savedInstanceState: Bundle) {
|
override def onCreatePreferences(bundle: Bundle, key: String) {
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
addPreferencesFromResource(R.xml.pref_all)
|
addPreferencesFromResource(R.xml.pref_all)
|
||||||
getPreferenceManager.getSharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
getPreferenceManager.getSharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||||
|
|
||||||
@@ -206,6 +201,15 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def onDisplayPreferenceDialog(preference: Preference) = preference match {
|
||||||
|
case _: EditTextPreference => displayPreferenceDialog(preference.getKey,
|
||||||
|
if (preference.getKey == Key.kcpcli) new KcpCliPreferenceDialogFragment()
|
||||||
|
else new EditTextPreferenceDialogFragment())
|
||||||
|
case _: NumberPickerPreference =>
|
||||||
|
displayPreferenceDialog(preference.getKey, new NumberPickerPreferenceDialogFragment())
|
||||||
|
case _ => super.onDisplayPreferenceDialog(preference)
|
||||||
|
}
|
||||||
|
|
||||||
def refreshProfile() {
|
def refreshProfile() {
|
||||||
profile = app.currentProfile match {
|
profile = app.currentProfile match {
|
||||||
case Some(p) => p
|
case Some(p) => p
|
||||||
|
|||||||
@@ -226,20 +226,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def startShadowsocksDaemon() {
|
def startShadowsocksDaemon() {
|
||||||
if (profile.route != Route.ALL && profile.route != Route.GFWLIST) {
|
|
||||||
val acl: Array[String] = profile.route match {
|
|
||||||
case Route.BYPASS_LAN => getResources.getStringArray(R.array.private_route)
|
|
||||||
case Route.BYPASS_CHN => getResources.getStringArray(R.array.chn_route)
|
|
||||||
case Route.BYPASS_LAN_CHN =>
|
|
||||||
getResources.getStringArray(R.array.private_route) ++ getResources.getStringArray(R.array.chn_route)
|
|
||||||
case Route.CHINALIST =>
|
|
||||||
Array("[bypass_all]", "[white_list]") ++ getResources.getStringArray(R.array.chn_route)
|
|
||||||
}
|
|
||||||
Utils.printToFile(new File(getApplicationInfo.dataDir + "/acl.list"))(p => {
|
|
||||||
acl.foreach(p.println)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
val conf = if (profile.kcp) {
|
val conf = if (profile.kcp) {
|
||||||
ConfigUtils
|
ConfigUtils
|
||||||
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, "127.0.0.1", profile.localPort + 90, profile.localPort,
|
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, "127.0.0.1", profile.localPort + 90, profile.localPort,
|
||||||
@@ -265,10 +251,13 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
|
|
||||||
if (profile.route != Route.ALL) {
|
if (profile.route != Route.ALL) {
|
||||||
cmd += "--acl"
|
cmd += "--acl"
|
||||||
if (profile.route == Route.GFWLIST)
|
profile.route match {
|
||||||
cmd += (getApplicationInfo.dataDir + "/gfwlist.acl")
|
case Route.BYPASS_LAN => cmd += (getApplicationInfo.dataDir + "/bypass_lan.acl")
|
||||||
else
|
case Route.BYPASS_CHN => cmd += (getApplicationInfo.dataDir + "/bypass_chn.acl")
|
||||||
cmd += (getApplicationInfo.dataDir + "/acl.list")
|
case Route.BYPASS_LAN_CHN => cmd += (getApplicationInfo.dataDir + "/bypass_lan_chn.acl")
|
||||||
|
case Route.GFWLIST => cmd += (getApplicationInfo.dataDir + "/gfwlist.acl")
|
||||||
|
case Route.CHINALIST => cmd += (getApplicationInfo.dataDir + "/chinalist.acl")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
|
if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
|
||||||
@@ -293,6 +282,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
})
|
})
|
||||||
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-tunnel"
|
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-tunnel"
|
||||||
, "-V"
|
, "-V"
|
||||||
|
, "-u"
|
||||||
, "-t", "10"
|
, "-t", "10"
|
||||||
, "-b", "127.0.0.1"
|
, "-b", "127.0.0.1"
|
||||||
, "-L", "8.8.8.8:53"
|
, "-L", "8.8.8.8:53"
|
||||||
@@ -309,17 +299,13 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
def startDnsDaemon() {
|
def startDnsDaemon() {
|
||||||
val ipv6 = if (profile.ipv6) "" else "reject = ::/0;"
|
val ipv6 = if (profile.ipv6) "" else "reject = ::/0;"
|
||||||
val conf = profile.route match {
|
val conf = profile.route match {
|
||||||
case Route.BYPASS_CHN | Route.BYPASS_LAN_CHN => {
|
case Route.BYPASS_CHN | Route.BYPASS_LAN_CHN | Route.GFWLIST => {
|
||||||
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
|
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
|
||||||
"0.0.0.0", profile.localPort + 53, getBlackList, profile.localPort + 63, ipv6)
|
"0.0.0.0", profile.localPort + 53, "1.2.4.8, 114.114.114.114, 208.67.222.222", ipv6, profile.localPort + 63, ipv6)
|
||||||
}
|
|
||||||
case Route.GFWLIST => {
|
|
||||||
ConfigUtils.PDNSD_UDP.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
|
|
||||||
"0.0.0.0", profile.localPort + 53, "1.2.4.8, 208.67.222.222", profile.localPort + 63, "")
|
|
||||||
}
|
}
|
||||||
case Route.CHINALIST => {
|
case Route.CHINALIST => {
|
||||||
ConfigUtils.PDNSD_UDP.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
|
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
|
||||||
"0.0.0.0", profile.localPort + 53, "8.8.8.8, 208.67.222.222", profile.localPort + 63, ipv6)
|
"0.0.0.0", profile.localPort + 53, "8.8.8.8, 208.67.222.222", ipv6, profile.localPort + 63, ipv6)
|
||||||
}
|
}
|
||||||
case _ => {
|
case _ => {
|
||||||
ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
|
ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ package com.github.shadowsocks.database
|
|||||||
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
import android.os.Binder
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import com.j256.ormlite.field.{DataType, DatabaseField}
|
import com.j256.ormlite.field.{DataType, DatabaseField}
|
||||||
|
|
||||||
@@ -54,8 +55,9 @@ class Profile {
|
|||||||
@DatabaseField
|
@DatabaseField
|
||||||
var host: String = ""
|
var host: String = ""
|
||||||
|
|
||||||
|
// hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat
|
||||||
@DatabaseField
|
@DatabaseField
|
||||||
var localPort: Int = 1080
|
var localPort: Int = 1080 + Binder.getCallingUserHandle.hashCode
|
||||||
|
|
||||||
@DatabaseField
|
@DatabaseField
|
||||||
var remotePort: Int = 8388
|
var remotePort: Int = 8388
|
||||||
|
|||||||
@@ -1,169 +0,0 @@
|
|||||||
package com.github.shadowsocks.preferences
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.TypedArray
|
|
||||||
import android.preference.Preference
|
|
||||||
import android.support.annotation.{ArrayRes, NonNull}
|
|
||||||
import android.support.v7.widget.AppCompatSpinner
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.{View, ViewGroup}
|
|
||||||
import android.widget.AdapterView.OnItemSelectedListener
|
|
||||||
import android.widget.{AdapterView, ArrayAdapter}
|
|
||||||
import com.github.shadowsocks.R
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Mygod
|
|
||||||
*/
|
|
||||||
final class DropDownPreference(private val mContext: Context, attrs: AttributeSet = null)
|
|
||||||
extends Preference(mContext, attrs) with SummaryPreference {
|
|
||||||
|
|
||||||
private val mAdapter = new ArrayAdapter[String](mContext, android.R.layout.simple_spinner_dropdown_item)
|
|
||||||
private val mSpinner = new AppCompatSpinner(mContext)
|
|
||||||
private var mEntries: Array[CharSequence] = _
|
|
||||||
private var mEntryValues: Array[CharSequence] = _
|
|
||||||
private var mSelectedIndex: Int = -1
|
|
||||||
|
|
||||||
mSpinner.setVisibility(View.INVISIBLE)
|
|
||||||
mSpinner.setAdapter(mAdapter)
|
|
||||||
mSpinner.setOnItemSelectedListener(new OnItemSelectedListener {
|
|
||||||
def onNothingSelected(parent: AdapterView[_]) { }
|
|
||||||
def onItemSelected(parent: AdapterView[_], view: View, position: Int, id: Long) = {
|
|
||||||
val value = getValue(position)
|
|
||||||
if (position != mSelectedIndex && callChangeListener(value)) setValue(position, value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
setOnPreferenceClickListener((preference: Preference) => {
|
|
||||||
// TODO: not working with scrolling
|
|
||||||
// mSpinner.setDropDownVerticalOffset(Utils.dpToPx(getContext, -48 * mSelectedIndex).toInt)
|
|
||||||
mSpinner.performClick
|
|
||||||
true
|
|
||||||
})
|
|
||||||
val a: TypedArray = mContext.obtainStyledAttributes(attrs, R.styleable.DropDownPreference)
|
|
||||||
setEntries(a.getTextArray(R.styleable.DropDownPreference_android_entries))
|
|
||||||
setEntryValues(a.getTextArray(R.styleable.DropDownPreference_android_entryValues))
|
|
||||||
a.recycle
|
|
||||||
|
|
||||||
protected override def getSummaryValue = getEntry
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the human-readable entries to be shown in the list. This will be shown in subsequent dialogs.
|
|
||||||
*
|
|
||||||
* Each entry must have a corresponding index in [[setEntryValues(CharSequence[])]].
|
|
||||||
*
|
|
||||||
* @param entries The entries.
|
|
||||||
* @see [[setEntryValues(CharSequence[])]]
|
|
||||||
*/
|
|
||||||
def setEntries(entries: Array[CharSequence]) {
|
|
||||||
mEntries = entries
|
|
||||||
mAdapter.clear
|
|
||||||
if (entries != null) for (entry <- entries) mAdapter.add(entry.toString)
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @see #setEntries(CharSequence[])
|
|
||||||
* @param entriesResId The entries array as a resource.
|
|
||||||
*/
|
|
||||||
def setEntries(@ArrayRes entriesResId: Int): Unit = setEntries(getContext.getResources.getTextArray(entriesResId))
|
|
||||||
/**
|
|
||||||
* The list of entries to be shown in the list in subsequent dialogs.
|
|
||||||
*
|
|
||||||
* @return The list as an array.
|
|
||||||
*/
|
|
||||||
def getEntries = mEntries
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The array to find the value to save for a preference when an entry from
|
|
||||||
* entries is selected. If a user clicks on the second item in entries, the
|
|
||||||
* second item in this array will be saved to the preference.
|
|
||||||
*
|
|
||||||
* @param entryValues The array to be used as values to save for the preference.
|
|
||||||
*/
|
|
||||||
def setEntryValues(entryValues: Array[CharSequence]) = mEntryValues = entryValues
|
|
||||||
/**
|
|
||||||
* @see #setEntryValues(CharSequence[])
|
|
||||||
* @param entryValuesResId The entry values array as a resource.
|
|
||||||
*/
|
|
||||||
def setEntryValues(@ArrayRes entryValuesResId: Int): Unit =
|
|
||||||
setEntryValues(getContext.getResources.getTextArray(entryValuesResId))
|
|
||||||
/**
|
|
||||||
* Returns the array of values to be saved for the preference.
|
|
||||||
*
|
|
||||||
* @return The array of values.
|
|
||||||
*/
|
|
||||||
def getEntryValues: Array[CharSequence] = mEntryValues
|
|
||||||
|
|
||||||
private def getValue(index: Int) = (if (mEntryValues == null) index else mEntryValues(index)).toString
|
|
||||||
/**
|
|
||||||
* Sets the value of the key. This should be one of the entries in [[getEntryValues]].
|
|
||||||
*
|
|
||||||
* @param value The value to set for the key.
|
|
||||||
*/
|
|
||||||
def setValue(value: String): Unit = setValue(findIndexOfValue(value), value)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the value to the given index from the entry values.
|
|
||||||
*
|
|
||||||
* @param index The index of the value to set.
|
|
||||||
*/
|
|
||||||
def setValueIndex(index: Int) = setValue(index, getValue(index))
|
|
||||||
|
|
||||||
private def setValue(index: Int, value: String) {
|
|
||||||
persistString(value)
|
|
||||||
mSelectedIndex = index
|
|
||||||
mSpinner.setSelection(index)
|
|
||||||
notifyChanged
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of the key. This should be one of the entries in [[getEntryValues]].
|
|
||||||
*
|
|
||||||
* @return The value of the key.
|
|
||||||
*/
|
|
||||||
def getValue = if (mEntryValues == null || mSelectedIndex < 0) null else mEntryValues(mSelectedIndex).toString
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the entry corresponding to the current value.
|
|
||||||
*
|
|
||||||
* @return The entry corresponding to the current value, or null.
|
|
||||||
*/
|
|
||||||
def getEntry = {
|
|
||||||
val index = getValueIndex
|
|
||||||
if (index >= 0 && mEntries != null) mEntries(index) else null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index of the given value (in the entry values array).
|
|
||||||
*
|
|
||||||
* @param value The value whose index should be returned.
|
|
||||||
* @return The index of the value, or -1 if not found.
|
|
||||||
*/
|
|
||||||
def findIndexOfValue(value: String): Int = {
|
|
||||||
if (value != null && mEntryValues != null) {
|
|
||||||
var i = mEntryValues.length - 1
|
|
||||||
while (i >= 0) {
|
|
||||||
if (mEntryValues(i) == value) return i
|
|
||||||
i -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-1
|
|
||||||
}
|
|
||||||
|
|
||||||
def getValueIndex = mSelectedIndex
|
|
||||||
|
|
||||||
protected override def onGetDefaultValue(a: TypedArray, index: Int) = a.getString(index)
|
|
||||||
protected override def onSetInitialValue (restoreValue: Boolean, defaultValue: AnyRef) =
|
|
||||||
setValue(if (restoreValue) getPersistedString(getValue) else defaultValue.asInstanceOf[String])
|
|
||||||
|
|
||||||
def setDropDownWidth(dimenResId: Int) =
|
|
||||||
mSpinner.setDropDownWidth(mContext.getResources.getDimensionPixelSize(dimenResId))
|
|
||||||
|
|
||||||
protected override def onBindView(@NonNull view: View) {
|
|
||||||
super.onBindView(view)
|
|
||||||
val parent = mSpinner.getParent.asInstanceOf[ViewGroup]
|
|
||||||
if (view eq parent) return
|
|
||||||
if (parent != null) parent.removeView(mSpinner)
|
|
||||||
view.asInstanceOf[ViewGroup].addView(mSpinner, 0)
|
|
||||||
val lp = mSpinner.getLayoutParams
|
|
||||||
lp.width = 0
|
|
||||||
mSpinner.setLayoutParams(lp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+7
-6
@@ -1,21 +1,22 @@
|
|||||||
package com.github.shadowsocks.preferences
|
package com.github.shadowsocks.preferences
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.{Context, DialogInterface}
|
import android.content.DialogInterface
|
||||||
import android.util.AttributeSet
|
|
||||||
import eu.chainfire.libsuperuser.Shell
|
import eu.chainfire.libsuperuser.Shell
|
||||||
|
import tk.mygod.preference.EditTextPreferenceDialogFragment
|
||||||
|
|
||||||
import scala.collection.JavaConversions._
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Mygod
|
* @author Mygod
|
||||||
*/
|
*/
|
||||||
class KcpCliPreference(context: Context, attrs: AttributeSet) extends SummaryEditTextPreference(context, attrs) {
|
class KcpCliPreferenceDialogFragment extends EditTextPreferenceDialogFragment {
|
||||||
override def onPrepareDialogBuilder(builder: AlertDialog.Builder) {
|
override def onPrepareDialogBuilder(builder: AlertDialog.Builder) {
|
||||||
super.onPrepareDialogBuilder(builder)
|
super.onPrepareDialogBuilder(builder)
|
||||||
builder.setNeutralButton("?", ((_, _) => new AlertDialog.Builder(context)
|
builder.setNeutralButton("?", ((_, _) => new AlertDialog.Builder(builder.getContext)
|
||||||
.setTitle("?")
|
.setTitle("?")
|
||||||
.setMessage(asScalaBuffer(Shell.SH.run(context.getApplicationInfo.dataDir + "/kcptun --help"))
|
.setMessage(Shell.SH.run(builder.getContext.getApplicationInfo.dataDir + "/kcptun --help")
|
||||||
|
.asScala
|
||||||
.dropWhile(line => line != "GLOBAL OPTIONS:")
|
.dropWhile(line => line != "GLOBAL OPTIONS:")
|
||||||
.drop(1)
|
.drop(1)
|
||||||
.takeWhile(line => line.length() > 3)
|
.takeWhile(line => line.length() > 3)
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
* Shadowsocks - A shadowsocks client for Android
|
|
||||||
* Copyright (C) 2013 <max.c.lv@gmail.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* ___====-_ _-====___
|
|
||||||
* _--^^^#####// \\#####^^^--_
|
|
||||||
* _-^##########// ( ) \\##########^-_
|
|
||||||
* -############// |\^^/| \\############-
|
|
||||||
* _/############// (@::@) \\############\_
|
|
||||||
* /#############(( \\// ))#############\
|
|
||||||
* -###############\\ (oo) //###############-
|
|
||||||
* -#################\\ / VV \ //#################-
|
|
||||||
* -###################\\/ \//###################-
|
|
||||||
* _#/|##########/\######( /\ )######/\##########|\#_
|
|
||||||
* |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
|
|
||||||
* ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
|
|
||||||
* ` ` ` ` / | | | | \ ' ' ' '
|
|
||||||
* ( | | | | )
|
|
||||||
* __\ | | | | /__
|
|
||||||
* (vvv(VVV)(VVV)vvv)
|
|
||||||
*
|
|
||||||
* HERE BE DRAGONS
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.github.shadowsocks.preferences
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.preference.EditTextPreference
|
|
||||||
import android.util.AttributeSet
|
|
||||||
|
|
||||||
class PasswordEditTextPreference(context: Context, attrs: AttributeSet, defStyle: Int)
|
|
||||||
extends EditTextPreference(context, attrs, defStyle) {
|
|
||||||
|
|
||||||
def this(context: Context, attrs: AttributeSet) = {
|
|
||||||
this(context, attrs, android.R.attr.editTextPreferenceStyle)
|
|
||||||
mDefaultSummary = getSummary
|
|
||||||
}
|
|
||||||
|
|
||||||
override def setText(text: String) {
|
|
||||||
super.setText(text)
|
|
||||||
setSummary(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
override def setSummary(summary: CharSequence) {
|
|
||||||
if (summary.toString.isEmpty) {
|
|
||||||
super.setSummary(mDefaultSummary)
|
|
||||||
} else {
|
|
||||||
super.setSummary(summary.toString.map(c => "*").mkString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var mDefaultSummary: CharSequence = getSummary
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Shadowsocks - A shadowsocks client for Android
|
|
||||||
* Copyright (C) 2013 <max.c.lv@gmail.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* ___====-_ _-====___
|
|
||||||
* _--^^^#####// \\#####^^^--_
|
|
||||||
* _-^##########// ( ) \\##########^-_
|
|
||||||
* -############// |\^^/| \\############-
|
|
||||||
* _/############// (@::@) \\############\_
|
|
||||||
* /#############(( \\// ))#############\
|
|
||||||
* -###############\\ (oo) //###############-
|
|
||||||
* -#################\\ / VV \ //#################-
|
|
||||||
* -###################\\/ \//###################-
|
|
||||||
* _#/|##########/\######( /\ )######/\##########|\#_
|
|
||||||
* |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
|
|
||||||
* ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
|
|
||||||
* ` ` ` ` / | | | | \ ' ' ' '
|
|
||||||
* ( | | | | )
|
|
||||||
* __\ | | | | /__
|
|
||||||
* (vvv(VVV)(VVV)vvv)
|
|
||||||
*
|
|
||||||
* HERE BE DRAGONS
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.github.shadowsocks.preferences
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.preference.EditTextPreference
|
|
||||||
import android.text.TextUtils
|
|
||||||
import android.util.AttributeSet
|
|
||||||
|
|
||||||
class SummaryEditTextPreference(context: Context, attrs: AttributeSet, defStyle: Int)
|
|
||||||
extends EditTextPreference(context, attrs, defStyle) {
|
|
||||||
|
|
||||||
def this(context: Context, attrs: AttributeSet) = {
|
|
||||||
this(context, attrs, android.R.attr.editTextPreferenceStyle)
|
|
||||||
mDefaultSummary = getSummary
|
|
||||||
}
|
|
||||||
|
|
||||||
override def setText(text: String) {
|
|
||||||
super.setText(text)
|
|
||||||
setSummary(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
override def setSummary(summary: CharSequence) {
|
|
||||||
if (TextUtils.isEmpty(summary)) {
|
|
||||||
super.setSummary(mDefaultSummary)
|
|
||||||
} else {
|
|
||||||
super.setSummary(summary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var mDefaultSummary: CharSequence = getSummary
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package com.github.shadowsocks.preferences
|
|
||||||
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
import android.preference.Preference
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make your preference support %s in summary. Override getSummaryValue to customize what to put in.
|
|
||||||
* @author Mygod
|
|
||||||
*/
|
|
||||||
trait SummaryPreference extends Preference {
|
|
||||||
protected def getSummaryValue: AnyRef
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the summary of this SummaryPreference. If the summary has a String formatting marker in it
|
|
||||||
* (i.e. "%s" or "%1$s"), then the current entry value will be substituted in its place.
|
|
||||||
*
|
|
||||||
* @return the summary with appropriate string substitution
|
|
||||||
*/
|
|
||||||
override def getSummary = super.getSummary.toString.formatLocal(Locale.ENGLISH, getSummaryValue)
|
|
||||||
}
|
|
||||||
@@ -115,54 +115,12 @@ object ConfigUtils {
|
|||||||
|}
|
|}
|
||||||
|
|
|
|
||||||
|server {
|
|server {
|
||||||
| label = "china-servers";
|
|
||||||
| ip = 1.2.4.8, 208.67.222.222, 114.114.114.114;
|
|
||||||
| timeout = 4;
|
|
||||||
| exclude = %s;
|
|
||||||
| policy = included;
|
|
||||||
| uptest = none;
|
|
||||||
| preset = on;
|
|
||||||
|}
|
|
||||||
|
|
|
||||||
|server {
|
|
||||||
| label = "local-server";
|
|
||||||
| ip = 127.0.0.1;
|
|
||||||
| port = %d;
|
|
||||||
| %s
|
|
||||||
| reject_policy = negate;
|
|
||||||
| reject_recursively = on;
|
|
||||||
|}
|
|
||||||
|
|
|
||||||
|rr {
|
|
||||||
| name=localhost;
|
|
||||||
| reverse=on;
|
|
||||||
| a=127.0.0.1;
|
|
||||||
| owner=localhost;
|
|
||||||
| soa=localhost,root.localhost,42,86400,900,86400,86400;
|
|
||||||
|}
|
|
||||||
""".stripMargin
|
|
||||||
|
|
||||||
val PDNSD_UDP =
|
|
||||||
"""
|
|
||||||
|global {
|
|
||||||
| perm_cache = 2048;
|
|
||||||
| cache_dir = "%s";
|
|
||||||
| server_ip = %s;
|
|
||||||
| server_port = %d;
|
|
||||||
| query_method = udp_tcp;
|
|
||||||
| run_ipv4 = on;
|
|
||||||
| min_ttl = 15m;
|
|
||||||
| max_ttl = 1w;
|
|
||||||
| timeout = 10;
|
|
||||||
| daemon = off;
|
|
||||||
|}
|
|
||||||
|
|
|
||||||
|server {
|
|
||||||
| label = "remote-servers";
|
| label = "remote-servers";
|
||||||
| ip = %s;
|
| ip = %s;
|
||||||
| timeout = 5;
|
| timeout = 5;
|
||||||
| uptest = none;
|
| uptest = none;
|
||||||
| preset = on;
|
| preset = on;
|
||||||
|
| %s
|
||||||
|}
|
|}
|
||||||
|
|
|
|
||||||
|server {
|
|server {
|
||||||
@@ -170,8 +128,6 @@ object ConfigUtils {
|
|||||||
| ip = 127.0.0.1;
|
| ip = 127.0.0.1;
|
||||||
| port = %d;
|
| port = %d;
|
||||||
| %s
|
| %s
|
||||||
| reject_policy = negate;
|
|
||||||
| reject_recursively = on;
|
|
||||||
|}
|
|}
|
||||||
|
|
|
|
||||||
|rr {
|
|rr {
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def startSsService(context: Context) {
|
def startSsService(context: Context) {
|
||||||
val isInstalled: Boolean = app.settings.getBoolean(BuildConfig.VERSION_NAME, false)
|
val isInstalled = app.settings.getInt(Key.currentVersionCode, -1) == BuildConfig.VERSION_CODE
|
||||||
if (!isInstalled) return
|
if (!isInstalled) return
|
||||||
|
|
||||||
val intent = new Intent(context, classOf[ShadowsocksRunnerService])
|
val intent = new Intent(context, classOf[ShadowsocksRunnerService])
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package tk.mygod.preference
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.support.v7.preference.{EditTextPreference => Parent}
|
||||||
|
import android.support.v7.widget.AppCompatEditText
|
||||||
|
import android.text.InputType
|
||||||
|
import android.util.AttributeSet
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixed EditTextPreference + SummaryPreference with password support!
|
||||||
|
* Based on: https://github.com/Gericop/Android-Support-Preference-V7-Fix/tree/master/app/src/main/java/android/support/v7/preference
|
||||||
|
*/
|
||||||
|
class EditTextPreference(context: Context, attrs: AttributeSet = null) extends Parent(context, attrs)
|
||||||
|
with SummaryPreference {
|
||||||
|
val editText = new AppCompatEditText(context, attrs)
|
||||||
|
editText.setId(android.R.id.edit)
|
||||||
|
|
||||||
|
override protected def getSummaryValue = {
|
||||||
|
var text = getText
|
||||||
|
if (text == null) text = ""
|
||||||
|
val inputType = editText.getInputType
|
||||||
|
if (inputType == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD) ||
|
||||||
|
inputType == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD) ||
|
||||||
|
inputType == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD))
|
||||||
|
"\u2022" * text.length else text
|
||||||
|
}
|
||||||
|
|
||||||
|
override def setText(text: String) = {
|
||||||
|
super.setText(text)
|
||||||
|
notifyChanged
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package tk.mygod.preference
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.support.v14.preference.PreferenceDialogFragment
|
||||||
|
import android.view.{View, ViewGroup}
|
||||||
|
|
||||||
|
class EditTextPreferenceDialogFragment extends PreferenceDialogFragment {
|
||||||
|
private lazy val preference = getPreference.asInstanceOf[EditTextPreference]
|
||||||
|
private lazy val editText = preference.editText
|
||||||
|
|
||||||
|
override protected def onCreateDialogView(context: Context) = {
|
||||||
|
val parent = editText.getParent.asInstanceOf[ViewGroup]
|
||||||
|
if (parent != null) parent.removeView(editText)
|
||||||
|
editText
|
||||||
|
}
|
||||||
|
|
||||||
|
override protected def onBindDialogView(view: View) {
|
||||||
|
super.onBindDialogView(view)
|
||||||
|
editText.setText(preference.getText)
|
||||||
|
val text = editText.getText
|
||||||
|
if (text != null) editText.setSelection(0, text.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
override protected def needInputMethod = true
|
||||||
|
|
||||||
|
def onDialogClosed(positiveResult: Boolean) = if (positiveResult) {
|
||||||
|
val value = editText.getText.toString
|
||||||
|
if (preference.callChangeListener(value)) preference.setText(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-32
@@ -1,20 +1,15 @@
|
|||||||
package com.github.shadowsocks.preferences
|
package tk.mygod.preference
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.TypedArray
|
import android.content.res.TypedArray
|
||||||
import android.os.Bundle
|
import android.support.v7.preference.DialogPreference
|
||||||
import android.preference.DialogPreference
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.{ViewGroup, WindowManager}
|
|
||||||
import android.widget.NumberPicker
|
import android.widget.NumberPicker
|
||||||
import com.github.shadowsocks.R
|
import com.github.shadowsocks.R
|
||||||
|
|
||||||
/**
|
class NumberPickerPreference(private val context: Context, attrs: AttributeSet = null)
|
||||||
* @author Mygod
|
|
||||||
*/
|
|
||||||
final class NumberPickerPreference(context: Context, attrs: AttributeSet = null)
|
|
||||||
extends DialogPreference(context, attrs) with SummaryPreference {
|
extends DialogPreference(context, attrs) with SummaryPreference {
|
||||||
private val picker = new NumberPicker(context)
|
private[preference] val picker = new NumberPicker(context)
|
||||||
private var value: Int = _
|
private var value: Int = _
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -28,7 +23,7 @@ final class NumberPickerPreference(context: Context, attrs: AttributeSet = null)
|
|||||||
def getMin = if (picker == null) 0 else picker.getMinValue
|
def getMin = if (picker == null) 0 else picker.getMinValue
|
||||||
def getMax = picker.getMaxValue
|
def getMax = picker.getMaxValue
|
||||||
def setValue(i: Int) {
|
def setValue(i: Int) {
|
||||||
if (i == getValue) return
|
if (i == value) return
|
||||||
picker.setValue(i)
|
picker.setValue(i)
|
||||||
value = picker.getValue
|
value = picker.getValue
|
||||||
persistInt(value)
|
persistInt(value)
|
||||||
@@ -37,28 +32,8 @@ final class NumberPickerPreference(context: Context, attrs: AttributeSet = null)
|
|||||||
def setMin(value: Int) = picker.setMinValue(value)
|
def setMin(value: Int) = picker.setMinValue(value)
|
||||||
def setMax(value: Int) = picker.setMaxValue(value)
|
def setMax(value: Int) = picker.setMaxValue(value)
|
||||||
|
|
||||||
override protected def showDialog(state: Bundle) {
|
override protected def onGetDefaultValue(a: TypedArray, index: Int): AnyRef =
|
||||||
super.showDialog(state)
|
a.getInt(index, getMin).asInstanceOf[AnyRef]
|
||||||
getDialog.getWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
|
||||||
}
|
|
||||||
override protected def onCreateDialogView = {
|
|
||||||
val parent = picker.getParent.asInstanceOf[ViewGroup]
|
|
||||||
if (parent != null) parent.removeView(picker)
|
|
||||||
picker
|
|
||||||
}
|
|
||||||
override protected def onDialogClosed(positiveResult: Boolean) {
|
|
||||||
picker.clearFocus // commit changes
|
|
||||||
super.onDialogClosed(positiveResult) // forward compatibility
|
|
||||||
if (positiveResult) {
|
|
||||||
val value = picker.getValue
|
|
||||||
if (callChangeListener(value)) {
|
|
||||||
setValue(value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
picker.setValue(value)
|
|
||||||
}
|
|
||||||
override protected def onGetDefaultValue(a: TypedArray, index: Int) = a.getInt(index, getMin).asInstanceOf[AnyRef]
|
|
||||||
override protected def onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any) {
|
override protected def onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any) {
|
||||||
val default = defaultValue.asInstanceOf[Int]
|
val default = defaultValue.asInstanceOf[Int]
|
||||||
setValue(if (restorePersistedValue) getPersistedInt(default) else default)
|
setValue(if (restorePersistedValue) getPersistedInt(default) else default)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package tk.mygod.preference
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.support.v14.preference.PreferenceDialogFragment
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.view.{View, ViewGroup}
|
||||||
|
|
||||||
|
class NumberPickerPreferenceDialogFragment extends PreferenceDialogFragment {
|
||||||
|
private lazy val preference = getPreference.asInstanceOf[NumberPickerPreference]
|
||||||
|
private lazy val picker = preference.picker
|
||||||
|
private lazy val inputMethodManager =
|
||||||
|
getActivity.getSystemService(Context.INPUT_METHOD_SERVICE).asInstanceOf[InputMethodManager]
|
||||||
|
|
||||||
|
override protected def onCreateDialogView(context: Context) = {
|
||||||
|
val parent = picker.getParent.asInstanceOf[ViewGroup]
|
||||||
|
if (parent != null) parent.removeView(picker)
|
||||||
|
picker
|
||||||
|
}
|
||||||
|
|
||||||
|
override protected def onBindDialogView(view: View) {
|
||||||
|
super.onBindDialogView(view)
|
||||||
|
picker.setValue(preference.getValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override protected def needInputMethod = true
|
||||||
|
|
||||||
|
def onDialogClosed(positiveResult: Boolean) {
|
||||||
|
picker.clearFocus // commit changes
|
||||||
|
if (positiveResult) {
|
||||||
|
val value = picker.getValue
|
||||||
|
if (preference.callChangeListener(value)) preference.setValue(value)
|
||||||
|
}
|
||||||
|
inputMethodManager.hideSoftInputFromWindow(getActivity.getCurrentFocus.getWindowToken,
|
||||||
|
InputMethodManager.HIDE_NOT_ALWAYS)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package tk.mygod.preference
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.support.v7.preference.{PreferenceViewHolder, PreferenceCategory => Base}
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on: https://github.com/Gericop/Android-Support-Preference-V7-Fix/blob/4c2bb2896895dbedb37b659b36bd2a96b33c1605/preference-v7/src/main/java/com/takisoft/fix/support/v7/preference/PreferenceCategory.java
|
||||||
|
*
|
||||||
|
* @author Mygod
|
||||||
|
*/
|
||||||
|
class PreferenceCategory(context: Context, attrs: AttributeSet = null) extends Base(context, attrs) {
|
||||||
|
override def onBindViewHolder(holder: PreferenceViewHolder) {
|
||||||
|
super.onBindViewHolder(holder)
|
||||||
|
holder.findViewById(android.R.id.title).getLayoutParams.asInstanceOf[MarginLayoutParams].bottomMargin = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package tk.mygod.preference
|
||||||
|
|
||||||
|
import android.app.DialogFragment
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v14.preference.{PreferenceFragment => Base}
|
||||||
|
import android.support.v7.preference.PreferenceScreen
|
||||||
|
import android.view.{LayoutInflater, ViewGroup}
|
||||||
|
|
||||||
|
abstract class PreferenceFragment extends Base {
|
||||||
|
override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle) =
|
||||||
|
super.onCreateView(inflater, container, savedInstanceState)
|
||||||
|
|
||||||
|
protected final def displayPreferenceDialog(key: String, fragment: DialogFragment) {
|
||||||
|
val bundle = new Bundle(1)
|
||||||
|
bundle.putString("key", key)
|
||||||
|
fragment.setArguments(bundle)
|
||||||
|
fragment.setTargetFragment(this, 0)
|
||||||
|
fragment.show(getFragmentManager, "android.support.v14.preference.PreferenceFragment.DIALOG")
|
||||||
|
}
|
||||||
|
|
||||||
|
override protected def onCreateAdapter(screen: PreferenceScreen) = new PreferenceGroupAdapter(screen)
|
||||||
|
|
||||||
|
override def onResume {
|
||||||
|
super.onResume
|
||||||
|
getListView.scrollBy(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package tk.mygod.preference
|
||||||
|
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
import java.util.List
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.support.v4.view.ViewCompat
|
||||||
|
import android.support.v7.preference.{PreferenceGroup, PreferenceViewHolder, PreferenceGroupAdapter => Old}
|
||||||
|
import android.view.{LayoutInflater, View, ViewGroup}
|
||||||
|
import com.github.shadowsocks.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix by: https://github.com/Gericop/Android-Support-Preference-V7-Fix/commit/7de016b007e28a264001a8bb353f110a7f64bb69
|
||||||
|
*
|
||||||
|
* @author Mygod
|
||||||
|
*/
|
||||||
|
object PreferenceGroupAdapter {
|
||||||
|
private var preferenceLayoutsField: Field = _
|
||||||
|
private var fieldResId: Field = _
|
||||||
|
private var fieldWidgetResId: Field = _
|
||||||
|
private val preferenceViewHolderConstructor = classOf[PreferenceViewHolder].getDeclaredConstructor(classOf[View])
|
||||||
|
|
||||||
|
{
|
||||||
|
val oldClass = classOf[Old]
|
||||||
|
preferenceLayoutsField = oldClass.getDeclaredField("mPreferenceLayouts")
|
||||||
|
preferenceLayoutsField.setAccessible(true)
|
||||||
|
val c = oldClass.getDeclaredClasses.filter(c => c.getSimpleName == "PreferenceLayout").head
|
||||||
|
fieldResId = c.getDeclaredField("resId")
|
||||||
|
fieldResId.setAccessible(true)
|
||||||
|
fieldWidgetResId = c.getDeclaredField("widgetResId")
|
||||||
|
fieldWidgetResId.setAccessible(true)
|
||||||
|
preferenceViewHolderConstructor.setAccessible(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PreferenceGroupAdapter(group: PreferenceGroup) extends Old(group) {
|
||||||
|
import PreferenceGroupAdapter._
|
||||||
|
|
||||||
|
protected lazy val preferenceLayouts = preferenceLayoutsField.get(this).asInstanceOf[List[AnyRef]]
|
||||||
|
|
||||||
|
override def onCreateViewHolder(parent: ViewGroup, viewType: Int) = if (Build.VERSION.SDK_INT < 21) {
|
||||||
|
val context = parent.getContext
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
val pl = preferenceLayouts.get(viewType)
|
||||||
|
val view = inflater.inflate(fieldResId.get(pl).asInstanceOf[Int], parent, false)
|
||||||
|
if (view.getBackground == null) {
|
||||||
|
val array = context.obtainStyledAttributes(null, R.styleable.BackgroundStyle)
|
||||||
|
var background = array.getDrawable(R.styleable.BackgroundStyle_android_selectableItemBackground)
|
||||||
|
if (background == null)
|
||||||
|
background = ContextCompat.getDrawable(context, android.R.drawable.list_selector_background)
|
||||||
|
array.recycle
|
||||||
|
val (s, t, e, b) = (ViewCompat.getPaddingStart(view), view.getPaddingTop,
|
||||||
|
ViewCompat.getPaddingEnd(view), view.getPaddingBottom)
|
||||||
|
view.setBackground(background)
|
||||||
|
ViewCompat.setPaddingRelative(view, s, t, e, b)
|
||||||
|
}
|
||||||
|
val widgetFrame = view.findViewById(android.R.id.widget_frame).asInstanceOf[ViewGroup]
|
||||||
|
if (widgetFrame != null) {
|
||||||
|
val widgetResId = fieldWidgetResId.get(pl).asInstanceOf[Int]
|
||||||
|
if (widgetResId != 0) inflater.inflate(widgetResId, widgetFrame) else widgetFrame.setVisibility(View.GONE)
|
||||||
|
}
|
||||||
|
preferenceViewHolderConstructor.newInstance(view)
|
||||||
|
} else super.onCreateViewHolder(parent, viewType)
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package tk.mygod.preference
|
||||||
|
|
||||||
|
import android.support.v7.preference.Preference
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make your preference support %s in summary. Override getSummaryValue to customize what to put in.
|
||||||
|
* Based on:
|
||||||
|
* https://github.com/android/platform_frameworks_base/blob/master/core/java/android/preference/ListPreference.java
|
||||||
|
*
|
||||||
|
* @author Mygod
|
||||||
|
*/
|
||||||
|
trait SummaryPreference extends Preference {
|
||||||
|
protected def getSummaryValue: AnyRef
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the summary of this SummaryPreference. If the summary has a String formatting marker in it
|
||||||
|
* (i.e. "%s" or "%1$s"), then the current entry value will be substituted in its place.
|
||||||
|
*
|
||||||
|
* @return the summary with appropriate string substitution
|
||||||
|
*/
|
||||||
|
override def getSummary = {
|
||||||
|
val summary = super.getSummary
|
||||||
|
if (summary == null) null else String.format(summary.toString, getSummaryValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user