Compare commits

..
22 Commits
Author SHA1 Message Date
Max Lv 040a6a2815 Bump version 2017-12-09 16:27:45 +08:00
Mygod 0b9b0a91c8 Fix #1490 2017-12-07 22:14:19 -08:00
Mygod b4f13ed062 Fix #1473 2017-12-06 17:14:41 -08:00
Max Lv 0b4b4b213d Update GFW List 2017-12-04 20:44:32 -08:00
Max Lv 0093e02849 Bump version 2017-12-04 20:29:43 -08:00
Max Lv 4768bb1aff Fix #1481 2017-12-04 17:13:48 -08:00
Max Lv 258191d02c Update overture's gfwlist 2017-12-04 21:06:46 +08:00
Max Lv bb7ad6df16 Update zh-cn values and bump version 2017-12-03 23:49:50 -08:00
Mygod 2f682ef3ec Fix service reattaching on entering settings 2017-11-30 22:07:32 -08:00
Mygod d3903cbc9d Fix another crash in Android 4.4 2017-11-30 22:07:12 -08:00
Mygod a6728e6025 Fix default ports not initialized in UI 2017-11-30 21:55:29 -08:00
Mygod be688205ce Remove obsolete icons 2017-11-30 21:51:58 -08:00
Mygod 5c75148813 Add support for downgrading the database for whatever reason
And it simply kills all the data.
2017-11-30 21:49:41 -08:00
Mygod ae833210c6 Revert to minimal libsodium source fileset 2017-11-30 21:49:26 -08:00
Mygod 722ca5b4f4 Handle malformed requests in TaskerActivity 2017-11-30 21:27:14 -08:00
Mygod 0171261465 Fix a crash on Android 4.4 2017-11-30 21:27:14 -08:00
Max Lv 273eca7bd1 Update shadowsocks-libev 2017-11-30 20:30:48 -08:00
Mygod 4e5c67e814 Move port default value to OrmLitePreferenceDataStore 2017-11-30 20:15:32 -08:00
Mygod 8c33345a1f Use Gericop's preference library (#1471)
* New SimpleMenuPreference;
* More Oreo-like preference style;
* I'm tired of fixing all those bugs;
* Remove NumberPickerPreference which nobody likes.
2017-11-30 20:10:47 -08:00
Mygod 74917eac13 Refine paddings 2017-11-30 19:03:37 -08:00
Mygod f2b2aba900 Update dependencies 2017-11-30 18:54:19 -08:00
Max Lv 0ae15f61d9 Fix #1469 2017-11-30 20:38:17 +08:00
57 changed files with 6737 additions and 6893 deletions
+4 -6
View File
@@ -3,16 +3,14 @@
Cannot connect to server:
1. Stop battery saver if it's active;
2. If an upgrade breaks anything, do a manual reset first by pressing Reset at the end of the list;
3. Check your config;
4. Wipe app data.
2. Check your config;
3. Wipe app data.
Crash: [Submit an issue](https://github.com/shadowsocks/shadowsocks-android/issues/new) with logcat attached, or submit a crash report to Google Play. Then, try wiping app data.
### UI tips
### How to create a widget and/or switch profile based on network connectivity?
* Tap the number to enter the port you wish to use; (if the keyboard doesn't pop up automatically for some reason)
* Use Tasker integration to create a desktop widget.
Use [Tasker](http://tasker.dinglisch.net/) integration.
### How to add QR code from local gallery?
+9 -2
View File
@@ -20,10 +20,13 @@ lazy val commonSettings = Seq(
resConfigs := Seq("fa", "ja", "ko", "ru", "zh-rCN", "zh-rTW"),
resolvers += Resolver.jcenterRepo,
resolvers += Resolver.bintrayRepo("gericop", "maven"),
resolvers += "google" at "https://maven.google.com"
)
val supportLibsVersion = "27.0.1"
val supportLibsVersion = "27.0.2"
val takisoftFixVersion = "27.0.2.0"
lazy val root = Project(id = "shadowsocks-android", base = file("."))
.settings(commonSettings)
.aggregate(plugin, mobile)
@@ -34,7 +37,11 @@ run in Android := (run in (mobile, Android)).evaluated
lazy val plugin = project
.settings(commonSettings)
.settings(
libraryDependencies += "com.android.support" % "preference-v14" % supportLibsVersion
libraryDependencies ++=
"com.android.support" % "preference-v14" % supportLibsVersion ::
"com.takisoft.fix" % "preference-v7" % takisoftFixVersion ::
"com.takisoft.fix" % "preference-v7-simplemenu" % takisoftFixVersion ::
Nil
)
lazy val mobile = project
+4 -4
View File
@@ -4,8 +4,8 @@ enablePlugins(AndroidGms)
android.useSupportVectors
name := "shadowsocks"
version := "4.3.0"
versionCode := Some(196)
version := "4.3.3"
versionCode := Some(199)
proguardOptions ++=
"-dontwarn com.google.android.gms.internal.**" ::
@@ -23,11 +23,11 @@ proguardOptions ++=
"-keep public class com.evernote.android.job.JobRescheduleService" ::
Nil
val playServicesVersion = "11.6.0"
val playServicesVersion = "11.6.2"
resolvers += Resolver.jcenterRepo
libraryDependencies ++=
"com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" ::
"com.evernote" % "android-job" % "1.2.0" ::
"com.evernote" % "android-job" % "1.2.1" ::
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" ::
"com.google.android.gms" % "play-services-ads" % playServicesVersion ::
"com.google.android.gms" % "play-services-analytics" % playServicesVersion ::
+13 -5
View File
@@ -4424,7 +4424,14 @@
223.255.236.0/22
223.255.252.0/23
[white_list]
[proxy_list]
# manhuaren
mangaapi.manhuaren.com
# Telegram IPs$
91.108.4.0/22
91.108.56.0/22
109.239.140.0/24
149.154.160.0/20
^(.*\.)?4tern\.com$
^(.*\.)?adorama\.com$
^(.*\.)?akiba-web\.com$
@@ -4603,6 +4610,7 @@
^(.*\.)?freewww\.info$
^(.*\.)?ftp1\.biz$
^(.*\.)?ftpserver\.biz$
^(.*\.)?gamer\.com\.tw$
^(.*\.)?gettrials\.com$
^(.*\.)?got-game\.org$
^(.*\.)?gr8domain\.biz$
@@ -4667,6 +4675,7 @@
^(.*\.)?organiccrap\.com$
^(.*\.)?otzo\.com$
^(.*\.)?ourhobby\.com$
^(.*\.)?pixiv\.net$
^(.*\.)?pcanywhere\.net$
^(.*\.)?port25\.biz$
^(.*\.)?qhigh\.com$
@@ -4733,7 +4742,7 @@
^(.*\.)?android\.com$
^(.*\.)?androidify\.com$
^(.*\.)?appspot\.com$
^(.*\.)?blogspot(\.[^.]{2,4}){1,2}$
^(.*\.)?blogspot(\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,2}$
^(.*\.)?certificate-transparency\.org$
^(.*\.)?chrome\.com$
^(.*\.)?chromecast\.com$
@@ -4756,8 +4765,8 @@
^(.*\.)?gmail\.com$
^(.*\.)?gmodules\.com$
^(.*\.)?goo\.gl$
^(.*\.)?google(\.[^.]{2,4}){1,2}$
^(.*\.)?googleapis(\.[^.]{2,4}){1,2}$
^(.*\.)?google(\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,2}$
^(.*\.)?googleapis(\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,2}$
^(.*\.)?googleapps\.com$
^(.*\.)?googleartproject\.com$
^(.*\.)?googleblog\.com$
@@ -4815,7 +4824,6 @@
^(.*\.)?zynamics\.com$
^(.*\.)?kat\.cr$
^(.*\.)?naughtyamerica\.com$
^(.*\.)?v2ex\.com$
^(.*\.)?0to255\.com$
^(.*\.)?100ke\.org$
^(.*\.)?1000giri\.net$
@@ -4439,6 +4439,13 @@
223.255.252.0/23
[proxy_list]
# manhuaren
mangaapi.manhuaren.com
# Telegram IPs$
91.108.4.0/22
91.108.56.0/22
109.239.140.0/24
149.154.160.0/20
^(.*\.)?4tern\.com$
^(.*\.)?adorama\.com$
^(.*\.)?akiba-web\.com$
@@ -4617,6 +4624,7 @@
^(.*\.)?freewww\.info$
^(.*\.)?ftp1\.biz$
^(.*\.)?ftpserver\.biz$
^(.*\.)?gamer\.com\.tw$
^(.*\.)?gettrials\.com$
^(.*\.)?got-game\.org$
^(.*\.)?gr8domain\.biz$
@@ -4681,6 +4689,7 @@
^(.*\.)?organiccrap\.com$
^(.*\.)?otzo\.com$
^(.*\.)?ourhobby\.com$
^(.*\.)?pixiv\.net$
^(.*\.)?pcanywhere\.net$
^(.*\.)?port25\.biz$
^(.*\.)?qhigh\.com$
@@ -4747,7 +4756,7 @@
^(.*\.)?android\.com$
^(.*\.)?androidify\.com$
^(.*\.)?appspot\.com$
^(.*\.)?blogspot(\.[^.]{2,4}){1,2}$
^(.*\.)?blogspot(\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,2}$
^(.*\.)?certificate-transparency\.org$
^(.*\.)?chrome\.com$
^(.*\.)?chromecast\.com$
@@ -4770,8 +4779,8 @@
^(.*\.)?gmail\.com$
^(.*\.)?gmodules\.com$
^(.*\.)?goo\.gl$
^(.*\.)?google(\.[^.]{2,4}){1,2}$
^(.*\.)?googleapis(\.[^.]{2,4}){1,2}$
^(.*\.)?google(\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,2}$
^(.*\.)?googleapis(\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,2}$
^(.*\.)?googleapps\.com$
^(.*\.)?googleartproject\.com$
^(.*\.)?googleblog\.com$
@@ -4829,7 +4838,6 @@
^(.*\.)?zynamics\.com$
^(.*\.)?kat\.cr$
^(.*\.)?naughtyamerica\.com$
^(.*\.)?v2ex\.com$
^(.*\.)?0to255\.com$
^(.*\.)?100ke\.org$
^(.*\.)?1000giri\.net$
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,15 +0,0 @@
package com.github.shadowsocks;
import android.app.Activity;
/**
* This helper class is used to solve Java-Scala interop problem. Use onAttach(Context) and remove this class
* when minSdkVersion >= 23.
*
* @author Mygod
*/
public class DialogFragment extends android.app.DialogFragment {
protected void superOnAttach(Activity activity) {
super.onAttach(activity);
}
}
+6 -61
View File
@@ -27,89 +27,34 @@ include $(CLEAR_VARS)
SODIUM_SOURCE := \
crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c \
crypto_aead/xchacha20poly1305/sodium/aead_xchacha20poly1305.c \
crypto_auth/crypto_auth.c \
crypto_auth/hmacsha256/auth_hmacsha256.c \
crypto_auth/hmacsha512/auth_hmacsha512.c \
crypto_auth/hmacsha512256/auth_hmacsha512256.c \
crypto_box/crypto_box.c \
crypto_box/crypto_box_easy.c \
crypto_box/crypto_box_seal.c \
crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305.c \
crypto_core/curve25519/ref10/curve25519_ref10.c \
crypto_core/hchacha20/core_hchacha20.c \
crypto_core/hsalsa20/ref2/core_hsalsa20_ref2.c \
crypto_core/hsalsa20/core_hsalsa20.c \
crypto_core/salsa/ref/core_salsa_ref.c \
crypto_generichash/crypto_generichash.c \
crypto_generichash/blake2b/generichash_blake2.c \
crypto_generichash/blake2b/ref/blake2b-compress-ref.c \
crypto_generichash/blake2b/ref/blake2b-ref.c \
crypto_generichash/blake2b/ref/generichash_blake2b.c \
crypto_hash/crypto_hash.c \
crypto_hash/sha256/hash_sha256.c \
crypto_hash/sha256/cp/hash_sha256_cp.c \
crypto_hash/sha512/hash_sha512.c \
crypto_hash/sha512/cp/hash_sha512_cp.c \
crypto_kdf/blake2b/kdf_blake2b.c \
crypto_kdf/crypto_kdf.c \
crypto_kx/crypto_kx.c \
crypto_onetimeauth/crypto_onetimeauth.c \
crypto_onetimeauth/poly1305/onetimeauth_poly1305.c \
crypto_onetimeauth/poly1305/donna/poly1305_donna.c \
crypto_pwhash/crypto_pwhash.c \
crypto_pwhash/argon2/argon2-core.c \
crypto_pwhash/argon2/argon2.c \
crypto_pwhash/argon2/argon2-encoding.c \
crypto_pwhash/argon2/argon2-fill-block-ref.c \
crypto_pwhash/argon2/argon2.c \
crypto_pwhash/argon2/blake2b-long.c \
crypto_pwhash/argon2/pwhash_argon2i.c \
crypto_pwhash/argon2/pwhash_argon2id.c \
crypto_pwhash/crypto_pwhash.c \
crypto_scalarmult/crypto_scalarmult.c \
crypto_scalarmult/curve25519/scalarmult_curve25519.c \
crypto_secretbox/crypto_secretbox.c \
crypto_secretbox/crypto_secretbox_easy.c \
crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305.c \
crypto_secretstream/xchacha20poly1305/secretstream_xchacha20poly1305.c \
crypto_shorthash/crypto_shorthash.c \
crypto_shorthash/siphash24/shorthash_siphash24.c \
crypto_shorthash/siphash24/ref/shorthash_siphash24_ref.c \
crypto_sign/crypto_sign.c \
crypto_sign/ed25519/sign_ed25519.c \
crypto_sign/ed25519/ref10/keypair.c \
crypto_sign/ed25519/ref10/open.c \
crypto_sign/ed25519/ref10/sign.c \
crypto_scalarmult/curve25519/ref10/x25519_ref10.c \
crypto_stream/chacha20/stream_chacha20.c \
crypto_stream/chacha20/ref/chacha20_ref.c \
crypto_stream/crypto_stream.c \
crypto_stream/salsa20/stream_salsa20.c \
crypto_stream/xsalsa20/stream_xsalsa20.c \
crypto_stream/salsa20/ref/salsa20_ref.c \
crypto_verify/sodium/verify.c \
randombytes/randombytes.c \
sodium/codecs.c \
randombytes/sysrandom/randombytes_sysrandom.c \
sodium/core.c \
sodium/runtime.c \
sodium/utils.c \
sodium/version.c \
randombytes/salsa20/randombytes_salsa20_random.c \
randombytes/sysrandom/randombytes_sysrandom.c \
crypto_scalarmult/curve25519/ref10/x25519_ref10.c \
crypto_stream/salsa20/ref/salsa20_ref.c \
crypto_box/curve25519xchacha20poly1305/box_curve25519xchacha20poly1305.c \
crypto_box/curve25519xchacha20poly1305/box_seal_curve25519xchacha20poly1305.c \
crypto_pwhash/scryptsalsa208sha256/crypto_scrypt-common.c \
crypto_pwhash/scryptsalsa208sha256/scrypt_platform.c \
crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.c \
crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c \
crypto_pwhash/scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c \
crypto_secretbox/xchacha20poly1305/secretbox_xchacha20poly1305.c \
crypto_shorthash/siphash24/shorthash_siphashx24.c \
crypto_shorthash/siphash24/ref/shorthash_siphashx24_ref.c \
crypto_sign/ed25519/ref10/obsolete.c \
crypto_stream/salsa2012/ref/stream_salsa2012_ref.c \
crypto_stream/salsa2012/stream_salsa2012.c \
crypto_stream/salsa208/ref/stream_salsa208_ref.c \
crypto_stream/salsa208/stream_salsa208.c \
crypto_stream/xchacha20/stream_xchacha20.c
sodium/version.c
LOCAL_MODULE := sodium
LOCAL_CFLAGS += -I$(LOCAL_PATH)/libsodium/src/libsodium/include \
@@ -41,6 +41,9 @@
/* Define to 1 if you have the <dlfcn.h> header file. */
#define HAVE_DLFCN_H 1
/* Define to 1 if you have the <linux/tcp.h> header file. */
#define HAVE_LINUX_TCP_H 1
/* Define to 1 if you have the `epoll_ctl' function. */
/* #undef HAVE_EPOLL_CTL */
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/material_primary_300" />
</shape>
</item>
<item>
<selector android:enterFadeDuration="@android:integer/config_mediumAnimTime"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_selected="true">
<shape android:shape="rectangle">
<solid android:color="@color/material_green_700" />
</shape>
</item>
</selector>
</item>
<item android:left="8dp">
<shape android:shape="rectangle">
<solid android:color="@android:color/white" />
</shape>
</item>
<item android:drawable="?android:attr/selectableItemBackground"/>
</layer-list>
@@ -20,5 +20,13 @@
<solid android:color="@android:color/white" />
</shape>
</item>
<item android:drawable="?android:attr/selectableItemBackground"/>
<item>
<selector>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#12000000" />
</shape>
</item>
</selector>
</item>
</layer-list>
@@ -14,7 +14,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:paddingLeft="24dp"
android:paddingLeft="16dp"
android:paddingRight="4dp"
android:id="@+id/container">
<TextView
@@ -54,10 +54,10 @@
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="24dp"
android:paddingLeft="16dp"
android:paddingRight="4dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp">
android:layout_marginBottom="16dp">
<TextView
android:id="@android:id/text2"
android:layout_width="wrap_content"
+1 -5
View File
@@ -9,11 +9,7 @@
<string name="remote_dns">"リモートDNS"</string>
<string name="stat_summary">"送信済み: \t%3$s\t↑\t%1$s/s
受信済み: \t%4$s\t↓\t%2$s/s"</string>
<string name="stat_profiles">"%1$s↑\t%2$s↓"</string>
<string name="traffic_summary">"%1$s/s↑\t%2$s/s↓"</string>
<plurals name="bytes">
<item quantity="other">"Byte"</item>
</plurals>
<string name="connection_test_pending">"接続状況確認"</string>
<string name="connection_test_testing">"テスト中…"</string>
<string name="connection_test_available">"成功: %dmsの遅延"</string>
+1 -5
View File
@@ -9,11 +9,7 @@
<string name="remote_dns">"원격 DNS"</string>
<string name="stat_summary">"송신: \t%3$s\t↑\t%1$s/s
수신: \t%4$s\t↓\t%2$s/s"</string>
<string name="stat_profiles">"%1$s↑\t%2$s↓"</string>
<string name="traffic_summary">"%1$s/s↑\t%2$s/s↓"</string>
<plurals name="bytes">
<item quantity="other">"Byte"</item>
</plurals>
<string name="connection_test_pending">"인터넷 연결 상태 검사하기"</string>
<string name="connection_test_testing">"검사 중…"</string>
<string name="connection_test_available">"성공: 지연시간 %dms"</string>
+1 -3
View File
@@ -9,9 +9,7 @@
<string name="remote_dns">"Удалённый DNS"</string>
<string name="stat_summary">"Отправлено: \t%3$s\t↑\t%1$s/s
Получено: \t%4$s\t↓\t%2$s/s"</string>
<string name="stat_profiles">"
%1$s↑\t%2$s↓"</string>
<string name="traffic_summary">"%1$s/s↑\t%2$s/s↓"</string>
<plurals name="bytes">
<item quantity="one">"байт"</item>
<item quantity="few">"байта"</item>
@@ -14,12 +14,6 @@
<string name="stat_summary">"上传: \t%3$s\t↑\t%1$s/s
下载: \t%4$s\t↓\t%2$s/s"</string>
<string name="stat_profiles">"1$s↑\t%2$s↓"</string>
<string name="traffic_summary">"1$s/s↑\t%2$s/s↓"</string>
<plurals name="bytes">
<item quantity="other">"字节"</item>
</plurals>
<string name="connection_test_pending">"检查网络连接"</string>
<string name="connection_test_testing">"测试中…"</string>
@@ -9,8 +9,6 @@
<string name="remote_dns">"遠程 DNS"</string>
<string name="stat_summary">"傳送: \t%3$s\t↑\t%1$s/s
接收: \t%4$s\t↓\t%2$s/s"</string>
<string name="stat_profiles">"%1$s↑\t%2$s↓"</string>
<string name="traffic_summary">"%1$s/s↑\t%2$s/s↓"</string>
<plurals name="bytes">
<item quantity="other">"位元组"</item>
</plurals>
+1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#7488A1</color>
<color name="material_drawer_selected_text">@color/material_accent_200</color>
</resources>
+2 -2
View File
@@ -18,8 +18,8 @@
<string name="remote_dns">Remote DNS</string>
<string name="stat_summary">Sent: \t\t\t\t\t%3$s\t↑\t%1$s/s\nReceived: \t%4$s\t↓\t%2$s/s</string>
<string name="stat_profiles">%1$s↑\t%2$s↓</string>
<string name="traffic_summary">%1$s/s↑\t%2$s/s↓</string>
<string name="stat_profiles" translatable="false">%1$s↑\t%2$s↓</string>
<string name="traffic_summary" translatable="false">%1$s/s↑\t%2$s/s↓</string>
<plurals name="bytes">
<item quantity="one">Byte</item>
<item quantity="other">Bytes</item>
-3
View File
@@ -1,8 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Shadowsocks.Material" parent="MaterialDrawerTheme.Light.DarkToolbar">
<item name="material_drawer_selected_text">@color/material_accent_200</item>
</style>
<style name="StyledScrollerTextAppearance" parent="@android:style/TextAppearance">
<item name="android:textSize">24sp</item>
<item name="android:textStyle">bold</item>
+15 -15
View File
@@ -8,32 +8,32 @@
<SwitchPreference android:key="tcp_fastopen"
android:summary="@string/tcp_fastopen_summary"
android:title="TCP Fast Open"/>
<be.mygod.preference.PreferenceCategory
<PreferenceCategory
android:title="@string/advanced">
<DropDownPreference
<SimpleMenuPreference
android:key="serviceMode"
android:entries="@array/service_modes"
android:entryValues="@array/service_mode_values"
android:defaultValue="vpn"
android:summary="%s"
android:title="@string/service_mode"/>
<be.mygod.preference.NumberPickerPreference
app:min="1025"
app:max="65535"
<AutoSummaryEditTextPreference
android:key="portProxy"
android:summary="%d"
android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:title="@string/port_proxy"/>
<be.mygod.preference.NumberPickerPreference
app:min="1025"
app:max="65535"
<AutoSummaryEditTextPreference
android:key="portLocalDns"
android:summary="%d"
android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:title="@string/port_local_dns"/>
<be.mygod.preference.NumberPickerPreference
app:min="1025"
app:max="65535"
<AutoSummaryEditTextPreference
android:key="portTransproxy"
android:summary="%d"
android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:title="@string/port_transproxy"/>
</be.mygod.preference.PreferenceCategory>
</PreferenceCategory>
</PreferenceScreen>
+23 -22
View File
@@ -2,51 +2,52 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks">
<be.mygod.preference.EditTextPreference
<AutoSummaryEditTextPreference
android:key="profileName"
android:summary="%s"
app:pref_summaryHasText="%s"
android:title="@string/profile_name"/>
<be.mygod.preference.PreferenceCategory
<PreferenceCategory
android:title="@string/proxy_cat">
<be.mygod.preference.EditTextPreference
<AutoSummaryEditTextPreference
android:key="proxy"
android:summary="%s"
app:pref_summaryHasText="%s"
android:title="@string/proxy"/>
<be.mygod.preference.NumberPickerPreference
app:min="1"
app:max="65535"
<AutoSummaryEditTextPreference
android:key="remotePortNum"
android:summary="%d"
android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:title="@string/remote_port"/>
<be.mygod.preference.EditTextPreference
<AutoSummaryEditTextPreference
android:inputType="textPassword"
app:pref_summaryPasswordSubstituteLength="0"
android:key="sitekey"
android:summary="%s"
app:pref_summaryHasText="%s"
android:title="@string/sitekey"/>
<DropDownPreference
<SimpleMenuPreference
android:key="encMethod"
android:entries="@array/enc_method_entry"
android:entryValues="@array/enc_method_value"
android:summary="%s"
android:title="@string/enc_method"/>
</be.mygod.preference.PreferenceCategory>
</PreferenceCategory>
<be.mygod.preference.PreferenceCategory
<PreferenceCategory
android:title="@string/feature_cat">
<DropDownPreference
<SimpleMenuPreference
android:key="route"
android:entries="@array/route_entry"
android:entryValues="@array/route_value"
android:summary="%s"
android:title="@string/route_list"/>
<be.mygod.preference.EditTextPreference
<AutoSummaryEditTextPreference
android:key="remoteDns"
android:title="@string/remote_dns"
android:summary="%s"/>
app:pref_summaryHasText="%s"/>
<SwitchPreference
android:key="isIpv6"
android:summary="@string/ipv6_summary"
@@ -60,9 +61,9 @@
android:summary="@string/udp_dns_summary"
android:title="@string/udp_dns"/>
</be.mygod.preference.PreferenceCategory>
</PreferenceCategory>
<be.mygod.preference.PreferenceCategory
<PreferenceCategory
android:title="@string/plugin">
<com.github.shadowsocks.preference.IconListPreference
@@ -70,12 +71,12 @@
android:persistent="false"
android:summary="%s"
android:title="@string/plugin"/>
<be.mygod.preference.EditTextPreference
<AutoSummaryEditTextPreference
android:key="plugin.configure"
android:persistent="false"
android:summary="%s"
app:pref_summaryHasText="%s"
android:title="@string/plugin_configure"/>
</be.mygod.preference.PreferenceCategory>
</PreferenceCategory>
</PreferenceScreen>
@@ -24,15 +24,15 @@ import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference
import android.support.v7.preference.Preference
import be.mygod.preference.PreferenceFragment
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.bg.ServiceState
import com.github.shadowsocks.utils.{Key, TcpFastOpen}
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompatDividers
class GlobalConfigFragment extends PreferenceFragment {
override def onCreatePreferences(bundle: Bundle, key: String) {
class GlobalConfigFragment extends PreferenceFragmentCompatDividers {
override def onCreatePreferencesFix(bundle: Bundle, key: String) {
getPreferenceManager.setPreferenceDataStore(app.dataStore)
app.dataStore.putString(Key.serviceMode, app.dataStore.serviceMode) // temporary workaround for support lib bug
app.dataStore.initGlobal()
addPreferencesFromResource(R.xml.pref_global)
val switch = findPreference(Key.isAutoConnect).asInstanceOf[SwitchPreference]
switch.setOnPreferenceChangeListener((_, value) => {
@@ -33,7 +33,7 @@ import android.os.{Bundle, Handler}
import android.support.customtabs.CustomTabsIntent
import android.support.design.widget.{FloatingActionButton, Snackbar}
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog
import android.support.v7.app.{AlertDialog, AppCompatActivity}
import android.support.v7.content.res.AppCompatResources
import android.support.v7.preference.PreferenceDataStore
import android.support.v7.widget.RecyclerView.ViewHolder
@@ -69,7 +69,7 @@ object MainActivity {
var stateListener: Int => Unit = _
}
class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawerItemClickListener
class MainActivity extends AppCompatActivity with ServiceBoundContext with Drawer.OnDrawerItemClickListener
with OnPreferenceDataStoreChangeListener {
import MainActivity._
@@ -152,8 +152,9 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
rxText.setText(TrafficMonitor.formatTraffic(rxTotal))
txRateText.setText(TrafficMonitor.formatTraffic(txRate) + "/s")
rxRateText.setText(TrafficMonitor.formatTraffic(rxRate) + "/s")
val child = getFragmentManager.findFragmentById(R.id.fragment_holder).asInstanceOf[ToolbarFragment]
if (state != ServiceState.STOPPING && child != null) child.onTrafficUpdated(profileId, txRate, rxRate, txTotal, rxTotal)
val child = getSupportFragmentManager.findFragmentById(R.id.fragment_holder).asInstanceOf[ToolbarFragment]
if (state != ServiceState.STOPPING && child != null)
child.onTrafficUpdated(profileId, txRate, rxRate, txTotal, rxTotal)
}
override def onServiceConnected(): Unit = changeState(bgService.getState)
@@ -352,7 +353,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
}
private def displayFragment(fragment: ToolbarFragment) {
getFragmentManager.beginTransaction().replace(R.id.fragment_holder, fragment).commitAllowingStateLoss()
getSupportFragmentManager.beginTransaction().replace(R.id.fragment_holder, fragment).commitAllowingStateLoss()
drawer.closeDrawer()
}
@@ -387,7 +388,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
}
override def onBackPressed(): Unit = if (drawer.isDrawerOpen) drawer.closeDrawer() else {
val currentFragment = getFragmentManager.findFragmentById(R.id.fragment_holder).asInstanceOf[ToolbarFragment]
val currentFragment = getSupportFragmentManager.findFragmentById(R.id.fragment_holder).asInstanceOf[ToolbarFragment]
if (!currentFragment.onBackPressed())
if (currentFragment.isInstanceOf[ProfilesFragment]) super.onBackPressed()
else drawer.setSelection(DRAWER_PROFILES)
@@ -23,20 +23,19 @@ package com.github.shadowsocks
import android.app.Activity
import android.content.{DialogInterface, Intent}
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.support.v7.app.{AlertDialog, AppCompatActivity}
import android.support.v7.widget.Toolbar
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.plugin.PluginContract
import com.github.shadowsocks.utils.Key
object ProfileConfigActivity {
final val REQUEST_CODE_PLUGIN_HELP = 1
}
class ProfileConfigActivity extends Activity {
class ProfileConfigActivity extends AppCompatActivity {
import ProfileConfigActivity._
private lazy val child = getFragmentManager.findFragmentById(R.id.content).asInstanceOf[ProfileConfigFragment]
private lazy val child = getSupportFragmentManager.findFragmentById(R.id.content).asInstanceOf[ProfileConfigFragment]
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
@@ -30,18 +30,19 @@ import android.support.v7.preference.{Preference, PreferenceDataStore}
import android.support.v7.widget.Toolbar.OnMenuItemClickListener
import android.text.TextUtils
import android.view.MenuItem
import be.mygod.preference.{EditTextPreference, PreferenceFragment}
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.plugin._
import com.github.shadowsocks.preference.{IconListPreference, OnPreferenceDataStoreChangeListener, PluginConfigurationDialogFragment}
import com.github.shadowsocks.utils.{Action, Key, Utils}
import com.github.shadowsocks.utils.{Action, Key}
import com.takisoft.fix.support.v7.preference.{EditTextPreference, PreferenceFragmentCompatDividers}
object ProfileConfigFragment {
private final val REQUEST_CODE_PLUGIN_CONFIGURE = 1
private final val FRAGMENT_DIALOG_TAG = "android.support.v7.preference.PreferenceFragment.DIALOG"
}
class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListener
class ProfileConfigFragment extends PreferenceFragmentCompatDividers with OnMenuItemClickListener
with OnPreferenceDataStoreChangeListener {
import ProfileConfigFragment._
@@ -51,7 +52,7 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
private var pluginConfigure: EditTextPreference = _
private var pluginConfiguration: PluginConfiguration = _
override def onCreatePreferences(bundle: Bundle, key: String) {
override def onCreatePreferencesFix(bundle: Bundle, key: String) {
getPreferenceManager.setPreferenceDataStore(app.dataStore)
app.profileManager.getProfile(getActivity.getIntent.getIntExtra(Action.EXTRA_PROFILE_ID, -1)) match {
case Some(p) =>
@@ -136,8 +137,12 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
private def showPluginEditor() {
val bundle = new Bundle()
bundle.putString("key", Key.pluginConfigure)
bundle.putString(PluginConfigurationDialogFragment.PLUGIN_ID_FRAGMENT_TAG, pluginConfiguration.selected)
displayPreferenceDialog(Key.pluginConfigure, new PluginConfigurationDialogFragment, bundle)
val fragment = new PluginConfigurationDialogFragment
fragment.setArguments(bundle)
fragment.setTargetFragment(this, 0)
fragment.show(getFragmentManager, FRAGMENT_DIALOG_TAG)
}
override def onDisplayPreferenceDialog(preference: Preference): Unit = if (preference.getKey == Key.pluginConfigure) {
@@ -23,8 +23,10 @@ package com.github.shadowsocks
import java.nio.charset.Charset
import android.app.Activity
import android.content.Context
import android.nfc.{NdefMessage, NdefRecord, NfcAdapter}
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.view.{LayoutInflater, View, ViewGroup}
import android.widget.{ImageView, LinearLayout}
import net.glxn.qrgen.android.QRCode
@@ -57,11 +59,12 @@ final class QRCodeDialog extends DialogFragment {
image
}
override def onAttach(activity: Activity) {
superOnAttach(activity)
override def onAttach(context: Context) {
super.onAttach(context)
adapter = NfcAdapter.getDefaultAdapter(getActivity)
if (adapter != null) adapter.setNdefPushMessage(new NdefMessage(Array(
new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI, nfcShareItem, Array[Byte](), nfcShareItem))), activity)
new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI, nfcShareItem, Array[Byte](), nfcShareItem))),
context.asInstanceOf[Activity])
}
override def onDetach() {
@@ -20,8 +20,7 @@
package com.github.shadowsocks
import android.app.{Activity, TaskStackBuilder}
import android.content.Intent
import android.app.TaskStackBuilder
import android.content.pm.{PackageManager, ShortcutManager}
import android.os.{Build, Bundle}
import android.support.v4.app.ActivityCompat
@@ -30,9 +29,9 @@ import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.text.TextUtils
import android.widget.Toast
import com.google.zxing.Result
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.Parser
import com.google.zxing.Result
import me.dm7.barcodescanner.zxing.ZXingScannerView
object ScannerActivity {
@@ -28,20 +28,21 @@ import android.app.{Application, NotificationChannel, NotificationManager}
import android.content._
import android.content.pm.{PackageInfo, PackageManager}
import android.content.res.Configuration
import android.os.{Binder, Build, LocaleList}
import android.os.{Build, LocaleList}
import android.support.v7.app.AppCompatDelegate
import android.util.Log
import com.evernote.android.job.JobManager
import com.github.shadowsocks.acl.DonaldTrump
import com.github.shadowsocks.bg.{BaseService, ProxyService, TransproxyService, VpnService}
import com.github.shadowsocks.bg.{ProxyService, TransproxyService, VpnService}
import com.github.shadowsocks.database.{DBHelper, Profile, ProfileManager}
import com.github.shadowsocks.preference.OrmLitePreferenceDataStore
import com.github.shadowsocks.preference.{BottomSheetPreferenceDialogFragment, IconListPreference, OrmLitePreferenceDataStore}
import com.github.shadowsocks.utils.CloseUtils._
import com.github.shadowsocks.utils._
import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders, StandardExceptionParser, Tracker}
import com.google.firebase.FirebaseApp
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.j256.ormlite.logger.LocalLog
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat
import scala.collection.JavaConversions._
@@ -92,8 +93,8 @@ class ShadowsocksApplication extends Application {
}
private def checkChineseLocale(locale: Locale): Locale = if (locale.getLanguage == "zh") locale.getCountry match {
case "CN" | "TW" => null // already supported
case _ => locale.getScript match { // fallback to the corresponding script
case "CN" | "TW" => null
case _ => (if (Build.VERSION.SDK_INT >= 21) locale.getScript else null) match {
case "Hans" => SIMPLIFIED_CHINESE
case "Hant" => TRADITIONAL_CHINESE
case script =>
@@ -102,7 +103,8 @@ class ShadowsocksApplication extends Application {
case "SG" => SIMPLIFIED_CHINESE
case "HK" | "MO" => TRADITIONAL_CHINESE
case _ =>
Log.w(TAG, "Unknown zh locale: %s. Falling back to zh-Hans-CN...".format(locale.toLanguageTag))
Log.w(TAG, "Unknown zh locale: %s. Falling back to zh-Hans-CN..."
.formatLocal(Locale.ENGLISH, if (Build.VERSION.SDK_INT >= 21) locale.toLanguageTag else locale))
SIMPLIFIED_CHINESE
}
}
@@ -156,15 +158,12 @@ class ShadowsocksApplication extends Application {
if (task.isSuccessful) remoteConfig.activateFetched() else Log.e(TAG, "Failed to fetch config"))
JobManager.create(this).addJobCreator(DonaldTrump)
PreferenceFragmentCompat.registerPreferenceFragment(classOf[IconListPreference],
classOf[BottomSheetPreferenceDialogFragment])
TcpFastOpen.enabled(dataStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
if (dataStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) copyAssets()
// hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat
lazy val userIndex = Binder.getCallingUserHandle.hashCode
if (!(1025 to 65535 contains dataStore.portProxy)) dataStore.putInt(Key.portProxy, 1080 + userIndex)
if (!(1025 to 65535 contains dataStore.portLocalDns)) dataStore.putInt(Key.portLocalDns, 5450 + userIndex)
if (!(1025 to 65535 contains dataStore.portTransproxy)) dataStore.putInt(Key.portTransproxy, 8200 + userIndex)
if (Build.VERSION.SDK_INT >= 26) {
val nm = getSystemService(classOf[NotificationManager])
@@ -82,6 +82,11 @@ class TaskerActivity extends AppCompatActivity {
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
try taskerOption = TaskerSettings.fromIntent(getIntent) catch {
case _: Exception =>
finish()
return
}
setContentView(R.layout.layout_tasker)
val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar]
@@ -89,7 +94,6 @@ class TaskerActivity extends AppCompatActivity {
toolbar.setNavigationIcon(R.drawable.ic_navigation_close)
toolbar.setNavigationOnClickListener(_ => finish())
taskerOption = TaskerSettings.fromIntent(getIntent)
switch = findViewById(R.id.serviceSwitch).asInstanceOf[Switch]
switch.setChecked(taskerOption.switchOn)
val profilesList = findViewById(R.id.list).asInstanceOf[RecyclerView]
@@ -20,8 +20,8 @@
package com.github.shadowsocks
import android.app.Fragment
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.Toolbar
import android.view.View
@@ -27,8 +27,8 @@ import android.content.pm.ApplicationInfo
import android.database.sqlite.SQLiteDatabase
import android.preference.PreferenceManager
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.preference.OrmLitePreferenceDataStore
import com.github.shadowsocks.utils.Key
import com.j256.ormlite.android.AndroidDatabaseConnection
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper
import com.j256.ormlite.dao.Dao
import com.j256.ormlite.support.ConnectionSource
@@ -68,96 +68,99 @@ class DBHelper(val context: Context)
onCreate(database, connectionSource)
}
def onUpgrade(database: SQLiteDatabase, connectionSource: ConnectionSource, oldVersion: Int,
newVersion: Int) {
if (oldVersion != newVersion) {
if (oldVersion < 7) {
def onUpgrade(database: SQLiteDatabase, connectionSource: ConnectionSource, oldVersion: Int, newVersion: Int) {
if (oldVersion < 7) {
recreate(database, connectionSource)
return
}
try {
if (oldVersion < 8) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;")
}
if (oldVersion < 9) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR DEFAULT 'all';")
} else if (oldVersion < 19) {
profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'all' WHERE route IS NULL;")
}
if (oldVersion < 11) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
}
if (oldVersion < 12) {
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
TableUtils.createTable(connectionSource, classOf[Profile])
profileDao.executeRawNoArgs(
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," +
" udpdns, ipv6, individual) " +
"SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, ipv6," +
" individual FROM `tmp`;")
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
profileDao.executeRawNoArgs("COMMIT;")
} else if (oldVersion < 13) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN tx LONG;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN rx LONG;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN date VARCHAR;")
}
if (oldVersion < 15) {
if (oldVersion >= 12) profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN userOrder LONG;")
var i = 0
for (profile <- profileDao.queryForAll.asScala) {
if (oldVersion < 14) profile.individual = updateProxiedApps(context, profile.individual)
profile.userOrder = i
profileDao.update(profile)
i += 1
}
}
if (oldVersion < 16) {
profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'bypass-lan-china' WHERE route = 'bypass-china'")
}
if (oldVersion < 21) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN remoteDns VARCHAR DEFAULT '8.8.8.8';")
}
if (oldVersion < 17) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN plugin VARCHAR;")
} else if (oldVersion < 22) {
// upgrade kcptun to SIP003 plugin
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
TableUtils.createTable(connectionSource, classOf[Profile])
profileDao.executeRawNoArgs(
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, remoteDns, " +
"proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, plugin) " +
"SELECT id, name, host, localPort, CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, " +
"route, remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " +
"CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;")
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
profileDao.executeRawNoArgs("COMMIT;")
}
if (oldVersion < 23) {
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
TableUtils.createTable(connectionSource, classOf[KeyValuePair])
profileDao.executeRawNoArgs("COMMIT;")
import KeyValuePair._
val old = PreferenceManager.getDefaultSharedPreferences(app)
kvPairDao.createOrUpdate(new KeyValuePair(Key.id, TYPE_INT,
ByteBuffer.allocate(4).putInt(old.getInt(Key.id, 0)).array()))
kvPairDao.createOrUpdate(new KeyValuePair(Key.tfo, TYPE_BOOLEAN,
ByteBuffer.allocate(1).put((if (old.getBoolean(Key.tfo, false)) 1 else 0).toByte).array()))
}
} catch {
case ex: Exception =>
app.track(ex)
recreate(database, connectionSource)
return
}
try {
if (oldVersion < 8) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;")
}
if (oldVersion < 9) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR DEFAULT 'all';")
} else if (oldVersion < 19) {
profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'all' WHERE route IS NULL;")
}
if (oldVersion < 11) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
}
if (oldVersion < 12) {
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
TableUtils.createTable(connectionSource, classOf[Profile])
profileDao.executeRawNoArgs(
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," +
" udpdns, ipv6, individual) " +
"SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, ipv6," +
" individual FROM `tmp`;")
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
profileDao.executeRawNoArgs("COMMIT;")
} else if (oldVersion < 13) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN tx LONG;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN rx LONG;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN date VARCHAR;")
}
if (oldVersion < 15) {
if (oldVersion >= 12) profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN userOrder LONG;")
var i = 0
for (profile <- profileDao.queryForAll.asScala) {
if (oldVersion < 14) profile.individual = updateProxiedApps(context, profile.individual)
profile.userOrder = i
profileDao.update(profile)
i += 1
}
}
if (oldVersion < 16) {
profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'bypass-lan-china' WHERE route = 'bypass-china'")
}
if (oldVersion < 21) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN remoteDns VARCHAR DEFAULT '8.8.8.8';")
}
if (oldVersion < 17) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN plugin VARCHAR;")
} else if (oldVersion < 22) {
// upgrade kcptun to SIP003 plugin
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
TableUtils.createTable(connectionSource, classOf[Profile])
profileDao.executeRawNoArgs(
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, remoteDns, " +
"proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, plugin) " +
"SELECT id, name, host, localPort, CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, " +
"route, remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " +
"CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;")
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
profileDao.executeRawNoArgs("COMMIT;")
}
if (oldVersion < 23) {
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
TableUtils.createTable(connectionSource, classOf[KeyValuePair])
profileDao.executeRawNoArgs("COMMIT;")
import KeyValuePair._
val old = PreferenceManager.getDefaultSharedPreferences(app)
kvPairDao.createOrUpdate(new KeyValuePair(Key.id, TYPE_INT,
ByteBuffer.allocate(4).putInt(old.getInt(Key.id, 0)).array()))
kvPairDao.createOrUpdate(new KeyValuePair(Key.tfo, TYPE_BOOLEAN,
ByteBuffer.allocate(1).put((if (old.getBoolean(Key.tfo, false)) 1 else 0).toByte).array()))
}
} catch {
case ex: Exception =>
app.track(ex)
recreate(database, connectionSource)
return
}
}
}
override def onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
val connection = new AndroidDatabaseConnection(db, true)
connectionSource.saveSpecialConnection(connection)
recreate(db, connectionSource)
connectionSource.clearSpecialConnection(connection)
}
}
@@ -26,7 +26,7 @@ import android.net.Uri
import android.util.Base64
import com.github.shadowsocks.plugin.PluginConfiguration
import com.github.shadowsocks.preference.OrmLitePreferenceDataStore
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.{Key, Utils}
import com.j256.ormlite.field.{DataType, DatabaseField}
class Profile {
@@ -106,7 +106,7 @@ class Profile {
def serialize(store: OrmLitePreferenceDataStore) {
store.putString(Key.name, name)
store.putString(Key.host, host)
store.putInt(Key.remotePort, remotePort)
store.putString(Key.remotePort, remotePort.toString)
store.putString(Key.password, password)
store.putString(Key.route, route)
store.putString(Key.remoteDns, remoteDns)
@@ -123,7 +123,7 @@ class Profile {
// It's assumed that default values are never used, so 0/false/null is always used even if that isn't the case
name = store.getString(Key.name, null)
host = store.getString(Key.host, null)
remotePort = store.getInt(Key.remotePort, 0)
remotePort = Utils.parsePort(store.getString(Key.remotePort), 8388, 1)
password = store.getString(Key.password, null)
method = store.getString(Key.method, null)
route = store.getString(Key.route, null)
@@ -7,7 +7,7 @@ import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import android.support.design.widget.BottomSheetDialog
import android.support.v14.preference.PreferenceDialogFragment
import android.support.v7.preference.PreferenceDialogFragmentCompat
import android.support.v7.widget.RecyclerView.ViewHolder
import android.support.v7.widget.{LinearLayoutManager, RecyclerView}
import android.view.ViewGroup.LayoutParams
@@ -18,7 +18,7 @@ import com.github.shadowsocks.R
/**
* @author Mygod
*/
final class BottomSheetPreferenceDialogFragment extends PreferenceDialogFragment {
final class BottomSheetPreferenceDialogFragment extends PreferenceDialogFragmentCompat {
private lazy val preference = getPreference.asInstanceOf[IconListPreference]
private lazy val index: Int = preference.selectedEntry
private lazy val entries: Array[CharSequence] = preference.getEntries
@@ -6,15 +6,11 @@ import android.support.annotation.ArrayRes
import android.support.v7.preference.ListPreference
import android.support.v7.preference.Preference.OnPreferenceChangeListener
import android.util.AttributeSet
import be.mygod.preference.DialogPreferencePlus
/**
* @author Mygod
*/
class IconListPreference(context: Context, attrs: AttributeSet = null) extends ListPreference(context, attrs)
with DialogPreferencePlus {
override def createDialog() = new BottomSheetPreferenceDialogFragment()
class IconListPreference(context: Context, attrs: AttributeSet = null) extends ListPreference(context, attrs) {
private var mEntryIcons: Array[Drawable] = _
def selectedEntry: Int = getEntryValues.indexOf(getValue)
@@ -4,9 +4,10 @@ import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
import java.util
import android.os.Binder
import android.support.v7.preference.PreferenceDataStore
import com.github.shadowsocks.database.{DBHelper, KeyValuePair}
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.{Key, Utils}
import scala.collection.JavaConversions._
@@ -98,12 +99,21 @@ final class OrmLitePreferenceDataStore(dbHelper: DBHelper) extends PreferenceDat
def registerChangeListener(listener: OnPreferenceDataStoreChangeListener): Unit = listeners += listener
def unregisterChangeListener(listener: OnPreferenceDataStoreChangeListener): Unit = listeners -= listener
// hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat
private lazy val userIndex = Binder.getCallingUserHandle.hashCode
private def getLocalPort(key: String, default: Int) = getInt(key, 0) match {
case 0 => Utils.parsePort(getString(key), default + userIndex)
case value =>
putString(key, value.toString)
value
}
def profileId: Int = getInt(Key.id, 0)
def profileId_=(i: Int): Unit = putInt(Key.id, i)
def serviceMode: String = getString(Key.serviceMode, Key.modeVpn)
def portProxy: Int = getInt(Key.portProxy, 0)
def portLocalDns: Int = getInt(Key.portLocalDns, 0)
def portTransproxy: Int = getInt(Key.portTransproxy, 0)
def portProxy: Int = getLocalPort(Key.portProxy, 1080)
def portLocalDns: Int = getLocalPort(Key.portLocalDns, 5450)
def portTransproxy: Int = getLocalPort(Key.portTransproxy, 8200)
def proxyApps: Boolean = getBoolean(Key.proxyApps)
def proxyApps_=(value: Boolean): Unit = putBoolean(Key.proxyApps, value)
@@ -115,4 +125,12 @@ final class OrmLitePreferenceDataStore(dbHelper: DBHelper) extends PreferenceDat
def plugin_=(value: String): Unit = putString(Key.plugin, value)
def dirty: Boolean = getBoolean(Key.dirty)
def dirty_=(value: Boolean): Unit = putBoolean(Key.dirty, value)
def initGlobal() {
// temporary workaround for support lib bug
if (getString(Key.serviceMode) == null) putString(Key.serviceMode, serviceMode)
if (getString(Key.portProxy) == null) putString(Key.portProxy, portProxy.toString)
if (getString(Key.portLocalDns) == null) putString(Key.portLocalDns, portLocalDns.toString)
if (getString(Key.portTransproxy) == null) putString(Key.portTransproxy, portTransproxy.toString)
}
}
@@ -20,9 +20,11 @@
package com.github.shadowsocks.preference
import android.app.AlertDialog
import android.content.DialogInterface
import be.mygod.preference.EditTextPreferenceDialogFragment
import android.support.v7.app.AlertDialog
import android.support.v7.preference.EditTextPreferenceDialogFragmentCompat
import android.view.View
import android.widget.EditText
import com.github.shadowsocks.ProfileConfigActivity
import com.github.shadowsocks.plugin.{PluginContract, PluginManager}
@@ -33,9 +35,11 @@ object PluginConfigurationDialogFragment {
final val PLUGIN_ID_FRAGMENT_TAG = "com.github.shadowsocks.preference.PluginConfigurationDialogFragment.PLUGIN_ID"
}
class PluginConfigurationDialogFragment extends EditTextPreferenceDialogFragment {
class PluginConfigurationDialogFragment extends EditTextPreferenceDialogFragmentCompat {
import PluginConfigurationDialogFragment._
private var editText: EditText = _
override def onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
val intent = PluginManager.buildIntent(getArguments.getString(PLUGIN_ID_FRAGMENT_TAG), PluginContract.ACTION_HELP)
@@ -43,4 +47,9 @@ class PluginConfigurationDialogFragment extends EditTextPreferenceDialogFragment
getActivity.startActivityForResult(intent.putExtra(PluginContract.EXTRA_OPTIONS, editText.getText.toString),
ProfileConfigActivity.REQUEST_CODE_PLUGIN_HELP)): DialogInterface.OnClickListener)
}
override def onBindDialogView(view: View) {
super.onBindDialogView(view)
editText = view.findViewById(android.R.id.edit)
}
}
@@ -30,8 +30,7 @@ object TaskerSettings {
private val KEY_SWITCH_ON = "switch_on"
private val KEY_PROFILE_ID = "profile_id"
def fromIntent(intent: Intent) = new TaskerSettings(if (intent.hasExtra(ApiIntent.EXTRA_BUNDLE))
intent.getBundleExtra(ApiIntent.EXTRA_BUNDLE) else Bundle.EMPTY)
def fromIntent(intent: Intent) = new TaskerSettings(intent.getBundleExtra(ApiIntent.EXTRA_BUNDLE))
}
class TaskerSettings(bundle: Bundle) {
@@ -152,4 +152,12 @@ object Utils {
}
def ThrowableFuture[T](f: => T): Unit = Future(f) onComplete handleFailure
def parsePort(str: String, default: Int, min: Int = 1025): Int = (try str.toInt catch {
case _: NumberFormatException => default
}) match {
case x if x < min => default
case x if x > 65535 => default
case x => x
}
}
@@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2015 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="48dp"
android:layout_marginBottom="48dp"
android:overScrollMode="ifContentScrolls">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="?attr/dialogPreferredPadding"
android:paddingEnd="?attr/dialogPreferredPadding"
android:orientation="vertical">
<TextView android:id="@android:id/message"
style="@style/TextAppearance.AppCompat.Subhead"
android:layout_marginBottom="48dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary" />
<EditText
android:id="@android:id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</ScrollView>
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Shadowsocks" parent="Theme.ShadowsocksBase"/>
</resources>
-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="NumberPickerPreference">
<attr name="max" format="integer" />
<attr name="min" format="integer" />
</declare-styleable>
</resources>
+3 -35
View File
@@ -1,45 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Shadowsocks.Material" parent="Theme.AppCompat.Light.NoActionBar"/>
<style name="Theme.ShadowsocksBase" parent="Theme.Shadowsocks.Material">
<style name="Theme.Shadowsocks" parent="PreferenceFixTheme.Light.NoActionBar">
<!--suppress NewApi -->
<item name="android:navigationBarColor">@color/material_primary_700</item>
<item name="colorAccent">@color/material_accent_200</item>
<item name="colorButtonNormal">@color/material_accent_200</item>
<item name="colorPrimary">@color/material_primary_500</item>
<item name="colorPrimaryDark">@color/material_primary_700</item>
<item name="preferenceTheme">@style/Theme.Shadowsocks.PreferenceThemeOverlay</item>
</style>
<style name="Theme.Shadowsocks.Dialog" parent="Theme.AppCompat.Light.Dialog">
<item name="colorAccent">@color/material_primary_500</item>
<item name="colorButtonNormal">@color/material_accent_200</item>
<item name="colorPrimary">@color/material_primary_500</item>
<item name="colorPrimaryDark">@color/material_primary_700</item>
<item name="preferenceTheme">@style/Theme.Shadowsocks.PreferenceThemeOverlay</item>
<item name="windowNoTitle">true</item>
</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.Shadowsocks" parent="Theme.ShadowsocksBase">
<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.Shadowsocks.PreferenceThemeOverlay" parent="@style/PreferenceThemeOverlay.v14.Material">
<item name="preferenceFragmentListStyle">@style/Theme.Shadowsocks.PreferenceFragmentList</item>
</style>
<style name="Theme.Shadowsocks.PreferenceFragmentList" parent="@style/PreferenceFragmentList.Material">
<item name="android:fadeScrollbars">true</item>
<item name="android:scrollbars">vertical</item>
</style>
<style name="NumberPickerStyle">
<item name="android:textSize">18sp</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material.Fix</item>
<item name="preferenceCategory_marginBottom">8dp</item>
</style>
</resources>
@@ -1,28 +0,0 @@
/*******************************************************************************/
/* */
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
/* */
/* 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/>. */
/* */
/*******************************************************************************/
package be.mygod.preference
import android.app.DialogFragment
import android.support.v7.preference.DialogPreference
trait DialogPreferencePlus extends DialogPreference {
def createDialog(): DialogFragment
}
@@ -1,67 +0,0 @@
/*******************************************************************************/
/* */
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
/* */
/* 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/>. */
/* */
/*******************************************************************************/
package be.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
import android.view.ViewGroup
import android.widget.FrameLayout
import com.github.shadowsocks.plugin.R
/**
* 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 DialogPreferencePlus with SummaryPreference {
val editText = new AppCompatEditText(context, attrs)
editText.setId(android.R.id.edit)
{
val arr = context.obtainStyledAttributes(Array(R.attr.dialogPreferredPadding))
val margin = arr.getDimensionPixelOffset(0, 0)
arr.recycle()
val params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
params.setMargins(margin, 0, margin, 0)
editText.setLayoutParams(params)
}
override def createDialog() = new EditTextPreferenceDialogFragment()
override protected def getSummaryValue: String = {
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): Unit = {
val old = getText
super.setText(text)
if (old != text) notifyChanged()
}
}
@@ -1,53 +0,0 @@
/*******************************************************************************/
/* */
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
/* */
/* 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/>. */
/* */
/*******************************************************************************/
package be.mygod.preference
import android.support.v14.preference.PreferenceDialogFragment
import android.support.v7.widget.AppCompatEditText
import android.view.{View, ViewGroup}
class EditTextPreferenceDialogFragment extends PreferenceDialogFragment {
private lazy val preference = getPreference.asInstanceOf[EditTextPreference]
protected lazy val editText: AppCompatEditText = preference.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)
val oldParent = editText.getParent.asInstanceOf[ViewGroup]
if (oldParent eq view) return
if (oldParent != null) oldParent.removeView(editText)
val oldEdit = view.findViewById[View](android.R.id.edit)
if (oldEdit == null) return
val container = oldEdit.getParent.asInstanceOf[ViewGroup]
if (container == null) return
container.removeView(oldEdit)
container.addView(editText, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}
override protected def needInputMethod = true
def onDialogClosed(positiveResult: Boolean): Unit = if (positiveResult) {
val value = editText.getText.toString
if (preference.callChangeListener(value)) preference.setText(value)
}
}
@@ -1,65 +0,0 @@
/*******************************************************************************/
/* */
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
/* */
/* 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/>. */
/* */
/*******************************************************************************/
package be.mygod.preference
import android.content.Context
import android.content.res.TypedArray
import android.support.v7.preference.DialogPreference
import android.util.AttributeSet
import android.view.ContextThemeWrapper
import android.widget.NumberPicker
import com.github.shadowsocks.plugin.R
class NumberPickerPreference(private val context: Context, attrs: AttributeSet = null)
extends DialogPreference(context, attrs) with DialogPreferencePlus with SummaryPreference {
private[preference] val picker = new NumberPicker(new ContextThemeWrapper(context, R.style.NumberPickerStyle))
private var value: Int = _
{
val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.NumberPickerPreference)
setMin(a.getInt(R.styleable.NumberPickerPreference_min, 0))
setMax(a.getInt(R.styleable.NumberPickerPreference_max, Int.MaxValue - 1))
a.recycle()
}
override def createDialog() = new NumberPickerPreferenceDialogFragment()
def getValue: Int = value
def getMin: Int = if (picker == null) 0 else picker.getMinValue
def getMax: Int = picker.getMaxValue
def setValue(i: Int) {
if (i == value) return
picker.setValue(i)
value = picker.getValue
persistInt(value)
notifyChanged()
}
def setMin(value: Int): Unit = picker.setMinValue(value)
def setMax(value: Int): Unit = picker.setMaxValue(value)
override protected def onGetDefaultValue(a: TypedArray, index: Int): AnyRef =
a.getInt(index, getMin).asInstanceOf[AnyRef]
override protected def onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any) {
val default = defaultValue.asInstanceOf[Int]
setValue(if (restorePersistedValue) getPersistedInt(default) else default)
}
protected def getSummaryValue: AnyRef = getValue.asInstanceOf[AnyRef]
}
@@ -1,52 +0,0 @@
/*******************************************************************************/
/* */
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
/* */
/* 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/>. */
/* */
/*******************************************************************************/
package be.mygod.preference
import android.content.Context
import android.support.v14.preference.PreferenceDialogFragment
import android.view.{View, ViewGroup}
import android.widget.NumberPicker
class NumberPickerPreferenceDialogFragment extends PreferenceDialogFragment {
private lazy val preference = getPreference.asInstanceOf[NumberPickerPreference]
private lazy val picker = preference.picker
override protected def onCreateDialogView(context: Context): NumberPicker = {
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)
}
}
}
@@ -1,38 +0,0 @@
/*******************************************************************************/
/* */
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
/* */
/* 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/>. */
/* */
/*******************************************************************************/
package be.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
}
}
@@ -1,55 +0,0 @@
/*******************************************************************************/
/* */
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
/* */
/* 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/>. */
/* */
/*******************************************************************************/
package be.mygod.preference
import android.app.DialogFragment
import android.os.Bundle
import android.support.v14.preference.{PreferenceFragment => Base}
import android.support.v7.preference.{Preference, PreferenceScreen}
import android.view.{LayoutInflater, View, ViewGroup}
abstract class PreferenceFragment extends Base {
override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View =
super.onCreateView(inflater, container, savedInstanceState)
protected final def displayPreferenceDialog(key: String, fragment: DialogFragment, other: Bundle = null) {
val bundle = new Bundle(1)
bundle.putString("key", key)
if (other != null) bundle.putAll(other)
fragment.setArguments(bundle)
fragment.setTargetFragment(this, 0)
getFragmentManager.beginTransaction()
.add(fragment, "android.support.v14.preference.PreferenceFragment.DIALOG")
.commitAllowingStateLoss()
}
override def onDisplayPreferenceDialog(preference: Preference): Unit = preference match {
case dpp: DialogPreferencePlus => displayPreferenceDialog(preference.getKey, dpp.createDialog())
case _ => super.onDisplayPreferenceDialog(preference)
}
override protected def onCreateAdapter(screen: PreferenceScreen) = new PreferenceGroupAdapter(screen)
override def onResume() {
super.onResume()
getListView.scrollBy(0, 0)
}
}
@@ -1,87 +0,0 @@
/*******************************************************************************/
/* */
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
/* */
/* 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/>. */
/* */
/*******************************************************************************/
package be.mygod.preference
import java.lang.reflect.Field
import java.util
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.plugin.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: util.List[AnyRef] =
preferenceLayoutsField.get(this).asInstanceOf[util.List[AnyRef]]
override def onCreateViewHolder(parent: ViewGroup, viewType: Int): PreferenceViewHolder =
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[ViewGroup](android.R.id.widget_frame)
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)
}
@@ -1,45 +0,0 @@
/*******************************************************************************/
/* */
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
/* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> */
/* */
/* 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/>. */
/* */
/*******************************************************************************/
package be.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: String = {
val summary = super.getSummary
if (summary == null) null else String.format(summary.toString, getSummaryValue)
}
}
@@ -1,8 +1,8 @@
package com.github.shadowsocks.plugin
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.Toast
/**
@@ -10,7 +10,7 @@ import android.widget.Toast
*
* @author Mygod
*/
trait OptionsCapableActivity extends Activity {
trait OptionsCapableActivity extends AppCompatActivity {
protected def pluginOptions(intent: Intent = getIntent): PluginOptions =
try new PluginOptions(intent.getStringExtra(PluginContract.EXTRA_OPTIONS)) catch {
case exc: IllegalArgumentException =>
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB