Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
com.ccwangluo.accelerator
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
sheteng
com.ccwangluo.accelerator
Commits
952ec45c
Commit
952ec45c
authored
Mar 15, 2019
by
Mygod
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' into preference-1.1
parents
ef1ee897
9a08abc0
Changes
32
Hide whitespace changes
Inline
Side-by-side
Showing
32 changed files
with
591 additions
and
172 deletions
+591
-172
.github/ISSUE_TEMPLATE/bug_report.md
.github/ISSUE_TEMPLATE/bug_report.md
+1
-1
build.gradle
build.gradle
+6
-5
core/build.gradle
core/build.gradle
+1
-1
core/src/main/java/com/github/shadowsocks/bg/BaseService.kt
core/src/main/java/com/github/shadowsocks/bg/BaseService.kt
+9
-7
core/src/main/java/com/github/shadowsocks/bg/Executable.kt
core/src/main/java/com/github/shadowsocks/bg/Executable.kt
+2
-2
core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt
.../src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt
+10
-12
core/src/main/java/com/github/shadowsocks/net/ChannelMonitor.kt
...rc/main/java/com/github/shadowsocks/net/ChannelMonitor.kt
+2
-2
core/src/main/java/com/github/shadowsocks/net/HttpsTest.kt
core/src/main/java/com/github/shadowsocks/net/HttpsTest.kt
+1
-1
core/src/main/java/com/github/shadowsocks/net/LocalDnsServer.kt
...rc/main/java/com/github/shadowsocks/net/LocalDnsServer.kt
+5
-2
core/src/main/res/values-es/strings.xml
core/src/main/res/values-es/strings.xml
+140
-0
core/src/main/res/values-fa/strings.xml
core/src/main/res/values-fa/strings.xml
+8
-0
core/src/main/res/values-fr/strings.xml
core/src/main/res/values-fr/strings.xml
+6
-0
core/src/main/res/values-ja/strings.xml
core/src/main/res/values-ja/strings.xml
+8
-0
core/src/main/res/values-ko/strings.xml
core/src/main/res/values-ko/strings.xml
+6
-0
core/src/main/res/values-ru/strings.xml
core/src/main/res/values-ru/strings.xml
+9
-2
core/src/main/res/values-tr/strings.xml
core/src/main/res/values-tr/strings.xml
+8
-0
core/src/main/res/values-zh-rCN/strings.xml
core/src/main/res/values-zh-rCN/strings.xml
+10
-3
core/src/main/res/values-zh-rTW/strings.xml
core/src/main/res/values-zh-rTW/strings.xml
+8
-0
core/src/main/res/values/strings.xml
core/src/main/res/values/strings.xml
+3
-1
gradle/wrapper/gradle-wrapper.properties
gradle/wrapper/gradle-wrapper.properties
+1
-1
mobile/build.gradle
mobile/build.gradle
+3
-2
mobile/src/main/java/com/github/shadowsocks/AppManager.kt
mobile/src/main/java/com/github/shadowsocks/AppManager.kt
+109
-66
mobile/src/main/java/com/github/shadowsocks/MainActivity.kt
mobile/src/main/java/com/github/shadowsocks/MainActivity.kt
+3
-2
mobile/src/main/java/com/github/shadowsocks/bg/TileService.kt
...le/src/main/java/com/github/shadowsocks/bg/TileService.kt
+2
-1
mobile/src/main/java/com/github/shadowsocks/widget/ServiceButton.kt
.../main/java/com/github/shadowsocks/widget/ServiceButton.kt
+9
-8
mobile/src/main/res/layout/layout_apps.xml
mobile/src/main/res/layout/layout_apps.xml
+69
-30
mobile/src/main/res/layout/layout_apps_item.xml
mobile/src/main/res/layout/layout_apps_item.xml
+55
-20
plugin/build.gradle
plugin/build.gradle
+1
-1
plugin/src/main/res/values-es/strings.xml
plugin/src/main/res/values-es/strings.xml
+7
-0
translate.py
translate.py
+86
-0
tv/build.gradle
tv/build.gradle
+1
-1
tv/src/main/AndroidManifest.xml
tv/src/main/AndroidManifest.xml
+2
-1
No files found.
.github/ISSUE_TEMPLATE/bug_report.md
View file @
952ec45c
...
...
@@ -48,7 +48,7 @@ Put an `x` inside the [ ] that applies.
*
[ ] Apps VPN mode
*
[ ] Bypass mode
*
Remote DNS: 8.8.8.8
*
[ ] DNS
Forwarding
*
[ ] DNS
over UDP
*
Plugin configuration (if applicable):
*
[ ] Auto Connect
*
[ ] TCP Fast Open
...
...
build.gradle
View file @
952ec45c
...
...
@@ -12,8 +12,9 @@ buildscript {
junitVersion
=
'4.12'
androidTestVersion
=
'1.1.1'
androidEspressoVersion
=
'3.1.1'
versionCode
=
4070150
versionName
=
'4.7.1-nightly'
versionCode
=
4070350
versionName
=
'4.7.3-nightly'
resConfigs
=
[
'es'
,
'fa'
,
'fr'
,
'ja'
,
'ko'
,
'ru'
,
'tr'
,
'zh-rCN'
,
'zh-rTW'
]
}
repositories
{
...
...
@@ -25,10 +26,10 @@ buildscript {
}
dependencies
{
classpath
'com.vanniktech:gradle-maven-publish-plugin:0.8.0'
classpath
'com.android.tools.build:gradle:3.3.1'
classpath
'com.github.ben-manes:gradle-versions-plugin:0.20.0'
classpath
'com.android.tools.build:gradle:3.3.2'
classpath
'com.github.ben-manes:gradle-versions-plugin:0.21.0'
classpath
'com.google.gms:google-services:4.2.0'
classpath
'com.vanniktech:gradle-maven-publish-plugin:0.8.0'
classpath
'io.fabric.tools:gradle:1.27.1'
classpath
"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
...
...
core/build.gradle
View file @
952ec45c
...
...
@@ -45,7 +45,7 @@ androidExtensions {
def
lifecycleVersion
=
'2.0.0'
def
roomVersion
=
'2.0.0'
def
workVersion
=
'1.0.0
-rc02
'
def
workVersion
=
'1.0.0'
dependencies
{
api
project
(
':plugin'
)
api
"android.arch.work:work-runtime-ktx:$workVersion"
...
...
core/src/main/java/com/github/shadowsocks/bg/BaseService.kt
View file @
952ec45c
...
...
@@ -106,8 +106,8 @@ object BaseService {
private
val
bandwidthListeners
=
mutableMapOf
<
IBinder
,
Long
>()
// the binder is the real identifier
private
val
handler
=
Handler
()
override
fun
getState
():
Int
=
data
!!
.
state
.
ordinal
override
fun
getProfileName
():
String
=
data
!!
.
proxy
?.
profile
?.
name
?:
"Idle"
override
fun
getState
():
Int
=
(
data
?.
state
?:
State
.
Idle
)
.
ordinal
override
fun
getProfileName
():
String
=
data
?
.
proxy
?.
profile
?.
name
?:
"Idle"
override
fun
registerCallback
(
cb
:
IShadowsocksServiceCallback
)
{
callbacks
.
register
(
cb
)
...
...
@@ -117,6 +117,7 @@ object BaseService {
repeat
(
callbacks
.
beginBroadcast
())
{
try
{
work
(
callbacks
.
getBroadcastItem
(
it
))
}
catch
(
_
:
DeadObjectException
)
{
}
catch
(
e
:
Exception
)
{
printLog
(
e
)
}
...
...
@@ -133,7 +134,7 @@ object BaseService {
.
map
{
Pair
(
it
.
profile
.
id
,
it
.
trafficMonitor
?.
requestUpdate
())
}
.
filter
{
it
.
second
!=
null
}
.
map
{
Triple
(
it
.
first
,
it
.
second
!!
.
first
,
it
.
second
!!
.
second
)
}
if
(
stats
.
any
{
it
.
third
}
&&
data
!!
.
state
==
State
.
Connected
&&
bandwidthListeners
.
isNotEmpty
())
{
if
(
stats
.
any
{
it
.
third
}
&&
data
?
.
state
==
State
.
Connected
&&
bandwidthListeners
.
isNotEmpty
())
{
val
sum
=
stats
.
fold
(
TrafficStats
())
{
a
,
b
->
a
+
b
.
second
}
broadcast
{
item
->
if
(
bandwidthListeners
.
contains
(
item
.
asBinder
()))
{
...
...
@@ -149,16 +150,17 @@ object BaseService {
val
wasEmpty
=
bandwidthListeners
.
isEmpty
()
if
(
bandwidthListeners
.
put
(
cb
.
asBinder
(),
timeout
)
==
null
)
{
if
(
wasEmpty
)
registerTimeout
()
if
(
data
!!
.
state
!=
State
.
Connected
)
return
if
(
data
?
.
state
!=
State
.
Connected
)
return
var
sum
=
TrafficStats
()
val
proxy
=
data
!!
.
proxy
?:
return
val
data
=
data
val
proxy
=
data
?.
proxy
?:
return
proxy
.
trafficMonitor
?.
out
.
also
{
stats
->
cb
.
trafficUpdated
(
proxy
.
profile
.
id
,
if
(
stats
==
null
)
sum
else
{
sum
+=
stats
stats
})
}
data
!!
.
udpFallback
?.
also
{
udpFallback
->
data
.
udpFallback
?.
also
{
udpFallback
->
udpFallback
.
trafficMonitor
?.
out
.
also
{
stats
->
cb
.
trafficUpdated
(
udpFallback
.
profile
.
id
,
if
(
stats
==
null
)
TrafficStats
()
else
{
sum
+=
stats
...
...
@@ -232,7 +234,7 @@ object BaseService {
File
(
Core
.
deviceStorage
.
noBackupFilesDir
,
"stat_main"
),
File
(
configRoot
,
CONFIG_FILE
),
if
(
udpFallback
==
null
)
"-u"
else
null
)
check
(
udpFallback
?.
pluginPath
==
null
)
check
(
udpFallback
?.
pluginPath
==
null
)
{
"UDP fallback cannot have plugins"
}
udpFallback
?.
start
(
this
,
File
(
Core
.
deviceStorage
.
noBackupFilesDir
,
"stat_udp"
),
File
(
configRoot
,
CONFIG_FILE_UDP
),
...
...
core/src/main/java/com/github/shadowsocks/bg/Executable.kt
View file @
952ec45c
...
...
@@ -27,7 +27,7 @@ import android.text.TextUtils
import
android.util.Log
import
com.crashlytics.android.Crashlytics
import
java.io.File
import
java.io.
FileNotFound
Exception
import
java.io.
IO
Exception
object
Executable
{
const
val
REDSOCKS
=
"libredsocks.so"
...
...
@@ -40,7 +40,7 @@ object Executable {
for
(
process
in
File
(
"/proc"
).
listFiles
{
_
,
name
->
TextUtils
.
isDigitsOnly
(
name
)
})
{
val
exe
=
File
(
try
{
File
(
process
,
"cmdline"
).
inputStream
().
bufferedReader
().
readText
()
}
catch
(
_
:
FileNotFound
Exception
)
{
}
catch
(
_
:
IO
Exception
)
{
continue
}.
split
(
Character
.
MIN_VALUE
,
limit
=
2
).
first
())
if
(
EXECUTABLES
.
contains
(
exe
.
name
))
try
{
...
...
core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt
View file @
952ec45c
...
...
@@ -61,14 +61,12 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
conn
.
doOutput
=
true
val
proxies
=
try
{
withTimeoutOrNull
(
10_000
)
{
withContext
(
Dispatchers
.
IO
)
{
conn
.
outputStream
.
bufferedWriter
().
use
{
it
.
write
(
"sig="
+
Base64
.
encodeToString
(
mdg
.
digest
(),
Base64
.
DEFAULT
))
}
conn
.
inputStream
.
bufferedReader
().
readText
()
withContext
(
Dispatchers
.
IO
)
{
conn
.
outputStream
.
bufferedWriter
().
use
{
it
.
write
(
"sig="
+
Base64
.
encodeToString
(
mdg
.
digest
(),
Base64
.
DEFAULT
))
}
}
?:
throw
UnknownHostException
()
conn
.
inputStream
.
bufferedReader
().
readText
()
}
}
finally
{
conn
.
disconnectFromMain
()
}.
split
(
'|'
).
toMutableList
()
...
...
@@ -85,19 +83,19 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
}
// it's hard to resolve DNS on a specific interface so we'll do it here
if
(
profile
.
host
.
parseNumericAddress
()
==
null
)
profile
.
host
=
withTimeoutOrNull
(
10_000
)
{
if
(
profile
.
host
.
parseNumericAddress
()
==
null
)
{
var
retries
=
0
while
(
isActiv
e
)
try
{
while
(
tru
e
)
try
{
val
io
=
GlobalScope
.
async
(
Dispatchers
.
IO
)
{
service
.
resolver
(
profile
.
host
)
}
return
@withTimeoutOrNull
io
.
await
().
firstOrNull
()
profile
.
host
=
io
.
await
().
firstOrNull
()
?.
hostAddress
?:
throw
UnknownHostException
()
return
}
catch
(
e
:
UnknownHostException
)
{
// retries are only needed on Chrome OS where arc0 is brought up/down during VPN changes
if
(!
DataStore
.
hasArc0
)
throw
e
Thread
.
yield
()
Crashlytics
.
log
(
Log
.
WARN
,
"ProxyInstance-resolver"
,
"Retry resolving attempt #${++retries}"
)
}
null
// only here for type resolving
}
?.
hostAddress
?:
throw
UnknownHostException
()
}
}
/**
...
...
core/src/main/java/com/github/shadowsocks/net/ChannelMonitor.kt
View file @
952ec45c
...
...
@@ -55,7 +55,7 @@ class ChannelMonitor : Thread("ChannelMonitor") {
pendingRegistrations
.
poll
()
!!
.
apply
{
try
{
result
.
complete
(
registerInternal
(
channel
,
ops
,
listener
))
}
catch
(
e
:
ClosedChannel
Exception
)
{
}
catch
(
e
:
Exception
)
{
result
.
completeExceptionally
(
e
)
}
}
...
...
@@ -100,7 +100,7 @@ class ChannelMonitor : Thread("ChannelMonitor") {
while
(
running
)
{
val
num
=
try
{
selector
.
select
()
}
catch
(
e
:
IO
Exception
)
{
}
catch
(
e
:
Exception
)
{
printLog
(
e
)
continue
}
...
...
core/src/main/java/com/github/shadowsocks/net/HttpsTest.kt
View file @
952ec45c
...
...
@@ -81,7 +81,7 @@ class HttpsTest : ViewModel() {
fun
testConnection
()
{
cancelTest
()
status
.
value
=
Status
.
Testing
val
url
=
URL
(
"https"
,
when
(
Core
.
currentProfile
!!
.
first
.
route
)
{
val
url
=
URL
(
"https"
,
when
(
(
Core
.
currentProfile
?:
return
)
.
first
.
route
)
{
Acl
.
CHINALIST
->
"www.qualcomm.cn"
else
->
"www.google.com"
},
"/generate_204"
)
...
...
core/src/main/java/com/github/shadowsocks/net/LocalDnsServer.kt
View file @
952ec45c
...
...
@@ -157,9 +157,12 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
channel
.
configureBlocking
(
false
)
monitor
.
wait
(
channel
,
SelectionKey
.
OP_WRITE
)
check
(
channel
.
send
(
remoteDns
.
udpWrap
(
packet
),
proxy
)
>
0
)
monitor
.
wait
(
channel
,
SelectionKey
.
OP_READ
)
val
result
=
remoteDns
.
udpReceiveBuffer
(
UDP_PACKET_SIZE
)
while
(
channel
.
receive
(
result
)
!=
proxy
)
result
.
clear
()
while
(
isActive
)
{
monitor
.
wait
(
channel
,
SelectionKey
.
OP_READ
)
if
(
channel
.
receive
(
result
)
==
proxy
)
break
result
.
clear
()
}
result
.
flip
()
remoteDns
.
udpUnwrap
(
result
)
result
...
...
core/src/main/res/values-es/strings.xml
0 → 100644
View file @
952ec45c
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string
name=
"quick_toggle"
>
"Alternar"
</string>
<string
name=
"remote_dns"
>
"DNS Remoto"
</string>
<string
name=
"stat_summary"
>
"Enviado: \t\t\t\t\t%3$s\t↑\t%1$s
Recibido: \t%4$s\t↓\t%2$s"
</string>
<string
name=
"connection_test_testing"
>
"Probando..."
</string>
<string
name=
"connection_test_available"
>
"Éxito: El establecimiento de la comunicación HTTPS tomó %dms"
</string>
<string
name=
"connection_test_error"
>
"Fallo al detectar conexión a Internet: %s"
</string>
<string
name=
"connection_test_fail"
>
"Internet no disponible"
</string>
<string
name=
"connection_test_error_status_code"
>
"Código de error: #%d"
</string>
<!-- proxy category -->
<string
name=
"profile_name"
>
"Nombre de Perfil"
</string>
<string
name=
"proxy"
>
"Servidor"
</string>
<string
name=
"remote_port"
>
"Puerto Remoto"
</string>
<string
name=
"sitekey"
>
"Contraseña"
</string>
<string
name=
"enc_method"
>
"Método de Cifrado"
</string>
<!-- feature category -->
<string
name=
"ipv6"
>
"Ruta IPv6"
</string>
<string
name=
"ipv6_summary"
>
"Redireccionar tráfico IPv6 a ruta"
</string>
<string
name=
"route_list"
>
"Ruta"
</string>
<string
name=
"route_entry_gfwlist"
>
"Lista GFW"
</string>
<string
name=
"proxied_apps"
>
"Apps en modo VPN"
</string>
<string
name=
"proxied_apps_summary"
>
"Configurar modo VPN para las apps seleccionadas"
</string>
<string
name=
"on"
>
"Encendido"
</string>
<!-- Fuzzy -->
<string
name=
"bypass_apps"
>
"Modo Bypass"
</string>
<string
name=
"auto_connect"
>
"Conexión Automática"
</string>
<string
name=
"auto_connect_summary"
>
"Activar Shadowsocks al iniciar"
</string>
<string
name=
"tcp_fastopen_summary_unsupported"
>
"Versión del Núcleo sin soporte: %s
<
3.7.1"
</string>
<string
name=
"udp_dns"
>
"Enviar DNS sobre UDP"
</string>
<!-- notification category -->
<string
name=
"forward_success"
>
"Shadowsocks inició."
</string>
<string
name=
"invalid_server"
>
"Nombre de servidor inválido"
</string>
<string
name=
"service_failed"
>
"Fallo al conectar al servidor remoto"
</string>
<string
name=
"stop"
>
"Detener"
</string>
<string
name=
"stopping"
>
"Apagando..."
</string>
<string
name=
"reboot_required"
>
"Fallo al iniciar servicio VPN. Puede que tengas que reiniciar tu dispositivo."
</string>
<string
name=
"profile_invalid_input"
>
"No se ha encontrado datos de perfil válidos."
</string>
<!-- alert category -->
<string
name=
"profile_empty"
>
"Por favor selecciona un perfil"
</string>
<string
name=
"proxy_empty"
>
"Proxy/Contraseña no deberían estar vacíos"
</string>
<string
name=
"connect"
>
"Conectar"
</string>
<!-- menu category -->
<string
name=
"profiles"
>
"Perfiles"
</string>
<string
name=
"settings"
>
"Preferencias"
</string>
<string
name=
"faq"
>
"FAQ"
</string>
<string
name=
"about"
>
"Acerca de"
</string>
<string
name=
"edit"
>
"Editar"
</string>
<string
name=
"share"
>
"Compartir"
</string>
<string
name=
"add_profile"
>
"Agregar Perfil"
</string>
<string
name=
"action_apply_all"
>
"Aplicar preferencias a todos los Perfiles"
</string>
<string
name=
"action_export"
>
"Exportar a portapapeles"
</string>
<string
name=
"action_import"
>
"Importar desde portapapeles"
</string>
<string
name=
"action_export_msg"
>
"¡Exportado satisfactoriamente!"
</string>
<string
name=
"action_export_err"
>
"Fallo al exportar."
</string>
<string
name=
"action_import_msg"
>
"¡Importado satisfactoriamente!"
</string>
<string
name=
"action_import_err"
>
"Fallo al importar."
</string>
<!-- profile -->
<string
name=
"profile_config"
>
"Configuración de Perfil"
</string>
<string
name=
"delete"
>
"Remover"
</string>
<string
name=
"delete_confirm_prompt"
>
"¿Estás seguro de querer remover este perfil?"
</string>
<string
name=
"share_qr_nfc"
>
"Código QR/NFC"
</string>
<string
name=
"add_profile_dialog"
>
"Agregar este Perfil Shadowsocks?"
</string>
<string
name=
"add_profile_methods_scan_qr_code"
>
"Escanear código QR"
</string>
<plurals
name=
"removed"
>
<item
quantity=
"one"
>
"Removido"
</item>
<item
quantity=
"other"
>
"%d elementos removidos"
</item>
</plurals>
<string
name=
"undo"
>
"Deshacer"
</string>
<!-- tasker -->
<string
name=
"toggle_service_state"
>
"Iniciar el servicio"
</string>
<string
name=
"start_service_default"
>
"Conectar al servidor actual"
</string>
<string
name=
"start_service"
>
"Conectar a %s"
</string>
<string
name=
"stop_service"
>
"Cambiar a %s"
</string>
<string
name=
"profile_default"
>
"Usar el perfil actual"
</string>
<!-- status -->
<string
name=
"sent"
>
"Enviado:"
</string>
<string
name=
"received"
>
"Recibido:"
</string>
<!-- status -->
<string
name=
"connecting"
>
"Conectando..."
</string>
<string
name=
"vpn_connected"
>
"Conectado, presiona para verificar la conexión"
</string>
<string
name=
"not_connected"
>
"No conectado"
</string>
<!-- acl -->
<string
name=
"custom_rules"
>
"Reglas personalizadas"
</string>
<string
name=
"action_add_rule"
>
"Agregar regla(s)..."
</string>
<string
name=
"edit_rule"
>
"Editar regla"
</string>
<string
name=
"route_entry_all"
>
"Todo"
</string>
<!-- plugin -->
<string
name=
"plugin"
>
"Complemento"
</string>
<string
name=
"plugin_configure"
>
"Configurar..."
</string>
<string
name=
"plugin_disabled"
>
"Deshabilitado"
</string>
<string
name=
"plugin_unknown"
>
"Complemento desconocido %s"
</string>
<string
name=
"plugin_untrusted"
>
"Precaución: Este complemento no parece venir de una fuente confiable."
</string>
<string
name=
"profile_plugin"
>
"Complemento: %s"
</string>
<string
name=
"add_profile_scanner_permission_required"
>
"Se requiere permiso de cámara para escanear códigos QR."
</string>
<!-- notification category -->
<string
name=
"service_vpn"
>
"Servicio VPN"
</string>
<string
name=
"add_profile_methods_manual_settings"
>
"Configuración manual"
</string>
<!-- misc -->
<string
name=
"advanced"
>
"Avanzado"
</string>
<!-- misc -->
<string
name=
"service_mode"
>
"Modo de servicio"
</string>
<string
name=
"service_mode_proxy"
>
"Sólo Proxy"
</string>
<string
name=
"service_mode_vpn"
>
"VPN"
</string>
<string
name=
"port_proxy"
>
"Puerto proxy SOCKS5"
</string>
<string
name=
"port_local_dns"
>
"Puerto DNS local"
</string>
<string
name=
"service_proxy"
>
"Servicio Proxy"
</string>
<string
name=
"service_transproxy"
>
"Servicio Transproxy"
</string>
<string
name=
"vpn_permission_denied"
>
"Permiso denegado al crear servicio VPN"
</string>
<string
name=
"direct_boot_aware_summary"
>
"La información del perfil seleccionado será menos protegida"
</string>
<string
name=
"action_import_file"
>
"Importar desde archivo..."
</string>
<string
name=
"night_mode"
>
"Modo nocturno"
</string>
<string
name=
"night_mode_system"
>
"Seguir sistema"
</string>
<string
name=
"night_mode_auto"
>
"Auto"
</string>
<string
name=
"night_mode_on"
>
"Encendido"
</string>
<string
name=
"night_mode_off"
>
"Apagado"
</string>
<string
name=
"send_email"
>
"Enviar email"
</string>
<string
name=
"action_export_more"
>
"Exportar..."
</string>
<string
name=
"action_export_file"
>
"Exportar a archivo..."
</string>
<string
name=
"share_over_lan"
>
"Compartir por LAN"
</string>
<string
name=
"connection_test_pending"
>
"Verificar conectividad"
</string>
<string
name=
"file_manager_missing"
>
"Por favor, instala un explorador de archivos como MiXplorer"
</string>
<string
name=
"action_replace_file"
>
"Reemplazar desde archivo..."
</string>
</resources>
\ No newline at end of file
core/src/main/res/values-fa/strings.xml
View file @
952ec45c
...
...
@@ -20,11 +20,17 @@
<string
name=
"proxied_apps"
>
"حالت "
</string>
<string
name=
"proxied_apps_summary"
>
"تنظیم حالت ویپیان برای اپلیکیشنهای انتخاب شده"
</string>
<string
name=
"on"
>
"روشن"
</string>
<!-- Fuzzy -->
<string
name=
"bypass_apps"
>
"حالت صرفنظر"
</string>
<string
name=
"bypass_apps_summary"
>
"این اپلیکیشنها از ویپیان استفاده نکنند"
</string>
<string
name=
"auto_connect"
>
"وصلشدن اتوماتیک"
</string>
<string
name=
"auto_connect_summary"
>
"فعالشدن شدوساکس لحظه روشنشدن گوشی"
</string>
<!-- Fuzzy -->
<string
name=
"udp_dns"
>
"ارسال دیاناس"
</string>
<!-- Fuzzy -->
<string
name=
"udp_dns_summary"
>
"ارسال کلیه درخواستهای دیاناس به سرور "
</string>
<!-- notification category -->
...
...
@@ -100,6 +106,8 @@
<!-- misc -->
<string
name=
"advanced"
>
"پیشرفته"
</string>
<!-- misc -->
<string
name=
"service_mode"
>
"وضعیت سرویس"
</string>
<string
name=
"service_mode_proxy"
>
"فقط پروکسی"
</string>
<string
name=
"service_mode_vpn"
>
"ویپیان"
</string>
...
...
core/src/main/res/values-fr/strings.xml
View file @
952ec45c
...
...
@@ -25,6 +25,8 @@ Reçu : \t\t\t%4$s\t↓\t%2$s"</string>
<string
name=
"proxied_apps"
>
"Mode VPN Apps"
</string>
<string
name=
"proxied_apps_summary"
>
"Configurer le mode VPN pour les applications sélectionnées"
</string>
<string
name=
"on"
>
"Activé"
</string>
<!-- Fuzzy -->
<string
name=
"bypass_apps"
>
"Mode exclusion"
</string>
<string
name=
"bypass_apps_summary"
>
"Activer cette option pour exclure les applications sélectionnées"
</string>
<string
name=
"auto_connect"
>
"Connection auto"
</string>
...
...
@@ -33,6 +35,8 @@ Reçu : \t\t\t%4$s\t↓\t%2$s"</string>
<!-- Fuzzy -->
<string
name=
"tcp_fastopen_summary"
>
"Activer nécessite la permission ROOT"
</string>
<string
name=
"tcp_fastopen_summary_unsupported"
>
"Version du noyau non supportée : %s
<
3.7.1"
</string>
<!-- Fuzzy -->
<string
name=
"udp_dns_summary"
>
"Faire suivre toutes les requêtes DNS au serveur distant"
</string>
<!-- notification category -->
...
...
@@ -116,6 +120,8 @@ Reçu : \t\t\t%4$s\t↓\t%2$s"</string>
<!-- misc -->
<string
name=
"advanced"
>
"Avancé"
</string>
<!-- misc -->
<string
name=
"service_mode"
>
"Mode Service"
</string>
<string
name=
"service_mode_proxy"
>
"Proxy seul"
</string>
<string
name=
"port_proxy"
>
"Port proxy SOCKS5"
</string>
...
...
core/src/main/res/values-ja/strings.xml
View file @
952ec45c
...
...
@@ -24,6 +24,8 @@
<string
name=
"proxied_apps"
>
"アプリ別のVPNモードを使用"
</string>
<string
name=
"proxied_apps_summary"
>
"アプリ別のVPNモードを設定"
</string>
<string
name=
"on"
>
"ON"
</string>
<!-- Fuzzy -->
<string
name=
"bypass_apps"
>
"バイパスモード(迂回モード)"
</string>
<string
name=
"bypass_apps_summary"
>
"このモードを選択した場合、指定したアプリのパケットはプロキシを経由しない"
</string>
<string
name=
"auto_connect"
>
"自動接続"
</string>
...
...
@@ -32,7 +34,11 @@
<!-- Fuzzy -->
<string
name=
"tcp_fastopen_summary"
>
"使用するには ROOT 権限が必要"
</string>
<string
name=
"tcp_fastopen_summary_unsupported"
>
"ご利用のカーネルバージョンはサポートしておりません:%s
<
3.7.1"
</string>
<!-- Fuzzy -->
<string
name=
"udp_dns"
>
"DNS 転送"
</string>
<!-- Fuzzy -->
<string
name=
"udp_dns_summary"
>
"DNS プロトコルでリモードサーバーにパケットを転送"
</string>
<!-- notification category -->
...
...
@@ -122,6 +128,8 @@
<!-- misc -->
<string
name=
"advanced"
>
"アドバンス"
</string>
<!-- misc -->
<string
name=
"service_mode"
>
"サービスモード"
</string>
<string
name=
"service_mode_proxy"
>
"プロキシのみ"
</string>
<string
name=
"service_mode_transproxy"
>
"トランスプロキシ"
</string>
...
...
core/src/main/res/values-ko/strings.xml
View file @
952ec45c
...
...
@@ -26,6 +26,8 @@
<string
name=
"proxied_apps"
>
"원하는 앱만 프락시 적용하기"
</string>
<string
name=
"proxied_apps_summary"
>
"선택한 앱에만 프락시를 적용합니다"
</string>
<string
name=
"on"
>
"활성화"
</string>
<!-- Fuzzy -->
<string
name=
"bypass_apps"
>
"선택된 앱들만 프록시 적용 제외하기"
</string>
<string
name=
"bypass_apps_summary"
>
"이 옵션을 활성화하면 선택된 앱들은 프락시를 사용하지 않습니다"
</string>
<string
name=
"auto_connect"
>
"자동 접속"
</string>
...
...
@@ -34,7 +36,11 @@
<!-- Fuzzy -->
<string
name=
"tcp_fastopen_summary"
>
"루트 권한이 필요합니다"
</string>
<string
name=
"tcp_fastopen_summary_unsupported"
>
"지원하지 않는 버전의 커널입니다: %s
<
3.7.1"
</string>
<!-- Fuzzy -->
<string
name=
"udp_dns"
>
"DNS 포워딩"
</string>
<!-- Fuzzy -->
<string
name=
"udp_dns_summary"
>
"모든 DNS 요청을 외부로 포워딩 합니다"
</string>
<!-- notification category -->
...
...
core/src/main/res/values-ru/strings.xml
View file @
952ec45c
...
...
@@ -5,6 +5,7 @@
<string
name=
"stat_summary"
>
"Отправлено: \t%3$s\t↑\t%1$s
Получено: \t\t\t%4$s\t↓\t%2$s"
</string>
<string
name=
"connection_test_testing"
>
"Проверка…"
</string>
<string
name=
"connection_test_available"
>
"Успешно: рукопожатие HTTPS заняло %dмс"
</string>
<string
name=
"connection_test_error"
>
"Сбой проверки интернет-соединения: %s"
</string>
<string
name=
"connection_test_fail"
>
"Интернет недоступен"
</string>
<string
name=
"connection_test_error_status_code"
>
"Код ошибки: #%d"
</string>
...
...
@@ -24,15 +25,19 @@
<string
name=
"proxied_apps"
>
"Режим VPN для выбранных приложений"
</string>
<string
name=
"proxied_apps_summary"
>
"Выбрать приложения, для которых нужно использовать режим VPN"
</string>
<string
name=
"on"
>
"Вкл"
</string>
<!-- Fuzzy -->
<string
name=
"bypass_apps"
>
"В обход прокси"
</string>
<string
name=
"bypass_apps_summary"
>
"Включите эту опцию для работы выбранных приложений в обход прокси"
</string>
<string
name=
"auto_connect"
>
"Авто-подключение"
</string>
<string
name=
"auto_connect_summary"
>
"Запускать Shadowsocks при включении"
</string>
<string
name=
"tcp_fastopen_summary"
>
"Переключение может запрашивать ROOT права"
</string>
<string
name=
"tcp_fastopen_summary_unsupported"
>
"Неподдерживаемая версия ядра: %s
<
3.7.1"
</string>
<!-- Fuzzy -->
<string
name=
"tcp_fastopen_summary"
>
"Переключение требует наличия ROOT прав"
</string>
<string
name=
"tcp_fastopen_summary_unsupported"
>
"Неподдерживаемая версия ядра: %s
<
3.7.1"
</string>
<string
name=
"udp_dns"
>
"Перенаправление DNS"
</string>
<!-- Fuzzy -->
<string
name=
"udp_dns_summary"
>
"Перенаправлять все DNS-запросы на удалённый сервер"
</string>
<!-- notification category -->
...
...
@@ -125,6 +130,8 @@
<!-- misc -->
<string
name=
"advanced"
>
"Дополнительно"
</string>
<!-- misc -->
<string
name=
"service_mode"
>
"Режим"
</string>
<string
name=
"service_mode_proxy"
>
"Только прокси"
</string>
<string
name=
"service_mode_transproxy"
>
"Прозрачный прокси"
</string>
...
...
core/src/main/res/values-tr/strings.xml
View file @
952ec45c
...
...
@@ -21,6 +21,8 @@
<string
name=
"proxied_apps"
>
"Uygulamalar VPN modu"
</string>
<string
name=
"proxied_apps_summary"
>
"Seçili uygulamalar için VPN'i yapılandır"
</string>
<string
name=
"on"
>
"Açık"
</string>
<!-- Fuzzy -->
<string
name=
"bypass_apps"
>
"Bypass Modu"
</string>
<string
name=
"bypass_apps_summary"
>
"Seçili uygulamaları bypass etmek için bu seçeneği açın"
</string>
<string
name=
"auto_connect"
>
"Otomatik Bağlan"
</string>
...
...
@@ -29,7 +31,11 @@
<!-- Fuzzy -->
<string
name=
"tcp_fastopen_summary"
>
"Açıp kapamak için ROOT yetkisi gerekiyor"
</string>
<string
name=
"tcp_fastopen_summary_unsupported"
>
"Desteklenmeyen kernel sürümü: %s
<
3.7.1"
</string>
<!-- Fuzzy -->
<string
name=
"udp_dns"
>
"DNS yönlendirme"
</string>
<!-- Fuzzy -->
<string
name=
"udp_dns_summary"
>
"Tüm DNS isteklerini remote'a yönlendir"
</string>
<!-- notification category -->
...
...
@@ -118,6 +124,8 @@
<!-- misc -->
<string
name=
"advanced"
>
"Gelişmiş"
</string>
<!-- misc -->
<string
name=
"service_mode"
>
"Servis modu"
</string>
<string
name=
"service_mode_proxy"
>
"Sadece proxy"
</string>
<string
name=
"port_proxy"
>
"SOCKS5 proxy portu"
</string>
...
...
core/src/main/res/values-zh-rCN/strings.xml
View file @
952ec45c
...
...
@@ -25,14 +25,14 @@
<string
name=
"proxied_apps"
>
"分应用 VPN"
</string>
<string
name=
"proxied_apps_summary"
>
"允许部分应用绕过 VPN"
</string>
<string
name=
"on"
>
"启用"
</string>
<string
name=
"bypass_apps"
>
"绕行
模式
"
</string>
<string
name=
"bypass_apps"
>
"绕行"
</string>
<string
name=
"bypass_apps_summary"
>
"绕过选择的应用"
</string>
<string
name=
"auto_connect"
>
"自动连接"
</string>
<string
name=
"auto_connect_summary"
>
"允许 Shadowsocks 随系统启动"
</string>
<string
name=
"tcp_fastopen_summary"
>
"切换可能需要 ROOT 权限"
</string>
<string
name=
"tcp_fastopen_summary_unsupported"
>
"不支持的内核版本: %s
<
3.7.1"
</string>
<string
name=
"udp_dns"
>
"
DNS 转发
"
</string>
<string
name=
"udp_dns_summary"
>
"
转发所有 DNS 请求到远程服务器
"
</string>
<string
name=
"udp_dns"
>
"
使用 UDP DNS
"
</string>
<string
name=
"udp_dns_summary"
>
"
需要远程服务器支持 UDP 转发
"
</string>
<!-- notification category -->
<string
name=
"forward_success"
>
"后台服务已开始运行。"
</string>
...
...
@@ -121,6 +121,8 @@
<!-- misc -->
<string
name=
"advanced"
>
"高级选项"
</string>
<!-- misc -->
<string
name=
"service_mode"
>
"服务模式"
</string>
<string
name=
"service_mode_proxy"
>
"仅代理"
</string>
<string
name=
"service_mode_transproxy"
>
"透明代理"
</string>
...
...
@@ -147,4 +149,9 @@
<string
name=
"share_over_lan"
>
"通过局域网分享"
</string>
<string
name=
"connection_test_pending"
>
"检查连接"
</string>
<string
name=
"file_manager_missing"
>
"请安装文件管理器,如 MiXplorer"
</string>
<string
name=
"tcp_fastopen_failure"
>
"切换失败"
</string>
<string
name=
"udp_fallback"
>
"UDP 配置"
</string>
<string
name=
"action_replace_file"
>
"从文件替换…"
</string>
<string
name=
"off"
>
"关"
</string>
<string
name=
"proxied_apps_mode"
>
"模式"
</string>
</resources>
\ No newline at end of file
core/src/main/res/values-zh-rTW/strings.xml
View file @
952ec45c
...
...
@@ -26,6 +26,8 @@
<string
name=
"proxied_apps"
>
"個別應用程式的 VPN"
</string>
<string
name=
"proxied_apps_summary"
>
"為已選擇的應用程式設定 VPN"
</string>
<string
name=
"on"
>
"開"
</string>
<!-- Fuzzy -->
<string
name=
"bypass_apps"
>
"略過模式"
</string>
<string
name=
"bypass_apps_summary"
>
"啟用此選項,會略過已選擇的應用程式"
</string>
<string
name=
"auto_connect"
>
"自動連線"
</string>
...
...
@@ -34,7 +36,11 @@
<!-- Fuzzy -->
<string
name=
"tcp_fastopen_summary"
>
"切換需要 ROOT 權限"
</string>
<string
name=
"tcp_fastopen_summary_unsupported"
>
"不支援的核心版本:%s
<
3.7.1"
</string>
<!-- Fuzzy -->
<string
name=
"udp_dns"
>
"DNS 轉送"
</string>
<!-- Fuzzy -->
<string
name=
"udp_dns_summary"
>
"使用 UDP 向遠端轉送 DNS 封包"
</string>
<!-- notification category -->
...
...
@@ -123,6 +129,8 @@
<!-- misc -->
<string
name=
"advanced"
>
"高級"
</string>
<!-- misc -->
<string
name=
"service_mode"
>
"服務模式"
</string>
<string
name=
"service_mode_proxy"
>
"仅代理"
</string>
<string
name=
"service_mode_transproxy"
>
"透明代理"
</string>
...
...
core/src/main/res/values/strings.xml
View file @
952ec45c
...
...
@@ -46,7 +46,9 @@
<string
name=
"proxied_apps"
>
Apps VPN mode
</string>
<string
name=
"proxied_apps_summary"
>
Configure VPN mode for selected apps
</string>
<string
name=
"on"
>
On
</string>
<string
name=
"bypass_apps"
>
Bypass Mode
</string>
<string
name=
"off"
>
Off
</string>
<string
name=
"proxied_apps_mode"
>
Mode
</string>
<string
name=
"bypass_apps"
>
Bypass
</string>
<string
name=
"bypass_apps_summary"
>
Enable this option to bypass selected apps
</string>
<string
name=
"auto_connect"
>
Auto Connect
</string>
<string
name=
"auto_connect_summary"
>
Enable Shadowsocks on startup
</string>
...
...
gradle/wrapper/gradle-wrapper.properties
View file @
952ec45c
...
...
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath
=
wrapper/dists
zipStoreBase
=
GRADLE_USER_HOME
zipStorePath
=
wrapper/dists
distributionUrl
=
https
\:
//services.gradle.org/distributions/gradle-5.2-all.zip
distributionUrl
=
https
\:
//services.gradle.org/distributions/gradle-5.2
.1
-all.zip
mobile/build.gradle
View file @
952ec45c
...
...
@@ -26,7 +26,7 @@ android {
versionCode
rootProject
.
versionCode
versionName
rootProject
.
versionName
testInstrumentationRunner
"androidx.test.runner.AndroidJUnitRunner"
resConfigs
"fa"
,
"fr"
,
"ja"
,
"ko"
,
"ru"
,
"tr"
,
"zh-rCN"
,
"zh-rTW"
resConfigs
rootProject
.
resConfigs
}
buildTypes
{
debug
{
...
...
@@ -57,8 +57,9 @@ androidExtensions {
dependencies
{
implementation
project
(
':core'
)
implementation
'androidx.browser:browser:1.0.0'
implementation
'androidx.constraintlayout:constraintlayout:1.1.3'
implementation
'com.google.android.gms:play-services-vision:17.0.2'
implementation
'com.google.firebase:firebase-ads:17.
1.3
'
implementation
'com.google.firebase:firebase-ads:17.
2.0
'
implementation
'com.takisoft.preferencex:preferencex-simplemenu:1.0.0'
implementation
'com.twofortyfouram:android-plugin-api-for-locale:1.0.4'
implementation
'net.glxn.qrgen:android:2.0'
...
...
mobile/src/main/java/com/github/shadowsocks/AppManager.kt
View file @
952ec45c
...
...
@@ -32,13 +32,15 @@ import android.content.pm.PackageInfo
import
android.content.pm.PackageManager
import
android.graphics.drawable.Drawable
import
android.os.Bundle
import
android.util.SparseBooleanArray
import
android.view.*
import
android.widget.ImageView
import
android.widget.Switch
import
android.widget.Filter
import
android.widget.Filterable
import
android.widget.SearchView
import
androidx.annotation.UiThread
import
androidx.appcompat.app.AppCompatActivity
import
androidx.appcompat.widget.Toolbar
import
androidx.core.content.getSystemService
import
androidx.core.util.set
import
androidx.recyclerview.widget.DefaultItemAnimator
import
androidx.recyclerview.widget.LinearLayoutManager
import
androidx.recyclerview.widget.RecyclerView
...
...
@@ -48,16 +50,19 @@ import com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.utils.DirectBoot
import
com.github.shadowsocks.utils.Key
import
com.google.android.material.snackbar.Snackbar
import
kotlinx.android.synthetic.main.layout_apps.*
import
kotlinx.android.synthetic.main.layout_apps_item.view.*
import
kotlinx.coroutines.*
class
AppManager
:
AppCompatActivity
()
{
companion
object
{
@SuppressLint
(
"StaticFieldLeak"
)
private
var
instance
:
AppManager
?
=
null
private
const
val
SWITCH
=
"switch"
private
var
receiver
:
BroadcastReceiver
?
=
null
private
var
cachedApps
:
List
<
PackageInfo
>?
=
null
private
suspend
fun
get
Apps
(
pm
:
PackageManager
)
=
synchronized
(
AppManager
)
{
private
var
cachedApps
:
Map
<
String
,
PackageInfo
>?
=
null
private
fun
getCached
Apps
(
pm
:
PackageManager
)
=
synchronized
(
AppManager
)
{
if
(
receiver
==
null
)
receiver
=
Core
.
listenForPackageChanges
{
synchronized
(
AppManager
)
{
receiver
=
null
...
...
@@ -67,13 +72,16 @@ class AppManager : AppCompatActivity() {
}
// Labels and icons can change on configuration (locale, etc.) changes, therefore they are not cached.
val
cachedApps
=
cachedApps
?:
pm
.
getInstalledPackages
(
PackageManager
.
GET_PERMISSIONS
)
.
filter
{
it
.
packageName
!=
app
.
packageName
&&
it
.
requestedPermissions
?.
contains
(
Manifest
.
permission
.
INTERNET
)
?:
false
}
.
filter
{
when
(
it
.
packageName
)
{
app
.
packageName
->
false
"android"
->
true
else
->
it
.
requestedPermissions
?.
contains
(
Manifest
.
permission
.
INTERNET
)
==
true
}
}
.
associateBy
{
it
.
packageName
}
this
.
cachedApps
=
cachedApps
cachedApps
}.
map
{
yield
()
ProxiedApp
(
pm
,
it
.
applicationInfo
,
it
.
packageName
)
}
}
...
...
@@ -81,59 +89,86 @@ class AppManager : AppCompatActivity() {
val
packageName
:
String
)
{
val
name
:
CharSequence
=
appInfo
.
loadLabel
(
pm
)
// cached for sorting
val
icon
:
Drawable
get
()
=
appInfo
.
loadIcon
(
pm
)
val
uid
get
()
=
appInfo
.
uid
}
private
inner
class
AppViewHolder
(
view
:
View
)
:
RecyclerView
.
ViewHolder
(
view
),
View
.
OnClickListener
{
private
val
icon
=
view
.
findViewById
<
ImageView
>(
R
.
id
.
itemicon
)
private
val
check
=
view
.
findViewById
<
Switch
>(
R
.
id
.
itemcheck
)
private
lateinit
var
item
:
ProxiedApp
private
val
proxied
get
()
=
proxiedApps
.
contains
(
item
.
packageName
)
init
{
view
.
setOnClickListener
(
this
)
}
fun
bind
(
app
:
ProxiedApp
)
{
this
.
item
=
app
icon
.
setImageDrawable
(
app
.
icon
)
check
.
text
=
app
.
name
check
.
isChecked
=
proxied
item
=
app
itemView
.
itemicon
.
setImageDrawable
(
app
.
icon
)
itemView
.
title
.
text
=
app
.
name
itemView
.
desc
.
text
=
"${app.packageName} (${app.uid})"
itemView
.
itemcheck
.
isChecked
=
isProxiedApp
(
app
)
}
fun
handlePayload
(
payloads
:
List
<
String
>)
{
if
(
payloads
.
contains
(
AppManager
.
SWITCH
))
itemView
.
itemcheck
.
isChecked
=
isProxiedApp
(
item
)
}
override
fun
onClick
(
v
:
View
?)
{
if
(
proxied
)
{
proxiedApps
.
remove
(
item
.
packageName
)
check
.
isChecked
=
false
}
else
{
proxiedApps
.
add
(
item
.
packageName
)
check
.
isChecked
=
true
}
DataStore
.
individual
=
proxiedApps
.
joinToString
(
"\n"
)
if
(
isProxiedApp
(
item
))
proxiedUids
.
delete
(
item
.
uid
)
else
proxiedUids
[
item
.
uid
]
=
true
DataStore
.
individual
=
apps
.
filter
{
isProxiedApp
(
it
)
}.
joinToString
(
"\n"
)
{
it
.
packageName
}
DataStore
.
dirty
=
true
appsAdapter
.
notifyItemRangeChanged
(
0
,
appsAdapter
.
itemCount
,
AppManager
.
SWITCH
)
}
}
private
inner
class
AppsAdapter
:
RecyclerView
.
Adapter
<
AppViewHolder
>()
{
private
var
apps
=
listOf
<
ProxiedApp
>()
private
inner
class
AppsAdapter
:
RecyclerView
.
Adapter
<
AppViewHolder
>()
,
Filterable
{
private
var
filteredApps
=
apps
suspend
fun
reload
()
{
apps
=
getApps
(
packageManager
)
.
sortedWith
(
compareBy
({
!
proxiedApps
.
contains
(
it
.
packageName
)
},
{
it
.
name
.
toString
()
}))
apps
=
getCachedApps
(
packageManager
).
map
{
(
packageName
,
packageInfo
)
->
yield
()
ProxiedApp
(
packageManager
,
packageInfo
.
applicationInfo
,
packageName
)
}.
sortedWith
(
compareBy
({
!
isProxiedApp
(
it
)
},
{
it
.
name
.
toString
()
}))
}
override
fun
onBindViewHolder
(
holder
:
AppViewHolder
,
position
:
Int
)
=
holder
.
bind
(
apps
[
position
])
override
fun
onBindViewHolder
(
holder
:
AppViewHolder
,
position
:
Int
)
=
holder
.
bind
(
filteredApps
[
position
])
override
fun
onBindViewHolder
(
holder
:
AppViewHolder
,
position
:
Int
,
payloads
:
List
<
Any
>)
{
if
(
payloads
.
isNotEmpty
())
{
@Suppress
(
"UNCHECKED_CAST"
)
holder
.
handlePayload
(
payloads
as
List
<
String
>)
return
}
onBindViewHolder
(
holder
,
position
)
}
override
fun
onCreateViewHolder
(
parent
:
ViewGroup
,
viewType
:
Int
):
AppViewHolder
=
AppViewHolder
(
LayoutInflater
.
from
(
parent
.
context
).
inflate
(
R
.
layout
.
layout_apps_item
,
parent
,
false
))
override
fun
getItemCount
():
Int
=
apps
.
size
override
fun
getItemCount
():
Int
=
filteredApps
.
size
private
val
filterImpl
=
object
:
Filter
()
{
override
fun
performFiltering
(
constraint
:
CharSequence
)
=
FilterResults
().
apply
{
val
filteredApps
=
if
(
constraint
.
isEmpty
())
apps
else
apps
.
filter
{
it
.
name
.
contains
(
constraint
,
true
)
||
it
.
packageName
.
contains
(
constraint
,
true
)
||
it
.
uid
.
toString
().
contains
(
constraint
)
}
count
=
filteredApps
.
size
values
=
filteredApps
}
override
fun
publishResults
(
constraint
:
CharSequence
,
results
:
FilterResults
)
{
@Suppress
(
"UNCHECKED_CAST"
)
filteredApps
=
results
.
values
as
List
<
ProxiedApp
>
notifyDataSetChanged
()
}
}
override
fun
getFilter
():
Filter
=
filterImpl
}
private
lateinit
var
proxiedApps
:
HashSet
<
String
>
private
lateinit
var
toolbar
:
Toolbar
private
lateinit
var
bypassSwitch
:
Switch
private
lateinit
var
appListView
:
RecyclerView
private
lateinit
var
loadingView
:
View
private
val
proxiedUids
=
SparseBooleanArray
()
private
val
clipboard
by
lazy
{
getSystemService
<
ClipboardManager
>()
!!
}
private
var
loader
:
Job
?
=
null
private
var
apps
=
emptyList
<
ProxiedApp
>()
private
val
appsAdapter
=
AppsAdapter
()
private
val
shortAnimTime
by
lazy
{
resources
.
getInteger
(
android
.
R
.
integer
.
config_shortAnimTime
).
toLong
()
}
private
fun
View
.
crossFadeFrom
(
other
:
View
)
{
...
...
@@ -150,25 +185,29 @@ class AppManager : AppCompatActivity() {
}).
duration
=
shortAnimTime
}
private
fun
initProxiedApps
(
str
:
String
=
DataStore
.
individual
)
{
proxiedApps
=
str
.
split
(
'\n'
).
toHashSet
()
private
fun
initProxiedUids
(
str
:
String
=
DataStore
.
individual
)
{
proxiedUids
.
clear
()
val
apps
=
getCachedApps
(
packageManager
)
for
(
line
in
str
.
lineSequence
())
proxiedUids
[(
apps
[
line
]
?:
continue
).
applicationInfo
.
uid
]
=
true
}
private
fun
isProxiedApp
(
app
:
ProxiedApp
)
=
proxiedUids
[
app
.
uid
]
@UiThread
private
fun
loadApps
()
{
loader
?.
cancel
()
loader
=
GlobalScope
.
launch
(
Dispatchers
.
Main
,
CoroutineStart
.
UNDISPATCHED
)
{
loading
View
.
crossFadeFrom
(
appListView
)
val
adapter
=
appListView
.
adapter
as
AppsAdapter
loading
.
crossFadeFrom
(
list
)
val
adapter
=
list
.
adapter
as
AppsAdapter
withContext
(
Dispatchers
.
IO
)
{
adapter
.
reload
()
}
adapter
.
notifyDataSetChanged
(
)
appListView
.
crossFadeFrom
(
loadingView
)
adapter
.
filter
.
filter
(
search
.
query
)
list
.
crossFadeFrom
(
loading
)
}
}
override
fun
onCreate
(
savedInstanceState
:
Bundle
?)
{
super
.
onCreate
(
savedInstanceState
)
setContentView
(
R
.
layout
.
layout_apps
)
toolbar
=
findViewById
(
R
.
id
.
toolbar
)
setSupportActionBar
(
toolbar
)
supportActionBar
!!
.
setDisplayHomeAsUpEnabled
(
true
)
...
...
@@ -176,25 +215,29 @@ class AppManager : AppCompatActivity() {
DataStore
.
proxyApps
=
true
DataStore
.
dirty
=
true
}
findViewById
<
Switch
>(
R
.
id
.
onSwitch
).
setOnCheckedChangeListener
{
_
,
checked
->
DataStore
.
proxyApps
=
checked
DataStore
.
dirty
=
true
finish
()
}
bypassSwitch
=
findViewById
(
R
.
id
.
bypassSwitch
)
bypassSwitch
.
isChecked
=
DataStore
.
bypass
bypassSwitch
.
setOnCheckedChangeListener
{
_
,
checked
->
DataStore
.
bypass
=
checked
bypassGroup
.
check
(
if
(
DataStore
.
bypass
)
R
.
id
.
btn_bypass
else
R
.
id
.
btn_on
)
bypassGroup
.
setOnCheckedChangeListener
{
_
,
checkedId
->
DataStore
.
dirty
=
true
when
(
checkedId
)
{
R
.
id
.
btn_off
->
{
DataStore
.
proxyApps
=
false
finish
()
}
R
.
id
.
btn_on
->
DataStore
.
bypass
=
false
R
.
id
.
btn_bypass
->
DataStore
.
bypass
=
true
}
}
initProxiedApps
()
loadingView
=
findViewById
(
R
.
id
.
loading
)
appListView
=
findViewById
(
R
.
id
.
list
)
appListView
.
layoutManager
=
LinearLayoutManager
(
this
,
RecyclerView
.
VERTICAL
,
false
)
appListView
.
itemAnimator
=
DefaultItemAnimator
()
appListView
.
adapter
=
AppsAdapter
()
initProxiedUids
()
list
.
layoutManager
=
LinearLayoutManager
(
this
,
RecyclerView
.
VERTICAL
,
false
)
list
.
itemAnimator
=
DefaultItemAnimator
()
list
.
adapter
=
appsAdapter
search
.
setOnQueryTextListener
(
object
:
SearchView
.
OnQueryTextListener
{
override
fun
onQueryTextSubmit
(
query
:
String
?)
=
false
override
fun
onQueryTextChange
(
newText
:
String
?)
=
true
.
also
{
appsAdapter
.
filter
.
filter
(
newText
)
}
})
instance
=
this
loadApps
()
...
...
@@ -215,14 +258,14 @@ class AppManager : AppCompatActivity() {
ProfileManager
.
updateProfile
(
it
)
}
if
(
DataStore
.
directBootAware
)
DirectBoot
.
update
()
Snackbar
.
make
(
appListView
,
R
.
string
.
action_apply_all
,
Snackbar
.
LENGTH_LONG
).
show
()
}
else
Snackbar
.
make
(
appListView
,
R
.
string
.
action_export_err
,
Snackbar
.
LENGTH_LONG
).
show
()
Snackbar
.
make
(
list
,
R
.
string
.
action_apply_all
,
Snackbar
.
LENGTH_LONG
).
show
()
}
else
Snackbar
.
make
(
list
,
R
.
string
.
action_export_err
,
Snackbar
.
LENGTH_LONG
).
show
()
return
true
}
R
.
id
.
action_export_clipboard
->
{
clipboard
.
primaryClip
=
ClipData
.
newPlainText
(
Key
.
individual
,
"${DataStore.bypass}\n${DataStore.individual}"
)
Snackbar
.
make
(
appListView
,
R
.
string
.
action_export_msg
,
Snackbar
.
LENGTH_LONG
).
show
()
Snackbar
.
make
(
list
,
R
.
string
.
action_export_msg
,
Snackbar
.
LENGTH_LONG
).
show
()
return
true
}
R
.
id
.
action_import_clipboard
->
{
...
...
@@ -232,16 +275,16 @@ class AppManager : AppCompatActivity() {
try
{
val
(
enabled
,
apps
)
=
if
(
i
<
0
)
Pair
(
proxiedAppString
,
""
)
else
Pair
(
proxiedAppString
.
substring
(
0
,
i
),
proxiedAppString
.
substring
(
i
+
1
))
bypass
Switch
.
isChecked
=
enabled
.
toBoolean
(
)
bypass
Group
.
check
(
if
(
enabled
.
toBoolean
())
R
.
id
.
btn_bypass
else
R
.
id
.
btn_on
)
DataStore
.
individual
=
apps
DataStore
.
dirty
=
true
Snackbar
.
make
(
appListView
,
R
.
string
.
action_import_msg
,
Snackbar
.
LENGTH_LONG
).
show
()
initProxied
App
s
(
apps
)
loadApps
(
)
Snackbar
.
make
(
list
,
R
.
string
.
action_import_msg
,
Snackbar
.
LENGTH_LONG
).
show
()
initProxied
Uid
s
(
apps
)
appsAdapter
.
notifyItemRangeChanged
(
0
,
appsAdapter
.
itemCount
,
AppManager
.
SWITCH
)
return
true
}
catch
(
_
:
IllegalArgumentException
)
{
}
}
Snackbar
.
make
(
appListView
,
R
.
string
.
action_import_err
,
Snackbar
.
LENGTH_LONG
).
show
()
Snackbar
.
make
(
list
,
R
.
string
.
action_import_err
,
Snackbar
.
LENGTH_LONG
).
show
()
}
}
return
false
...
...
mobile/src/main/java/com/github/shadowsocks/MainActivity.kt
View file @
952ec45c
...
...
@@ -108,7 +108,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
// service
var
state
=
BaseService
.
State
.
Idle
override
fun
stateChanged
(
state
:
BaseService
.
State
,
profileName
:
String
?,
msg
:
String
?)
=
changeState
(
state
,
msg
,
true
)
override
fun
stateChanged
(
state
:
BaseService
.
State
,
profileName
:
String
?,
msg
:
String
?)
=
changeState
(
state
,
msg
,
true
)
override
fun
trafficUpdated
(
profileId
:
Long
,
stats
:
TrafficStats
)
{
if
(
profileId
==
0L
)
this
@MainActivity
.
stats
.
updateTraffic
(
stats
.
txRate
,
stats
.
rxRate
,
stats
.
txTotal
,
stats
.
rxTotal
)
...
...
@@ -122,7 +123,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
}
private
fun
changeState
(
state
:
BaseService
.
State
,
msg
:
String
?
=
null
,
animate
:
Boolean
=
false
)
{
fab
.
changeState
(
state
,
animate
)
fab
.
changeState
(
state
,
this
.
state
,
animate
)
stats
.
changeState
(
state
)
if
(
msg
!=
null
)
snackbar
(
getString
(
R
.
string
.
vpn_error
,
msg
)).
show
()
this
.
state
=
state
...
...
mobile/src/main/java/com/github/shadowsocks/bg/TileService.kt
View file @
952ec45c
...
...
@@ -41,7 +41,8 @@ class TileService : BaseTileService(), ShadowsocksConnection.Callback {
private
var
tapPending
=
false
private
val
connection
=
ShadowsocksConnection
()
override
fun
stateChanged
(
state
:
BaseService
.
State
,
profileName
:
String
?,
msg
:
String
?)
=
updateTile
(
state
)
{
profileName
}
override
fun
stateChanged
(
state
:
BaseService
.
State
,
profileName
:
String
?,
msg
:
String
?)
=
updateTile
(
state
)
{
profileName
}
override
fun
onServiceConnected
(
service
:
IShadowsocksService
)
{
updateTile
(
BaseService
.
State
.
values
()[
service
.
state
])
{
service
.
profileName
}
if
(
tapPending
)
{
...
...
mobile/src/main/java/com/github/shadowsocks/widget/ServiceButton.kt
View file @
952ec45c
...
...
@@ -68,11 +68,13 @@ class ServiceButton @JvmOverloads constructor(context: Context, attrs: Attribute
return
drawableState
}
fun
changeState
(
state
:
BaseService
.
State
,
animate
:
Boolean
)
{
fun
changeState
(
state
:
BaseService
.
State
,
previousState
:
BaseService
.
State
,
animate
:
Boolean
)
{
when
(
state
)
{
BaseService
.
State
.
Connecting
->
changeState
(
iconConnecting
,
animate
)
BaseService
.
State
.
Connected
->
changeState
(
iconConnected
,
animate
)
BaseService
.
State
.
Stopping
->
changeState
(
iconStopping
,
animate
)
BaseService
.
State
.
Stopping
->
{
changeState
(
iconStopping
,
animate
&&
previousState
==
BaseService
.
State
.
Connected
)
}
else
->
changeState
(
iconStopped
,
animate
)
}
if
(
state
==
BaseService
.
State
.
Connected
)
{
...
...
@@ -86,13 +88,12 @@ class ServiceButton @JvmOverloads constructor(context: Context, attrs: Attribute
isEnabled
=
state
.
canStop
||
state
==
BaseService
.
State
.
Stopped
}
private
fun
counters
(
a
:
AnimatedVectorDrawableCompat
,
b
:
AnimatedVectorDrawableCompat
):
Boolean
=
a
==
iconStopped
&&
b
==
iconConnecting
||
a
==
iconConnecting
&&
b
==
iconStopped
||
a
==
iconConnected
&&
b
==
iconStopping
||
a
==
iconStopping
&&
b
==
iconConnected
private
fun
changeState
(
icon
:
AnimatedVectorDrawableCompat
,
animate
:
Boolean
)
{
fun
counters
(
a
:
AnimatedVectorDrawableCompat
,
b
:
AnimatedVectorDrawableCompat
):
Boolean
=
a
==
iconStopped
&&
b
==
iconConnecting
||
a
==
iconConnecting
&&
b
==
iconStopped
||
a
==
iconConnected
&&
b
==
iconStopping
||
a
==
iconStopping
&&
b
==
iconConnected
if
(
animate
)
{
if
(
animationQueue
.
size
<
2
||
!
counters
(
animationQueue
.
last
,
icon
))
{
animationQueue
.
add
(
icon
)
...
...
mobile/src/main/res/layout/layout_apps.xml
View file @
952ec45c
...
...
@@ -7,41 +7,80 @@
android:layout_height=
"match_parent"
android:orientation=
"vertical"
android:duplicateParentState=
"false"
>
<include
layout=
"@layout/toolbar_light_dark"
/>
<LinearLayout
android:layout_width=
"fill_parent"
android:layout_height=
"wrap_content"
android:elevation=
"1dp"
android:background=
"?android:colorBackground"
android:orientation=
"vertical"
tools:ignore=
"RtlSymmetry"
>
<androidx.appcompat.widget.Toolbar
android:layout_height=
"?attr/actionBarSize"
android:layout_width=
"match_parent"
android:background=
"?attr/colorPrimary"
android:elevation=
"4dp"
android:touchscreenBlocksFocus=
"false"
android:theme=
"@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme=
"@style/ThemeOverlay.AppCompat.DayNight"
android:id=
"@+id/toolbar"
>
<S
witch
android:id=
"@+id/
onSwit
ch"
<S
earchView
android:id=
"@+id/
sear
ch"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:checked=
"true"
android:paddingBottom=
"12dp"
android:paddingEnd=
"16dp"
android:paddingStart=
"72dp"
android:paddingTop=
"12dp"
android:text=
"@string/on"
android:textColor=
"?android:attr/textColorSecondary"
android:textSize=
"18sp"
/>
android:layout_centerHorizontal=
"true"
android:iconifiedByDefault=
"false"
android:queryHint=
"@android:string/search_go"
/>
<Switch
android:id=
"@+id/bypassSwitch"
android:layout_width=
"match_parent"
</androidx.appcompat.widget.Toolbar>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width=
"fill_parent"
android:layout_height=
"wrap_content"
android:background=
"?android:colorBackground"
android:elevation=
"1dp"
>
<TextView
android:id=
"@+id/tv_mode"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginStart=
"16dp"
android:minWidth=
"42dp"
android:text=
"@string/proxied_apps_mode"
app:layout_constraintBottom_toBottomOf=
"@+id/bypassGroup"
app:layout_constraintStart_toStartOf=
"parent"
app:layout_constraintTop_toTopOf=
"@+id/bypassGroup"
/>
<RadioGroup
android:id=
"@+id/bypassGroup"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
android:checked=
"true"
android:paddingBottom=
"12dp"
android:paddingEnd=
"16dp"
android:paddingStart=
"72dp"
android:paddingTop=
"12dp"
android:text=
"@string/bypass_apps"
android:textColor=
"?android:attr/textColorSecondary"
android:textSize=
"18sp"
android:tooltipText=
"@string/bypass_apps_summary"
/>
</LinearLayout>
android:layout_marginStart=
"8dp"
android:layout_marginEnd=
"8dp"
android:orientation=
"horizontal"
app:layout_constraintEnd_toEndOf=
"parent"
app:layout_constraintStart_toEndOf=
"@+id/tv_mode"
app:layout_constraintTop_toTopOf=
"parent"
>
<androidx.appcompat.widget.AppCompatRadioButton
android:id=
"@+id/btn_off"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
android:layout_weight=
"1"
android:text=
"@string/off"
/>
<androidx.appcompat.widget.AppCompatRadioButton
android:id=
"@+id/btn_on"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
android:layout_weight=
"1"
android:text=
"@string/on"
/>
<androidx.appcompat.widget.AppCompatRadioButton
android:id=
"@+id/btn_bypass"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
android:layout_weight=
"1"
android:text=
"@string/bypass_apps"
tools:checked=
"true"
android:tooltipText=
"@string/bypass_apps_summary"
/>
</RadioGroup>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id=
"@+id/snackbar"
android:layout_width=
"match_parent"
...
...
mobile/src/main/res/layout/layout_apps_item.xml
View file @
952ec45c
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:focusable=
"true"
android:background=
"?android:attr/selectableItemBackground"
>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:app=
"http://schemas.android.com/apk/res-auto"
xmlns:tools=
"http://schemas.android.com/tools"
android:id=
"@+id/linearLayout"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:background=
"?android:attr/selectableItemBackground"
android:focusable=
"true"
>
<ImageView
android:id=
"@+id/itemicon"
android:layout_width=
"72dp"
android:layout_height=
"56dp"
android:paddingBottom=
"4dp"
android:paddingEnd=
"16dp"
android:paddingStart=
"8dp"
android:paddingTop=
"4dp"
android:scaleType=
"fitCenter"
/>
android:layout_width=
"40dp"
android:layout_height=
"40dp"
android:layout_marginStart=
"16dp"
android:layout_marginTop=
"8dp"
android:layout_marginBottom=
"8dp"
android:scaleType=
"fitCenter"
app:layout_constraintBottom_toBottomOf=
"parent"
app:layout_constraintStart_toStartOf=
"parent"
app:layout_constraintTop_toTopOf=
"parent"
tools:src=
"@android:drawable/sym_def_app_icon"
/>
<TextView
android:id=
"@+id/title"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
android:layout_marginStart=
"16dp"
android:layout_marginTop=
"4dp"
android:layout_marginEnd=
"8dp"
android:ellipsize=
"end"
android:textColor=
"?android:attr/textColorSecondary"
android:textSize=
"18sp"
app:layout_constraintEnd_toStartOf=
"@+id/itemcheck"
app:layout_constraintStart_toEndOf=
"@+id/itemicon"
app:layout_constraintTop_toTopOf=
"parent"
tools:text=
"Package label"
/>
<TextView
android:id=
"@+id/desc"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
android:layout_marginTop=
"4dp"
android:layout_marginBottom=
"4dp"
android:ellipsize=
"end"
android:textColor=
"?android:attr/textColorTertiary"
android:textSize=
"12sp"
android:breakStrategy=
"high_quality"
app:layout_constraintBottom_toBottomOf=
"parent"
app:layout_constraintEnd_toEndOf=
"@+id/title"
app:layout_constraintStart_toStartOf=
"@+id/title"
app:layout_constraintTop_toBottomOf=
"@+id/title"
tools:text=
"com.package.name (10000)"
/>
<Switch
android:id=
"@+id/itemcheck"
android:layout_width=
"
0dp
"
android:layout_height=
"
fill_par
ent"
android:layout_width=
"
wrap_content
"
android:layout_height=
"
wrap_cont
ent"
android:layout_marginEnd=
"16dp"
android:layout_weight=
"1"
android:clickable=
"false"
android:ellipsize=
"end"
android:focusable=
"false"
android:focusableInTouchMode=
"false"
a
ndroid:gravity=
"center_vertical
"
a
ndroid:textColor=
"?android:attr/textColorSecondary
"
a
ndroid:textSize=
"18sp"
/>
a
pp:layout_constraintBottom_toBottomOf=
"parent
"
a
pp:layout_constraintEnd_toEndOf=
"parent
"
a
pp:layout_constraintTop_toTopOf=
"parent"
/>
</
Linear
Layout>
</
androidx.constraintlayout.widget.Constraint
Layout>
plugin/build.gradle
View file @
952ec45c
...
...
@@ -50,7 +50,7 @@ mavenPublish {
dependencies
{
api
'androidx.core:core-ktx:1.0.1'
api
'com.google.android.material:material:1.1.0-alpha0
3
'
api
'com.google.android.material:material:1.1.0-alpha0
4
'
api
"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
testImplementation
"junit:junit:$junitVersion"
androidTestImplementation
"androidx.test:runner:$androidTestVersion"
...
...
plugin/src/main/res/values-es/strings.xml
0 → 100644
View file @
952ec45c
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string
name=
"proxy_cat"
>
"Propiedades del Servidor"
</string>
<string
name=
"yes"
>
"Sí"
</string>
<string
name=
"no"
>
"No"
</string>
<string
name=
"apply"
>
"Aplicar"
</string>
</resources>
\ No newline at end of file
translate.py
0 → 100755
View file @
952ec45c
#!/usr/bin/env python3
"""
Copyright (C) 2019 by Max Lv <max.c.lv@gmail.com>
Copyright (C) 2019 by Mygod Studio <contact-shadowsocks-android@mygod.be>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Usage: ./translate.py <API token>
Prerequisite: pip install poeditor
See also: https://poeditor.com/docs/languages
"""
import
itertools
import
multiprocessing
import
os
import
sys
import
threading
from
multiprocessing.pool
import
ThreadPool
from
poeditor
import
POEditorAPI
def
main
(
api_token
,
project_id
,
languages
,
tags
,
file_path
):
client
=
POEditorAPI
(
api_token
)
# See also: https://github.com/sporteasy/python-poeditor/pull/15
client
.
FILTER_BY
+=
'proofread'
done
=
0
lock
=
threading
.
Lock
()
def
export_worker
(
params
):
((
language_code
,
language_id
),
(
tag
,
module
))
=
params
output
=
file_path
.
format
(
language_id
,
module
)
try
:
os
.
makedirs
(
os
.
path
.
dirname
(
output
))
except
FileExistsError
:
pass
client
.
export
(
project_id
,
language_code
,
'android_strings'
,
'proofread'
,
tag
,
output
)
with
lock
:
nonlocal
done
done
+=
1
print
(
"{}/{}: {}"
.
format
(
done
,
len
(
languages
)
*
len
(
tags
),
output
))
pool
=
ThreadPool
(
max
(
64
,
multiprocessing
.
cpu_count
()))
pool
.
map
(
export_worker
,
itertools
.
product
(
languages
.
items
(),
tags
.
items
()))
if
__name__
==
"__main__"
:
sys
.
exit
(
main
(
api_token
=
sys
.
argv
[
1
],
project_id
=
'89595'
,
languages
=
{
# Chinese (simplified)
'zh-CN'
:
'zh-rCN'
,
# Chinese (traditional)
'zh-TW'
:
'zh-rTW'
,
# French
'fr'
:
'fr'
,
# Japanese
'ja'
:
'ja'
,
# Korean
'ko'
:
'ko'
,
# Persian
'fa'
:
'fa'
,
# Russian
'ru'
:
'ru'
,
# Spanish
'es'
:
'es'
,
# Turkish
'tr'
:
'tr'
,
},
tags
=
{
'mobile'
:
'core'
,
'plugin'
:
'plugin'
,
},
file_path
=
'{1}/src/main/res/values-{0}/strings.xml'
,
))
tv/build.gradle
View file @
952ec45c
...
...
@@ -25,7 +25,7 @@ android {
targetSdkVersion
rootProject
.
sdkVersion
versionCode
rootProject
.
versionCode
versionName
rootProject
.
versionName
resConfigs
"fa"
,
"fr"
,
"ja"
,
"ko"
,
"ru"
,
"tr"
,
"zh-rCN"
,
"zh-rTW"
resConfigs
rootProject
.
resConfigs
}
buildTypes
{
debug
{
...
...
tv/src/main/AndroidManifest.xml
View file @
952ec45c
...
...
@@ -17,7 +17,8 @@
tools:replace=
"theme"
>
<activity
android:name=
".MainActivity"
android:label=
"@string/app_name"
>
android:label=
"@string/app_name"
android:launchMode=
"singleTask"
>
<intent-filter>
<action
android:name=
"android.intent.action.MAIN"
/>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment