Unverified Commit a95c86ad authored by initrider's avatar initrider Committed by GitHub

Merge pull request #4 from shadowsocks/master

Sync
parents 74dad98f 0f9ca2c5
...@@ -34,6 +34,7 @@ The exclamation mark in the Wi-Fi/cellular icon appears because the system fails ...@@ -34,6 +34,7 @@ The exclamation mark in the Wi-Fi/cellular icon appears because the system fails
* Fixes for Huawei: [#1091 (comment)](https://github.com/shadowsocks/shadowsocks-android/issues/1091#issuecomment-276949836) * Fixes for Huawei: [#1091 (comment)](https://github.com/shadowsocks/shadowsocks-android/issues/1091#issuecomment-276949836)
* Related to Xposed: [#1414](https://github.com/shadowsocks/shadowsocks-android/issues/1414) * Related to Xposed: [#1414](https://github.com/shadowsocks/shadowsocks-android/issues/1414)
* Samsung and/or Brevent: [#1410](https://github.com/shadowsocks/shadowsocks-android/issues/1410) * Samsung and/or Brevent: [#1410](https://github.com/shadowsocks/shadowsocks-android/issues/1410)
* Another Samsung: [#1712](https://github.com/shadowsocks/shadowsocks-android/issues/1712)
* Don't install this app on SD card because of permission issues: [#1124 (comment)](https://github.com/shadowsocks/shadowsocks-android/issues/1124#issuecomment-307556453) * Don't install this app on SD card because of permission issues: [#1124 (comment)](https://github.com/shadowsocks/shadowsocks-android/issues/1124#issuecomment-307556453)
### How to pause Shadowsocks service? ### How to pause Shadowsocks service?
......
Please check the [FAQ](https://github.com/shadowsocks/shadowsocks-android/wiki/FAQ) before submitting new issues. Please check [FAQ](.github/faq.md) before submitting new issues.
And just in case you don't know,
[don't ask questions on issues](https://medium.com/@methane/why-you-must-not-ask-questions-on-github-issues-51d741d83fde).
...@@ -20,10 +20,14 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Kotlin. ...@@ -20,10 +20,14 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Kotlin.
### BUILD ### BUILD
You can check whether the latest commit builds under UNIX environment by checking Travis status.
Building on Windows is also possible since [#1570](https://github.com/shadowsocks/shadowsocks-android/pull/1570),
but probably painful. Further contributions regarding building on Windows are also welcome.
* Set environment variable `ANDROID_HOME` to `/path/to/android-sdk` * Set environment variable `ANDROID_HOME` to `/path/to/android-sdk`
* (optional) Set environment variable `ANDROID_NDK_HOME` to `/path/to/android-ndk` (default: `$ANDROID_HOME/ndk-bundle`) * (optional) Set environment variable `ANDROID_NDK_HOME` to `/path/to/android-ndk` (default: `$ANDROID_HOME/ndk-bundle`)
* Set environment variable `GOROOT_BOOTSTRAP` to `/path/to/go` * Set environment variable `GOROOT_BOOTSTRAP` to `/path/to/go`
* Fetch submodules using `$ git submodule update --init --recursive` * Clone the repo using `git clone --recurse-submodules <repo>` or update submodules using `git submodule update --init --recursive`
* Build it using Android Studio or gradle script * Build it using Android Studio or gradle script
### TRANSLATE ### TRANSLATE
......
...@@ -4,13 +4,13 @@ apply plugin: 'com.github.ben-manes.versions' ...@@ -4,13 +4,13 @@ apply plugin: 'com.github.ben-manes.versions'
buildscript { buildscript {
ext { ext {
kotlinVersion = '1.2.30' kotlinVersion = '1.2.31'
minSdkVersion = 21 minSdkVersion = 21
sdkVersion = 27 sdkVersion = 27
buildToolsVersion = '27.0.3' buildToolsVersion = '27.0.3'
supportLibraryVersion = '27.1.0' supportLibraryVersion = '27.1.0'
takisoftFixVersion = '27.1.0.0' takisoftFixVersion = '27.1.0.0'
playServicesVersion = '11.8.0' playServicesVersion = '12.0.1'
junitVersion = '4.12' junitVersion = '4.12'
androidTestVersion = '1.0.1' androidTestVersion = '1.0.1'
androidEspressoVersion = '3.0.1' androidEspressoVersion = '3.0.1'
...@@ -22,9 +22,9 @@ buildscript { ...@@ -22,9 +22,9 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.0.1' classpath 'com.android.tools.build:gradle:3.1.0'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0' classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0'
classpath 'com.google.gms:google-services:3.2.0' classpath 'com.google.gms:google-services:3.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
} }
} }
......
...@@ -38,7 +38,7 @@ android { ...@@ -38,7 +38,7 @@ android {
task goBuild(type: Exec) { task goBuild(type: Exec) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) { if (Os.isFamily(Os.FAMILY_WINDOWS)) {
executable "cmd.exe" executable "cmd.exe"
args "/c", "src\\overture\\make.bat " + minSdkVersion args "/c", file("src/overture/make.bat").absolutePath , minSdkVersion
} else { } else {
executable "sh" executable "sh"
args "-c", "src/overture/make.bash " + minSdkVersion args "-c", "src/overture/make.bash " + minSdkVersion
...@@ -48,7 +48,7 @@ task goBuild(type: Exec) { ...@@ -48,7 +48,7 @@ task goBuild(type: Exec) {
task goClean(type: Exec) { task goClean(type: Exec) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) { if (Os.isFamily(Os.FAMILY_WINDOWS)) {
executable "cmd.exe" executable "cmd.exe"
args "/c", "src\\overture\\clean.bat" args "/c", file("src/overture/clean.bat").absolutePath
} else { } else {
executable "sh" executable "sh"
args "-c", "src/overture/clean.bash" args "-c", "src/overture/clean.bash"
......
#!/usr/bin/python
# -*- coding: utf-8 -*-
import pkgutil
import urlparse
import socket
import logging
from argparse import ArgumentParser
from datetime import date
__all__ = ['main']
def parse_args():
parser = ArgumentParser()
parser.add_argument('-i', '--input', dest='input', required=True,
help='path to gfwlist', metavar='GFWLIST')
parser.add_argument('-f', '--file', dest='output', required=True,
help='path to output acl', metavar='ACL')
return parser.parse_args()
def decode_gfwlist(content):
# decode base64 if have to
try:
return content.decode('base64')
except:
return content
def get_hostname(something):
try:
# quite enough for GFW
if not something.startswith('http:'):
something = 'http://' + something
r = urlparse.urlparse(something)
return r.hostname
except Exception as e:
logging.error(e)
return None
def add_domain_to_set(s, something):
hostname = get_hostname(something)
if hostname is not None:
if hostname.startswith('.'):
hostname = hostname.lstrip('.')
if hostname.endswith('/'):
hostname = hostname.rstrip('/')
if hostname:
s.add(hostname)
def parse_gfwlist(content):
gfwlist = content.splitlines(False)
domains = set()
for line in gfwlist:
if line.find('.*') >= 0:
continue
elif line.find('*') >= 0:
line = line.replace('*', '/')
if line.startswith('!'):
continue
elif line.startswith('['):
continue
elif line.startswith('@'):
# ignore white list
continue
elif line.startswith('||'):
add_domain_to_set(domains, line.lstrip('||'))
elif line.startswith('|'):
add_domain_to_set(domains, line.lstrip('|'))
elif line.startswith('.'):
add_domain_to_set(domains, line.lstrip('.'))
else:
add_domain_to_set(domains, line)
# TODO: reduce ['www.google.com', 'google.com'] to ['google.com']
return domains
def generate_acl(domains):
header ="""#
# GFW list from https://github.com/gfwlist/gfwlist/blob/master/gfwlist.txt
# updated on DATE
#
[bypass_all]
[proxy_list]
"""
header = header.replace('DATE', str(date.today()))
proxy_content = ""
ip_content = ""
for domain in sorted(domains):
try:
socket.inet_aton(domain)
ip_content += (domain + "\n")
except socket.error:
domain = domain.replace('.', '\.')
proxy_content += ('(^|\.)' + domain + '$\n')
proxy_content = header + ip_content + proxy_content
return proxy_content
def main():
args = parse_args()
with open(args.input, 'rb') as f:
content = f.read()
content = decode_gfwlist(content)
domains = parse_gfwlist(content)
acl_content = generate_acl(domains)
with open(args.output, 'wb') as f:
f.write(acl_content)
if __name__ == '__main__':
main()
Subproject commit 1a21989566e778139c489d5f8972979bda9b16c1 Subproject commit a4c9059ffe515d86b79724a289c0772ce9f2cf09
...@@ -28,21 +28,35 @@ MKDIR %TARGET%\armeabi-v7a>nul 2>nul ...@@ -28,21 +28,35 @@ MKDIR %TARGET%\armeabi-v7a>nul 2>nul
MKDIR %TARGET%\x86>nul 2>nul MKDIR %TARGET%\x86>nul 2>nul
MKDIR %TARGET%\arm64-v8a>nul 2>nul MKDIR %TARGET%\arm64-v8a>nul 2>nul
SET CC=%ANDROID_ARM_TOOLCHAIN%\bin\arm-linux-androideabi-gcc.exe
REM Check environment availability
IF NOT EXIST %CC% (
ECHO "gcc not found"
EXIT 1
)
WHERE python.exe
IF "%ERRORLEVEL%" == 1 (
ECHO "python not found"
EXIT 1
)
IF NOT EXIST %ANDROID_ARM_CC% ( IF NOT EXIST %ANDROID_ARM_CC% (
ECHO "Make standalone toolchain for ARM arch" ECHO "Make standalone toolchain for ARM arch"
%ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch arm ^ python.exe %ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch arm ^
--api %MIN_API% --install-dir %ANDROID_ARM_TOOLCHAIN% --api %MIN_API% --install-dir %ANDROID_ARM_TOOLCHAIN%
) )
IF NOT EXIST %ANDROID_ARM64_CC% ( IF NOT EXIST %ANDROID_ARM64_CC% (
ECHO "Make standalone toolchain for ARM64 arch" ECHO "Make standalone toolchain for ARM64 arch"
%ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch arm64 ^ python.exe %ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch arm64 ^
--api %MIN_API% --install-dir %ANDROID_ARM64_TOOLCHAIN% --api %MIN_API% --install-dir %ANDROID_ARM64_TOOLCHAIN%
) )
IF NOT EXIST %ANDROID_X86_CC% ( IF NOT EXIST %ANDROID_X86_CC% (
ECHO "Make standalone toolchain for X86 arch" ECHO "Make standalone toolchain for X86 arch"
%ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch x86 ^ python.exe %ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch x86 ^
--api %MIN_API% --install-dir %ANDROID_X86_TOOLCHAIN% --api %MIN_API% --install-dir %ANDROID_X86_TOOLCHAIN%
) )
...@@ -118,4 +132,4 @@ IF %BUILD% == 1 ( ...@@ -118,4 +132,4 @@ IF %BUILD% == 1 (
) )
ECHO "Successfully build overture" ECHO "Successfully build overture"
ENDLOCAL ENDLOCAL
\ No newline at end of file
Subproject commit e7594118e8cf164ffb53923b9b89ae169cb87156 Subproject commit afe34f99d4914ab35f6e423c042ed072f5697e05
# https://github.com/arturbosch/detekt/blob/801994bd60cc759b181649356cea045b8125301b/detekt-cli/src/main/resources/default-detekt-config.yml # https://github.com/arturbosch/detekt/blob/RC6-4/detekt-cli/src/main/resources/default-detekt-config.yml
comments: comments:
active: false active: false
...@@ -7,7 +7,7 @@ complexity: ...@@ -7,7 +7,7 @@ complexity:
active: true active: true
ComplexCondition: ComplexCondition:
active: true active: true
threshold: 3 threshold: 4
ComplexInterface: ComplexInterface:
active: true active: true
threshold: 10 threshold: 10
...@@ -15,6 +15,7 @@ complexity: ...@@ -15,6 +15,7 @@ complexity:
ComplexMethod: ComplexMethod:
active: true active: true
threshold: 10 threshold: 10
ignoreSingleWhenExpression: false
LabeledExpression: LabeledExpression:
active: true active: true
LargeClass: LargeClass:
...@@ -25,31 +26,32 @@ complexity: ...@@ -25,31 +26,32 @@ complexity:
threshold: 20 threshold: 20
LongParameterList: LongParameterList:
active: true active: true
threshold: 5 threshold: 6
ignoreDefaultParameters: true ignoreDefaultParameters: true
MethodOverloading: MethodOverloading:
active: false active: false
NestedBlockDepth: NestedBlockDepth:
active: true active: true
threshold: 3 threshold: 4
StringLiteralDuplication: StringLiteralDuplication:
active: true active: true
threshold: 2 threshold: 3
ignoreAnnotation: true ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^' ignoreStringsRegex: '$^'
TooManyFunctions: TooManyFunctions:
active: true active: true
thresholdInFiles: 10 thresholdInFiles: 11
thresholdInClasses: 10 thresholdInClasses: 11
thresholdInInterfaces: 10 thresholdInInterfaces: 11
thresholdInObjects: 10 thresholdInObjects: 11
thresholdInEnums: 10 thresholdInEnums: 11
empty-blocks: empty-blocks:
active: true active: true
EmptyCatchBlock: EmptyCatchBlock:
active: false active: true
allowedExceptionNameRegex: "^(_|ignore|expected).*"
EmptyClassBlock: EmptyClassBlock:
active: true active: true
EmptyDefaultConstructor: EmptyDefaultConstructor:
...@@ -64,6 +66,7 @@ empty-blocks: ...@@ -64,6 +66,7 @@ empty-blocks:
active: true active: true
EmptyFunctionBlock: EmptyFunctionBlock:
active: true active: true
ignoreOverriddenFunctions: false
EmptyIfBlock: EmptyIfBlock:
active: true active: true
EmptyInitBlock: EmptyInitBlock:
...@@ -105,7 +108,7 @@ exceptions: ...@@ -105,7 +108,7 @@ exceptions:
active: true active: true
TooGenericExceptionCaught: TooGenericExceptionCaught:
active: true active: true
exceptions: exceptionNames:
- ArrayIndexOutOfBoundsException - ArrayIndexOutOfBoundsException
- Error - Error
- Exception - Exception
...@@ -116,10 +119,9 @@ exceptions: ...@@ -116,10 +119,9 @@ exceptions:
- Throwable - Throwable
TooGenericExceptionThrown: TooGenericExceptionThrown:
active: true active: true
exceptions: exceptionNames:
- Error - Error
- Exception - Exception
- NullPointerException
- Throwable - Throwable
- RuntimeException - RuntimeException
...@@ -143,6 +145,7 @@ naming: ...@@ -143,6 +145,7 @@ naming:
FunctionNaming: FunctionNaming:
active: true active: true
functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$' functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
excludeClassPattern: '$^'
MatchingDeclarationName: MatchingDeclarationName:
active: true active: true
MemberNameEqualsClassName: MemberNameEqualsClassName:
...@@ -167,6 +170,7 @@ naming: ...@@ -167,6 +170,7 @@ naming:
active: true active: true
variablePattern: '[a-z][A-Za-z0-9]*' variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
performance: performance:
active: true active: true
...@@ -239,15 +243,17 @@ style: ...@@ -239,15 +243,17 @@ style:
maxLineLength: 120 maxLineLength: 120
excludePackageStatements: false excludePackageStatements: false
excludeImportStatements: false excludeImportStatements: false
MayBeConst:
active: true
ModifierOrder: ModifierOrder:
active: true active: true
NestedClassesVisibility: NestedClassesVisibility:
active: true active: true
NewLineAtEndOfFile: NewLineAtEndOfFile:
active: true active: true
OptionalAbstractKeyword: NoTabs:
active: true active: true
OptionalReturnKeyword: OptionalAbstractKeyword:
active: true active: true
OptionalUnit: OptionalUnit:
active: true active: true
...@@ -268,6 +274,8 @@ style: ...@@ -268,6 +274,8 @@ style:
ThrowsCount: ThrowsCount:
active: true active: true
max: 2 max: 2
TrailingWhitespace:
active: true
UnnecessaryAbstractClass: UnnecessaryAbstractClass:
active: true active: true
UnnecessaryInheritance: UnnecessaryInheritance:
...@@ -278,6 +286,8 @@ style: ...@@ -278,6 +286,8 @@ style:
active: true active: true
UnusedImports: UnusedImports:
active: true active: true
UnusedPrivateMember:
active: true
UseDataClass: UseDataClass:
active: false active: false
UtilityClassWithPublicConstructor: UtilityClassWithPublicConstructor:
......
...@@ -21,8 +21,8 @@ android { ...@@ -21,8 +21,8 @@ android {
applicationId "com.github.shadowsocks" applicationId "com.github.shadowsocks"
minSdkVersion rootProject.minSdkVersion minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.sdkVersion targetSdkVersion rootProject.sdkVersion
versionCode 4050100 versionCode 4050400
versionName "4.5.1" versionName "4.5.4"
testApplicationId "com.github.shadowsocks.test" testApplicationId "com.github.shadowsocks.test"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
resConfigs "fa", "fr", "ja", "ko", "ru", "zh-rCN", "zh-rTW" resConfigs "fa", "fr", "ja", "ko", "ru", "zh-rCN", "zh-rTW"
...@@ -58,7 +58,7 @@ dependencies { ...@@ -58,7 +58,7 @@ dependencies {
implementation "com.android.support:design:$supportLibraryVersion" implementation "com.android.support:design:$supportLibraryVersion"
implementation "com.android.support:gridlayout-v7:$supportLibraryVersion" implementation "com.android.support:gridlayout-v7:$supportLibraryVersion"
implementation 'com.futuremind.recyclerfastscroll:fastscroll:0.2.5' implementation 'com.futuremind.recyclerfastscroll:fastscroll:0.2.5'
implementation 'com.evernote:android-job:1.2.4' implementation 'com.evernote:android-job:1.2.5'
implementation "com.google.android.gms:play-services-ads:$playServicesVersion" implementation "com.google.android.gms:play-services-ads:$playServicesVersion"
implementation "com.google.android.gms:play-services-analytics:$playServicesVersion" implementation "com.google.android.gms:play-services-analytics:$playServicesVersion"
implementation "com.google.android.gms:play-services-vision:$playServicesVersion" implementation "com.google.android.gms:play-services-vision:$playServicesVersion"
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -23,6 +23,7 @@ package com.github.shadowsocks ...@@ -23,6 +23,7 @@ package com.github.shadowsocks
import android.app.Application import android.app.Application
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.admin.DevicePolicyManager
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
...@@ -33,8 +34,8 @@ import android.content.res.Configuration ...@@ -33,8 +34,8 @@ import android.content.res.Configuration
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.UserManager
import android.support.annotation.RequiresApi import android.support.annotation.RequiresApi
import android.support.v4.os.UserManagerCompat
import android.support.v7.app.AppCompatDelegate import android.support.v7.app.AppCompatDelegate
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
...@@ -71,7 +72,12 @@ class App : Application() { ...@@ -71,7 +72,12 @@ class App : Application() {
val deviceContext: Context by lazy { if (Build.VERSION.SDK_INT < 24) this else DeviceContext(this) } val deviceContext: Context by lazy { if (Build.VERSION.SDK_INT < 24) this else DeviceContext(this) }
val remoteConfig: FirebaseRemoteConfig by lazy { FirebaseRemoteConfig.getInstance() } val remoteConfig: FirebaseRemoteConfig by lazy { FirebaseRemoteConfig.getInstance() }
private val tracker: Tracker by lazy { GoogleAnalytics.getInstance(deviceContext).newTracker(R.xml.tracker) } private val tracker: Tracker by lazy { GoogleAnalytics.getInstance(deviceContext).newTracker(R.xml.tracker) }
private val exceptionParser by lazy { StandardExceptionParser(this, null) }
val info: PackageInfo by lazy { getPackageInfo(packageName) } val info: PackageInfo by lazy { getPackageInfo(packageName) }
val directBootSupported by lazy {
Build.VERSION.SDK_INT >= 24 && getSystemService(DevicePolicyManager::class.java)
.storageEncryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER
}
fun getPackageInfo(packageName: String) = fun getPackageInfo(packageName: String) =
packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)!! packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)!!
...@@ -101,7 +107,7 @@ class App : Application() { ...@@ -101,7 +107,7 @@ class App : Application() {
fun track(t: Throwable) = track(Thread.currentThread(), t) fun track(t: Throwable) = track(Thread.currentThread(), t)
fun track(thread: Thread, t: Throwable) { fun track(thread: Thread, t: Throwable) {
tracker.send(HitBuilders.ExceptionBuilder() tracker.send(HitBuilders.ExceptionBuilder()
.setDescription(StandardExceptionParser(this, null).getDescription(thread.name, t)) .setDescription("${exceptionParser.getDescription(thread.name, t)} - ${t.message}")
.setFatal(false) .setFatal(false)
.build()) .build())
t.printStackTrace() t.printStackTrace()
...@@ -138,14 +144,15 @@ class App : Application() { ...@@ -138,14 +144,15 @@ class App : Application() {
app.track(e) app.track(e)
} }
// handle data restored // handle data restored/crash
if (DataStore.directBootAware && UserManagerCompat.isUserUnlocked(this)) DirectBoot.update() if (Build.VERSION.SDK_INT >= 24 && DataStore.directBootAware &&
(getSystemService(Context.USER_SERVICE) as UserManager).isUserUnlocked) DirectBoot.flushTrafficStats()
TcpFastOpen.enabledAsync(DataStore.publicStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled)) TcpFastOpen.enabledAsync(DataStore.publicStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
if (DataStore.publicStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) { if (DataStore.publicStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) {
val assetManager = assets val assetManager = assets
for (dir in arrayOf("acl", "overture")) for (dir in arrayOf("acl", "overture"))
try { try {
for (file in assetManager.list(dir)) assetManager.open(dir + '/' + file).use { input -> for (file in assetManager.list(dir)) assetManager.open("$dir/$file").use { input ->
File(deviceContext.filesDir, file).outputStream().use { output -> input.copyTo(output) } File(deviceContext.filesDir, file).outputStream().use { output -> input.copyTo(output) }
} }
} catch (e: IOException) { } catch (e: IOException) {
......
...@@ -62,23 +62,21 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener { ...@@ -62,23 +62,21 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
private var receiver: BroadcastReceiver? = null private var receiver: BroadcastReceiver? = null
private var cachedApps: List<PackageInfo>? = null private var cachedApps: List<PackageInfo>? = null
private fun getApps(pm: PackageManager): List<ProxiedApp> { private fun getApps(pm: PackageManager) = synchronized(AppManager) {
return synchronized(AppManager) { if (receiver == null) receiver = app.listenForPackageChanges {
if (receiver == null) receiver = app.listenForPackageChanges { synchronized(AppManager) {
synchronized(AppManager) { receiver = null
receiver = null cachedApps = null
cachedApps = null
}
AppManager.instance?.reloadApps()
} }
// Labels and icons can change on configuration (locale, etc.) changes, therefore they are not cached. AppManager.instance?.reloadApps()
val cachedApps = cachedApps ?: pm.getInstalledPackages(PackageManager.GET_PERMISSIONS) }
.filter { it.packageName != app.packageName && // Labels and icons can change on configuration (locale, etc.) changes, therefore they are not cached.
it.requestedPermissions?.contains(Manifest.permission.INTERNET) ?: false } val cachedApps = cachedApps ?: pm.getInstalledPackages(PackageManager.GET_PERMISSIONS)
this.cachedApps = cachedApps .filter { it.packageName != app.packageName &&
cachedApps it.requestedPermissions?.contains(Manifest.permission.INTERNET) ?: false }
}.map { ProxiedApp(pm, it.applicationInfo, it.packageName) } this.cachedApps = cachedApps
} cachedApps
}.map { ProxiedApp(pm, it.applicationInfo, it.packageName) }
} }
private class ProxiedApp(private val pm: PackageManager, private val appInfo: ApplicationInfo, private class ProxiedApp(private val pm: PackageManager, private val appInfo: ApplicationInfo,
...@@ -155,7 +153,7 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener { ...@@ -155,7 +153,7 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
appListView.visibility = View.GONE appListView.visibility = View.GONE
fastScroller.visibility = View.GONE fastScroller.visibility = View.GONE
loadingView.visibility = View.VISIBLE loadingView.visibility = View.VISIBLE
thread { thread("AppManager-loader") {
val adapter = appListView.adapter as AppsAdapter val adapter = appListView.adapter as AppsAdapter
do { do {
appsLoading.set(true) appsLoading.set(true)
......
...@@ -20,12 +20,12 @@ ...@@ -20,12 +20,12 @@
package com.github.shadowsocks package com.github.shadowsocks
import android.app.admin.DevicePolicyManager
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.Snackbar import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference import android.support.v14.preference.SwitchPreference
import android.support.v7.preference.Preference import android.support.v7.preference.Preference
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.DirectBoot
...@@ -39,20 +39,17 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompatDividers() { ...@@ -39,20 +39,17 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompatDividers() {
DataStore.initGlobal() DataStore.initGlobal()
addPreferencesFromResource(R.xml.pref_global) addPreferencesFromResource(R.xml.pref_global)
val boot = findPreference(Key.isAutoConnect) as SwitchPreference val boot = findPreference(Key.isAutoConnect) as SwitchPreference
val directBootAwareListener = Preference.OnPreferenceChangeListener { _, newValue ->
if (newValue as Boolean) DirectBoot.update() else DirectBoot.clean()
true
}
boot.setOnPreferenceChangeListener { _, value -> boot.setOnPreferenceChangeListener { _, value ->
BootReceiver.enabled = value as Boolean BootReceiver.enabled = value as Boolean
directBootAwareListener.onPreferenceChange(null, DataStore.directBootAware) true
} }
boot.isChecked = BootReceiver.enabled boot.isChecked = BootReceiver.enabled
val dba = findPreference(Key.directBootAware) val canToggleLocked = findPreference(Key.directBootAware)
if (Build.VERSION.SDK_INT >= 24 && requireContext().getSystemService(DevicePolicyManager::class.java) if (Build.VERSION.SDK_INT >= 24) canToggleLocked.setOnPreferenceChangeListener { _, newValue ->
.storageEncryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER) if (app.directBootSupported && newValue as Boolean) DirectBoot.update() else DirectBoot.clean()
dba.onPreferenceChangeListener = directBootAwareListener else dba.parent!!.removePreference(dba) true
} else canToggleLocked.parent!!.removePreference(canToggleLocked)
val tfo = findPreference(Key.tfo) as SwitchPreference val tfo = findPreference(Key.tfo) as SwitchPreference
tfo.isChecked = TcpFastOpen.sendEnabled tfo.isChecked = TcpFastOpen.sendEnabled
......
...@@ -266,18 +266,20 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe ...@@ -266,18 +266,20 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
++testCount ++testCount
statusText.setText(R.string.connection_test_testing) statusText.setText(R.string.connection_test_testing)
val id = testCount // it would change by other code val id = testCount // it would change by other code
thread { testConnection(id) } thread("ConnectionTest") { testConnection(id) }
} }
} }
fab = findViewById(R.id.fab) fab = findViewById(R.id.fab)
fab.setOnClickListener { fab.setOnClickListener {
if (state == BaseService.CONNECTED) app.stopService() else thread { when {
if (BaseService.usingVpnMode) { state == BaseService.CONNECTED -> app.stopService()
BaseService.usingVpnMode -> {
val intent = VpnService.prepare(this) val intent = VpnService.prepare(this)
if (intent != null) startActivityForResult(intent, REQUEST_CONNECT) if (intent != null) startActivityForResult(intent, REQUEST_CONNECT)
else app.handler.post { onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null) } else onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)
} else app.startService() }
else -> app.startService()
} }
} }
......
...@@ -49,6 +49,7 @@ import okhttp3.OkHttpClient ...@@ -49,6 +49,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.json.JSONObject import org.json.JSONObject
import java.io.File import java.io.File
import java.io.IOException
import java.net.UnknownHostException import java.net.UnknownHostException
import java.security.MessageDigest import java.security.MessageDigest
import java.util.* import java.util.*
...@@ -116,7 +117,10 @@ object BaseService { ...@@ -116,7 +117,10 @@ object BaseService {
val item = callbacks.getBroadcastItem(i) val item = callbacks.getBroadcastItem(i)
if (bandwidthListeners.contains(item.asBinder())) if (bandwidthListeners.contains(item.asBinder()))
item.trafficUpdated(profile!!.id, txRate, rxRate, txTotal, rxTotal) item.trafficUpdated(profile!!.id, txRate, rxRate, txTotal, rxTotal)
} catch (_: Exception) { } // ignore } catch (e: Exception) {
e.printStackTrace()
app.track(e)
}
callbacks.finishBroadcast() callbacks.finishBroadcast()
} }
} }
...@@ -145,22 +149,35 @@ object BaseService { ...@@ -145,22 +149,35 @@ object BaseService {
} }
internal fun updateTrafficTotal(tx: Long, rx: Long) { internal fun updateTrafficTotal(tx: Long, rx: Long) {
// this.profile may have host, etc. modified and thus a re-fetch is necessary (possible race condition) try {
val profile = ProfileManager.getProfile((profile ?: return).id) ?: return // this.profile may have host, etc. modified and thus a re-fetch is necessary (possible race condition)
profile.tx += tx val profile = ProfileManager.getProfile((profile ?: return).id) ?: return
profile.rx += rx profile.tx += tx
ProfileManager.updateProfile(profile) profile.rx += rx
app.handler.post { ProfileManager.updateProfile(profile)
if (bandwidthListeners.isNotEmpty()) { app.handler.post {
val n = callbacks.beginBroadcast() if (bandwidthListeners.isNotEmpty()) {
for (i in 0 until n) { val n = callbacks.beginBroadcast()
try { for (i in 0 until n) {
val item = callbacks.getBroadcastItem(i) try {
if (bandwidthListeners.contains(item.asBinder())) item.trafficPersisted(profile.id) val item = callbacks.getBroadcastItem(i)
} catch (_: Exception) { } // ignore if (bandwidthListeners.contains(item.asBinder())) item.trafficPersisted(profile.id)
} catch (e: Exception) {
e.printStackTrace()
app.track(e)
}
}
callbacks.finishBroadcast()
} }
callbacks.finishBroadcast()
} }
} catch (e: IOException) {
if (!DataStore.directBootAware) throw e // we should only reach here because we're in direct boot
val profile = DirectBoot.getDeviceProfile()!!
profile.tx += tx
profile.rx += rx
profile.dirty = true
DirectBoot.update(profile)
DirectBoot.listenForUnlock()
} }
} }
...@@ -200,7 +217,10 @@ object BaseService { ...@@ -200,7 +217,10 @@ object BaseService {
val n = callbacks.beginBroadcast() val n = callbacks.beginBroadcast()
for (i in 0 until n) try { for (i in 0 until n) try {
callbacks.getBroadcastItem(i).stateChanged(s, binder.profileName, msg) callbacks.getBroadcastItem(i).stateChanged(s, binder.profileName, msg)
} catch (_: Exception) { } // ignore } catch (e: Exception) {
e.printStackTrace()
app.track(e)
}
callbacks.finishBroadcast() callbacks.finishBroadcast()
} }
state = s state = s
...@@ -235,6 +255,7 @@ object BaseService { ...@@ -235,6 +255,7 @@ object BaseService {
fun startNativeProcesses() { fun startNativeProcesses() {
val data = data val data = data
val profile = data.profile!!
val cmd = buildAdditionalArguments(arrayListOf( val cmd = buildAdditionalArguments(arrayListOf(
File((this as Context).applicationInfo.nativeLibraryDir, Executable.SS_LOCAL).absolutePath, File((this as Context).applicationInfo.nativeLibraryDir, Executable.SS_LOCAL).absolutePath,
"-u", "-u",
...@@ -249,6 +270,8 @@ object BaseService { ...@@ -249,6 +270,8 @@ object BaseService {
cmd += acl.absolutePath cmd += acl.absolutePath
} }
if (profile.udpdns) cmd += "-D"
if (TcpFastOpen.sendEnabled) cmd += "--fast-open" if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
data.sslocalProcess = GuardedProcess(cmd).start() data.sslocalProcess = GuardedProcess(cmd).start()
...@@ -341,7 +364,7 @@ object BaseService { ...@@ -341,7 +364,7 @@ object BaseService {
data.changeState(CONNECTING) data.changeState(CONNECTING)
thread { thread("$tag-Connecting") {
try { try {
if (profile.host == "198.199.101.152") { if (profile.host == "198.199.101.152") {
val client = OkHttpClient.Builder() val client = OkHttpClient.Builder()
......
...@@ -28,6 +28,7 @@ import com.github.shadowsocks.BuildConfig ...@@ -28,6 +28,7 @@ import com.github.shadowsocks.BuildConfig
import com.github.shadowsocks.JniHelper import com.github.shadowsocks.JniHelper
import com.github.shadowsocks.utils.Commandline import com.github.shadowsocks.utils.Commandline
import com.github.shadowsocks.utils.thread import com.github.shadowsocks.utils.thread
import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.util.concurrent.Semaphore import java.util.concurrent.Semaphore
...@@ -42,8 +43,9 @@ class GuardedProcess(private val cmd: List<String>) { ...@@ -42,8 +43,9 @@ class GuardedProcess(private val cmd: List<String>) {
private var isDestroyed = false private var isDestroyed = false
@Volatile @Volatile
private lateinit var process: Process private lateinit var process: Process
private val name = File(cmd.first()).nameWithoutExtension
private fun streamLogger(input: InputStream, logger: (String, String) -> Int) = thread { private fun streamLogger(input: InputStream, logger: (String, String) -> Int) = thread("StreamLogger-$name") {
try { try {
input.bufferedReader().useLines { it.forEach { logger(TAG, it) } } input.bufferedReader().useLines { it.forEach { logger(TAG, it) } }
} catch (_: IOException) { } // ignore } catch (_: IOException) { } // ignore
...@@ -53,7 +55,7 @@ class GuardedProcess(private val cmd: List<String>) { ...@@ -53,7 +55,7 @@ class GuardedProcess(private val cmd: List<String>) {
val semaphore = Semaphore(1) val semaphore = Semaphore(1)
semaphore.acquire() semaphore.acquire()
var ioException: IOException? = null var ioException: IOException? = null
guardThread = thread(name = "GuardThread-" + cmd.first()) { guardThread = thread("GuardThread-$name") {
try { try {
var callback: (() -> Unit)? = null var callback: (() -> Unit)? = null
while (!isDestroyed) { while (!isDestroyed) {
......
...@@ -72,19 +72,18 @@ object LocalDnsService { ...@@ -72,19 +72,18 @@ object LocalDnsService {
.put("MinimumTTL", 120) .put("MinimumTTL", 120)
.put("CacheSize", 4096) .put("CacheSize", 4096)
val remoteDns = JSONArray(profile.remoteDns.split(",") val remoteDns = JSONArray(profile.remoteDns.split(",")
.mapIndexed { i, dns -> makeDns("UserDef-" + i, .mapIndexed { i, dns -> makeDns("UserDef-$i", dns.trim() + ":53", 9) })
dns.trim() + ":53", 9) })
val localDns = JSONArray(arrayOf( val localDns = JSONArray(arrayOf(
makeDns("Primary-1", "119.29.29.29:53", 3, false), makeDns("Primary-1", "208.67.222.222:443", 3, false),
makeDns("Primary-2", "114.114.114.114:53", 3, false), makeDns("Primary-2", "119.29.29.29:53", 3, false),
makeDns("Primary-3", "208.67.222.222:443", 3, false) makeDns("Primary-3", "114.114.114.114:53", 3, false)
)) ))
when (profile.route) { when (profile.route) {
Acl.BYPASS_CHN, Acl.BYPASS_LAN_CHN, Acl.GFWLIST, Acl.CUSTOM_RULES -> config Acl.BYPASS_CHN, Acl.BYPASS_LAN_CHN, Acl.GFWLIST, Acl.CUSTOM_RULES -> config
.put("PrimaryDNS", localDns) .put("PrimaryDNS", localDns)
.put("AlternativeDNS", remoteDns) .put("AlternativeDNS", remoteDns)
.put("DomainFile", data.aclFile!!.absolutePath) .put("IPNetworkFile", "china_ip_list.txt")
Acl.CHINALIST -> config Acl.CHINALIST -> config
.put("PrimaryDNS", localDns) .put("PrimaryDNS", localDns)
.put("AlternativeDNS", remoteDns) .put("AlternativeDNS", remoteDns)
......
...@@ -28,7 +28,7 @@ import com.github.shadowsocks.App.Companion.app ...@@ -28,7 +28,7 @@ import com.github.shadowsocks.App.Companion.app
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
abstract class LocalSocketListener(protected val tag: String) : Thread() { abstract class LocalSocketListener(protected val tag: String) : Thread(tag) {
init { init {
setUncaughtExceptionHandler(app::track) setUncaughtExceptionHandler(app::track)
} }
......
...@@ -82,7 +82,7 @@ class TileService : BaseTileService(), ShadowsocksConnection.Interface { ...@@ -82,7 +82,7 @@ class TileService : BaseTileService(), ShadowsocksConnection.Interface {
} }
override fun onClick() { override fun onClick() {
if (isLocked && !DataStore.directBootAware) unlockAndRun(this::toggle) else toggle() if (isLocked && !DataStore.canToggleLocked) unlockAndRun(this::toggle) else toggle()
} }
private fun toggle() { private fun toggle() {
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
package com.github.shadowsocks.bg package com.github.shadowsocks.bg
import android.annotation.TargetApi
import android.app.Service import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
...@@ -28,6 +29,7 @@ import android.net.* ...@@ -28,6 +29,7 @@ import android.net.*
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.support.v4.os.BuildCompat
import android.util.Log import android.util.Log
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.JniHelper import com.github.shadowsocks.JniHelper
...@@ -116,12 +118,14 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -116,12 +118,14 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
private var worker: ProtectWorker? = null private var worker: ProtectWorker? = null
private var tun2socksProcess: GuardedProcess? = null private var tun2socksProcess: GuardedProcess? = null
private var underlyingNetwork: Network? = null private var underlyingNetwork: Network? = null
@TargetApi(28)
set(value) { set(value) {
if (Build.VERSION.SDK_INT >= 22) setUnderlyingNetworks(if (value == null) null else arrayOf(value)) setUnderlyingNetworks(if (value == null) null else arrayOf(value))
field = value field = value
} }
private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager } private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
@TargetApi(28)
private val defaultNetworkCallback = object : ConnectivityManager.NetworkCallback() { private val defaultNetworkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) { override fun onAvailable(network: Network) {
underlyingNetwork = network underlyingNetwork = network
...@@ -229,9 +233,11 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -229,9 +233,11 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
this.conn = conn this.conn = conn
val fd = conn.fd val fd = conn.fd
// we want REQUEST here instead of LISTEN if (BuildCompat.isAtLeastP()) {
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback) // we want REQUEST here instead of LISTEN
listeningForDefaultNetwork = true connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
listeningForDefaultNetwork = true
}
val cmd = arrayListOf(File(applicationInfo.nativeLibraryDir, Executable.TUN2SOCKS).absolutePath, val cmd = arrayListOf(File(applicationInfo.nativeLibraryDir, Executable.TUN2SOCKS).absolutePath,
"--netif-ipaddr", PRIVATE_VLAN.format(Locale.ENGLISH, "2"), "--netif-ipaddr", PRIVATE_VLAN.format(Locale.ENGLISH, "2"),
......
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.database
import android.database.sqlite.SQLiteDatabaseLockedException
import com.j256.ormlite.dao.Dao
import com.j256.ormlite.support.ConnectionSource
import com.j256.ormlite.table.TableUtils
import java.sql.SQLException
private val Throwable.ultimateCause: Throwable get() {
var result = this
while (true) {
val cause = result.cause ?: return result
result = cause
}
}
@Throws(SQLException::class)
fun <T> safeWrapper(func: () -> T): T {
while (true) {
try {
return func()
} catch (e: SQLException) {
if (e.ultimateCause !is SQLiteDatabaseLockedException) throw e
}
}
}
@Throws(SQLException::class)
inline fun <reified T> ConnectionSource.createTableSafe() = safeWrapper { TableUtils.createTable(this, T::class.java) }
@Throws(SQLException::class)
fun <T, ID> Dao<T, ID>.queryAllSafe(): MutableList<T> = safeWrapper { queryForAll() }
@Throws(SQLException::class)
fun <T, ID> Dao<T, ID>.queryByIdSafe(id: ID?): T? = safeWrapper { queryForId(id) }
@Throws(SQLException::class)
fun <T, ID> Dao<T, ID>.updateSafe(data: T?): Int = safeWrapper { update(data) }
@Throws(SQLException::class)
fun <T, ID> Dao<T, ID>.replaceSafe(data: T?): Dao.CreateOrUpdateStatus? = safeWrapper { createOrUpdate(data) }
@Throws(SQLException::class)
fun <T, ID> Dao<T, ID>.deleteByIdSafe(id: ID?) = safeWrapper { deleteById(id) }
@Throws(SQLException::class)
fun <T, ID> Dao<T, ID>.executeSafe(statement: String?) = safeWrapper { executeRawNoArgs(statement) }
...@@ -37,18 +37,18 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25) ...@@ -37,18 +37,18 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val kvPairDao: Dao<KeyValuePair, String?> by lazy { getDao(KeyValuePair::class.java) as Dao<KeyValuePair, String?> } val kvPairDao: Dao<KeyValuePair, String?> by lazy { getDao(KeyValuePair::class.java) as Dao<KeyValuePair, String?> }
override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource) { override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource?) {
connectionSource.createTableSafe<Profile>() TableUtils.createTable(connectionSource, Profile::class.java)
connectionSource.createTableSafe<KeyValuePair>() TableUtils.createTable(connectionSource, KeyValuePair::class.java)
} }
private fun recreate(database: SQLiteDatabase?, connectionSource: ConnectionSource) { private fun recreate(database: SQLiteDatabase?, connectionSource: ConnectionSource?) {
TableUtils.dropTable<Profile, Int>(connectionSource, Profile::class.java, true) TableUtils.dropTable<Profile, Int>(connectionSource, Profile::class.java, true)
TableUtils.dropTable<KeyValuePair, String?>(connectionSource, KeyValuePair::class.java, true) TableUtils.dropTable<KeyValuePair, String?>(connectionSource, KeyValuePair::class.java, true)
onCreate(database, connectionSource) onCreate(database, connectionSource)
} }
override fun onUpgrade(database: SQLiteDatabase?, connectionSource: ConnectionSource, override fun onUpgrade(database: SQLiteDatabase?, connectionSource: ConnectionSource?,
oldVersion: Int, newVersion: Int) { oldVersion: Int, newVersion: Int) {
if (oldVersion < 7) { if (oldVersion < 7) {
recreate(database, connectionSource) recreate(database, connectionSource)
...@@ -57,38 +57,38 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25) ...@@ -57,38 +57,38 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25)
try { try {
if (oldVersion < 8) { if (oldVersion < 8) {
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;") profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;")
} }
if (oldVersion < 9) { if (oldVersion < 9) {
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN route VARCHAR DEFAULT 'all';") profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR DEFAULT 'all';")
} else if (oldVersion < 19) { } else if (oldVersion < 19) {
profileDao.executeSafe("UPDATE `profile` SET route = 'all' WHERE route IS NULL;") profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'all' WHERE route IS NULL;")
} }
if (oldVersion < 11) { if (oldVersion < 11) {
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;") profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
} }
if (oldVersion < 12) { if (oldVersion < 12) {
profileDao.executeSafe("BEGIN TRANSACTION;") profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
profileDao.executeSafe("ALTER TABLE `profile` RENAME TO `tmp`;") profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
connectionSource.createTableSafe<Profile>() TableUtils.createTable(connectionSource, Profile::class.java)
profileDao.executeSafe( profileDao.executeRawNoArgs(
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route," + "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route," +
" proxyApps, bypass, udpdns, ipv6, individual) " + " proxyApps, bypass, udpdns, ipv6, individual) " +
"SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global," + "SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global," +
" bypass, udpdns, ipv6, individual FROM `tmp`;") " bypass, udpdns, ipv6, individual FROM `tmp`;")
profileDao.executeSafe("DROP TABLE `tmp`;") profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
profileDao.executeSafe("COMMIT;") profileDao.executeRawNoArgs("COMMIT;")
} else if (oldVersion < 13) { } else if (oldVersion < 13) {
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN tx LONG;") profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN tx LONG;")
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN rx LONG;") profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN rx LONG;")
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN date VARCHAR;") profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN date VARCHAR;")
} }
if (oldVersion < 15) { if (oldVersion < 15) {
if (oldVersion >= 12) profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN userOrder LONG;") if (oldVersion >= 12) profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN userOrder LONG;")
var i = 0L var i = 0L
val apps by lazy { app.packageManager.getInstalledApplications(0) } val apps by lazy { app.packageManager.getInstalledApplications(0) }
for (profile in profileDao.queryAllSafe()) { for (profile in profileDao.queryForAll()) {
if (oldVersion < 14) { if (oldVersion < 14) {
val uidSet = profile.individual.split('|').filter(TextUtils::isDigitsOnly) val uidSet = profile.individual.split('|').filter(TextUtils::isDigitsOnly)
.map(String::toInt).toSet() .map(String::toInt).toSet()
...@@ -96,28 +96,28 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25) ...@@ -96,28 +96,28 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25)
.joinToString("\n") { it.packageName } .joinToString("\n") { it.packageName }
} }
profile.userOrder = i profile.userOrder = i
profileDao.updateSafe(profile) profileDao.update(profile)
i += 1 i += 1
} }
} }
if (oldVersion < 16) { if (oldVersion < 16) {
profileDao.executeSafe( profileDao.executeRawNoArgs(
"UPDATE `profile` SET route = 'bypass-lan-china' WHERE route = 'bypass-china'") "UPDATE `profile` SET route = 'bypass-lan-china' WHERE route = 'bypass-china'")
} }
if (oldVersion < 21) { if (oldVersion < 21) {
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN remoteDns VARCHAR DEFAULT '8.8.8.8';") profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN remoteDns VARCHAR DEFAULT '8.8.8.8';")
} }
if (oldVersion < 17) { if (oldVersion < 17) {
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN plugin VARCHAR;") profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN plugin VARCHAR;")
} else if (oldVersion < 22) { } else if (oldVersion < 22) {
// upgrade kcptun to SIP003 plugin // upgrade kcptun to SIP003 plugin
profileDao.executeSafe("BEGIN TRANSACTION;") profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
profileDao.executeSafe("ALTER TABLE `profile` RENAME TO `tmp`;") profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
connectionSource.createTableSafe<Profile>() TableUtils.createTable(connectionSource, Profile::class.java)
profileDao.executeSafe( profileDao.executeRawNoArgs(
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, " + "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, " +
"remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " + "remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " +
"plugin) " + "plugin) " +
...@@ -125,17 +125,17 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25) ...@@ -125,17 +125,17 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25)
"CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, route, " + "CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, route, " +
"remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " + "remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " +
"CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;") "CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;")
profileDao.executeSafe("DROP TABLE `tmp`;") profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
profileDao.executeSafe("COMMIT;") profileDao.executeRawNoArgs("COMMIT;")
} }
if (oldVersion < 23) { if (oldVersion < 23) {
profileDao.executeSafe("BEGIN TRANSACTION;") profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
connectionSource.createTableSafe<KeyValuePair>() TableUtils.createTable(connectionSource, KeyValuePair::class.java)
profileDao.executeSafe("COMMIT;") profileDao.executeRawNoArgs("COMMIT;")
val old = PreferenceManager.getDefaultSharedPreferences(app) val old = PreferenceManager.getDefaultSharedPreferences(app)
PublicDatabase.kvPairDao.replaceSafe(KeyValuePair(Key.id).put(old.getInt(Key.id, 0))) PublicDatabase.kvPairDao.createOrUpdate(KeyValuePair(Key.id).put(old.getInt(Key.id, 0)))
PublicDatabase.kvPairDao.replaceSafe(KeyValuePair(Key.tfo).put(old.getBoolean(Key.tfo, false))) PublicDatabase.kvPairDao.createOrUpdate(KeyValuePair(Key.tfo).put(old.getBoolean(Key.tfo, false)))
} }
if (oldVersion < 25) { if (oldVersion < 25) {
......
...@@ -147,6 +147,9 @@ class Profile : Serializable { ...@@ -147,6 +147,9 @@ class Profile : Serializable {
@DatabaseField @DatabaseField
var plugin: String? = null var plugin: String? = null
// not persisted in db, only used by direct boot
var dirty: Boolean = false
val formattedAddress get() = (if (host.contains(":")) "[%s]:%d" else "%s:%d").format(host, remotePort) val formattedAddress get() = (if (host.contains(":")) "[%s]:%d" else "%s:%d").format(host, remotePort)
val formattedName get() = if (name.isNullOrEmpty()) formattedAddress else name!! val formattedName get() = if (name.isNullOrEmpty()) formattedAddress else name!!
......
...@@ -20,11 +20,13 @@ ...@@ -20,11 +20,13 @@
package com.github.shadowsocks.database package com.github.shadowsocks.database
import android.database.sqlite.SQLiteCantOpenDatabaseException
import android.util.Log import android.util.Log
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.ProfilesFragment import com.github.shadowsocks.ProfilesFragment
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.DirectBoot
import java.io.IOException
import java.sql.SQLException import java.sql.SQLException
/** /**
...@@ -48,12 +50,10 @@ object ProfileManager { ...@@ -48,12 +50,10 @@ object ProfileManager {
profile.individual = oldProfile.individual profile.individual = oldProfile.individual
profile.udpdns = oldProfile.udpdns profile.udpdns = oldProfile.udpdns
} }
val last = safeWrapper { val last = PrivateDatabase.profileDao.queryRaw(PrivateDatabase.profileDao.queryBuilder()
PrivateDatabase.profileDao.queryRaw(PrivateDatabase.profileDao.queryBuilder()
.selectRaw("MAX(userOrder)").prepareStatementString()).firstResult .selectRaw("MAX(userOrder)").prepareStatementString()).firstResult
}
if (last != null && last.size == 1 && last[0] != null) profile.userOrder = last[0].toLong() + 1 if (last != null && last.size == 1 && last[0] != null) profile.userOrder = last[0].toLong() + 1
PrivateDatabase.profileDao.replaceSafe(profile) PrivateDatabase.profileDao.createOrUpdate(profile)
ProfilesFragment.instance?.profilesAdapter?.add(profile) ProfilesFragment.instance?.profilesAdapter?.add(profile)
return profile return profile
} }
...@@ -62,11 +62,13 @@ object ProfileManager { ...@@ -62,11 +62,13 @@ object ProfileManager {
* Note: It's caller's responsibility to update DirectBoot profile if necessary. * Note: It's caller's responsibility to update DirectBoot profile if necessary.
*/ */
@Throws(SQLException::class) @Throws(SQLException::class)
fun updateProfile(profile: Profile) = PrivateDatabase.profileDao.updateSafe(profile) fun updateProfile(profile: Profile) = PrivateDatabase.profileDao.update(profile)
@Throws(IOException::class)
fun getProfile(id: Int): Profile? = try { fun getProfile(id: Int): Profile? = try {
PrivateDatabase.profileDao.queryByIdSafe(id) PrivateDatabase.profileDao.queryForId(id)
} catch (ex: SQLException) { } catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getProfile", ex) Log.e(TAG, "getProfile", ex)
app.track(ex) app.track(ex)
null null
...@@ -74,28 +76,26 @@ object ProfileManager { ...@@ -74,28 +76,26 @@ object ProfileManager {
@Throws(SQLException::class) @Throws(SQLException::class)
fun delProfile(id: Int) { fun delProfile(id: Int) {
PrivateDatabase.profileDao.deleteByIdSafe(id) PrivateDatabase.profileDao.deleteById(id)
ProfilesFragment.instance?.profilesAdapter?.removeId(id) ProfilesFragment.instance?.profilesAdapter?.removeId(id)
if (id == DataStore.profileId && DataStore.directBootAware) DirectBoot.clean() if (id == DataStore.profileId && DataStore.directBootAware) DirectBoot.clean()
} }
@Throws(IOException::class)
fun getFirstProfile(): Profile? = try { fun getFirstProfile(): Profile? = try {
safeWrapper { PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare()).singleOrNull()
PrivateDatabase.profileDao.query(
PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare()).singleOrNull()
}
} catch (ex: SQLException) { } catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getFirstProfile", ex) Log.e(TAG, "getFirstProfile", ex)
app.track(ex) app.track(ex)
null null
} }
@Throws(IOException::class)
fun getAllProfiles(): List<Profile>? = try { fun getAllProfiles(): List<Profile>? = try {
safeWrapper { PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().orderBy("userOrder", true).prepare())
PrivateDatabase.profileDao.query(
PrivateDatabase.profileDao.queryBuilder().orderBy("userOrder", true).prepare())
}
} catch (ex: SQLException) { } catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getAllProfiles", ex) Log.e(TAG, "getAllProfiles", ex)
app.track(ex) app.track(ex)
null null
......
...@@ -34,21 +34,20 @@ object PublicDatabase : OrmLiteSqliteOpenHelper(app.deviceContext, Key.DB_PUBLIC ...@@ -34,21 +34,20 @@ object PublicDatabase : OrmLiteSqliteOpenHelper(app.deviceContext, Key.DB_PUBLIC
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val kvPairDao: Dao<KeyValuePair, String?> by lazy { getDao(KeyValuePair::class.java) as Dao<KeyValuePair, String?> } val kvPairDao: Dao<KeyValuePair, String?> by lazy { getDao(KeyValuePair::class.java) as Dao<KeyValuePair, String?> }
override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource) { override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource?) {
connectionSource.createTableSafe<KeyValuePair>() TableUtils.createTable(connectionSource, KeyValuePair::class.java)
} }
override fun onUpgrade(database: SQLiteDatabase?, connectionSource: ConnectionSource?, override fun onUpgrade(database: SQLiteDatabase?, connectionSource: ConnectionSource?,
oldVersion: Int, newVersion: Int) { oldVersion: Int, newVersion: Int) {
if (oldVersion < 1) { if (oldVersion < 1) {
safeWrapper { PrivateDatabase.kvPairDao.queryBuilder().where().`in`("key",
PrivateDatabase.kvPairDao.queryBuilder().where().`in`("key",
Key.id, Key.tfo, Key.serviceMode, Key.portProxy, Key.portLocalDns, Key.portTransproxy).query() Key.id, Key.tfo, Key.serviceMode, Key.portProxy, Key.portLocalDns, Key.portTransproxy).query()
}.forEach { kvPairDao.replaceSafe(it) } .forEach { kvPairDao.createOrUpdate(it) }
} }
if (oldVersion < 2) { if (oldVersion < 2) {
kvPairDao.replaceSafe(KeyValuePair(Acl.CUSTOM_RULES).put(Acl().fromId(Acl.CUSTOM_RULES).toString())) kvPairDao.createOrUpdate(KeyValuePair(Acl.CUSTOM_RULES).put(Acl().fromId(Acl.CUSTOM_RULES).toString()))
} }
} }
......
...@@ -87,21 +87,19 @@ object PluginManager { ...@@ -87,21 +87,19 @@ object PluginManager {
private var receiver: BroadcastReceiver? = null private var receiver: BroadcastReceiver? = null
private var cachedPlugins: Map<String, Plugin>? = null private var cachedPlugins: Map<String, Plugin>? = null
fun fetchPlugins(): Map<String, Plugin> { fun fetchPlugins(): Map<String, Plugin> = synchronized(this) {
return synchronized(this) { if (receiver == null) receiver = app.listenForPackageChanges {
if (receiver == null) receiver = app.listenForPackageChanges { synchronized(this) {
synchronized(this) { receiver = null
receiver = null cachedPlugins = null
cachedPlugins = null
}
}
if (cachedPlugins == null) {
val pm = app.packageManager
cachedPlugins = (pm.queryIntentContentProviders(Intent(PluginContract.ACTION_NATIVE_PLUGIN),
PackageManager.GET_META_DATA).map { NativePlugin(it) } + NoPlugin).associate { it.id to it }
} }
cachedPlugins!!
} }
if (cachedPlugins == null) {
val pm = app.packageManager
cachedPlugins = (pm.queryIntentContentProviders(Intent(PluginContract.ACTION_NATIVE_PLUGIN),
PackageManager.GET_META_DATA).map { NativePlugin(it) } + NoPlugin).associate { it.id to it }
}
cachedPlugins!!
} }
private fun buildUri(id: String) = Uri.Builder() private fun buildUri(id: String) = Uri.Builder()
...@@ -134,10 +132,10 @@ object PluginManager { ...@@ -134,10 +132,10 @@ object PluginManager {
private fun initNative(options: PluginOptions): String? { private fun initNative(options: PluginOptions): String? {
val providers = app.packageManager.queryIntentContentProviders( val providers = app.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(options.id)), 0) Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(options.id)), 0)
check(providers.size == 1) if (providers.isEmpty()) return null
val uri = Uri.Builder() val uri = Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT) .scheme(ContentResolver.SCHEME_CONTENT)
.authority(providers[0].providerInfo.authority) .authority(providers.single().providerInfo.authority)
.build() .build()
val cr = app.contentResolver val cr = app.contentResolver
return try { return try {
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
package com.github.shadowsocks.preference package com.github.shadowsocks.preference
import android.os.Binder import android.os.Binder
import com.github.shadowsocks.BootReceiver import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.database.PrivateDatabase import com.github.shadowsocks.database.PrivateDatabase
import com.github.shadowsocks.database.PublicDatabase import com.github.shadowsocks.database.PublicDatabase
import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.DirectBoot
...@@ -49,10 +49,8 @@ object DataStore { ...@@ -49,10 +49,8 @@ object DataStore {
publicStore.putInt(Key.id, value) publicStore.putInt(Key.id, value)
if (DataStore.directBootAware) DirectBoot.update() if (DataStore.directBootAware) DirectBoot.update()
} }
/** val canToggleLocked: Boolean get() = publicStore.getBoolean(Key.directBootAware) == true
* Setter is defined in MainActivity.onPreferenceDataStoreChanged. val directBootAware: Boolean get() = app.directBootSupported && canToggleLocked
*/
val directBootAware: Boolean get() = BootReceiver.enabled && publicStore.getBoolean(Key.directBootAware) == true
var serviceMode: String var serviceMode: String
get() = publicStore.getString(Key.serviceMode) ?: Key.modeVpn get() = publicStore.getString(Key.serviceMode) ?: Key.modeVpn
set(value) = publicStore.putString(Key.serviceMode, value) set(value) = publicStore.putString(Key.serviceMode, value)
......
...@@ -22,20 +22,17 @@ package com.github.shadowsocks.preference ...@@ -22,20 +22,17 @@ package com.github.shadowsocks.preference
import android.support.v7.preference.PreferenceDataStore import android.support.v7.preference.PreferenceDataStore
import com.github.shadowsocks.database.KeyValuePair import com.github.shadowsocks.database.KeyValuePair
import com.github.shadowsocks.database.deleteByIdSafe
import com.github.shadowsocks.database.queryByIdSafe
import com.github.shadowsocks.database.replaceSafe
import com.j256.ormlite.dao.Dao import com.j256.ormlite.dao.Dao
import java.util.HashSet import java.util.HashSet
@Suppress("MemberVisibilityCanPrivate", "unused") @Suppress("MemberVisibilityCanPrivate", "unused")
open class OrmLitePreferenceDataStore(private val kvPairDao: Dao<KeyValuePair, String?>) : PreferenceDataStore() { open class OrmLitePreferenceDataStore(private val kvPairDao: Dao<KeyValuePair, String?>) : PreferenceDataStore() {
fun getBoolean(key: String?) = kvPairDao.queryByIdSafe(key)?.boolean fun getBoolean(key: String?) = kvPairDao.queryForId(key)?.boolean
fun getFloat(key: String?) = kvPairDao.queryByIdSafe(key)?.float fun getFloat(key: String?) = kvPairDao.queryForId(key)?.float
fun getInt(key: String?) = kvPairDao.queryByIdSafe(key)?.int fun getInt(key: String?) = kvPairDao.queryForId(key)?.int
fun getLong(key: String?) = kvPairDao.queryByIdSafe(key)?.long fun getLong(key: String?) = kvPairDao.queryForId(key)?.long
fun getString(key: String?) = kvPairDao.queryByIdSafe(key)?.string fun getString(key: String?) = kvPairDao.queryForId(key)?.string
fun getStringSet(key: String?) = kvPairDao.queryByIdSafe(key)?.stringSet fun getStringSet(key: String?) = kvPairDao.queryForId(key)?.stringSet
override fun getBoolean(key: String?, defValue: Boolean) = getBoolean(key) ?: defValue override fun getBoolean(key: String?, defValue: Boolean) = getBoolean(key) ?: defValue
override fun getFloat(key: String?, defValue: Float) = getFloat(key) ?: defValue override fun getFloat(key: String?, defValue: Float) = getFloat(key) ?: defValue
...@@ -49,32 +46,32 @@ open class OrmLitePreferenceDataStore(private val kvPairDao: Dao<KeyValuePair, S ...@@ -49,32 +46,32 @@ open class OrmLitePreferenceDataStore(private val kvPairDao: Dao<KeyValuePair, S
fun putInt(key: String?, value: Int?) = if (value == null) remove(key) else putInt(key, value) fun putInt(key: String?, value: Int?) = if (value == null) remove(key) else putInt(key, value)
fun putLong(key: String?, value: Long?) = if (value == null) remove(key) else putLong(key, value) fun putLong(key: String?, value: Long?) = if (value == null) remove(key) else putLong(key, value)
override fun putBoolean(key: String?, value: Boolean) { override fun putBoolean(key: String?, value: Boolean) {
kvPairDao.replaceSafe(KeyValuePair(key).put(value)) kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
fireChangeListener(key) fireChangeListener(key)
} }
override fun putFloat(key: String?, value: Float) { override fun putFloat(key: String?, value: Float) {
kvPairDao.replaceSafe(KeyValuePair(key).put(value)) kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
fireChangeListener(key) fireChangeListener(key)
} }
override fun putInt(key: String?, value: Int) { override fun putInt(key: String?, value: Int) {
kvPairDao.replaceSafe(KeyValuePair(key).put(value)) kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
fireChangeListener(key) fireChangeListener(key)
} }
override fun putLong(key: String?, value: Long) { override fun putLong(key: String?, value: Long) {
kvPairDao.replaceSafe(KeyValuePair(key).put(value)) kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
fireChangeListener(key) fireChangeListener(key)
} }
override fun putString(key: String?, value: String?) = if (value == null) remove(key) else { override fun putString(key: String?, value: String?) = if (value == null) remove(key) else {
kvPairDao.replaceSafe(KeyValuePair(key).put(value)) kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
fireChangeListener(key) fireChangeListener(key)
} }
override fun putStringSet(key: String?, values: MutableSet<String>?) = if (values == null) remove(key) else { override fun putStringSet(key: String?, values: MutableSet<String>?) = if (values == null) remove(key) else {
kvPairDao.replaceSafe(KeyValuePair(key).put(values)) kvPairDao.createOrUpdate(KeyValuePair(key).put(values))
fireChangeListener(key) fireChangeListener(key)
} }
fun remove(key: String?) { fun remove(key: String?) {
kvPairDao.deleteByIdSafe(key) kvPairDao.deleteById(key)
fireChangeListener(key) fireChangeListener(key)
} }
......
...@@ -29,8 +29,8 @@ import com.twofortyfouram.locale.api.Intent as ApiIntent ...@@ -29,8 +29,8 @@ import com.twofortyfouram.locale.api.Intent as ApiIntent
class Settings(bundle: Bundle?) { class Settings(bundle: Bundle?) {
companion object { companion object {
private val KEY_SWITCH_ON = "switch_on" private const val KEY_SWITCH_ON = "switch_on"
private val KEY_PROFILE_ID = "profile_id" private const val KEY_PROFILE_ID = "profile_id"
fun fromIntent(intent: Intent) = Settings(intent.getBundleExtra(ApiIntent.EXTRA_BUNDLE)) fun fromIntent(intent: Intent) = Settings(intent.getBundleExtra(ApiIntent.EXTRA_BUNDLE))
} }
......
package com.github.shadowsocks.utils package com.github.shadowsocks.utils
import android.annotation.TargetApi import android.annotation.TargetApi
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
...@@ -12,8 +16,9 @@ import java.io.ObjectInputStream ...@@ -12,8 +16,9 @@ import java.io.ObjectInputStream
import java.io.ObjectOutputStream import java.io.ObjectOutputStream
@TargetApi(24) @TargetApi(24)
object DirectBoot { object DirectBoot : BroadcastReceiver() {
private val file = File(app.deviceContext.noBackupFilesDir, "directBootProfile") private val file = File(app.deviceContext.noBackupFilesDir, "directBootProfile")
private var registered = false
fun getDeviceProfile(): Profile? = try { fun getDeviceProfile(): Profile? = try {
ObjectInputStream(file.inputStream()).use { it.readObject() as Profile } ObjectInputStream(file.inputStream()).use { it.readObject() as Profile }
...@@ -24,8 +29,26 @@ object DirectBoot { ...@@ -24,8 +29,26 @@ object DirectBoot {
File(app.deviceContext.noBackupFilesDir, BaseService.CONFIG_FILE).delete() File(app.deviceContext.noBackupFilesDir, BaseService.CONFIG_FILE).delete()
} }
fun update() { /**
val profile = ProfileManager.getProfile(DataStore.profileId) // app.currentProfile will call this * app.currentProfile will call this.
if (profile == null) clean() else ObjectOutputStream(file.outputStream()).use { it.writeObject(profile) } */
fun update(profile: Profile? = ProfileManager.getProfile(DataStore.profileId)) =
if (profile == null) clean() else ObjectOutputStream(file.outputStream()).use { it.writeObject(profile) }
fun flushTrafficStats() {
val profile = getDeviceProfile()
if (profile?.dirty == true) ProfileManager.updateProfile(profile)
update()
}
fun listenForUnlock() {
if (registered) return
app.registerReceiver(this, IntentFilter(Intent.ACTION_BOOT_COMPLETED))
registered = true
}
override fun onReceive(context: Context, intent: Intent) {
flushTrafficStats()
app.unregisterReceiver(this)
registered = false
} }
} }
...@@ -52,5 +52,5 @@ object TcpFastOpen { ...@@ -52,5 +52,5 @@ object TcpFastOpen {
"else", "else",
" echo Failed.", " echo Failed.",
"fi"), null, true)?.joinToString("\n") "fi"), null, true)?.joinToString("\n")
fun enabledAsync(value: Boolean) = thread { enabled(value) }.join(1000) fun enabledAsync(value: Boolean) = thread("TcpFastOpen") { enabled(value) }.join(1000)
} }
...@@ -39,8 +39,8 @@ fun broadcastReceiver(callback: (Context, Intent) -> Unit): BroadcastReceiver = ...@@ -39,8 +39,8 @@ fun broadcastReceiver(callback: (Context, Intent) -> Unit): BroadcastReceiver =
/** /**
* Wrapper for kotlin.concurrent.thread that tracks uncaught exceptions. * Wrapper for kotlin.concurrent.thread that tracks uncaught exceptions.
*/ */
fun thread(start: Boolean = true, isDaemon: Boolean = false, contextClassLoader: ClassLoader? = null, fun thread(name: String? = null, start: Boolean = true, isDaemon: Boolean = false,
name: String? = null, priority: Int = -1, block: () -> Unit): Thread { contextClassLoader: ClassLoader? = null, priority: Int = -1, block: () -> Unit): Thread {
val thread = kotlin.concurrent.thread(false, isDaemon, contextClassLoader, name, priority, block) val thread = kotlin.concurrent.thread(false, isDaemon, contextClassLoader, name, priority, block)
thread.setUncaughtExceptionHandler(app::track) thread.setUncaughtExceptionHandler(app::track)
if (start) thread.start() if (start) thread.start()
......
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/material_grey_100"/>
<foreground>
<!-- 44dp icon scaled to 52dp in 72dp, padding = (1-52/44*24/72)/2 -->
<inset
android:drawable="@drawable/ic_image_camera_alt"
android:inset="30.303%"/>
</foreground>
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/material_grey_100"/>
<foreground>
<!-- 44dp icon scaled to 52dp in 72dp, padding = (1-52/44*24/72)/2 -->
<inset
android:drawable="@drawable/ic_qu_shadowsocks_foreground"
android:inset="30.303%"/>
</foreground>
</adaptive-icon>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the License for the specific language governing permissions and limitations under
the License.
-->
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/material_grey_100" />
<size android:width="44dp" android:height="44dp" />
</shape>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="@color/material_primary_600"
android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path
android:fillColor="@color/material_primary_600"
android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
android:width="24dp"
android:height="24dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path <item
android:fillColor="#f5f5f5" android:drawable="@drawable/ic_app_shortcut_background"
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" /> android:left="2dp"
<path android:top="2dp"
android:fillColor="#546e7a" android:right="2dp"
android:pathData="M24,21.8 C25.7673,21.8,27.2,23.2327,27.2,25 C27.2,26.7673,25.7673,28.2,24,28.2 android:bottom="2dp" />
C22.2327,28.2,20.8,26.7673,20.8,25 C20.8,23.2327,22.2327,21.8,24,21.8 Z" />
<path <item
android:fillColor="#546e7a" android:drawable="@drawable/ic_image_camera_alt"
android:pathData="M21,15 L19.17,17 L16,17 A2,2,0,0,0,14,19 L14,31 A2,2,0,0,0,16,33 L32,33 android:left="12dp"
A2,2,0,0,0,34,31 L34,19 A2,2,0,0,0,32,17 L28.83,17 L27,15 Z M24,30 android:top="12dp"
A5,5,0,1,1,29,25 A5,5,0,0,1,24,30 Z" /> android:right="12dp"
</vector> android:bottom="12dp" />
\ No newline at end of file
</layer-list>
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="path"
android:fillColor="@color/material_blue_grey_600"
android:pathData="M 21.25 2.28 L 17.55 18.55 L 9.26 15.89 L 16.58 7.16 L 6.83 15.37 L 0 12.8 L 21.25 2.28 ZM 9.45 17.56 L 12.09 18.41 L 9.46 22 L 9.45 17.56 Z" />
</vector>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
android:width="24dp"
android:height="24dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path <item
android:fillColor="#f5f5f5" android:drawable="@drawable/ic_app_shortcut_background"
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" /> android:left="2dp"
<path android:top="2dp"
android:fillColor="#546e7a" android:right="2dp"
android:pathData="M34,14.72 L30.52,30.04 L22.72,27.53 L29.61,19.31 L20.43,27.04 L14,24.62 android:bottom="2dp" />
L34,14.72 Z" />
<path <item
android:fillColor="#546e7a" android:drawable="@drawable/ic_qu_shadowsocks_foreground"
android:pathData="M22.89,29.1 L25.38,29.9 L22.9,33.28 L22.89,29.1 Z" /> android:left="12dp"
</vector> android:top="12dp"
android:right="12dp"
android:bottom="12dp" />
</layer-list>
\ No newline at end of file
...@@ -41,7 +41,6 @@ ...@@ -41,7 +41,6 @@
<string name="delete_confirm_prompt">"آیا مطمئن هستید که می‌خواهید این پروفایل را حذف کنید؟"</string> <string name="delete_confirm_prompt">"آیا مطمئن هستید که می‌خواهید این پروفایل را حذف کنید؟"</string>
<string name="share_qr_nfc">"NFC یا QR Code"</string> <string name="share_qr_nfc">"NFC یا QR Code"</string>
<string name="add_profile_methods_scan_qr_code">"اسکن‌کردن QR Code"</string> <string name="add_profile_methods_scan_qr_code">"اسکن‌کردن QR Code"</string>
<string name="add_profile_methods_manual_settings">"تنظیمات دستی"</string>
<!-- tasker --> <!-- tasker -->
<string name="toggle_service_state">"فعال‌کردن سرویس"</string> <string name="toggle_service_state">"فعال‌کردن سرویس"</string>
...@@ -55,4 +54,5 @@ ...@@ -55,4 +54,5 @@
<string name="vpn_connected">"وصل شد. برای بررسی اتصال ضربه (Tap) بزنید."</string> <string name="vpn_connected">"وصل شد. برای بررسی اتصال ضربه (Tap) بزنید."</string>
<string name="not_connected">"اتصال برقرار نیست!"</string> <string name="not_connected">"اتصال برقرار نیست!"</string>
<string name="route_entry_all">"همه"</string> <string name="route_entry_all">"همه"</string>
<string name="add_profile_methods_manual_settings">"تنظیمات دستی"</string>
</resources> </resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="quick_toggle">"Basculer"</string>
<!-- misc --> <!-- misc -->
<string name="profile">"Profil"</string> <string name="profile">"Profil"</string>
<string name="profile_summary">"Passer à un autre profil ou ajouter de nouveaux profils"</string>
<string name="remote_dns">"DNS Distant"</string>
<string name="stat_summary">"Envoyé : \t%3$s\t↑\t%1$s
Reçu : \t\t\t%4$s\t↓\t%2$s"</string>
<string name="connection_test_testing">"Essai..."</string>
<string name="connection_test_error">"Impossible de détecter la connexion Internet :%s"</string> <string name="connection_test_error">"Impossible de détecter la connexion Internet :%s"</string>
<string name="connection_test_fail">"Internet Indisponible"</string> <string name="connection_test_fail">"Internet Indisponible"</string>
<string name="connection_test_error_status_code">"Code de l'Erreur : #%d"</string> <string name="connection_test_error_status_code">"Code de l'Erreur : #%d"</string>
<!-- proxy category -->
<string name="profile_name">"Nom du Profil"</string>
<string name="proxy">"Serveur"</string> <string name="proxy">"Serveur"</string>
<string name="remote_port">"Port Distant"</string>
<string name="sitekey">"Mot de passe"</string> <string name="sitekey">"Mot de passe"</string>
<string name="enc_method">"Méthode d'Encryption"</string>
<string name="auto_connect_summary">"Activer Shadowsocks au démarrage"</string>
<string name="tcp_fastopen_summary_unsupported">"Version du noyau non supportée : %s &lt;3.7.1"</string>
<!-- notification category -->
<string name="forward_success">"Shadowsocks a démarré"</string>
<string name="invalid_server">"Nom de serveur invalide"</string>
<string name="service_failed">"Échec de la connexion au serveur distant"</string> <string name="service_failed">"Échec de la connexion au serveur distant"</string>
<string name="reboot_required">"Impossible de démarrer le service VPN. Vous devrez peut-être redémarrer votre appareil."</string>
<!-- alert category --> <!-- alert category -->
<string name="close">"Fermer"</string> <string name="close">"Fermer"</string>
<string name="profile_empty">"Veuillez sélectionner un profil"</string> <string name="profile_empty">"Veuillez sélectionner un profil"</string>
<string name="connect">"Connexion"</string>
<string name="remove_profile">"Supprimer ce profil %s ?"</string>
<!-- menu category --> <!-- menu category -->
<string name="profiles">"Profils"</string> <string name="profiles">"Profils"</string>
<string name="settings">"Paramètres"</string> <string name="settings">"Paramètres"</string>
<string name="about">"À propos"</string> <string name="about">"À propos"</string>
<string name="edit">"Éditer"</string>
<string name="share">"Partager"</string> <string name="share">"Partager"</string>
<string name="add_profile">"Ajouter un Profil"</string> <string name="add_profile">"Ajouter un Profil"</string>
<string name="action_apply_all">"Appliquer les Paramètres à tous les Profils"</string> <string name="action_apply_all">"Appliquer les Paramètres à tous les Profils"</string>
<string name="action_export">"Exporter vers le presse-papiers"</string> <string name="action_export">"Exporter vers le presse-papiers"</string>
<string name="action_import">"Importer depuis le presse-papiers"</string> <string name="action_import">"Importer depuis le presse-papiers"</string>
<string name="action_export_msg">"Exporter avec succès !"</string>
<string name="action_export_err">"Échec de l'exportation"</string>
<string name="action_import_msg">"Importer avec succès !"</string>
<string name="action_import_err">"Échec de l’importation."</string>
<string name="delete">"Supprimer"</string> <string name="delete">"Supprimer"</string>
<string name="delete_confirm_prompt">"Êtes-vous sûr de vouloir supprimer ce profil?"</string> <string name="delete_confirm_prompt">"Êtes-vous sûr de vouloir supprimer ce profil?"</string>
<string name="share_qr_nfc">"QR Code/NFC"</string> <string name="share_qr_nfc">"QR Code/NFC"</string>
<string name="add_profile_dialog">"Ajouter ce profil Shadowsocks?"</string>
<string name="add_profile_methods_scan_qr_code">"Scanner le QR Code"</string> <string name="add_profile_methods_scan_qr_code">"Scanner le QR Code"</string>
<plurals name="removed"> <plurals name="removed">
<item quantity="one">"Supprimé"</item> <item quantity="one">"Supprimé"</item>
<item quantity="other">"%d éléments supprimés"</item> <item quantity="other">"%d éléments supprimés"</item>
</plurals> </plurals>
<string name="undo">"Annuler"</string> <string name="undo">"Annuler"</string>
<!-- tasker -->
<string name="toggle_service_state">"Démarrer le service"</string>
<string name="profile_default">"Utiliser le profil actuel"</string>
<!-- status -->
<string name="sent">"Envoyé :"</string>
<string name="received">"Reçu :"</string>
<string name="connecting">"Connexion..."</string>
<string name="not_connected">"Non connecté"</string>
<!-- acl -->
<string name="custom_rules">"Règles personnalisées"</string>
<string name="action_add_rule">"Ajouter une/des règle(s)"</string>
<string name="edit_rule">"Modifier une règle"</string>
<string name="route_entry_all">"Toutes"</string>
<string name="plugin_configure">"Configurer..."</string>
<string name="plugin_disabled">"Désactivé"</string>
<string name="plugin_unknown">"Plugin inconnu %s"</string>
<string name="plugin_untrusted">"Attention : ce plugin ne semble pas provenir d'une source fiable reconnue."</string>
<string name="profile_plugin">"Plugin : %s"</string>
<string name="advanced">"Avancé"</string>
<string name="auto_connect_summary_v24">"Activer Shadowsocks au démarrage. Il est plutôt recommandé d'utiliser le VPN en permanence"</string>
<string name="direct_boot_aware_summary">"Vos informations de profil sélectionnées seront moins protégées"</string>
</resources> </resources>
\ No newline at end of file
...@@ -15,9 +15,6 @@ ...@@ -15,9 +15,6 @@
<string name="connection_test_fail">"インターネット利用不可"</string> <string name="connection_test_fail">"インターネット利用不可"</string>
<string name="connection_test_error_status_code">"ステータスコード無効: #%d"</string> <string name="connection_test_error_status_code">"ステータスコード無効: #%d"</string>
<!-- proxy category -->
<string name="proxy_cat">"サーバー設定"</string>
<!-- proxy category --> <!-- proxy category -->
<string name="profile_name">"サーバー名"</string> <string name="profile_name">"サーバー名"</string>
<string name="proxy">"サーバーアドレス"</string> <string name="proxy">"サーバーアドレス"</string>
...@@ -25,9 +22,6 @@ ...@@ -25,9 +22,6 @@
<string name="sitekey">"パスワード"</string> <string name="sitekey">"パスワード"</string>
<string name="enc_method">"暗号化方式"</string> <string name="enc_method">"暗号化方式"</string>
<!-- feature category -->
<string name="feature_cat">"ファンクション設定"</string>
<!-- feature category --> <!-- feature category -->
<string name="ipv6">"IPv6 プロキシ"</string> <string name="ipv6">"IPv6 プロキシ"</string>
<string name="ipv6_summary">"リモートサーバーに IPv6 パケットを転送"</string> <string name="ipv6_summary">"リモートサーバーに IPv6 パケットを転送"</string>
...@@ -55,10 +49,6 @@ ...@@ -55,10 +49,6 @@
<string name="reboot_required">"VPN サービスの起動に失敗しました、デバイスの再起動を試みて下さい"</string> <string name="reboot_required">"VPN サービスの起動に失敗しました、デバイスの再起動を試みて下さい"</string>
<string name="profile_invalid_input">"有効なプロファイルが見つかりません"</string> <string name="profile_invalid_input">"有効なプロファイルが見つかりません"</string>
<!-- alert category -->
<string name="yes">"はい"</string>
<string name="no">"いいえ"</string>
<!-- alert category --> <!-- alert category -->
<string name="close">"閉じる"</string> <string name="close">"閉じる"</string>
<string name="profile_empty">"プロファイルを選択して下さい"</string> <string name="profile_empty">"プロファイルを選択して下さい"</string>
...@@ -84,8 +74,6 @@ ...@@ -84,8 +74,6 @@
<!-- profile --> <!-- profile -->
<string name="profile_config">"プロファイル編集"</string> <string name="profile_config">"プロファイル編集"</string>
<string name="unsaved_changes_prompt">"変更は適応されておりません、保存しますか?"</string>
<string name="apply">"適用"</string>
<string name="delete">"削除"</string> <string name="delete">"削除"</string>
<string name="delete_confirm_prompt">"このプロファイルを削除しますか"</string> <string name="delete_confirm_prompt">"このプロファイルを削除しますか"</string>
<string name="share_qr_nfc">"QR コード / NFC"</string> <string name="share_qr_nfc">"QR コード / NFC"</string>
...@@ -147,7 +135,7 @@ ...@@ -147,7 +135,7 @@
<string name="service_transproxy">"トランスプロキシサービス"</string> <string name="service_transproxy">"トランスプロキシサービス"</string>
<string name="vpn_permission_denied">"VPNサービス作成のアクセス許可が拒否されました"</string> <string name="vpn_permission_denied">"VPNサービス作成のアクセス許可が拒否されました"</string>
<string name="auto_connect_summary_v24">"起動時にShadowsockを有効。 VPN常時接続の使用をお勧めします"</string> <string name="auto_connect_summary_v24">"起動時にShadowsockを有効。 VPN常時接続の使用をお勧めします"</string>
<string name="direct_boot_aware">"ダイレクトブート"</string> <string name="direct_boot_aware_summary">"選択されたプロファイル情報はより少ない保護を受けることになります"</string>
<string name="direct_boot_aware_summary">"デバイスがロック解除される前にShadowsocksの自動起動を許可(選択されたプロファイル情報はより少ない保護を受けることになります)"</string>
<string name="acl_rule_online_config">"オンライン設定のURL"</string> <string name="acl_rule_online_config">"オンライン設定のURL"</string>
<string name="action_import_file">"ファイルからのインポート"</string>
</resources> </resources>
\ No newline at end of file
...@@ -18,9 +18,6 @@ ...@@ -18,9 +18,6 @@
<string name="connection_test_fail">"인터넷에 연결할 수 없습니다"</string> <string name="connection_test_fail">"인터넷에 연결할 수 없습니다"</string>
<string name="connection_test_error_status_code">"오류 코드: #%d"</string> <string name="connection_test_error_status_code">"오류 코드: #%d"</string>
<!-- proxy category -->
<string name="proxy_cat">"서버 설정"</string>
<!-- proxy category --> <!-- proxy category -->
<string name="profile_name">"프로필 이름"</string> <string name="profile_name">"프로필 이름"</string>
<string name="proxy">"서버 주소"</string> <string name="proxy">"서버 주소"</string>
...@@ -28,9 +25,6 @@ ...@@ -28,9 +25,6 @@
<string name="sitekey">"비밀번호"</string> <string name="sitekey">"비밀번호"</string>
<string name="enc_method">"암호화 방법"</string> <string name="enc_method">"암호화 방법"</string>
<!-- feature category -->
<string name="feature_cat">"기능 설정"</string>
<!-- feature category --> <!-- feature category -->
<string name="ipv6">"IPv6 라우팅"</string> <string name="ipv6">"IPv6 라우팅"</string>
<string name="ipv6_summary">"IPv6 트래픽도 원격으로 리다이렉트 합니다"</string> <string name="ipv6_summary">"IPv6 트래픽도 원격으로 리다이렉트 합니다"</string>
...@@ -58,10 +52,6 @@ ...@@ -58,10 +52,6 @@
<string name="reboot_required">"VPN 서비스를 시작하는 데 실패했습니다. 장치를 재시작해 보세요."</string> <string name="reboot_required">"VPN 서비스를 시작하는 데 실패했습니다. 장치를 재시작해 보세요."</string>
<string name="profile_invalid_input">"올바른 프로필 데이터를 찾을 수 없습니다"</string> <string name="profile_invalid_input">"올바른 프로필 데이터를 찾을 수 없습니다"</string>
<!-- alert category -->
<string name="yes">"예"</string>
<string name="no">"아니오"</string>
<!-- alert category --> <!-- alert category -->
<string name="close">"닫기"</string> <string name="close">"닫기"</string>
<string name="profile_empty">"프로필을 선택해 주세요"</string> <string name="profile_empty">"프로필을 선택해 주세요"</string>
...@@ -86,8 +76,6 @@ ...@@ -86,8 +76,6 @@
<!-- profile --> <!-- profile -->
<string name="profile_config">"프로필 설정"</string> <string name="profile_config">"프로필 설정"</string>
<string name="unsaved_changes_prompt">"변경 사항이 저장되지 않았습니다. 저장하시겠습니까?"</string>
<string name="apply">"적용"</string>
<string name="delete">"삭제"</string> <string name="delete">"삭제"</string>
<string name="delete_confirm_prompt">"정말 이 프로필을 삭제하시겠습니까?"</string> <string name="delete_confirm_prompt">"정말 이 프로필을 삭제하시겠습니까?"</string>
<string name="share_qr_nfc">"QR 코드/NFC"</string> <string name="share_qr_nfc">"QR 코드/NFC"</string>
......
...@@ -21,9 +21,6 @@ ...@@ -21,9 +21,6 @@
<string name="connection_test_fail">"Интернет недоступен"</string> <string name="connection_test_fail">"Интернет недоступен"</string>
<string name="connection_test_error_status_code">"Код ошибки: #%d"</string> <string name="connection_test_error_status_code">"Код ошибки: #%d"</string>
<!-- proxy category -->
<string name="proxy_cat">"Настройки Сервера"</string>
<!-- proxy category --> <!-- proxy category -->
<string name="profile_name">"Имя профиля"</string> <string name="profile_name">"Имя профиля"</string>
<string name="proxy">"Сервер"</string> <string name="proxy">"Сервер"</string>
...@@ -31,9 +28,6 @@ ...@@ -31,9 +28,6 @@
<string name="sitekey">"Пароль"</string> <string name="sitekey">"Пароль"</string>
<string name="enc_method">"Метод шифрования"</string> <string name="enc_method">"Метод шифрования"</string>
<!-- feature category -->
<string name="feature_cat">"Дополнительные Настройки"</string>
<!-- feature category --> <!-- feature category -->
<string name="ipv6">"IPv6 Маршрут"</string> <string name="ipv6">"IPv6 Маршрут"</string>
<string name="ipv6_summary">"Перенаправлять трафик IPv6 на удалённый сервер"</string> <string name="ipv6_summary">"Перенаправлять трафик IPv6 на удалённый сервер"</string>
...@@ -60,10 +54,6 @@ ...@@ -60,10 +54,6 @@
<string name="reboot_required">"Не удалось запустить службу VPN. Возможно, требуется перезагрузить ваше устройство."</string> <string name="reboot_required">"Не удалось запустить службу VPN. Возможно, требуется перезагрузить ваше устройство."</string>
<string name="profile_invalid_input">"Не найдено действительных данных профиля."</string> <string name="profile_invalid_input">"Не найдено действительных данных профиля."</string>
<!-- alert category -->
<string name="yes">"Да"</string>
<string name="no">"Нет"</string>
<!-- alert category --> <!-- alert category -->
<string name="close">"Закрыть"</string> <string name="close">"Закрыть"</string>
<string name="profile_empty">"Пожалуйста, выберите профиль"</string> <string name="profile_empty">"Пожалуйста, выберите профиль"</string>
...@@ -91,8 +81,6 @@ ...@@ -91,8 +81,6 @@
<!-- profile --> <!-- profile -->
<string name="profile_config">"Настройка профиля"</string> <string name="profile_config">"Настройка профиля"</string>
<string name="unsaved_changes_prompt">"Изменения не сохранены. Сохранить?"</string>
<string name="apply">"Применить"</string>
<string name="delete">"Удалить"</string> <string name="delete">"Удалить"</string>
<string name="delete_confirm_prompt">"Вы уверены, что хотите удалить этот профиль?"</string> <string name="delete_confirm_prompt">"Вы уверены, что хотите удалить этот профиль?"</string>
<string name="share_qr_nfc">"QR-код/NFC"</string> <string name="share_qr_nfc">"QR-код/NFC"</string>
...@@ -157,7 +145,7 @@ ...@@ -157,7 +145,7 @@
<string name="vpn_permission_denied">"Нет разрешения на создание VPN соединения"</string> <string name="vpn_permission_denied">"Нет разрешения на создание VPN соединения"</string>
<string name="auto_connect_summary_v24">"Включить Shadowsocks во время запуска. Лучше использовать режим постоянной VPN."</string> <string name="auto_connect_summary_v24">"Включить Shadowsocks во время запуска. Лучше использовать режим постоянной VPN."</string>
<string name="speed">"%s/с"</string> <string name="speed">"%s/с"</string>
<string name="direct_boot_aware">"Непосредственная загрузка"</string> <string name="direct_boot_aware_summary">"данные выбранного профиля будут хуже защищены"</string>
<string name="direct_boot_aware_summary">"Позволить Shadowsocks запускаться до того, как устройство будет разблокировано (данные выбранного профиля будут хуже защищены)"</string>
<string name="acl_rule_online_config">"URL конфигурации"</string> <string name="acl_rule_online_config">"URL конфигурации"</string>
<string name="action_import_file">"Импортировать из файла..."</string>
</resources> </resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">"影梭"</string>
<string name="quick_toggle">"开关"</string> <string name="quick_toggle">"开关"</string>
<!-- misc --> <!-- misc -->
...@@ -16,9 +15,6 @@ ...@@ -16,9 +15,6 @@
<string name="connection_test_fail">"无互联网连接"</string> <string name="connection_test_fail">"无互联网连接"</string>
<string name="connection_test_error_status_code">"状态码无效(#%d)"</string> <string name="connection_test_error_status_code">"状态码无效(#%d)"</string>
<!-- proxy category -->
<string name="proxy_cat">"服务器设置"</string>
<!-- proxy category --> <!-- proxy category -->
<string name="profile_name">"配置名称"</string> <string name="profile_name">"配置名称"</string>
<string name="proxy">"服务器"</string> <string name="proxy">"服务器"</string>
...@@ -26,9 +22,6 @@ ...@@ -26,9 +22,6 @@
<string name="sitekey">"密码"</string> <string name="sitekey">"密码"</string>
<string name="enc_method">"加密方式"</string> <string name="enc_method">"加密方式"</string>
<!-- feature category -->
<string name="feature_cat">"功能设置"</string>
<!-- feature category --> <!-- feature category -->
<string name="ipv6">"IPv6 路由"</string> <string name="ipv6">"IPv6 路由"</string>
<string name="ipv6_summary">"转发 IPv6 流量到远程服务器"</string> <string name="ipv6_summary">"转发 IPv6 流量到远程服务器"</string>
...@@ -56,10 +49,6 @@ ...@@ -56,10 +49,6 @@
<string name="reboot_required">"VPN 服务启动失败。你可能需要重启设备。"</string> <string name="reboot_required">"VPN 服务启动失败。你可能需要重启设备。"</string>
<string name="profile_invalid_input">"未找到有效的配置文件。"</string> <string name="profile_invalid_input">"未找到有效的配置文件。"</string>
<!-- alert category -->
<string name="yes">"是"</string>
<string name="no">"否"</string>
<!-- alert category --> <!-- alert category -->
<string name="close">"关闭"</string> <string name="close">"关闭"</string>
<string name="profile_empty">"请选择配置文件"</string> <string name="profile_empty">"请选择配置文件"</string>
...@@ -86,8 +75,6 @@ ...@@ -86,8 +75,6 @@
<!-- profile --> <!-- profile -->
<string name="profile_config">"配置文件设置"</string> <string name="profile_config">"配置文件设置"</string>
<string name="unsaved_changes_prompt">"保存修改吗?"</string>
<string name="apply">"应用"</string>
<string name="delete">"删除"</string> <string name="delete">"删除"</string>
<string name="delete_confirm_prompt">"您确定要删除此配置文件?"</string> <string name="delete_confirm_prompt">"您确定要删除此配置文件?"</string>
<string name="share_qr_nfc">"二维码 / NFC"</string> <string name="share_qr_nfc">"二维码 / NFC"</string>
...@@ -148,7 +135,8 @@ ...@@ -148,7 +135,8 @@
<string name="service_transproxy">"透明代理模式"</string> <string name="service_transproxy">"透明代理模式"</string>
<string name="vpn_permission_denied">"创建 VPN 服务权限不足"</string> <string name="vpn_permission_denied">"创建 VPN 服务权限不足"</string>
<string name="auto_connect_summary_v24">"允许 Shadowsocks 随系统启动,建议使用始终开启的 VPN"</string> <string name="auto_connect_summary_v24">"允许 Shadowsocks 随系统启动,建议使用始终开启的 VPN"</string>
<string name="direct_boot_aware">"直接启动"</string> <string name="direct_boot_aware">"允许锁屏下切换"</string>
<string name="direct_boot_aware_summary">"允许在设备解锁前启动服务(选中的配置信息会不那么安全)"</string> <string name="direct_boot_aware_summary">"选中的配置信息会不那么安全"</string>
<string name="acl_rule_online_config">"在线规则文件 URL"</string> <string name="acl_rule_online_config">"在线规则文件 URL"</string>
<string name="action_import_file">"从文件导入…"</string>
</resources> </resources>
\ No newline at end of file
...@@ -19,9 +19,6 @@ ...@@ -19,9 +19,6 @@
<string name="connection_test_fail">"無法使用網際網路"</string> <string name="connection_test_fail">"無法使用網際網路"</string>
<string name="connection_test_error_status_code">"錯誤碼: (#%d)"</string> <string name="connection_test_error_status_code">"錯誤碼: (#%d)"</string>
<!-- proxy category -->
<string name="proxy_cat">"伺服器設定"</string>
<!-- proxy category --> <!-- proxy category -->
<string name="profile_name">"設定檔名稱"</string> <string name="profile_name">"設定檔名稱"</string>
<string name="proxy">"伺服器"</string> <string name="proxy">"伺服器"</string>
...@@ -29,9 +26,6 @@ ...@@ -29,9 +26,6 @@
<string name="sitekey">"密碼"</string> <string name="sitekey">"密碼"</string>
<string name="enc_method">"加密方法"</string> <string name="enc_method">"加密方法"</string>
<!-- feature category -->
<string name="feature_cat">"功能設定"</string>
<!-- feature category --> <!-- feature category -->
<string name="ipv6">"IPv6 路由"</string> <string name="ipv6">"IPv6 路由"</string>
<string name="ipv6_summary">"向遠端重新導向 IPv6 流量"</string> <string name="ipv6_summary">"向遠端重新導向 IPv6 流量"</string>
...@@ -58,10 +52,6 @@ ...@@ -58,10 +52,6 @@
<string name="reboot_required">"VPN 服務啟動失敗。您或許需要重新啟動您的裝置。"</string> <string name="reboot_required">"VPN 服務啟動失敗。您或許需要重新啟動您的裝置。"</string>
<string name="profile_invalid_input">"未找到有效的設定檔資料。"</string> <string name="profile_invalid_input">"未找到有效的設定檔資料。"</string>
<!-- alert category -->
<string name="yes">"是"</string>
<string name="no">"否"</string>
<!-- alert category --> <!-- alert category -->
<string name="close">"關閉"</string> <string name="close">"關閉"</string>
<string name="profile_empty">"請選擇設定檔"</string> <string name="profile_empty">"請選擇設定檔"</string>
...@@ -88,8 +78,6 @@ ...@@ -88,8 +78,6 @@
<!-- profile --> <!-- profile -->
<string name="profile_config">"設定檔設定"</string> <string name="profile_config">"設定檔設定"</string>
<string name="unsaved_changes_prompt">"要儲存變更嗎?"</string>
<string name="apply">"套用"</string>
<string name="delete">"刪除"</string> <string name="delete">"刪除"</string>
<string name="delete_confirm_prompt">"您確定要移除這個設定檔嗎?"</string> <string name="delete_confirm_prompt">"您確定要移除這個設定檔嗎?"</string>
<string name="share_qr_nfc">"QR 碼 / NFC"</string> <string name="share_qr_nfc">"QR 碼 / NFC"</string>
......
...@@ -57,9 +57,8 @@ ...@@ -57,9 +57,8 @@
<string name="auto_connect_summary">Enable Shadowsocks on startup</string> <string name="auto_connect_summary">Enable Shadowsocks on startup</string>
<string name="auto_connect_summary_v24">Enable Shadowsocks on startup. Recommended to use always-on VPN <string name="auto_connect_summary_v24">Enable Shadowsocks on startup. Recommended to use always-on VPN
instead</string> instead</string>
<string name="direct_boot_aware">Direct Boot Aware</string> <string name="direct_boot_aware">Allow Toggling in Lock Screen</string>
<string name="direct_boot_aware_summary">Allow Shadowsocks to start before your device is unlocked (your selected <string name="direct_boot_aware_summary">Your selected profile information will be less protected</string>
profile information will be less protected)</string>
<string name="tcp_fastopen_summary">Toggling requires ROOT permission</string> <string name="tcp_fastopen_summary">Toggling requires ROOT permission</string>
<string name="tcp_fastopen_summary_unsupported">Unsupported kernel version: %s &lt; 3.7.1</string> <string name="tcp_fastopen_summary_unsupported">Unsupported kernel version: %s &lt; 3.7.1</string>
<string name="udp_dns">DNS Forwarding</string> <string name="udp_dns">DNS Forwarding</string>
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
android:title="@string/auto_connect"/> android:title="@string/auto_connect"/>
<!-- Direct Boot Aware alone doesn't do anything without auto connect --> <!-- Direct Boot Aware alone doesn't do anything without auto connect -->
<SwitchPreference android:key="directBootAware" <SwitchPreference android:key="directBootAware"
android:dependency="isAutoConnect"
android:summary="@string/direct_boot_aware_summary" android:summary="@string/direct_boot_aware_summary"
android:title="@string/direct_boot_aware"/> android:title="@string/direct_boot_aware"/>
<SwitchPreference android:key="tcp_fastopen" <SwitchPreference android:key="tcp_fastopen"
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="proxy_cat">"Paramètres du Serveur"</string>
<string name="feature_cat">"Paramètres des Fonctionnalités"</string>
<string name="yes">"Oui"</string>
<string name="no">"Non"</string>
<string name="apply">"Appliquer"</string>
</resources> </resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- proxy category -->
<string name="proxy_cat">"서버 설정"</string> <string name="proxy_cat">"서버 설정"</string>
<!-- feature category -->
<string name="feature_cat">"기능 설정"</string> <string name="feature_cat">"기능 설정"</string>
<string name="unsaved_changes_prompt">"변경 사항이 저장되지 않았습니다. 저장하시겠습니까?"</string>
<!-- alert category -->
<string name="yes">"예"</string> <string name="yes">"예"</string>
<string name="no">"아니오"</string> <string name="no">"아니오"</string>
<string name="unsaved_changes_prompt">"변경 사항이 저장되지 않았습니다. 저장하시겠습니까?"</string>
<string name="apply">"적용"</string> <string name="apply">"적용"</string>
</resources> </resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- proxy category -->
<string name="proxy_cat">"伺服器設定"</string> <string name="proxy_cat">"伺服器設定"</string>
<!-- feature category -->
<string name="feature_cat">"功能設定"</string> <string name="feature_cat">"功能設定"</string>
<string name="unsaved_changes_prompt">"要儲存變更嗎?"</string>
<!-- alert category -->
<string name="yes">"是"</string> <string name="yes">"是"</string>
<string name="no">"否"</string> <string name="no">"否"</string>
<string name="unsaved_changes_prompt">"要儲存變更嗎?"</string>
<string name="apply">"套用"</string> <string name="apply">"套用"</string>
</resources> </resources>
\ No newline at end of file
...@@ -7,10 +7,12 @@ ...@@ -7,10 +7,12 @@
<color name="material_blue_grey_100">#CFD8DC</color> <color name="material_blue_grey_100">#CFD8DC</color>
<color name="material_blue_grey_300">#90A4AE</color> <color name="material_blue_grey_300">#90A4AE</color>
<color name="material_blue_grey_500">#607D8B</color> <color name="material_blue_grey_500">#607D8B</color>
<color name="material_blue_grey_600">#546E7A</color>
<color name="material_blue_grey_700">#455A64</color> <color name="material_blue_grey_700">#455A64</color>
<color name="material_primary_100">@color/material_blue_grey_100</color> <color name="material_primary_100">@color/material_blue_grey_100</color>
<color name="material_primary_300">@color/material_blue_grey_300</color> <color name="material_primary_300">@color/material_blue_grey_300</color>
<color name="material_primary_500">@color/material_blue_grey_500</color> <color name="material_primary_500">@color/material_blue_grey_500</color>
<color name="material_primary_600">@color/material_blue_grey_600</color>
<color name="material_primary_700">@color/material_blue_grey_700</color> <color name="material_primary_700">@color/material_blue_grey_700</color>
<color name="material_accent_200">@color/material_green_a700</color> <color name="material_accent_200">@color/material_green_a700</color>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment