Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3facb73cf1 | ||
|
|
8f10e95329 | ||
|
|
b620a2e666 | ||
|
|
9cf8c4c483 | ||
|
|
bfbbb90ac3 | ||
|
|
dc840fd0e9 | ||
|
|
79d1ccba23 | ||
|
|
19df6caa93 | ||
|
|
5f268caa0a | ||
|
|
04c86a537a | ||
|
|
f9667d767e | ||
|
|
5ddd8a1686 | ||
|
|
7d44e4fc9f | ||
|
|
92b9c52944 | ||
|
|
4f3ebf9395 | ||
|
|
bf135107d8 | ||
|
|
5a3d2c07bc | ||
|
|
d8ab3733dc | ||
|
|
b6fc013a06 | ||
|
|
7d01ca81c8 | ||
|
|
9feb5bd96a | ||
|
|
2641de436d | ||
|
|
4d7bf8d707 | ||
|
|
0018a7ca9e | ||
|
|
51ceb3dac5 | ||
|
|
179aab8094 | ||
|
|
6bff4f1a44 | ||
|
|
8dcfd5a8c5 | ||
|
|
ef162cd47e | ||
|
|
569f056a22 | ||
|
|
971ea09077 | ||
|
|
fd01712b6c | ||
|
|
6a0955c082 | ||
|
|
92c8e0856c | ||
|
|
a5ef0cd6f3 | ||
|
|
0612a00474 | ||
|
|
0282975e6c | ||
|
|
c4583bea22 | ||
|
|
0d582f2392 | ||
|
|
8cf37954d7 | ||
|
|
eadb95ca2d | ||
|
|
0f9b686e55 | ||
|
|
c93a0348ad | ||
|
|
0cf509833a | ||
|
|
aed6308841 | ||
|
|
9fb0639f86 | ||
|
|
164becfed0 | ||
|
|
eed7ec6eba | ||
|
|
7e5b44d36f | ||
|
|
2a5d884694 | ||
|
|
a5a503c982 | ||
|
|
fcb368d2f8 | ||
|
|
d180801b87 | ||
|
|
74b4f27428 | ||
|
|
121f453aaa | ||
|
|
0ee615ac6c | ||
|
|
ce023bf49b | ||
|
|
0b6fe8a4da | ||
|
|
51d1a0d5d3 | ||
|
|
10ebf88484 | ||
|
|
62a572ee3a | ||
|
|
e82c7ccb99 | ||
|
|
3108306b6e | ||
|
|
88b875370e | ||
|
|
063d97b28b | ||
|
|
4c1d47dffa | ||
|
|
cf98a57736 | ||
|
|
d44b3d263d | ||
|
|
4f9e0f6c59 | ||
|
|
dde491cb6a | ||
|
|
e24ccfae4e | ||
|
|
8b0bd3e0b7 | ||
|
|
3f68a44074 |
@@ -21,7 +21,6 @@ before_cache:
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.android
|
||||
- $HOME/.ivy2/cache
|
||||
- $HOME/.sbt/boot
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Help to translate shadowsocks: http://crowdin.net/project/shadowsocks/invite
|
||||
|
||||
### PREREQUISITES
|
||||
|
||||
* JDK 1.7+
|
||||
* JDK 1.7
|
||||
* SBT 0.13.0+
|
||||
* Android SDK r24+
|
||||
* Android NDK r10d+
|
||||
@@ -61,16 +61,16 @@ Help to translate shadowsocks: http://crowdin.net/project/shadowsocks/invite
|
||||
## OPEN SOURCE LICENSES
|
||||
|
||||
* shadowsocks-libev: [GPLv3](https://github.com/shadowsocks/shadowsocks-libev/blob/master/LICENSE)
|
||||
* tun2socks: [BSD](https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/badvpn/COPYING)
|
||||
* redsocks: [APL 2.0](https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/redsocks/README)
|
||||
* OpenSSL: [OpenSSL](https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/openssl/NOTICE)
|
||||
* tun2socks: [BSD](https://github.com/shadowsocks/badvpn/blob/shadowsocks-android/COPYING)
|
||||
* redsocks: [APL 2.0](https://github.com/shadowsocks/redsocks/blob/master/README)
|
||||
* OpenSSL: [OpenSSL](https://github.com/shadowsocks/openssl-android/blob/master/NOTICE)
|
||||
* pdnsd: [GPLv3](https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/pdnsd/COPYING)
|
||||
* libev: [GPLv2](https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/libev/LICENSE)
|
||||
* libevent: [BSD](https://github.com/shadowsocks/shadowsocks-android/blob/master/src/main/jni/libevent/LICENSE)
|
||||
* libevent: [BSD](https://github.com/shadowsocks/libevent/blob/master/LICENSE)
|
||||
|
||||
### LICENSE
|
||||
|
||||
Copyright (C) 2014 Max Lv <max.c.lv@gmail.com>
|
||||
Copyright (C) 2015 Max Lv <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
|
||||
|
||||
@@ -29,13 +29,13 @@ resolvers += "JRAF" at "http://JRAF.org/static/maven/2"
|
||||
libraryDependencies ++= Seq(
|
||||
"dnsjava" % "dnsjava" % "2.1.7",
|
||||
"com.github.kevinsawicki" % "http-request" % "6.0",
|
||||
"commons-net" % "commons-net" % "3.3",
|
||||
"commons-net" % "commons-net" % "3.4",
|
||||
"eu.chainfire" % "libsuperuser" % "1.0.0.201510071325",
|
||||
"com.google.zxing" % "android-integration" % "3.2.1",
|
||||
"net.glxn.qrgen" % "android" % "2.0",
|
||||
"com.google.android.gms" % "play-services-base" % "8.3.0",
|
||||
"com.google.android.gms" % "play-services-ads" % "8.3.0",
|
||||
"com.google.android.gms" % "play-services-analytics" % "8.3.0",
|
||||
"com.google.android.gms" % "play-services-base" % "8.4.0",
|
||||
"com.google.android.gms" % "play-services-ads" % "8.4.0",
|
||||
"com.google.android.gms" % "play-services-analytics" % "8.4.0",
|
||||
"com.android.support" % "design" % "23.1.1",
|
||||
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
|
||||
"com.j256.ormlite" % "ormlite-core" % "4.48",
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
resolvers += Resolver.url("scalasbt releases", new URL("http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-snapshots"))(Resolver.ivyStylePatterns)
|
||||
addSbtPlugin("com.hanhuy.sbt" % "android-sdk-plugin" % "1.5.10")
|
||||
addSbtPlugin("com.hanhuy.sbt" % "android-sdk-plugin" % "1.5.13")
|
||||
|
||||
resolvers += "Sonatype snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/"
|
||||
addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.7.0-SNAPSHOT")
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.github.shadowsocks"
|
||||
android:versionCode="124"
|
||||
android:versionName="2.9.2">
|
||||
android:versionCode="128"
|
||||
android:versionName="2.9.6">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_SUPERUSER"/>
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
@@ -109,14 +108,6 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver android:name=".AppManagerReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_ADDED" />
|
||||
<action android:name="android.intent.action.PACKAGE_REMOVED" />
|
||||
<data android:scheme="package"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name=".ShadowsocksReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
|
||||
@@ -2,6 +2,5 @@ package com.github.shadowsocks.aidl;
|
||||
|
||||
interface IShadowsocksServiceCallback {
|
||||
oneway void stateChanged(int state, String msg);
|
||||
oneway void trafficUpdated(String txRate, String rxRate,
|
||||
String txTotal, String rxTotal);
|
||||
oneway void trafficUpdated(long txRate, long rxRate, long txTotal, long rxTotal);
|
||||
}
|
||||
|
||||
+170
-38
@@ -22,59 +22,189 @@ ROOT_PATH := $(LOCAL_PATH)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
crypto_aead_src := crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c
|
||||
SODIUM_SOURCE := \
|
||||
crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c \
|
||||
crypto_auth/crypto_auth.c \
|
||||
crypto_auth/hmacsha256/auth_hmacsha256_api.c \
|
||||
crypto_auth/hmacsha256/cp/hmac_hmacsha256.c \
|
||||
crypto_auth/hmacsha256/cp/verify_hmacsha256.c \
|
||||
crypto_auth/hmacsha512/auth_hmacsha512_api.c \
|
||||
crypto_auth/hmacsha512/cp/hmac_hmacsha512.c \
|
||||
crypto_auth/hmacsha512/cp/verify_hmacsha512.c \
|
||||
crypto_auth/hmacsha512256/auth_hmacsha512256_api.c \
|
||||
crypto_auth/hmacsha512256/cp/hmac_hmacsha512256.c \
|
||||
crypto_auth/hmacsha512256/cp/verify_hmacsha512256.c \
|
||||
crypto_box/crypto_box.c \
|
||||
crypto_box/crypto_box_easy.c \
|
||||
crypto_box/crypto_box_seal.c \
|
||||
crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305_api.c \
|
||||
crypto_box/curve25519xsalsa20poly1305/ref/after_curve25519xsalsa20poly1305.c \
|
||||
crypto_box/curve25519xsalsa20poly1305/ref/before_curve25519xsalsa20poly1305.c \
|
||||
crypto_box/curve25519xsalsa20poly1305/ref/box_curve25519xsalsa20poly1305.c \
|
||||
crypto_box/curve25519xsalsa20poly1305/ref/keypair_curve25519xsalsa20poly1305.c \
|
||||
crypto_core/hsalsa20/ref2/core_hsalsa20.c \
|
||||
crypto_core/hsalsa20/core_hsalsa20_api.c \
|
||||
crypto_core/salsa20/ref/core_salsa20.c \
|
||||
crypto_core/salsa20/core_salsa20_api.c \
|
||||
crypto_generichash/crypto_generichash.c \
|
||||
crypto_generichash/blake2/generichash_blake2_api.c \
|
||||
crypto_generichash/blake2/ref/blake2-impl.h \
|
||||
crypto_generichash/blake2/ref/blake2.h \
|
||||
crypto_generichash/blake2/ref/blake2b-compress-ref.c \
|
||||
crypto_generichash/blake2/ref/blake2b-load-sse2.h \
|
||||
crypto_generichash/blake2/ref/blake2b-load-sse41.h \
|
||||
crypto_generichash/blake2/ref/blake2b-ref.c \
|
||||
crypto_generichash/blake2/ref/blake2b-round.h \
|
||||
crypto_generichash/blake2/ref/generichash_blake2b.c \
|
||||
crypto_hash/crypto_hash.c \
|
||||
crypto_hash/sha256/hash_sha256_api.c \
|
||||
crypto_hash/sha256/cp/hash_sha256.c \
|
||||
crypto_hash/sha512/hash_sha512_api.c \
|
||||
crypto_hash/sha512/cp/hash_sha512.c \
|
||||
crypto_onetimeauth/crypto_onetimeauth.c \
|
||||
crypto_onetimeauth/poly1305/onetimeauth_poly1305.c \
|
||||
crypto_onetimeauth/poly1305/onetimeauth_poly1305.h \
|
||||
crypto_onetimeauth/poly1305/donna/poly1305_donna.h \
|
||||
crypto_onetimeauth/poly1305/donna/poly1305_donna32.h \
|
||||
crypto_onetimeauth/poly1305/donna/poly1305_donna64.h \
|
||||
crypto_onetimeauth/poly1305/donna/poly1305_donna.c \
|
||||
crypto_pwhash/scryptsalsa208sha256/crypto_scrypt-common.c \
|
||||
crypto_pwhash/scryptsalsa208sha256/crypto_scrypt.h \
|
||||
crypto_pwhash/scryptsalsa208sha256/scrypt_platform.c \
|
||||
crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.c \
|
||||
crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.h \
|
||||
crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c \
|
||||
crypto_pwhash/scryptsalsa208sha256/sysendian.h \
|
||||
crypto_pwhash/scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c \
|
||||
crypto_scalarmult/crypto_scalarmult.c \
|
||||
crypto_scalarmult/curve25519/scalarmult_curve25519.c \
|
||||
crypto_scalarmult/curve25519/scalarmult_curve25519.h \
|
||||
crypto_secretbox/crypto_secretbox.c \
|
||||
crypto_secretbox/crypto_secretbox_easy.c \
|
||||
crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305_api.c \
|
||||
crypto_secretbox/xsalsa20poly1305/ref/box_xsalsa20poly1305.c \
|
||||
crypto_shorthash/crypto_shorthash.c \
|
||||
crypto_shorthash/siphash24/shorthash_siphash24_api.c \
|
||||
crypto_shorthash/siphash24/ref/shorthash_siphash24.c \
|
||||
crypto_sign/crypto_sign.c \
|
||||
crypto_sign/ed25519/ref10/base.h \
|
||||
crypto_sign/ed25519/ref10/base2.h \
|
||||
crypto_sign/ed25519/sign_ed25519_api.c \
|
||||
crypto_sign/ed25519/ref10/d.h \
|
||||
crypto_sign/ed25519/ref10/d2.h \
|
||||
crypto_sign/ed25519/ref10/fe.h \
|
||||
crypto_sign/ed25519/ref10/fe_0.c \
|
||||
crypto_sign/ed25519/ref10/fe_1.c \
|
||||
crypto_sign/ed25519/ref10/fe_add.c \
|
||||
crypto_sign/ed25519/ref10/fe_cmov.c \
|
||||
crypto_sign/ed25519/ref10/fe_copy.c \
|
||||
crypto_sign/ed25519/ref10/fe_frombytes.c \
|
||||
crypto_sign/ed25519/ref10/fe_invert.c \
|
||||
crypto_sign/ed25519/ref10/fe_isnegative.c \
|
||||
crypto_sign/ed25519/ref10/fe_isnonzero.c \
|
||||
crypto_sign/ed25519/ref10/fe_mul.c \
|
||||
crypto_sign/ed25519/ref10/fe_neg.c \
|
||||
crypto_sign/ed25519/ref10/fe_pow22523.c \
|
||||
crypto_sign/ed25519/ref10/fe_sq.c \
|
||||
crypto_sign/ed25519/ref10/fe_sq2.c \
|
||||
crypto_sign/ed25519/ref10/fe_sub.c \
|
||||
crypto_sign/ed25519/ref10/fe_tobytes.c \
|
||||
crypto_sign/ed25519/ref10/ge.h \
|
||||
crypto_sign/ed25519/ref10/ge_add.c \
|
||||
crypto_sign/ed25519/ref10/ge_add.h \
|
||||
crypto_sign/ed25519/ref10/ge_double_scalarmult.c \
|
||||
crypto_sign/ed25519/ref10/ge_frombytes.c \
|
||||
crypto_sign/ed25519/ref10/ge_madd.c \
|
||||
crypto_sign/ed25519/ref10/ge_madd.h \
|
||||
crypto_sign/ed25519/ref10/ge_msub.c \
|
||||
crypto_sign/ed25519/ref10/ge_msub.h \
|
||||
crypto_sign/ed25519/ref10/ge_p1p1_to_p2.c \
|
||||
crypto_sign/ed25519/ref10/ge_p1p1_to_p3.c \
|
||||
crypto_sign/ed25519/ref10/ge_p2_0.c \
|
||||
crypto_sign/ed25519/ref10/ge_p2_dbl.c \
|
||||
crypto_sign/ed25519/ref10/ge_p2_dbl.h \
|
||||
crypto_sign/ed25519/ref10/ge_p3_0.c \
|
||||
crypto_sign/ed25519/ref10/ge_p3_dbl.c \
|
||||
crypto_sign/ed25519/ref10/ge_p3_to_cached.c \
|
||||
crypto_sign/ed25519/ref10/ge_p3_to_p2.c \
|
||||
crypto_sign/ed25519/ref10/ge_p3_tobytes.c \
|
||||
crypto_sign/ed25519/ref10/ge_precomp_0.c \
|
||||
crypto_sign/ed25519/ref10/ge_scalarmult_base.c \
|
||||
crypto_sign/ed25519/ref10/ge_sub.c \
|
||||
crypto_sign/ed25519/ref10/ge_sub.h \
|
||||
crypto_sign/ed25519/ref10/ge_tobytes.c \
|
||||
crypto_sign/ed25519/ref10/keypair.c \
|
||||
crypto_sign/ed25519/ref10/open.c \
|
||||
crypto_sign/ed25519/ref10/pow22523.h \
|
||||
crypto_sign/ed25519/ref10/pow225521.h \
|
||||
crypto_sign/ed25519/ref10/sc.h \
|
||||
crypto_sign/ed25519/ref10/sc_muladd.c \
|
||||
crypto_sign/ed25519/ref10/sc_reduce.c \
|
||||
crypto_sign/ed25519/ref10/sign.c \
|
||||
crypto_sign/ed25519/ref10/sqrtm1.h \
|
||||
crypto_stream/crypto_stream.c \
|
||||
crypto_stream/chacha20/stream_chacha20.c \
|
||||
crypto_stream/chacha20/stream_chacha20.h \
|
||||
crypto_stream/chacha20/ref/stream_chacha20_ref.h \
|
||||
crypto_stream/chacha20/ref/stream_chacha20_ref.c \
|
||||
crypto_stream/salsa20/stream_salsa20_api.c \
|
||||
crypto_stream/xsalsa20/stream_xsalsa20_api.c \
|
||||
crypto_stream/xsalsa20/ref/stream_xsalsa20.c \
|
||||
crypto_stream/xsalsa20/ref/xor_xsalsa20.c \
|
||||
crypto_verify/16/verify_16_api.c \
|
||||
crypto_verify/16/ref/verify_16.c \
|
||||
crypto_verify/32/verify_32_api.c \
|
||||
crypto_verify/32/ref/verify_32.c \
|
||||
crypto_verify/64/verify_64_api.c \
|
||||
crypto_verify/64/ref/verify_64.c \
|
||||
randombytes/randombytes.c \
|
||||
sodium/core.c \
|
||||
sodium/runtime.c \
|
||||
sodium/utils.c \
|
||||
sodium/version.c
|
||||
|
||||
crypto_auth_src := crypto_auth/crypto_auth.c crypto_auth/hmacsha256/auth_hmacsha256_api.c crypto_auth/hmacsha256/cp/hmac_hmacsha256.c crypto_auth/hmacsha256/cp/verify_hmacsha256.c crypto_auth/hmacsha512/auth_hmacsha512_api.c crypto_auth/hmacsha512/cp/hmac_hmacsha512.c crypto_auth/hmacsha512/cp/verify_hmacsha512.c crypto_auth/hmacsha512256/auth_hmacsha512256_api.c crypto_auth/hmacsha512256/cp/hmac_hmacsha512256.c crypto_auth/hmacsha512256/cp/verify_hmacsha512256.c
|
||||
SODIUM_SOURCE += \
|
||||
crypto_scalarmult/curve25519/ref10/curve25519_ref10.c \
|
||||
crypto_scalarmult/curve25519/ref10/curve25519_ref10.h \
|
||||
crypto_scalarmult/curve25519/ref10/fe.h \
|
||||
crypto_scalarmult/curve25519/ref10/fe_0_curve25519_ref10.c \
|
||||
crypto_scalarmult/curve25519/ref10/fe_1_curve25519_ref10.c \
|
||||
crypto_scalarmult/curve25519/ref10/fe_add_curve25519_ref10.c \
|
||||
crypto_scalarmult/curve25519/ref10/fe_copy_curve25519_ref10.c \
|
||||
crypto_scalarmult/curve25519/ref10/fe_cswap_curve25519_ref10.c \
|
||||
crypto_scalarmult/curve25519/ref10/fe_frombytes_curve25519_ref10.c \
|
||||
crypto_scalarmult/curve25519/ref10/fe_invert_curve25519_ref10.c \
|
||||
crypto_scalarmult/curve25519/ref10/fe_mul_curve25519_ref10.c \
|
||||
crypto_scalarmult/curve25519/ref10/fe_mul121666_curve25519_ref10.c \
|
||||
crypto_scalarmult/curve25519/ref10/fe_sq_curve25519_ref10.c \
|
||||
crypto_scalarmult/curve25519/ref10/fe_sub_curve25519_ref10.c \
|
||||
crypto_scalarmult/curve25519/ref10/fe_tobytes_curve25519_ref10.c \
|
||||
crypto_scalarmult/curve25519/ref10/montgomery.h \
|
||||
crypto_scalarmult/curve25519/ref10/pow225521.h
|
||||
|
||||
crypto_box_src := crypto_box/crypto_box.c crypto_box/crypto_box_easy.c crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305_api.c crypto_box/curve25519xsalsa20poly1305/ref/after_curve25519xsalsa20poly1305.c crypto_box/curve25519xsalsa20poly1305/ref/before_curve25519xsalsa20poly1305.c crypto_box/curve25519xsalsa20poly1305/ref/box_curve25519xsalsa20poly1305.c crypto_box/curve25519xsalsa20poly1305/ref/keypair_curve25519xsalsa20poly1305.c
|
||||
SODIUM_SOURCE += \
|
||||
crypto_stream/salsa20/ref/stream_salsa20_ref.c \
|
||||
crypto_stream/salsa20/ref/xor_salsa20_ref.c
|
||||
|
||||
crypto_core_src := crypto_core/hsalsa20/core_hsalsa20_api.c crypto_core/hsalsa20/ref2/core_hsalsa20.c crypto_core/salsa20/core_salsa20_api.c crypto_core/salsa20/ref/core_salsa20.c crypto_core/salsa2012/core_salsa2012_api.c crypto_core/salsa2012/ref/core_salsa2012.c crypto_core/salsa208/core_salsa208_api.c crypto_core/salsa208/ref/core_salsa208.c
|
||||
|
||||
crypto_generichash_src := crypto_generichash/blake2/generichash_blake2_api.c crypto_generichash/blake2/ref/blake2b-ref.c crypto_generichash/blake2/ref/generichash_blake2b.c crypto_generichash/crypto_generichash.c
|
||||
|
||||
crypto_hash_src := crypto_hash/crypto_hash.c crypto_hash/sha256/cp/hash_sha256.c crypto_hash/sha256/hash_sha256_api.c crypto_hash/sha512/cp/hash_sha512.c crypto_hash/sha512/hash_sha512_api.c
|
||||
|
||||
crypto_onetimeauth_src := crypto_onetimeauth/crypto_onetimeauth.c crypto_onetimeauth/poly1305/donna/auth_poly1305_donna.c crypto_onetimeauth/poly1305/donna/verify_poly1305_donna.c crypto_onetimeauth/poly1305/onetimeauth_poly1305.c crypto_onetimeauth/poly1305/onetimeauth_poly1305_api.c crypto_onetimeauth/poly1305/onetimeauth_poly1305_try.c
|
||||
|
||||
crypto_pwhash_src := crypto_pwhash/scryptsalsa208sha256/crypto_scrypt-common.c crypto_pwhash/scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.c crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c crypto_pwhash/scryptsalsa208sha256/scrypt_platform.c
|
||||
|
||||
crypto_scalarmult_src := crypto_scalarmult/crypto_scalarmult.c crypto_scalarmult/curve25519/donna_c64/base_curve25519_donna_c64.c crypto_scalarmult/curve25519/donna_c64/smult_curve25519_donna_c64.c crypto_scalarmult/curve25519/ref10/base_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_0_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_1_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_add_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_copy_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_cswap_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_frombytes_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_invert_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_mul121666_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_mul_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_sq_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_sub_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_tobytes_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/scalarmult_curve25519_ref10.c crypto_scalarmult/curve25519/scalarmult_curve25519_api.c
|
||||
|
||||
crypto_secretbox_src := crypto_secretbox/crypto_secretbox.c crypto_secretbox/crypto_secretbox_easy.c crypto_secretbox/xsalsa20poly1305/ref/box_xsalsa20poly1305.c crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305_api.c
|
||||
|
||||
crypto_shorthash_src := crypto_shorthash/crypto_shorthash.c crypto_shorthash/siphash24/ref/shorthash_siphash24.c crypto_shorthash/siphash24/shorthash_siphash24_api.c
|
||||
|
||||
crypto_sign_src := crypto_sign/crypto_sign.c crypto_sign/ed25519/ref10/fe_0.c crypto_sign/ed25519/ref10/fe_1.c crypto_sign/ed25519/ref10/fe_add.c crypto_sign/ed25519/ref10/fe_cmov.c crypto_sign/ed25519/ref10/fe_copy.c crypto_sign/ed25519/ref10/fe_frombytes.c crypto_sign/ed25519/ref10/fe_invert.c crypto_sign/ed25519/ref10/fe_isnegative.c crypto_sign/ed25519/ref10/fe_isnonzero.c crypto_sign/ed25519/ref10/fe_mul.c crypto_sign/ed25519/ref10/fe_neg.c crypto_sign/ed25519/ref10/fe_pow22523.c crypto_sign/ed25519/ref10/fe_sq.c crypto_sign/ed25519/ref10/fe_sq2.c crypto_sign/ed25519/ref10/fe_sub.c crypto_sign/ed25519/ref10/fe_tobytes.c crypto_sign/ed25519/ref10/ge_add.c crypto_sign/ed25519/ref10/ge_double_scalarmult.c crypto_sign/ed25519/ref10/ge_frombytes.c crypto_sign/ed25519/ref10/ge_madd.c crypto_sign/ed25519/ref10/ge_msub.c crypto_sign/ed25519/ref10/ge_p1p1_to_p2.c crypto_sign/ed25519/ref10/ge_p1p1_to_p3.c crypto_sign/ed25519/ref10/ge_p2_0.c crypto_sign/ed25519/ref10/ge_p2_dbl.c crypto_sign/ed25519/ref10/ge_p3_0.c crypto_sign/ed25519/ref10/ge_p3_dbl.c crypto_sign/ed25519/ref10/ge_p3_to_cached.c crypto_sign/ed25519/ref10/ge_p3_to_p2.c crypto_sign/ed25519/ref10/ge_p3_tobytes.c crypto_sign/ed25519/ref10/ge_precomp_0.c crypto_sign/ed25519/ref10/ge_scalarmult_base.c crypto_sign/ed25519/ref10/ge_sub.c crypto_sign/ed25519/ref10/ge_tobytes.c crypto_sign/ed25519/ref10/keypair.c crypto_sign/ed25519/ref10/open.c crypto_sign/ed25519/ref10/sc_muladd.c crypto_sign/ed25519/ref10/sc_reduce.c crypto_sign/ed25519/ref10/sign.c crypto_sign/ed25519/sign_ed25519_api.c crypto_sign/edwards25519sha512batch/ref/fe25519_edwards25519sha512batch.c crypto_sign/edwards25519sha512batch/ref/ge25519_edwards25519sha512batch.c crypto_sign/edwards25519sha512batch/ref/sc25519_edwards25519sha512batch.c crypto_sign/edwards25519sha512batch/ref/sign_edwards25519sha512batch.c crypto_sign/edwards25519sha512batch/sign_edwards25519sha512batch_api.c
|
||||
|
||||
crypto_stream_src := crypto_stream/aes128ctr/portable/afternm_aes128ctr.c crypto_stream/aes128ctr/portable/beforenm_aes128ctr.c crypto_stream/aes128ctr/portable/common_aes128ctr.c crypto_stream/aes128ctr/portable/consts_aes128ctr.c crypto_stream/aes128ctr/portable/int128_aes128ctr.c crypto_stream/aes128ctr/portable/stream_aes128ctr.c crypto_stream/aes128ctr/portable/xor_afternm_aes128ctr.c crypto_stream/aes128ctr/stream_aes128ctr_api.c crypto_stream/chacha20/ref/stream_chacha20_ref.c crypto_stream/chacha20/stream_chacha20_api.c crypto_stream/crypto_stream.c crypto_stream/salsa20/ref/stream_salsa20_ref.c crypto_stream/salsa20/ref/xor_salsa20_ref.c crypto_stream/salsa20/stream_salsa20_api.c crypto_stream/salsa2012/ref/stream_salsa2012.c crypto_stream/salsa2012/ref/xor_salsa2012.c crypto_stream/salsa2012/stream_salsa2012_api.c crypto_stream/salsa208/ref/stream_salsa208.c crypto_stream/salsa208/ref/xor_salsa208.c crypto_stream/salsa208/stream_salsa208_api.c crypto_stream/xsalsa20/ref/stream_xsalsa20.c crypto_stream/xsalsa20/ref/xor_xsalsa20.c crypto_stream/xsalsa20/stream_xsalsa20_api.c
|
||||
|
||||
crypto_verify_src := crypto_verify/16/ref/verify_16.c crypto_verify/16/verify_16_api.c crypto_verify/32/ref/verify_32.c crypto_verify/32/verify_32_api.c crypto_verify/64/ref/verify_64.c crypto_verify/64/verify_64_api.c
|
||||
|
||||
randombytes_src := randombytes/randombytes.c randombytes/salsa20/randombytes_salsa20_random.c randombytes/sysrandom/randombytes_sysrandom.c
|
||||
|
||||
sodium_src := sodium/core.c sodium/runtime.c sodium/utils.c sodium/version.c
|
||||
|
||||
SODIUM_SOURCE := $(crypto_aead_src) $(crypto_auth_src) $(crypto_box_src) \
|
||||
$(crypto_core_src) $(crypto_generichash_src) $(crypto_hash_src) \
|
||||
$(crypto_onetimeauth_src) $(crypto_pwhash_src) $(crypto_scalarmult_src) \
|
||||
$(crypto_secretbox_src) $(crypto_shorthash_src) $(crypto_sign_src) \
|
||||
$(crypto_stream_src) $(crypto_verify_src) $(randombytes_src) $(sodium_src)
|
||||
SODIUM_SOURCE += \
|
||||
randombytes/sysrandom/randombytes_sysrandom.c
|
||||
|
||||
LOCAL_MODULE := sodium
|
||||
LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/shadowsocks-libev/libsodium/src/libsodium/include \
|
||||
-I$(LOCAL_PATH)/include \
|
||||
-I$(LOCAL_PATH)/include/sodium \
|
||||
-I$(LOCAL_PATH)/shadowsocks-libev/libsodium/src/libsodium/include/sodium \
|
||||
-DPACKAGE_NAME=\"libsodium\" -DPACKAGE_TARNAME=\"libsodium\" \
|
||||
-DPACKAGE_VERSION=\"1.0.1\" -DPACKAGE_STRING=\"libsodium\ 1.0.1\" \
|
||||
-DPACKAGE_VERSION=\"1.0.7\" -DPACKAGE_STRING=\"libsodium\ 1.0.7\" \
|
||||
-DPACKAGE_BUGREPORT=\"https://github.com/jedisct1/libsodium/issues\" \
|
||||
-DPACKAGE_URL=\"https://github.com/jedisct1/libsodium\" \
|
||||
-DPACKAGE=\"libsodium\" -DVERSION=\"1.0.1\" -DSTDC_HEADERS=1 \
|
||||
-DPACKAGE=\"libsodium\" -DVERSION=\"1.0.7\" -DSTDC_HEADERS=1 \
|
||||
-DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 \
|
||||
-DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 \
|
||||
-DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 \
|
||||
-D__EXTENSIONS__=1 -D_ALL_SOURCE=1 -D_GNU_SOURCE=1 \
|
||||
-D_POSIX_PTHREAD_SEMANTICS=1 -D_TANDEM_SOURCE=1 \
|
||||
-DHAVE_DLFCN_H=1 -DLT_OBJDIR=\".libs/\" -DHAVE_MMINTRIN_H=1 \
|
||||
-DHAVE_EMMINTRIN_H=1 -DHAVE_PMMINTRIN_H=1 -DHAVE_TMMINTRIN_H=1 \
|
||||
-DHAVE_DLFCN_H=1 -DLT_OBJDIR=\".libs/\" \
|
||||
-DHAVE_SYS_MMAN_H=1 -DNATIVE_LITTLE_ENDIAN=1 \
|
||||
-DHAVE_WEAK_SYMBOLS=1 -DHAVE_ARC4RANDOM=1 -DHAVE_ARC4RANDOM_BUF=1 \
|
||||
-DHAVE_MLOCK=1 -DHAVE_MPROTECT=1 -DHAVE_POSIX_MEMALIGN=1
|
||||
@@ -256,6 +386,7 @@ LOCAL_MODULE := ss-local
|
||||
LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES))
|
||||
LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_LOCAL \
|
||||
-DUSE_CRYPTO_OPENSSL -DANDROID -DHAVE_CONFIG_H \
|
||||
-I$(LOCAL_PATH)/include \
|
||||
-I$(LOCAL_PATH)/libev \
|
||||
-I$(LOCAL_PATH)/libancillary \
|
||||
-I$(LOCAL_PATH)/openssl/include \
|
||||
@@ -285,6 +416,7 @@ LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_TUNNEL \
|
||||
-DUSE_CRYPTO_OPENSSL -DANDROID -DHAVE_CONFIG_H -DSSTUNNEL_JNI \
|
||||
-I$(LOCAL_PATH)/libev \
|
||||
-I$(LOCAL_PATH)/libancillary \
|
||||
-I$(LOCAL_PATH)/include \
|
||||
-I$(LOCAL_PATH)/shadowsocks-libev/libudns \
|
||||
-I$(LOCAL_PATH)/shadowsocks-libev/libcork/include \
|
||||
-I$(LOCAL_PATH)/shadowsocks-libev/libsodium/src/libsodium/include \
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
#ifndef sodium_version_H
|
||||
#define sodium_version_H
|
||||
|
||||
#include "export.h"
|
||||
|
||||
#define SODIUM_VERSION_STRING "1.0.7"
|
||||
|
||||
#define SODIUM_LIBRARY_VERSION_MAJOR 9
|
||||
#define SODIUM_LIBRARY_VERSION_MINOR 0
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
SODIUM_EXPORT
|
||||
const char *sodium_version_string(void);
|
||||
|
||||
SODIUM_EXPORT
|
||||
int sodium_library_version_major(void);
|
||||
|
||||
SODIUM_EXPORT
|
||||
int sodium_library_version_minor(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Submodule src/main/jni/shadowsocks-libev updated: f3428e621a...68eaf3b12d
@@ -41,7 +41,7 @@
|
||||
<string name="vpn_error">后台服务启动失败:%s</string>
|
||||
<string name="reboot_required">VPN 服务启动失败。你可能需要重启设备。</string>
|
||||
<string name="close">关闭</string>
|
||||
<string name="proxy_empty">代理服务器地址不能为空</string>
|
||||
<string name="proxy_empty">代理服务器地址及密码不能为空</string>
|
||||
<string name="connect">连接</string>
|
||||
<string name="initializing">正在初始化……</string>
|
||||
<string name="recovering">重置中……</string>
|
||||
@@ -78,8 +78,17 @@
|
||||
<plurals name="bytes">
|
||||
<item quantity="other">字节</item>
|
||||
</plurals>
|
||||
<string name="connection_test_pending">点击检查是否接入互联网。</string>
|
||||
<string name="connection_test_testing">测试中……</string>
|
||||
<string name="connection_test_available">已接入互联网。(延时:%d 毫秒)</string>
|
||||
<string name="connection_test_error">失败:%s</string>
|
||||
<string name="connection_test_error_status_code">状态码无效。(#%d)</string>
|
||||
|
||||
<!-- profile -->
|
||||
<string name="profile_manager_dialog">配置文件</string>
|
||||
<string name="profile_manager_dialog_content">• 点击选择配置文件;\n• 滑动删除;\n•
|
||||
长按拖动改变顺序。</string>
|
||||
<string name="gotcha">好的</string>
|
||||
<string name="add_profile_dialog">为影梭添加此配置文件?</string>
|
||||
<string name="add_profile_methods_scan_qr_code">扫描二维码</string>
|
||||
<string name="add_profile_methods_manual_settings">手动设置</string>
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<item>SEED-CFB</item>
|
||||
<item>SALSA20</item>
|
||||
<item>CHACHA20</item>
|
||||
<item>CHACHA20-IETF</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="enc_method_value">
|
||||
@@ -38,6 +39,7 @@
|
||||
<item>seed-cfb</item>
|
||||
<item>salsa20</item>
|
||||
<item>chacha20</item>
|
||||
<item>chacha20-ietf</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="route_entry">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<resources>
|
||||
|
||||
<color name="material_green_a700">#64DD17</color>
|
||||
<color name="material_green_700">#388E3C</color>
|
||||
<color name="material_blue_grey_300">#90A4AE</color>
|
||||
<color name="material_blue_grey_500">#607D8B</color>
|
||||
<color name="material_blue_grey_700">#455A64</color>
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
<item quantity="one">Byte</item>
|
||||
<item quantity="other">Bytes</item>
|
||||
</plurals>
|
||||
<string name="connection_test_pending">Tap to check Internet connection availability.</string>
|
||||
<string name="connection_test_testing">Testing…</string>
|
||||
<string name="connection_test_available">Internet connection available. (latency: %dms)</string>
|
||||
<string name="connection_test_error">Fail: %s</string>
|
||||
<string name="connection_test_error_status_code">Invalid status code. (#%d)</string>
|
||||
|
||||
<!-- proxy category -->
|
||||
<string name="proxy_cat">Server Settings</string>
|
||||
@@ -69,7 +74,7 @@
|
||||
|
||||
<!-- alert category -->
|
||||
<string name="close">Close</string>
|
||||
<string name="proxy_empty">Proxy should not be empty</string>
|
||||
<string name="proxy_empty">Proxy/Password should not be empty</string>
|
||||
<string name="connect">Connect</string>
|
||||
<string name="initializing">Initializing…</string>
|
||||
<string name="recovering">Resetting…</string>
|
||||
@@ -98,6 +103,10 @@
|
||||
<string name="action_import_err">Failed to import.</string>
|
||||
|
||||
<!-- profile -->
|
||||
<string name="profile_manager_dialog">Profiles</string>
|
||||
<string name="profile_manager_dialog_content">• Tap to select a profile;\n• Swipe to remove;\n•
|
||||
Long press to reorder profiles.</string>
|
||||
<string name="gotcha">Gotcha!</string>
|
||||
<string name="add_profile_dialog">Add this Shadowsocks Profile?</string>
|
||||
<string name="add_profile_methods_scan_qr_code">Scan QR code</string>
|
||||
<string name="add_profile_methods_manual_settings">Manual Settings</string>
|
||||
|
||||
@@ -33,12 +33,7 @@
|
||||
|
||||
<style name="PopupTheme" parent="Theme.Material.Dialog">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowFrame">@null</item>
|
||||
<item name="android:windowIsFloating">false</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
|
||||
<item name="android:windowSoftInputMode">stateAlwaysHidden</item>
|
||||
<item name="android:windowActionModeOverlay">true</item>
|
||||
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
|
||||
@@ -39,8 +39,11 @@
|
||||
|
||||
package com.github.shadowsocks
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
import android.Manifest.permission
|
||||
import android.content._
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.{ClipData, ClipboardManager, Context}
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.{Bundle, Handler}
|
||||
@@ -54,7 +57,6 @@ import android.view._
|
||||
import android.widget.AbsListView.OnScrollListener
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener
|
||||
import android.widget._
|
||||
import android.Manifest.permission
|
||||
import com.github.shadowsocks.utils.{Key, Utils}
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
@@ -65,13 +67,31 @@ object AppManager {
|
||||
case class ProxiedApp(name: String, packageName: String, icon: Drawable)
|
||||
private case class ListEntry(switch: Switch, text: TextView, icon: ImageView)
|
||||
|
||||
var cachedApps: Array[ProxiedApp] = _
|
||||
private var instance: AppManager = _
|
||||
|
||||
private var receiverRegistered: Boolean = _
|
||||
private var cachedApps: Array[ProxiedApp] = _
|
||||
private def getApps(pm: PackageManager) = {
|
||||
if (cachedApps == null) cachedApps = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS)
|
||||
.filter(p => p.requestedPermissions != null && p.requestedPermissions.contains(permission.INTERNET))
|
||||
.map(p => new ProxiedApp(pm.getApplicationLabel(p.applicationInfo).toString, p.packageName,
|
||||
p.applicationInfo.loadIcon(pm))).toArray
|
||||
cachedApps
|
||||
if (!receiverRegistered) {
|
||||
val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED)
|
||||
filter.addAction(Intent.ACTION_PACKAGE_REMOVED)
|
||||
filter.addDataScheme("package")
|
||||
ShadowsocksApplication.instance.registerReceiver((context: Context, intent: Intent) =>
|
||||
if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED ||
|
||||
!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
|
||||
synchronized(cachedApps = null)
|
||||
val instance = AppManager.instance
|
||||
if (instance != null) instance.reloadApps()
|
||||
}, filter)
|
||||
receiverRegistered = true
|
||||
}
|
||||
synchronized {
|
||||
if (cachedApps == null) cachedApps = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS)
|
||||
.filter(p => p.requestedPermissions != null && p.requestedPermissions.contains(permission.INTERNET))
|
||||
.map(p => new ProxiedApp(pm.getApplicationLabel(p.applicationInfo).toString, p.packageName,
|
||||
p.applicationInfo.loadIcon(pm))).toArray
|
||||
cachedApps
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,43 +106,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
|
||||
private var loadingView: View = _
|
||||
private var overlay: TextView = _
|
||||
private var adapter: ListAdapter = _
|
||||
@volatile private var appsLoading: Boolean = _
|
||||
|
||||
def loadApps() {
|
||||
appsLoading = true
|
||||
proxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "").split('\n').to[mutable.HashSet]
|
||||
apps = getApps(getPackageManager).sortWith((a, b) => {
|
||||
val aProxied = proxiedApps.contains(a.packageName)
|
||||
if (aProxied ^ proxiedApps.contains(b.packageName)) aProxied else a.name.compareToIgnoreCase(b.name) < 0
|
||||
})
|
||||
adapter = new ArrayAdapter[ProxiedApp](this, R.layout.layout_apps_item, R.id.itemtext, apps) {
|
||||
override def getView(position: Int, view: View, parent: ViewGroup): View = {
|
||||
var convertView = view
|
||||
var entry: ListEntry = null
|
||||
if (convertView == null) {
|
||||
convertView = getLayoutInflater.inflate(R.layout.layout_apps_item, parent, false)
|
||||
entry = new ListEntry(convertView.findViewById(R.id.itemcheck).asInstanceOf[Switch],
|
||||
convertView.findViewById(R.id.itemtext).asInstanceOf[TextView],
|
||||
convertView.findViewById(R.id.itemicon).asInstanceOf[ImageView])
|
||||
convertView.setOnClickListener(AppManager.this)
|
||||
convertView.setTag(entry)
|
||||
entry.switch.setOnCheckedChangeListener(AppManager.this)
|
||||
} else {
|
||||
entry = convertView.getTag.asInstanceOf[ListEntry]
|
||||
}
|
||||
|
||||
val app: ProxiedApp = apps(position)
|
||||
|
||||
entry.text.setText(app.name)
|
||||
entry.icon.setImageDrawable(app.icon)
|
||||
val switch = entry.switch
|
||||
switch.setTag(app)
|
||||
switch.setChecked(proxiedApps.contains(app.packageName))
|
||||
entry.text.setTag(switch)
|
||||
convertView
|
||||
}
|
||||
}
|
||||
}
|
||||
private val appsLoading = new AtomicBoolean
|
||||
|
||||
private def setProxied(pn: String, proxied: Boolean) = if (proxied) proxiedApps.add(pn) else proxiedApps.remove(pn)
|
||||
|
||||
@@ -145,11 +129,16 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
|
||||
}
|
||||
|
||||
override def onDestroy() {
|
||||
instance = null
|
||||
super.onDestroy()
|
||||
if (handler != null) {
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
handler = null
|
||||
}
|
||||
if (overlay != null) {
|
||||
getWindowManager.removeViewImmediate(overlay)
|
||||
overlay = null
|
||||
}
|
||||
}
|
||||
|
||||
def onMenuItemClick(item: MenuItem): Boolean = {
|
||||
@@ -182,7 +171,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
|
||||
// Restart activity
|
||||
appListView.setVisibility(View.GONE)
|
||||
loadingView.setVisibility(View.VISIBLE)
|
||||
if (appsLoading) appsLoading = false else loadAppsAsync()
|
||||
reloadApps()
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -210,16 +199,17 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
|
||||
toolbar.inflateMenu(R.menu.app_manager_menu)
|
||||
toolbar.setOnMenuItemClickListener(this)
|
||||
|
||||
this.overlay = View.inflate(this, R.layout.overlay, null).asInstanceOf[TextView]
|
||||
overlay = View.inflate(this, R.layout.overlay, null).asInstanceOf[TextView]
|
||||
getWindowManager.addView(overlay, new
|
||||
WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
|
||||
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT))
|
||||
|
||||
ShadowsocksApplication.settings.edit().putBoolean(Key.isProxyApps, true).commit()
|
||||
findViewById(R.id.onSwitch).asInstanceOf[Switch]
|
||||
.setOnCheckedChangeListener((button: CompoundButton, checked: Boolean) => {
|
||||
ShadowsocksApplication.settings.edit().putBoolean(Key.isProxyApps, checked).apply()
|
||||
ShadowsocksApplication.settings.edit().putBoolean(Key.isProxyApps, checked).commit()
|
||||
finish()
|
||||
})
|
||||
|
||||
@@ -247,13 +237,49 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
|
||||
}
|
||||
}
|
||||
})
|
||||
instance = this
|
||||
loadAppsAsync()
|
||||
}
|
||||
|
||||
def reloadApps() = if (!appsLoading.compareAndSet(true, false)) loadAppsAsync()
|
||||
def loadAppsAsync() {
|
||||
if (!appsLoading.compareAndSet(false, true)) return
|
||||
ThrowableFuture {
|
||||
while (!appsLoading) loadApps()
|
||||
appsLoading = false
|
||||
do {
|
||||
proxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "").split('\n').to[mutable.HashSet]
|
||||
appsLoading.set(true)
|
||||
apps = getApps(getPackageManager).sortWith((a, b) => {
|
||||
val aProxied = proxiedApps.contains(a.packageName)
|
||||
if (aProxied ^ proxiedApps.contains(b.packageName)) aProxied else a.name.compareToIgnoreCase(b.name) < 0
|
||||
})
|
||||
adapter = new ArrayAdapter[ProxiedApp](this, R.layout.layout_apps_item, R.id.itemtext, apps) {
|
||||
override def getView(position: Int, view: View, parent: ViewGroup): View = {
|
||||
var convertView = view
|
||||
var entry: ListEntry = null
|
||||
if (convertView == null) {
|
||||
convertView = getLayoutInflater.inflate(R.layout.layout_apps_item, parent, false)
|
||||
entry = new ListEntry(convertView.findViewById(R.id.itemcheck).asInstanceOf[Switch],
|
||||
convertView.findViewById(R.id.itemtext).asInstanceOf[TextView],
|
||||
convertView.findViewById(R.id.itemicon).asInstanceOf[ImageView])
|
||||
convertView.setOnClickListener(AppManager.this)
|
||||
convertView.setTag(entry)
|
||||
entry.switch.setOnCheckedChangeListener(AppManager.this)
|
||||
} else {
|
||||
entry = convertView.getTag.asInstanceOf[ListEntry]
|
||||
}
|
||||
|
||||
val app: ProxiedApp = apps(position)
|
||||
|
||||
entry.text.setText(app.name)
|
||||
entry.icon.setImageDrawable(app.icon)
|
||||
val switch = entry.switch
|
||||
switch.setTag(app)
|
||||
switch.setChecked(proxiedApps.contains(app.packageName))
|
||||
entry.text.setTag(switch)
|
||||
convertView
|
||||
}
|
||||
}
|
||||
} while (!appsLoading.compareAndSet(true, false))
|
||||
handler.post(() => {
|
||||
appListView.setAdapter(adapter)
|
||||
Utils.crossFade(AppManager.this, loadingView, appListView)
|
||||
@@ -262,7 +288,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
|
||||
}
|
||||
|
||||
def saveAppSettings(context: Context) {
|
||||
if (!appsLoading) ShadowsocksApplication.settings.edit.putString(Key.proxied, proxiedApps.mkString("\n")).apply
|
||||
if (!appsLoading.get) ShadowsocksApplication.settings.edit.putString(Key.proxied, proxiedApps.mkString("\n")).apply
|
||||
}
|
||||
|
||||
var handler: Handler = null
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.github.shadowsocks
|
||||
|
||||
import android.content.{Intent, Context, BroadcastReceiver}
|
||||
|
||||
/**
|
||||
* @author Mygod
|
||||
*/
|
||||
class AppManagerReceiver extends BroadcastReceiver {
|
||||
override def onReceive(context: Context, intent: Intent) = if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED ||
|
||||
!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) AppManager.synchronized(AppManager.cachedApps = null)
|
||||
}
|
||||
@@ -42,7 +42,7 @@ package com.github.shadowsocks
|
||||
import java.util.{Timer, TimerTask}
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.{Intent, Context}
|
||||
import android.os.{Handler, RemoteCallbackList}
|
||||
import com.github.shadowsocks.aidl.{Config, IShadowsocksService, IShadowsocksServiceCallback}
|
||||
import com.github.shadowsocks.utils.{State, TrafficMonitor, TrafficMonitorThread}
|
||||
@@ -50,13 +50,13 @@ import com.github.shadowsocks.utils.{State, TrafficMonitor, TrafficMonitorThread
|
||||
trait BaseService extends Service {
|
||||
|
||||
@volatile private var state = State.STOPPED
|
||||
@volatile private var callbackCount = 0
|
||||
@volatile protected var config: Config = null
|
||||
|
||||
var timer: Timer = null
|
||||
var trafficMonitorThread: TrafficMonitorThread = null
|
||||
var config: Config = null
|
||||
|
||||
final val callbacks = new RemoteCallbackList[IShadowsocksServiceCallback]
|
||||
var callbacksCount: Int = _
|
||||
|
||||
val binder = new IShadowsocksService.Stub {
|
||||
override def getMode: Int = {
|
||||
@@ -68,35 +68,29 @@ trait BaseService extends Service {
|
||||
}
|
||||
|
||||
override def unregisterCallback(cb: IShadowsocksServiceCallback) {
|
||||
if (cb != null ) {
|
||||
callbacks.unregister(cb)
|
||||
callbackCount -= 1
|
||||
}
|
||||
if (callbackCount == 0 && timer != null) {
|
||||
timer.cancel()
|
||||
timer = null
|
||||
}
|
||||
if (callbackCount == 0 && state == State.STOPPED) {
|
||||
stopBackgroundService()
|
||||
if (cb != null && callbacks.unregister(cb)) {
|
||||
callbacksCount -= 1
|
||||
if (callbacksCount == 0 && timer != null) {
|
||||
timer.cancel()
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def registerCallback(cb: IShadowsocksServiceCallback) {
|
||||
if (cb != null) {
|
||||
if (callbackCount == 0 && timer == null) {
|
||||
if (cb != null && callbacks.register(cb)) {
|
||||
callbacksCount += 1
|
||||
if (callbacksCount != 0 && timer == null) {
|
||||
val task = new TimerTask {
|
||||
def run {
|
||||
TrafficMonitor.updateRate()
|
||||
updateTrafficTotal(TrafficMonitor.getDeltaTx, TrafficMonitor.getDeltaRx)
|
||||
updateTrafficRate(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate,
|
||||
TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal)
|
||||
if (TrafficMonitor.updateRate()) updateTrafficRate()
|
||||
}
|
||||
}
|
||||
timer = new Timer(true)
|
||||
timer.schedule(task, 1000, 1000)
|
||||
}
|
||||
callbacks.register(cb)
|
||||
callbackCount += 1
|
||||
TrafficMonitor.updateRate()
|
||||
cb.trafficUpdated(TrafficMonitor.txRate, TrafficMonitor.rxRate, TrafficMonitor.txTotal, TrafficMonitor.rxTotal)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +110,7 @@ trait BaseService extends Service {
|
||||
def startRunner(config: Config) {
|
||||
this.config = config
|
||||
|
||||
startService(new Intent(getContext, getClass))
|
||||
TrafficMonitor.reset()
|
||||
trafficMonitorThread = new TrafficMonitorThread()
|
||||
trafficMonitorThread.start()
|
||||
@@ -123,38 +118,38 @@ trait BaseService extends Service {
|
||||
|
||||
def stopRunner() {
|
||||
// Make sure update total traffic when stopping the runner
|
||||
updateTrafficTotal(TrafficMonitor.getDeltaTx, TrafficMonitor.getDeltaRx)
|
||||
updateTrafficTotal(TrafficMonitor.txTotal, TrafficMonitor.rxTotal)
|
||||
|
||||
TrafficMonitor.reset()
|
||||
if (trafficMonitorThread != null) {
|
||||
trafficMonitorThread.stopThread()
|
||||
trafficMonitorThread = null
|
||||
}
|
||||
|
||||
// change the state
|
||||
changeState(State.STOPPED)
|
||||
|
||||
// stop the service if nothing has bound to it
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
def updateTrafficTotal(tx: Long, rx: Long) {
|
||||
val handler = new Handler(getContext.getMainLooper)
|
||||
handler.post(() => {
|
||||
if (config != null) {
|
||||
ShadowsocksApplication.profileManager.getProfile(config.profileId) match {
|
||||
case Some(profile) =>
|
||||
profile.tx += tx
|
||||
profile.rx += rx
|
||||
ShadowsocksApplication.profileManager.updateProfile(profile)
|
||||
case None => // Ignore
|
||||
}
|
||||
val config = this.config // avoid race conditions without locking
|
||||
if (config != null) {
|
||||
ShadowsocksApplication.profileManager.getProfile(config.profileId) match {
|
||||
case Some(profile) =>
|
||||
profile.tx += tx
|
||||
profile.rx += rx
|
||||
ShadowsocksApplication.profileManager.updateProfile(profile)
|
||||
case None => // Ignore
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
def stopBackgroundService()
|
||||
def getServiceMode: Int
|
||||
def getTag: String
|
||||
def getContext: Context
|
||||
|
||||
def getCallbackCount: Int = {
|
||||
callbackCount
|
||||
}
|
||||
def getState: Int = {
|
||||
state
|
||||
}
|
||||
@@ -162,10 +157,14 @@ trait BaseService extends Service {
|
||||
changeState(s, null)
|
||||
}
|
||||
|
||||
def updateTrafficRate(txRate: String, rxRate: String, txTotal: String, rxTotal: String) {
|
||||
def updateTrafficRate() {
|
||||
val handler = new Handler(getContext.getMainLooper)
|
||||
handler.post(() => {
|
||||
if (callbackCount > 0) {
|
||||
if (callbacksCount > 0) {
|
||||
val txRate = TrafficMonitor.txRate
|
||||
val rxRate = TrafficMonitor.rxRate
|
||||
val txTotal = TrafficMonitor.txTotal
|
||||
val rxTotal = TrafficMonitor.rxTotal
|
||||
val n = callbacks.beginBroadcast()
|
||||
for (i <- 0 until n) {
|
||||
try {
|
||||
@@ -182,7 +181,7 @@ trait BaseService extends Service {
|
||||
protected def changeState(s: Int, msg: String) {
|
||||
val handler = new Handler(getContext.getMainLooper)
|
||||
handler.post(() => if (state != s) {
|
||||
if (callbackCount > 0) {
|
||||
if (callbacksCount > 0) {
|
||||
val n = callbacks.beginBroadcast()
|
||||
for (i <- 0 until n) {
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.github.shadowsocks
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.{Handler, Bundle}
|
||||
import android.os.{Bundle, Handler}
|
||||
import android.support.design.widget.Snackbar
|
||||
import android.support.v7.app.{AlertDialog, AppCompatActivity}
|
||||
import android.support.v7.widget.RecyclerView.ViewHolder
|
||||
@@ -25,7 +25,13 @@ import scala.collection.mutable.ArrayBuffer
|
||||
/**
|
||||
* @author Mygod
|
||||
*/
|
||||
object ProfileManagerActivity {
|
||||
private final val profileTip = "profileTip"
|
||||
}
|
||||
|
||||
class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListener with ServiceBoundContext {
|
||||
import ProfileManagerActivity._
|
||||
|
||||
private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener {
|
||||
private var item: Profile = _
|
||||
private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView]
|
||||
@@ -55,28 +61,27 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
|
||||
})
|
||||
}
|
||||
|
||||
def updateText(refetch: Boolean = false) {
|
||||
def updateText(txTotal: Long = 0, rxTotal: Long = 0) {
|
||||
val builder = new SpannableStringBuilder
|
||||
val item = if (refetch) ShadowsocksApplication.profileManager.getProfile(this.item.id) match {
|
||||
case Some(profile) => profile
|
||||
case None => return
|
||||
} else this.item
|
||||
val tx = item.tx + txTotal
|
||||
val rx = item.rx + rxTotal
|
||||
builder.append(item.name)
|
||||
if (item.tx != 0 || item.rx != 0) {
|
||||
if (tx != 0 || rx != 0) {
|
||||
val start = builder.length
|
||||
builder.append(getString(R.string.stat_profiles,
|
||||
TrafficMonitor.formatTraffic(item.tx), TrafficMonitor.formatTraffic(item.rx)))
|
||||
TrafficMonitor.formatTraffic(tx), TrafficMonitor.formatTraffic(rx)))
|
||||
builder.setSpan(new TextAppearanceSpan(ProfileManagerActivity.this, android.R.style.TextAppearance_Small),
|
||||
start + 1, builder.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
handler.post(() => text.setText(builder))
|
||||
}
|
||||
|
||||
def bind(item: Profile) {
|
||||
def bind(item: Profile, i: Int) {
|
||||
this.item = item
|
||||
updateText()
|
||||
if (item.id == ShadowsocksApplication.profileId) {
|
||||
text.setChecked(true)
|
||||
selectedIndex = i
|
||||
selectedItem = this
|
||||
} else {
|
||||
text.setChecked(false)
|
||||
@@ -92,12 +97,12 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
|
||||
|
||||
private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] {
|
||||
private val recycleBin = new ArrayBuffer[(Int, Profile)]
|
||||
private var profiles = new ArrayBuffer[Profile]
|
||||
var profiles = new ArrayBuffer[Profile]
|
||||
profiles ++= ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List.empty[Profile])
|
||||
|
||||
def getItemCount = profiles.length
|
||||
|
||||
def onBindViewHolder(vh: ProfileViewHolder, i: Int) = vh.bind(profiles(i))
|
||||
def onBindViewHolder(vh: ProfileViewHolder, i: Int) = vh.bind(profiles(i), i)
|
||||
|
||||
def onCreateViewHolder(vg: ViewGroup, i: Int) =
|
||||
new ProfileViewHolder(LayoutInflater.from(vg.getContext).inflate(R.layout.layout_profiles_item, vg, false))
|
||||
@@ -110,6 +115,24 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
|
||||
notifyItemInserted(pos)
|
||||
}
|
||||
|
||||
def move(from: Int, to: Int) {
|
||||
val step = if (from < to) 1 else -1
|
||||
val first = profiles(from)
|
||||
var previousOrder = profiles(from).userOrder
|
||||
for (i <- from until to by step) {
|
||||
val next = profiles(i + step)
|
||||
val order = next.userOrder
|
||||
next.userOrder = previousOrder
|
||||
previousOrder = order
|
||||
profiles(i) = next
|
||||
ShadowsocksApplication.profileManager.updateProfile(next)
|
||||
}
|
||||
first.userOrder = previousOrder
|
||||
profiles(to) = first
|
||||
ShadowsocksApplication.profileManager.updateProfile(first)
|
||||
notifyItemMoved(from, to)
|
||||
}
|
||||
|
||||
def remove(pos: Int) {
|
||||
recycleBin.append((pos, profiles(pos)))
|
||||
profiles.remove(pos)
|
||||
@@ -131,6 +154,7 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
|
||||
}
|
||||
}
|
||||
|
||||
private var selectedIndex = -1
|
||||
private var selectedItem: ProfileViewHolder = _
|
||||
private val handler = new Handler
|
||||
|
||||
@@ -153,28 +177,52 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
|
||||
|
||||
ShadowsocksApplication.profileManager.setProfileAddedListener(profilesAdapter.add)
|
||||
val profilesList = findViewById(R.id.profilesList).asInstanceOf[RecyclerView]
|
||||
profilesList.setLayoutManager(new LinearLayoutManager(this))
|
||||
val layoutManager = new LinearLayoutManager(this)
|
||||
profilesList.setLayoutManager(layoutManager)
|
||||
profilesList.setItemAnimator(new DefaultItemAnimator)
|
||||
profilesList.setAdapter(profilesAdapter)
|
||||
if (selectedIndex < 0) selectedIndex = profilesAdapter.profiles.zipWithIndex.collectFirst {
|
||||
case (profile, i) if profile.id == ShadowsocksApplication.profileId => i
|
||||
}.getOrElse(-1)
|
||||
layoutManager.scrollToPosition(selectedIndex)
|
||||
removedSnackbar = Snackbar.make(profilesList, R.string.removed, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.undo, ((v: View) => profilesAdapter.undoRemoves): OnClickListener)
|
||||
removedSnackbar.getView.addOnAttachStateChangeListener(new OnAttachStateChangeListener {
|
||||
def onViewDetachedFromWindow(v: View) = profilesAdapter.commitRemoves
|
||||
def onViewAttachedToWindow(v: View) = ()
|
||||
})
|
||||
new ItemTouchHelper(new SimpleCallback(0, ItemTouchHelper.START | ItemTouchHelper.END) {
|
||||
new ItemTouchHelper(new SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
|
||||
ItemTouchHelper.START | ItemTouchHelper.END) {
|
||||
def onSwiped(viewHolder: ViewHolder, direction: Int) = {
|
||||
profilesAdapter.remove(viewHolder.getAdapterPosition)
|
||||
removedSnackbar.show
|
||||
}
|
||||
def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = false // TODO?
|
||||
def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = {
|
||||
profilesAdapter.move(viewHolder.getAdapterPosition, target.getAdapterPosition)
|
||||
true
|
||||
}
|
||||
}).attachToRecyclerView(profilesList)
|
||||
|
||||
attachService(new IShadowsocksServiceCallback.Stub {
|
||||
def stateChanged(state: Int, msg: String) = () // ignore
|
||||
def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) =
|
||||
if (selectedItem != null) selectedItem.updateText(true)
|
||||
def trafficUpdated(txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) =
|
||||
if (selectedItem != null) selectedItem.updateText(txTotal, rxTotal)
|
||||
})
|
||||
|
||||
if (ShadowsocksApplication.settings.getBoolean(profileTip, true)) {
|
||||
ShadowsocksApplication.settings.edit.putBoolean(profileTip, false).commit
|
||||
new AlertDialog.Builder(this).setTitle(R.string.profile_manager_dialog)
|
||||
.setMessage(R.string.profile_manager_dialog_content).setPositiveButton(R.string.gotcha, null).create.show
|
||||
}
|
||||
}
|
||||
|
||||
override def onStart() {
|
||||
super.onStart()
|
||||
registerCallback
|
||||
}
|
||||
override def onStop() {
|
||||
super.onStop()
|
||||
unregisterCallback
|
||||
}
|
||||
|
||||
override def onDestroy {
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.{ComponentName, Context, Intent, ServiceConnection}
|
||||
import android.os.{RemoteException, IBinder}
|
||||
import com.github.shadowsocks.aidl.{IShadowsocksServiceCallback, IShadowsocksService}
|
||||
import com.github.shadowsocks.utils.Action
|
||||
import com.github.shadowsocks.utils.Utils
|
||||
|
||||
/**
|
||||
* @author Mygod
|
||||
@@ -13,20 +14,12 @@ trait ServiceBoundContext extends Context {
|
||||
class ShadowsocksServiceConnection extends ServiceConnection {
|
||||
override def onServiceConnected(name: ComponentName, service: IBinder) {
|
||||
bgService = IShadowsocksService.Stub.asInterface(service)
|
||||
if (callback != null) try {
|
||||
bgService.registerCallback(callback)
|
||||
} catch {
|
||||
case ignored: RemoteException => // Nothing
|
||||
}
|
||||
registerCallback
|
||||
ServiceBoundContext.this.onServiceConnected()
|
||||
}
|
||||
override def onServiceDisconnected(name: ComponentName) {
|
||||
if (callback != null) {
|
||||
try {
|
||||
if (bgService != null) bgService.unregisterCallback(callback)
|
||||
} catch {
|
||||
case ignored: RemoteException => // Nothing
|
||||
}
|
||||
unregisterCallback
|
||||
callback = null
|
||||
}
|
||||
ServiceBoundContext.this.onServiceDisconnected()
|
||||
@@ -34,11 +27,25 @@ trait ServiceBoundContext extends Context {
|
||||
}
|
||||
}
|
||||
|
||||
def registerCallback = if (bgService != null && callback != null && !callbackRegistered) try {
|
||||
bgService.registerCallback(callback)
|
||||
callbackRegistered = true
|
||||
} catch {
|
||||
case ignored: RemoteException => // Nothing
|
||||
}
|
||||
def unregisterCallback = if (bgService != null && callback != null && callbackRegistered) try {
|
||||
bgService.unregisterCallback(callback)
|
||||
callbackRegistered = false
|
||||
} catch {
|
||||
case ignored: RemoteException => // Nothing
|
||||
}
|
||||
|
||||
def onServiceConnected() = ()
|
||||
def onServiceDisconnected() = ()
|
||||
|
||||
private var callback: IShadowsocksServiceCallback.Stub = _
|
||||
private var connection: ShadowsocksServiceConnection = _
|
||||
private var callbackRegistered: Boolean = _
|
||||
|
||||
// Variables
|
||||
var bgService: IShadowsocksService = _
|
||||
@@ -54,19 +61,14 @@ trait ServiceBoundContext extends Context {
|
||||
|
||||
connection = new ShadowsocksServiceConnection()
|
||||
bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
|
||||
startService(new Intent(this, s))
|
||||
startService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
def deattachService() {
|
||||
if (bgService != null) {
|
||||
if (callback != null) {
|
||||
try {
|
||||
bgService.unregisterCallback(callback)
|
||||
} catch {
|
||||
case ignored: RemoteException => // Nothing
|
||||
}
|
||||
unregisterCallback
|
||||
callback = null
|
||||
}
|
||||
if (connection != null) {
|
||||
|
||||
@@ -49,7 +49,6 @@ import android.content.res.AssetManager
|
||||
import android.graphics.Typeface
|
||||
import android.net.VpnService
|
||||
import android.os._
|
||||
import android.preference.{Preference, SwitchPreference}
|
||||
import android.support.design.widget.{FloatingActionButton, Snackbar}
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
@@ -60,7 +59,6 @@ import android.widget._
|
||||
import com.github.jorgecastilloprz.FABProgressCircle
|
||||
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
|
||||
import com.github.shadowsocks.database._
|
||||
import com.github.shadowsocks.preferences.{DropDownPreference, NumberPickerPreference, PasswordEditTextPreference, SummaryEditTextPreference}
|
||||
import com.github.shadowsocks.utils._
|
||||
import com.google.android.gms.ads.{AdRequest, AdSize, AdView}
|
||||
|
||||
@@ -91,52 +89,9 @@ object Shadowsocks {
|
||||
// Constants
|
||||
val TAG = "Shadowsocks"
|
||||
val REQUEST_CONNECT = 1
|
||||
|
||||
val PREFS_NAME = "Shadowsocks"
|
||||
val PROXY_PREFS = Array(Key.profileName, Key.proxy, Key.remotePort, Key.localPort, Key.sitekey, Key.encMethod,
|
||||
Key.isAuth)
|
||||
val FEATURE_PREFS = Array(Key.route, Key.isProxyApps, Key.isUdpDns, Key.isIpv6)
|
||||
val EXECUTABLES = Array(Executable.PDNSD, Executable.REDSOCKS, Executable.SS_TUNNEL, Executable.SS_LOCAL,
|
||||
Executable.TUN2SOCKS)
|
||||
|
||||
// Helper functions
|
||||
def updateDropDownPreference(pref: Preference, value: String) {
|
||||
pref.asInstanceOf[DropDownPreference].setValue(value)
|
||||
}
|
||||
|
||||
def updatePasswordEditTextPreference(pref: Preference, value: String) {
|
||||
pref.setSummary(value)
|
||||
pref.asInstanceOf[PasswordEditTextPreference].setText(value)
|
||||
}
|
||||
|
||||
def updateNumberPickerPreference(pref: Preference, value: Int) {
|
||||
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) {
|
||||
pref.asInstanceOf[SwitchPreference].setChecked(value)
|
||||
}
|
||||
|
||||
def updatePreference(pref: Preference, name: String, profile: Profile) {
|
||||
name match {
|
||||
case Key.profileName => updateSummaryEditTextPreference(pref, profile.name)
|
||||
case Key.proxy => updateSummaryEditTextPreference(pref, profile.host)
|
||||
case Key.remotePort => updateNumberPickerPreference(pref, profile.remotePort)
|
||||
case Key.localPort => updateNumberPickerPreference(pref, profile.localPort)
|
||||
case Key.sitekey => updatePasswordEditTextPreference(pref, profile.password)
|
||||
case Key.encMethod => updateDropDownPreference(pref, profile.method)
|
||||
case Key.route => updateDropDownPreference(pref, profile.route)
|
||||
case Key.isProxyApps => updateSwitchPreference(pref, profile.proxyApps)
|
||||
case Key.isUdpDns => updateSwitchPreference(pref, profile.udpdns)
|
||||
case Key.isAuth => updateSwitchPreference(pref, profile.auth)
|
||||
case Key.isIpv6 => updateSwitchPreference(pref, profile.ipv6)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Shadowsocks
|
||||
@@ -149,9 +104,10 @@ class Shadowsocks
|
||||
var progressDialog: ProgressDialog = _
|
||||
var progressTag = -1
|
||||
var state = State.STOPPED
|
||||
var prepared = false
|
||||
var currentProfile = new Profile
|
||||
var vpnEnabled = -1
|
||||
var trafficCache: String = _
|
||||
var connectionTestResult: String = _
|
||||
|
||||
// Services
|
||||
var currentServiceName = classOf[ShadowsocksNatService].getName
|
||||
@@ -164,20 +120,20 @@ class Shadowsocks
|
||||
fab.setImageResource(R.drawable.ic_cloud_queue)
|
||||
fab.setEnabled(false)
|
||||
fabProgressCircle.show()
|
||||
setPreferenceEnabled(enabled = false)
|
||||
preferences.setEnabled(false)
|
||||
case State.CONNECTED =>
|
||||
fab.setBackgroundTintList(greenTint)
|
||||
if (state == State.CONNECTING) {
|
||||
fabProgressCircle.beginFinalAnimation()
|
||||
} else {
|
||||
handler.postDelayed(() => fabProgressCircle.hide(), 1000)
|
||||
fabProgressCircle.postDelayed(hideCircle, 1000)
|
||||
}
|
||||
fab.setEnabled(true)
|
||||
changeSwitch(checked = true)
|
||||
setPreferenceEnabled(enabled = false)
|
||||
preferences.setEnabled(false)
|
||||
case State.STOPPED =>
|
||||
fab.setBackgroundTintList(greyTint)
|
||||
handler.postDelayed(() => fabProgressCircle.hide(), 1000)
|
||||
fabProgressCircle.postDelayed(hideCircle, 1000)
|
||||
fab.setEnabled(true)
|
||||
changeSwitch(checked = false)
|
||||
if (m != null) {
|
||||
@@ -187,23 +143,31 @@ class Shadowsocks
|
||||
(_ => preferences.natSwitch.setChecked(false)): View.OnClickListener)
|
||||
snackbar.show
|
||||
}
|
||||
setPreferenceEnabled(enabled = true)
|
||||
preferences.setEnabled(true)
|
||||
case State.STOPPING =>
|
||||
fab.setBackgroundTintList(greyTint)
|
||||
fab.setImageResource(R.drawable.ic_cloud_queue)
|
||||
fab.setEnabled(false)
|
||||
if (state == State.CONNECTED) fabProgressCircle.show() // ignore for stopped
|
||||
setPreferenceEnabled(enabled = false)
|
||||
preferences.setEnabled(false)
|
||||
}
|
||||
state = s
|
||||
})
|
||||
}
|
||||
def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) {
|
||||
val trafficStat = getString(R.string.stat_summary)
|
||||
.formatLocal(Locale.ENGLISH, txRate, rxRate, txTotal, rxTotal)
|
||||
handler.post(() => {
|
||||
preferences.findPreference(Key.stat).setSummary(trafficStat)
|
||||
})
|
||||
def trafficUpdated(txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
|
||||
trafficCache = getString(R.string.stat_summary).formatLocal(Locale.ENGLISH,
|
||||
TrafficMonitor.formatTraffic(txRate), TrafficMonitor.formatTraffic(rxRate),
|
||||
TrafficMonitor.formatTraffic(txTotal), TrafficMonitor.formatTraffic(rxTotal))
|
||||
handler.post(updateTraffic)
|
||||
}
|
||||
}
|
||||
|
||||
def updateTraffic(): Unit = if (trafficCache == null) callback.trafficUpdated(0, 0, 0, 0) else {
|
||||
if (connectionTestResult == null) connectionTestResult = getString(R.string.connection_test_pending)
|
||||
if (preferences.natSwitch.isChecked) {
|
||||
preferences.stat.setSummary(trafficCache)
|
||||
} else {
|
||||
preferences.stat.setSummary(connectionTestResult + "\n" + trafficCache)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +176,7 @@ class Shadowsocks
|
||||
override def onServiceConnected() {
|
||||
// Update the UI
|
||||
if (fab != null) fab.setEnabled(true)
|
||||
stateUpdate()
|
||||
updateState()
|
||||
|
||||
if (!ShadowsocksApplication.settings.getBoolean(ShadowsocksApplication.getVersionName, false)) {
|
||||
ShadowsocksApplication.settings.edit.putBoolean(ShadowsocksApplication.getVersionName, true).apply()
|
||||
@@ -246,7 +210,7 @@ class Shadowsocks
|
||||
getFragmentManager.findFragmentById(android.R.id.content).asInstanceOf[ShadowsocksSettings]
|
||||
private var adView: AdView = _
|
||||
private lazy val greyTint = ContextCompat.getColorStateList(this, R.color.material_blue_grey_700)
|
||||
private lazy val greenTint = ContextCompat.getColorStateList(this, R.color.material_green_a700)
|
||||
private lazy val greenTint = ContextCompat.getColorStateList(this, R.color.material_green_700)
|
||||
|
||||
var handler = new Handler()
|
||||
|
||||
@@ -358,12 +322,7 @@ class Shadowsocks
|
||||
changeSwitch(checked = false)
|
||||
}
|
||||
|
||||
def isReady: Boolean = {
|
||||
if (!checkText(Key.proxy)) return false
|
||||
if (!checkText(Key.sitekey)) return false
|
||||
if (bgService == null) return false
|
||||
true
|
||||
}
|
||||
def isReady: Boolean = checkText(Key.proxy) && checkText(Key.sitekey) && bgService != null
|
||||
|
||||
def prepareStartService() {
|
||||
ThrowableFuture {
|
||||
@@ -429,9 +388,10 @@ class Shadowsocks
|
||||
currentProfile = ShadowsocksApplication.currentProfile match {
|
||||
case Some(profile) => profile // updated
|
||||
case None => // removed
|
||||
val profiles = ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List[Profile]())
|
||||
if (profiles.isEmpty) ShadowsocksApplication.profileManager.createDefault()
|
||||
else ShadowsocksApplication.switchProfile(profiles.head.id)
|
||||
ShadowsocksApplication.profileManager.getFirstProfile match {
|
||||
case Some(first) => ShadowsocksApplication.switchProfile(first.id)
|
||||
case None => ShadowsocksApplication.profileManager.createDefault()
|
||||
}
|
||||
}
|
||||
|
||||
updatePreferenceScreen()
|
||||
@@ -442,36 +402,43 @@ class Shadowsocks
|
||||
protected override def onPause() {
|
||||
super.onPause()
|
||||
ShadowsocksApplication.profileManager.save
|
||||
prepared = false
|
||||
}
|
||||
|
||||
private def stateUpdate() {
|
||||
private def hideCircle() {
|
||||
try {
|
||||
fabProgressCircle.hide()
|
||||
} catch {
|
||||
case _: java.lang.NullPointerException => // Ignore
|
||||
}
|
||||
}
|
||||
|
||||
private def updateState() {
|
||||
if (bgService != null) {
|
||||
bgService.getState match {
|
||||
case State.CONNECTING =>
|
||||
fab.setBackgroundTintList(greyTint)
|
||||
serviceStarted = false
|
||||
fab.setImageResource(R.drawable.ic_cloud_queue)
|
||||
setPreferenceEnabled(false)
|
||||
preferences.setEnabled(false)
|
||||
fabProgressCircle.show()
|
||||
case State.CONNECTED =>
|
||||
fab.setBackgroundTintList(greenTint)
|
||||
serviceStarted = true
|
||||
fab.setImageResource(R.drawable.ic_cloud)
|
||||
setPreferenceEnabled(false)
|
||||
fabProgressCircle.postDelayed(fabProgressCircle.hide, 100)
|
||||
preferences.setEnabled(false)
|
||||
fabProgressCircle.postDelayed(hideCircle, 100)
|
||||
case State.STOPPING =>
|
||||
fab.setBackgroundTintList(greyTint)
|
||||
serviceStarted = false
|
||||
fab.setImageResource(R.drawable.ic_cloud_queue)
|
||||
setPreferenceEnabled(false)
|
||||
preferences.setEnabled(false)
|
||||
fabProgressCircle.show()
|
||||
case _ =>
|
||||
fab.setBackgroundTintList(greyTint)
|
||||
serviceStarted = false
|
||||
fab.setImageResource(R.drawable.ic_cloud_off)
|
||||
setPreferenceEnabled(true)
|
||||
fabProgressCircle.postDelayed(fabProgressCircle.hide, 100)
|
||||
preferences.setEnabled(true)
|
||||
fabProgressCircle.postDelayed(hideCircle, 100)
|
||||
}
|
||||
state = bgService.getState
|
||||
}
|
||||
@@ -479,63 +446,43 @@ class Shadowsocks
|
||||
|
||||
protected override def onResume() {
|
||||
super.onResume()
|
||||
stateUpdate()
|
||||
ConfigUtils.refresh(this)
|
||||
|
||||
// Check if current profile changed
|
||||
if (ShadowsocksApplication.profileId != currentProfile.id) reloadProfile()
|
||||
|
||||
callback.trafficUpdated(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate,
|
||||
TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal)
|
||||
}
|
||||
|
||||
private def setPreferenceEnabled(enabled: Boolean) {
|
||||
preferences.findPreference(Key.isNAT).setEnabled(enabled)
|
||||
for (name <- Shadowsocks.PROXY_PREFS) {
|
||||
val pref = preferences.findPreference(name)
|
||||
if (pref != null) {
|
||||
pref.setEnabled(enabled)
|
||||
}
|
||||
}
|
||||
for (name <- Shadowsocks.FEATURE_PREFS) {
|
||||
val pref = preferences.findPreference(name)
|
||||
if (pref != null) {
|
||||
if (name == Key.isProxyApps) {
|
||||
pref.setEnabled(enabled && (Utils.isLollipopOrAbove || !ShadowsocksApplication.isVpnEnabled))
|
||||
} else {
|
||||
pref.setEnabled(enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
updateTraffic()
|
||||
updateState()
|
||||
}
|
||||
|
||||
private def updatePreferenceScreen() {
|
||||
val profile = currentProfile
|
||||
if (profile.host == "198.199.101.152" && adView == null) {
|
||||
if (profile.host == "198.199.101.152") if (adView == null) {
|
||||
adView = new AdView(this)
|
||||
adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322")
|
||||
adView.setAdSize(AdSize.SMART_BANNER)
|
||||
preferences.getView.asInstanceOf[ViewGroup].addView(adView, 0)
|
||||
adView.loadAd(new AdRequest.Builder().build())
|
||||
}
|
||||
} else adView.setVisibility(View.VISIBLE) else if (adView != null) adView.setVisibility(View.GONE)
|
||||
|
||||
for (name <- Shadowsocks.PROXY_PREFS) {
|
||||
val pref = preferences.findPreference(name)
|
||||
Shadowsocks.updatePreference(pref, name, profile)
|
||||
}
|
||||
for (name <- Shadowsocks.FEATURE_PREFS) {
|
||||
val pref = preferences.findPreference(name)
|
||||
Shadowsocks.updatePreference(pref, name, profile)
|
||||
}
|
||||
preferences.update(profile)
|
||||
}
|
||||
|
||||
override def onStart() {
|
||||
super.onStart()
|
||||
registerCallback
|
||||
}
|
||||
override def onStop() {
|
||||
super.onStop()
|
||||
unregisterCallback
|
||||
clearDialog()
|
||||
}
|
||||
|
||||
private var _isDestroyed: Boolean = _
|
||||
override def isDestroyed = if (Build.VERSION.SDK_INT >= 17) super.isDestroyed else _isDestroyed
|
||||
override def onDestroy() {
|
||||
super.onDestroy()
|
||||
_isDestroyed = true
|
||||
deattachService()
|
||||
new BackupManager(this).dataChanged()
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
@@ -577,7 +524,6 @@ class Shadowsocks
|
||||
|
||||
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) = resultCode match {
|
||||
case Activity.RESULT_OK =>
|
||||
prepared = true
|
||||
serviceStart()
|
||||
case _ =>
|
||||
cancelStart()
|
||||
@@ -605,8 +551,8 @@ class Shadowsocks
|
||||
}
|
||||
|
||||
def clearDialog() {
|
||||
if (progressDialog != null) {
|
||||
progressDialog.dismiss()
|
||||
if (progressDialog != null && progressDialog.isShowing) {
|
||||
if (!isDestroyed) progressDialog.dismiss()
|
||||
progressDialog = null
|
||||
progressTag = -1
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import java.util.concurrent.TimeUnit
|
||||
import android.app.Application
|
||||
import android.content.pm.PackageManager
|
||||
import android.preference.PreferenceManager
|
||||
import com.j256.ormlite.logger.LocalLog
|
||||
import com.github.shadowsocks.database.{DBHelper, ProfileManager}
|
||||
import com.github.shadowsocks.utils.{Key, Utils}
|
||||
import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders}
|
||||
@@ -89,6 +90,7 @@ class ShadowsocksApplication extends Application {
|
||||
import ShadowsocksApplication._
|
||||
|
||||
override def onCreate() {
|
||||
java.lang.System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR");
|
||||
ShadowsocksApplication.instance = this
|
||||
val tm = TagManager.getInstance(this)
|
||||
val pending = tm.loadContainerPreferNonDefault("GTM-NT8WS8", R.raw.gtm_default_container)
|
||||
|
||||
@@ -44,6 +44,7 @@ import java.lang.Process
|
||||
import java.net.{Inet6Address, InetAddress}
|
||||
import java.util.Locale
|
||||
|
||||
import android.app.Service
|
||||
import android.content._
|
||||
import android.content.pm.{PackageInfo, PackageManager}
|
||||
import android.net.{ConnectivityManager, Network}
|
||||
@@ -331,6 +332,8 @@ class ShadowsocksNatService extends BaseService {
|
||||
ConfigUtils.refresh(this)
|
||||
}
|
||||
|
||||
override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = Service.START_STICKY
|
||||
|
||||
def killProcesses() {
|
||||
if (sslocalProcess != null) {
|
||||
sslocalProcess.destroy()
|
||||
@@ -368,7 +371,7 @@ class ShadowsocksNatService extends BaseService {
|
||||
init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "--dport 53"))
|
||||
|
||||
init_sb.append(Utils.getIptables
|
||||
+ " -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:" + 8153)
|
||||
+ " -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:8153")
|
||||
|
||||
if (!config.isProxyApps || config.isBypassApps) {
|
||||
http_sb.append(Utils.getIptables + CMD_IPTABLES_DNAT_ADD_SOCKS)
|
||||
@@ -458,8 +461,6 @@ class ShadowsocksNatService extends BaseService {
|
||||
|
||||
override def stopRunner() {
|
||||
|
||||
super.stopRunner()
|
||||
|
||||
// channge the state
|
||||
changeState(State.STOPPING)
|
||||
|
||||
@@ -476,17 +477,7 @@ class ShadowsocksNatService extends BaseService {
|
||||
// reset NAT
|
||||
killProcesses()
|
||||
|
||||
// stop the service if no callback registered
|
||||
if (getCallbackCount == 0) {
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
// change the state
|
||||
changeState(State.STOPPED)
|
||||
}
|
||||
|
||||
override def stopBackgroundService() {
|
||||
stopSelf()
|
||||
super.stopRunner()
|
||||
}
|
||||
|
||||
override def getTag = TAG
|
||||
|
||||
@@ -2,31 +2,35 @@ package com.github.shadowsocks
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
import android.app.{KeyguardManager, PendingIntent}
|
||||
import android.app.{KeyguardManager, NotificationManager, PendingIntent}
|
||||
import android.content.{BroadcastReceiver, Context, Intent, IntentFilter}
|
||||
import android.os.PowerManager
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.support.v4.app.NotificationCompat.BigTextStyle
|
||||
import android.support.v4.content.ContextCompat
|
||||
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback.Stub
|
||||
import com.github.shadowsocks.utils.{Action, State, Utils}
|
||||
import com.github.shadowsocks.utils.{TrafficMonitor, Action, State, Utils}
|
||||
|
||||
/**
|
||||
* @author Mygod
|
||||
*/
|
||||
class ShadowsocksNotification(private val service: BaseService, profileName: String, visible: Boolean = false) {
|
||||
private lazy val keyGuard = service.getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager]
|
||||
private val keyGuard = service.getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager]
|
||||
private lazy val nm = service.getSystemService(Context.NOTIFICATION_SERVICE).asInstanceOf[NotificationManager]
|
||||
private lazy val callback = new Stub {
|
||||
override def stateChanged(state: Int, msg: String) = () // ignore
|
||||
override def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) {
|
||||
builder.setContentText(service.getString(R.string.traffic_summary).formatLocal(Locale.ENGLISH, txRate, rxRate))
|
||||
style.bigText(service.getString(R.string.stat_summary)
|
||||
.formatLocal(Locale.ENGLISH, txRate, rxRate, txTotal, rxTotal))
|
||||
override def trafficUpdated(txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
|
||||
val txr = TrafficMonitor.formatTraffic(txRate)
|
||||
val rxr = TrafficMonitor.formatTraffic(rxRate)
|
||||
builder.setContentText(service.getString(R.string.traffic_summary).formatLocal(Locale.ENGLISH, txr, rxr))
|
||||
style.bigText(service.getString(R.string.stat_summary).formatLocal(Locale.ENGLISH, txr, rxr,
|
||||
TrafficMonitor.formatTraffic(txTotal), TrafficMonitor.formatTraffic(rxTotal)))
|
||||
show()
|
||||
}
|
||||
}
|
||||
private var lockReceiver: BroadcastReceiver = _
|
||||
private var callbackRegistered: Boolean = _
|
||||
private var started: Boolean = _
|
||||
|
||||
private val builder = new NotificationCompat.Builder(service)
|
||||
.setWhen(0)
|
||||
@@ -73,8 +77,10 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
|
||||
show()
|
||||
} else if (forceShow) show()
|
||||
|
||||
def notification = builder.build()
|
||||
def show() = service.startForeground(1, notification)
|
||||
def show() = if (started) nm.notify(1, builder.build) else {
|
||||
service.startForeground(1, builder.build)
|
||||
started = true
|
||||
}
|
||||
|
||||
def destroy() {
|
||||
if (lockReceiver != null) {
|
||||
@@ -83,5 +89,6 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
|
||||
}
|
||||
unregisterCallback
|
||||
service.stopForeground(true)
|
||||
nm.cancel(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,11 +43,10 @@ import android.app.{Activity, KeyguardManager}
|
||||
import android.content.{Intent, IntentFilter, Context, BroadcastReceiver}
|
||||
import android.net.VpnService
|
||||
import android.os.{Bundle, Handler}
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.util.Log
|
||||
import com.github.shadowsocks.utils.ConfigUtils
|
||||
|
||||
class ShadowsocksRunnerActivity extends AppCompatActivity with ServiceBoundContext {
|
||||
class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
|
||||
val handler = new Handler()
|
||||
|
||||
// Variables
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.github.shadowsocks
|
||||
|
||||
import java.lang.System.currentTimeMillis
|
||||
import java.net.{HttpURLConnection, URL}
|
||||
import java.util.Locale
|
||||
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
@@ -9,24 +11,117 @@ import android.os.{Build, Bundle}
|
||||
import android.preference.{Preference, PreferenceFragment, SwitchPreference}
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.webkit.{WebView, WebViewClient}
|
||||
import com.github.shadowsocks.utils.Key
|
||||
import com.github.shadowsocks.database.Profile
|
||||
import com.github.shadowsocks.preferences.{DropDownPreference, NumberPickerPreference, PasswordEditTextPreference, SummaryEditTextPreference}
|
||||
import com.github.shadowsocks.utils.CloseUtils._
|
||||
import com.github.shadowsocks.utils.{Key, Utils}
|
||||
|
||||
object ShadowsocksSettings {
|
||||
// Constants
|
||||
val PREFS_NAME = "Shadowsocks"
|
||||
val PROXY_PREFS = Array(Key.profileName, Key.proxy, Key.remotePort, Key.localPort, Key.sitekey, Key.encMethod,
|
||||
Key.isAuth)
|
||||
val FEATURE_PREFS = Array(Key.route, Key.isProxyApps, Key.isUdpDns, Key.isIpv6)
|
||||
|
||||
// Helper functions
|
||||
def updateDropDownPreference(pref: Preference, value: String) {
|
||||
pref.asInstanceOf[DropDownPreference].setValue(value)
|
||||
}
|
||||
|
||||
def updatePasswordEditTextPreference(pref: Preference, value: String) {
|
||||
pref.setSummary(value)
|
||||
pref.asInstanceOf[PasswordEditTextPreference].setText(value)
|
||||
}
|
||||
|
||||
def updateNumberPickerPreference(pref: Preference, value: Int) {
|
||||
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) {
|
||||
pref.asInstanceOf[SwitchPreference].setChecked(value)
|
||||
}
|
||||
|
||||
def updatePreference(pref: Preference, name: String, profile: Profile) {
|
||||
name match {
|
||||
case Key.profileName => updateSummaryEditTextPreference(pref, profile.name)
|
||||
case Key.proxy => updateSummaryEditTextPreference(pref, profile.host)
|
||||
case Key.remotePort => updateNumberPickerPreference(pref, profile.remotePort)
|
||||
case Key.localPort => updateNumberPickerPreference(pref, profile.localPort)
|
||||
case Key.sitekey => updatePasswordEditTextPreference(pref, profile.password)
|
||||
case Key.encMethod => updateDropDownPreference(pref, profile.method)
|
||||
case Key.route => updateDropDownPreference(pref, profile.route)
|
||||
case Key.isProxyApps => updateSwitchPreference(pref, profile.proxyApps)
|
||||
case Key.isUdpDns => updateSwitchPreference(pref, profile.udpdns)
|
||||
case Key.isAuth => updateSwitchPreference(pref, profile.auth)
|
||||
case Key.isIpv6 => updateSwitchPreference(pref, profile.ipv6)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Move related logic here
|
||||
class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChangeListener {
|
||||
import ShadowsocksSettings._
|
||||
|
||||
private def activity = getActivity.asInstanceOf[Shadowsocks]
|
||||
lazy val natSwitch = findPreference(Key.isNAT).asInstanceOf[SwitchPreference]
|
||||
var stat: Preference = _
|
||||
|
||||
private var isProxyApps: SwitchPreference = _
|
||||
private var testCount: Int = _
|
||||
|
||||
override def onCreate(savedInstanceState: Bundle) {
|
||||
super.onCreate(savedInstanceState)
|
||||
addPreferencesFromResource(R.xml.pref_all)
|
||||
getPreferenceManager.getSharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
|
||||
stat = findPreference(Key.stat)
|
||||
stat.setOnPreferenceClickListener(_ => {
|
||||
val id = synchronized {
|
||||
testCount += 1
|
||||
activity.connectionTestResult = getString(R.string.connection_test_testing)
|
||||
activity.updateTraffic()
|
||||
testCount
|
||||
}
|
||||
ThrowableFuture {
|
||||
// Based on: https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/connectivity/NetworkMonitor.java#640
|
||||
autoDisconnect(new URL("https", "www.google.com", "/generate_204").openConnection()
|
||||
.asInstanceOf[HttpURLConnection]) { conn =>
|
||||
conn.setConnectTimeout(5 * 1000)
|
||||
conn.setReadTimeout(5 * 1000)
|
||||
conn.setInstanceFollowRedirects(false)
|
||||
conn.setUseCaches(false)
|
||||
if (testCount == id) {
|
||||
var result: String = null
|
||||
try {
|
||||
val start = currentTimeMillis
|
||||
conn.getInputStream
|
||||
val elapsed = currentTimeMillis - start
|
||||
val code = conn.getResponseCode
|
||||
if (code == 204 || code == 200 && conn.getContentLength == 0)
|
||||
result = getString(R.string.connection_test_available, elapsed: java.lang.Long)
|
||||
else throw new Exception(getString(R.string.connection_test_error_status_code, code: Integer))
|
||||
} catch {
|
||||
case e: Exception => result = getString(R.string.connection_test_error, e.getMessage)
|
||||
}
|
||||
synchronized(if (testCount == id) {
|
||||
activity.connectionTestResult = result
|
||||
activity.handler.post(activity.updateTraffic)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
})
|
||||
|
||||
isProxyApps = findPreference(Key.isProxyApps).asInstanceOf[SwitchPreference]
|
||||
isProxyApps.setOnPreferenceChangeListener((preference: Preference, newValue: Any) => {
|
||||
isProxyApps.setOnPreferenceClickListener((preference: Preference) => {
|
||||
startActivity(new Intent(activity, classOf[AppManager]))
|
||||
newValue.asInstanceOf[Boolean] // keep it ON
|
||||
isProxyApps.setChecked(true)
|
||||
false
|
||||
})
|
||||
|
||||
findPreference("recovery").setOnPreferenceClickListener((preference: Preference) => {
|
||||
@@ -49,7 +144,11 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
|
||||
web.loadUrl("file:///android_asset/pages/about.html")
|
||||
web.setWebViewClient(new WebViewClient() {
|
||||
override def shouldOverrideUrlLoading(view: WebView, url: String): Boolean = {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
try {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
} catch {
|
||||
case _: android.content.ActivityNotFoundException => // Ignore
|
||||
}
|
||||
true
|
||||
}
|
||||
})
|
||||
@@ -67,10 +166,15 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
|
||||
}
|
||||
|
||||
override def onResume {
|
||||
super.onResume
|
||||
super.onResume()
|
||||
isProxyApps.setChecked(ShadowsocksApplication.settings.getBoolean(Key.isProxyApps, false)) // update
|
||||
}
|
||||
|
||||
override def onDestroy {
|
||||
super.onDestroy()
|
||||
ShadowsocksApplication.settings.unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) = key match {
|
||||
case Key.isNAT => activity.handler.post(() => {
|
||||
activity.deattachService
|
||||
@@ -78,4 +182,30 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
|
||||
})
|
||||
case _ =>
|
||||
}
|
||||
|
||||
private var enabled = true
|
||||
def setEnabled(enabled: Boolean) {
|
||||
this.enabled = enabled
|
||||
findPreference(Key.isNAT).setEnabled(enabled)
|
||||
for (name <- PROXY_PREFS) {
|
||||
val pref = findPreference(name)
|
||||
if (pref != null) pref.setEnabled(enabled)
|
||||
}
|
||||
for (name <- FEATURE_PREFS) {
|
||||
val pref = findPreference(name)
|
||||
if (pref != null) pref.setEnabled(enabled &&
|
||||
(name != Key.isProxyApps || Utils.isLollipopOrAbove || !ShadowsocksApplication.isVpnEnabled))
|
||||
}
|
||||
}
|
||||
|
||||
def update(profile: Profile) {
|
||||
for (name <- PROXY_PREFS) {
|
||||
val pref = findPreference(name)
|
||||
updatePreference(pref, name, profile)
|
||||
}
|
||||
for (name <- FEATURE_PREFS) {
|
||||
val pref = findPreference(name)
|
||||
updatePreference(pref, name, profile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@ import android.net.VpnService
|
||||
import android.os._
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import android.content.pm.PackageManager.NameNotFoundException
|
||||
|
||||
import com.github.shadowsocks.aidl.Config
|
||||
import com.github.shadowsocks.utils._
|
||||
import org.apache.commons.net.util.SubnetUtils
|
||||
@@ -113,8 +115,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
|
||||
override def stopRunner() {
|
||||
|
||||
super.stopRunner()
|
||||
|
||||
if (vpnThread != null) {
|
||||
vpnThread.stopThread()
|
||||
vpnThread = null
|
||||
@@ -136,19 +136,13 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
conn = null
|
||||
}
|
||||
|
||||
// stop the service if no callback registered
|
||||
if (getCallbackCount == 0) {
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
// clean up recevier
|
||||
if (closeReceiver != null) {
|
||||
unregisterReceiver(closeReceiver)
|
||||
closeReceiver = null
|
||||
}
|
||||
|
||||
// channge the state
|
||||
changeState(State.STOPPED)
|
||||
super.stopRunner()
|
||||
}
|
||||
|
||||
def getVersionName: String = {
|
||||
@@ -389,10 +383,15 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
|
||||
if (config.isProxyApps) {
|
||||
for (pkg <- config.proxiedAppString.split('\n')) {
|
||||
if (!config.isBypassApps) {
|
||||
builder.addAllowedApplication(pkg)
|
||||
} else {
|
||||
builder.addDisallowedApplication(pkg)
|
||||
try {
|
||||
if (!config.isBypassApps) {
|
||||
builder.addAllowedApplication(pkg)
|
||||
} else {
|
||||
builder.addDisallowedApplication(pkg)
|
||||
}
|
||||
} catch {
|
||||
case ex: NameNotFoundException =>
|
||||
Log.e(TAG, "Invalid package name", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -458,10 +457,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
fd
|
||||
}
|
||||
|
||||
override def stopBackgroundService() {
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override def getTag = TAG
|
||||
|
||||
override def getServiceMode = Mode.VPN
|
||||
|
||||
@@ -45,7 +45,12 @@ import java.util.concurrent.Executors
|
||||
import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress}
|
||||
import android.util.Log
|
||||
|
||||
object ShadowsocksVpnThread {
|
||||
val getInt = classOf[FileDescriptor].getDeclaredMethod("getInt$")
|
||||
}
|
||||
|
||||
class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread {
|
||||
import ShadowsocksVpnThread._
|
||||
|
||||
val TAG = "ShadowsocksVpnService"
|
||||
val PATH = "/data/data/com.github.shadowsocks/protect_path"
|
||||
@@ -103,11 +108,8 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread {
|
||||
val fds = socket.getAncillaryFileDescriptors
|
||||
|
||||
if (fds.nonEmpty) {
|
||||
var ret = false
|
||||
|
||||
val getInt = classOf[FileDescriptor].getDeclaredMethod("getInt$")
|
||||
val fd = getInt.invoke(fds(0)).asInstanceOf[Int]
|
||||
ret = vpnService.protect(fd)
|
||||
val ret = vpnService.protect(fd)
|
||||
|
||||
// Trick to close file decriptor
|
||||
System.jniclose(fd)
|
||||
|
||||
@@ -64,7 +64,7 @@ object DBHelper {
|
||||
}
|
||||
|
||||
class DBHelper(val context: Context)
|
||||
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 14) {
|
||||
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 15) {
|
||||
import DBHelper._
|
||||
|
||||
lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile])
|
||||
@@ -110,10 +110,15 @@ class DBHelper(val context: Context)
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN rx LONG;")
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN date VARCHAR;")
|
||||
}
|
||||
if (oldVersion < 14) {
|
||||
|
||||
if (oldVersion < 15) {
|
||||
if (oldVersion >= 12) profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN userOrder LONG;")
|
||||
var i = 0
|
||||
for (profile <- profileDao.queryForAll.asScala) {
|
||||
profile.individual = updateProxiedApps(context, profile.individual)
|
||||
if (oldVersion < 14) profile.individual = updateProxiedApps(context, profile.individual)
|
||||
profile.userOrder = i
|
||||
profileDao.update(profile)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,13 +55,13 @@ class Profile {
|
||||
var localPort: Int = 1080
|
||||
|
||||
@DatabaseField
|
||||
var remotePort: Int = 8338
|
||||
var remotePort: Int = 8388
|
||||
|
||||
@DatabaseField
|
||||
var password: String = ""
|
||||
|
||||
@DatabaseField
|
||||
var method: String = "rc4"
|
||||
var method: String = "aes-256-cfb"
|
||||
|
||||
@DatabaseField
|
||||
var route: String = "all"
|
||||
@@ -92,4 +92,7 @@ class Profile {
|
||||
|
||||
@DatabaseField
|
||||
val date: java.util.Date = new java.util.Date()
|
||||
|
||||
@DatabaseField
|
||||
var userOrder: Long = _
|
||||
}
|
||||
|
||||
@@ -64,6 +64,9 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
|
||||
profile.udpdns = oldProfile.udpdns
|
||||
case _ =>
|
||||
}
|
||||
val last = dbHelper.profileDao.queryRaw(dbHelper.profileDao.queryBuilder.selectRaw("MAX(userOrder)")
|
||||
.prepareStatementString).getFirstResult
|
||||
if (last != null && last.length == 1 && last(0) != null) profile.userOrder = last(0).toInt + 1
|
||||
dbHelper.profileDao.createOrUpdate(profile)
|
||||
if (profileAddedListener != null) profileAddedListener(profile)
|
||||
profile
|
||||
@@ -109,10 +112,21 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
|
||||
}
|
||||
}
|
||||
|
||||
def getFirstProfile = {
|
||||
try {
|
||||
val result = dbHelper.profileDao.query(dbHelper.profileDao.queryBuilder.limit(1L).prepare)
|
||||
if (result.size == 1) Option(result.get(0)) else None
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(Shadowsocks.TAG, "getAllProfiles", ex)
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def getAllProfiles: Option[List[Profile]] = {
|
||||
try {
|
||||
import scala.collection.JavaConversions._
|
||||
Option(dbHelper.profileDao.queryForAll().toList)
|
||||
Option(dbHelper.profileDao.query(dbHelper.profileDao.queryBuilder.orderBy("userOrder", true).prepare).toList)
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(Shadowsocks.TAG, "getAllProfiles", ex)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.github.shadowsocks.preferences
|
||||
|
||||
import java.util.Locale
|
||||
|
||||
import android.preference.Preference
|
||||
|
||||
/**
|
||||
@@ -15,5 +17,5 @@ trait SummaryPreference extends Preference {
|
||||
*
|
||||
* @return the summary with appropriate string substitution
|
||||
*/
|
||||
override def getSummary = String.format(super.getSummary.toString, getSummaryValue)
|
||||
override def getSummary = super.getSummary.toString.formatLocal(Locale.ENGLISH, getSummaryValue)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.github.shadowsocks.utils
|
||||
|
||||
/**
|
||||
* @author Mygod
|
||||
*/
|
||||
object CloseUtils {
|
||||
type Disconnectable = {
|
||||
def disconnect()
|
||||
}
|
||||
|
||||
def autoClose[A <: AutoCloseable, B](x: => A)(block: A => B): B = {
|
||||
var a: Option[A] = None
|
||||
try {
|
||||
a = Some(x)
|
||||
block(a.get)
|
||||
} finally if (a.nonEmpty) try {
|
||||
val v = a.get
|
||||
if (v ne null) v.close
|
||||
} catch {
|
||||
case ignore: Exception =>
|
||||
}
|
||||
}
|
||||
def autoDisconnect[A <: Disconnectable, B](x: => A)(block: A => B): B = {
|
||||
var a: Option[A] = None
|
||||
try {
|
||||
a = Some(x)
|
||||
block(a.get)
|
||||
} finally if (a.nonEmpty) try {
|
||||
val v = a.get
|
||||
if (v ne null) v.disconnect
|
||||
} catch {
|
||||
case ignore: Exception =>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,26 +5,20 @@ import java.text.DecimalFormat
|
||||
|
||||
import com.github.shadowsocks.{R, ShadowsocksApplication}
|
||||
|
||||
case class Traffic(tx: Long, rx: Long, timestamp: Long)
|
||||
|
||||
object TrafficMonitor {
|
||||
var last: Traffic = getTraffic(0, 0)
|
||||
|
||||
// Bytes per second
|
||||
var txRate: Long = 0
|
||||
var rxRate: Long = 0
|
||||
var txRate: Long = _
|
||||
var rxRate: Long = _
|
||||
|
||||
// Bytes for the current session
|
||||
var txTotal: Long = 0
|
||||
var rxTotal: Long = 0
|
||||
var txTotal: Long = _
|
||||
var rxTotal: Long = _
|
||||
|
||||
// Bytes for the last query
|
||||
var txLast: Long = 0
|
||||
var rxLast: Long = 0
|
||||
|
||||
def getTraffic(tx: Long, rx: Long): Traffic = {
|
||||
new Traffic(tx, rx, System.currentTimeMillis())
|
||||
}
|
||||
var txLast: Long = _
|
||||
var rxLast: Long = _
|
||||
var timestampLast: Long = _
|
||||
@volatile var dirty = true
|
||||
|
||||
private val units = Array("KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "NB", "DB", "CB")
|
||||
private val numberFormat = new DecimalFormat("@@@")
|
||||
@@ -39,21 +33,42 @@ object TrafficMonitor {
|
||||
else numberFormat.format(n) + ' ' + units(i)
|
||||
}
|
||||
|
||||
def updateRate() {
|
||||
val now = getTraffic(txTotal, rxTotal)
|
||||
val delta = now.timestamp - last.timestamp
|
||||
val deltaTx = now.tx - last.tx
|
||||
val deltaRx = now.rx - last.rx
|
||||
def updateRate() = {
|
||||
val now = System.currentTimeMillis()
|
||||
val delta = now - timestampLast
|
||||
var updated = false
|
||||
if (delta != 0) {
|
||||
txRate = deltaTx * 1000 / delta
|
||||
rxRate = deltaRx * 1000 / delta
|
||||
if (dirty) {
|
||||
txRate = (txTotal - txLast) * 1000 / delta
|
||||
rxRate = (rxTotal - rxLast) * 1000 / delta
|
||||
txLast = txTotal
|
||||
rxLast = rxTotal
|
||||
dirty = false
|
||||
updated = true
|
||||
} else {
|
||||
if (txRate != 0) {
|
||||
txRate = 0
|
||||
updated = true
|
||||
}
|
||||
if (rxRate != 0) {
|
||||
rxRate = 0
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
timestampLast = now
|
||||
}
|
||||
last = now
|
||||
updated
|
||||
}
|
||||
|
||||
def update(tx: Long, rx: Long) {
|
||||
txTotal = tx
|
||||
rxTotal = rx
|
||||
if (txTotal != tx) {
|
||||
txTotal = tx
|
||||
dirty = true
|
||||
}
|
||||
if (rxTotal != rx) {
|
||||
rxTotal = rx
|
||||
dirty = true
|
||||
}
|
||||
}
|
||||
|
||||
def reset() {
|
||||
@@ -63,43 +78,7 @@ object TrafficMonitor {
|
||||
rxTotal = 0
|
||||
txLast = 0
|
||||
rxLast = 0
|
||||
last = getTraffic(0, 0)
|
||||
}
|
||||
|
||||
def getTxTotal(): String = {
|
||||
formatTraffic(txTotal)
|
||||
}
|
||||
|
||||
def getRxTotal(): String = {
|
||||
formatTraffic(rxTotal)
|
||||
}
|
||||
|
||||
def getTotal(): String = {
|
||||
formatTraffic(txTotal + rxTotal)
|
||||
}
|
||||
|
||||
def getTxRate(): String = {
|
||||
formatTraffic(txRate)
|
||||
}
|
||||
|
||||
def getRxRate(): String = {
|
||||
formatTraffic(rxRate)
|
||||
}
|
||||
|
||||
def getRate(): String = {
|
||||
formatTraffic(txRate + rxRate)
|
||||
}
|
||||
|
||||
def getDeltaTx(): Long = {
|
||||
val last = txLast
|
||||
txLast = txTotal
|
||||
txTotal - last
|
||||
}
|
||||
|
||||
def getDeltaRx(): Long = {
|
||||
val last = rxLast
|
||||
rxLast = rxTotal
|
||||
rxTotal - last
|
||||
dirty = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,11 +40,11 @@
|
||||
package com.github.shadowsocks.utils
|
||||
|
||||
import java.io.{File, IOException}
|
||||
import java.nio.{ByteBuffer, ByteOrder}
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress}
|
||||
import android.util.Log
|
||||
import com.github.shadowsocks.BaseService
|
||||
|
||||
class TrafficMonitorThread() extends Thread {
|
||||
|
||||
@@ -99,16 +99,10 @@ class TrafficMonitorThread() extends Thread {
|
||||
val input = socket.getInputStream
|
||||
val output = socket.getOutputStream
|
||||
|
||||
val buffer = new Array[Byte](256)
|
||||
val size = input.read(buffer)
|
||||
|
||||
val array = new Array[Byte](size)
|
||||
Array.copy(buffer, 0, array, 0, size)
|
||||
|
||||
val stat = new String(array, "UTF-8").split("\\|")
|
||||
if (stat.length == 2) {
|
||||
TrafficMonitor.update(stat(0).toLong, stat(1).toLong)
|
||||
}
|
||||
val buffer = new Array[Byte](16)
|
||||
if (input.read(buffer) != 16) throw new IOException("Unexpected traffic stat length")
|
||||
val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN)
|
||||
TrafficMonitor.update(stat.getLong(0), stat.getLong(8))
|
||||
|
||||
output.write(0)
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ import android.util.{DisplayMetrics, Base64, Log}
|
||||
import android.view.View.MeasureSpec
|
||||
import android.view.{Gravity, View, Window}
|
||||
import android.widget.Toast
|
||||
import com.github.shadowsocks.{ShadowsocksRunnerActivity, ShadowsocksRunnerService, ShadowsocksApplication, BuildConfig}
|
||||
import com.github.shadowsocks.{ShadowsocksRunnerService, ShadowsocksApplication, BuildConfig}
|
||||
import org.xbill.DNS._
|
||||
|
||||
|
||||
@@ -405,15 +405,8 @@ object Utils {
|
||||
val isInstalled: Boolean = ShadowsocksApplication.settings.getBoolean(ShadowsocksApplication.getVersionName, false)
|
||||
if (!isInstalled) return
|
||||
|
||||
if (Utils.isLollipopOrAbove) {
|
||||
val intent = new Intent(context, classOf[ShadowsocksRunnerService])
|
||||
context.startService(intent)
|
||||
} else {
|
||||
val intent = new Intent(context, classOf[ShadowsocksRunnerActivity])
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
val intent = new Intent(context, classOf[ShadowsocksRunnerService])
|
||||
context.startService(intent)
|
||||
}
|
||||
|
||||
def stopSsService(context: Context): Unit = {
|
||||
|
||||
Reference in New Issue
Block a user