Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
com.ccwangluo.accelerator
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
sheteng
com.ccwangluo.accelerator
Commits
3cfc17c2
Unverified
Commit
3cfc17c2
authored
Jan 24, 2019
by
Mygod
Committed by
GitHub
Jan 24, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2089 from Mygod/coroutines
Refactor using coroutines
parents
1658ac57
2241c5aa
Changes
22
Show whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
363 additions
and
388 deletions
+363
-388
build.gradle
build.gradle
+1
-1
core/build.gradle
core/build.gradle
+2
-2
core/src/main/java/com/github/shadowsocks/Core.kt
core/src/main/java/com/github/shadowsocks/Core.kt
+1
-4
core/src/main/java/com/github/shadowsocks/aidl/ShadowsocksConnection.kt
...java/com/github/shadowsocks/aidl/ShadowsocksConnection.kt
+7
-6
core/src/main/java/com/github/shadowsocks/bg/BaseService.kt
core/src/main/java/com/github/shadowsocks/bg/BaseService.kt
+65
-54
core/src/main/java/com/github/shadowsocks/bg/GuardedProcessPool.kt
...main/java/com/github/shadowsocks/bg/GuardedProcessPool.kt
+77
-103
core/src/main/java/com/github/shadowsocks/bg/LocalDnsService.kt
...rc/main/java/com/github/shadowsocks/bg/LocalDnsService.kt
+3
-3
core/src/main/java/com/github/shadowsocks/bg/LocalSocketListener.kt
...ain/java/com/github/shadowsocks/bg/LocalSocketListener.kt
+22
-23
core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt
.../src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt
+29
-32
core/src/main/java/com/github/shadowsocks/bg/RemoteConfig.kt
core/src/main/java/com/github/shadowsocks/bg/RemoteConfig.kt
+2
-1
core/src/main/java/com/github/shadowsocks/bg/TrafficMonitor.kt
...src/main/java/com/github/shadowsocks/bg/TrafficMonitor.kt
+15
-23
core/src/main/java/com/github/shadowsocks/bg/TransproxyService.kt
.../main/java/com/github/shadowsocks/bg/TransproxyService.kt
+6
-6
core/src/main/java/com/github/shadowsocks/bg/VpnService.kt
core/src/main/java/com/github/shadowsocks/bg/VpnService.kt
+40
-30
core/src/main/java/com/github/shadowsocks/utils/HttpsTest.kt
core/src/main/java/com/github/shadowsocks/utils/HttpsTest.kt
+33
-27
core/src/main/java/com/github/shadowsocks/utils/TcpFastOpen.kt
...src/main/java/com/github/shadowsocks/utils/TcpFastOpen.kt
+3
-1
core/src/main/java/com/github/shadowsocks/utils/Utils.kt
core/src/main/java/com/github/shadowsocks/utils/Utils.kt
+0
-11
mobile/proguard-rules.pro
mobile/proguard-rules.pro
+0
-2
mobile/src/main/java/com/github/shadowsocks/AppManager.kt
mobile/src/main/java/com/github/shadowsocks/AppManager.kt
+38
-40
mobile/src/main/java/com/github/shadowsocks/MainActivity.kt
mobile/src/main/java/com/github/shadowsocks/MainActivity.kt
+9
-9
mobile/src/main/res/layout/layout_apps.xml
mobile/src/main/res/layout/layout_apps.xml
+3
-1
tv/proguard-rules.pro
tv/proguard-rules.pro
+0
-2
tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt
.../java/com/github/shadowsocks/tv/MainPreferenceFragment.kt
+7
-7
No files found.
build.gradle
View file @
3cfc17c2
...
@@ -4,7 +4,7 @@ apply plugin: 'com.github.ben-manes.versions'
...
@@ -4,7 +4,7 @@ apply plugin: 'com.github.ben-manes.versions'
buildscript
{
buildscript
{
ext
{
ext
{
kotlinVersion
=
'1.3.
11
'
kotlinVersion
=
'1.3.
20
'
minSdkVersion
=
21
minSdkVersion
=
21
sdkVersion
=
28
sdkVersion
=
28
compileSdkVersion
=
28
compileSdkVersion
=
28
...
...
core/build.gradle
View file @
3cfc17c2
...
@@ -62,13 +62,13 @@ dependencies {
...
@@ -62,13 +62,13 @@ dependencies {
api
"android.arch.work:work-runtime-ktx:$workVersion"
api
"android.arch.work:work-runtime-ktx:$workVersion"
api
"androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
api
"androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
api
"androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
api
"androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
api
"androidx.preference:preference:1.0.0"
api
'androidx.preference:preference:1.0.0'
api
"androidx.room:room-runtime:$roomVersion"
api
"androidx.room:room-runtime:$roomVersion"
api
'com.crashlytics.sdk.android:crashlytics:2.9.8'
api
'com.crashlytics.sdk.android:crashlytics:2.9.8'
api
'com.google.firebase:firebase-config:16.1.3'
api
'com.google.firebase:firebase-config:16.1.3'
api
'com.google.firebase:firebase-core:16.0.6'
api
'com.google.firebase:firebase-core:16.0.6'
api
'com.squareup.okhttp3:okhttp:3.12.1'
api
"com.takisoft.preferencex:preferencex:$preferencexVersion"
api
"com.takisoft.preferencex:preferencex:$preferencexVersion"
api
'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0'
kapt
"androidx.lifecycle:lifecycle-compiler:$lifecycleVersion"
kapt
"androidx.lifecycle:lifecycle-compiler:$lifecycleVersion"
kapt
"androidx.room:room-compiler:$roomVersion"
kapt
"androidx.room:room-compiler:$roomVersion"
testImplementation
"androidx.arch.core:core-testing:$lifecycleVersion"
testImplementation
"androidx.arch.core:core-testing:$lifecycleVersion"
...
...
core/src/main/java/com/github/shadowsocks/Core.kt
View file @
3cfc17c2
...
@@ -32,8 +32,6 @@ import android.content.IntentFilter
...
@@ -32,8 +32,6 @@ import android.content.IntentFilter
import
android.content.pm.PackageInfo
import
android.content.pm.PackageInfo
import
android.content.pm.PackageManager
import
android.content.pm.PackageManager
import
android.os.Build
import
android.os.Build
import
android.os.Handler
import
android.os.Looper
import
android.os.UserManager
import
android.os.UserManager
import
androidx.annotation.RequiresApi
import
androidx.annotation.RequiresApi
import
androidx.core.content.ContextCompat
import
androidx.core.content.ContextCompat
...
@@ -60,7 +58,6 @@ object Core {
...
@@ -60,7 +58,6 @@ object Core {
lateinit
var
app
:
Application
lateinit
var
app
:
Application
lateinit
var
configureIntent
:
(
Context
)
->
PendingIntent
lateinit
var
configureIntent
:
(
Context
)
->
PendingIntent
val
handler
by
lazy
{
Handler
(
Looper
.
getMainLooper
())
}
val
packageInfo
:
PackageInfo
by
lazy
{
getPackageInfo
(
app
.
packageName
)
}
val
packageInfo
:
PackageInfo
by
lazy
{
getPackageInfo
(
app
.
packageName
)
}
val
deviceStorage
by
lazy
{
if
(
Build
.
VERSION
.
SDK_INT
<
24
)
app
else
DeviceStorageApp
(
app
)
}
val
deviceStorage
by
lazy
{
if
(
Build
.
VERSION
.
SDK_INT
<
24
)
app
else
DeviceStorageApp
(
app
)
}
val
analytics
:
FirebaseAnalytics
by
lazy
{
FirebaseAnalytics
.
getInstance
(
deviceStorage
)
}
val
analytics
:
FirebaseAnalytics
by
lazy
{
FirebaseAnalytics
.
getInstance
(
deviceStorage
)
}
...
@@ -106,7 +103,7 @@ object Core {
...
@@ -106,7 +103,7 @@ object Core {
// handle data restored/crash
// handle data restored/crash
if
(
Build
.
VERSION
.
SDK_INT
>=
24
&&
DataStore
.
directBootAware
&&
if
(
Build
.
VERSION
.
SDK_INT
>=
24
&&
DataStore
.
directBootAware
&&
app
.
getSystemService
<
UserManager
>()
?.
isUserUnlocked
==
true
)
DirectBoot
.
flushTrafficStats
()
app
.
getSystemService
<
UserManager
>()
?.
isUserUnlocked
==
true
)
DirectBoot
.
flushTrafficStats
()
if
(
DataStore
.
tcpFastOpen
&&
!
TcpFastOpen
.
sendEnabled
)
TcpFastOpen
.
enable
Async
()
if
(
DataStore
.
tcpFastOpen
&&
!
TcpFastOpen
.
sendEnabled
)
TcpFastOpen
.
enable
Timeout
()
if
(
DataStore
.
publicStore
.
getLong
(
Key
.
assetUpdateTime
,
-
1
)
!=
packageInfo
.
lastUpdateTime
)
{
if
(
DataStore
.
publicStore
.
getLong
(
Key
.
assetUpdateTime
,
-
1
)
!=
packageInfo
.
lastUpdateTime
)
{
val
assetManager
=
app
.
assets
val
assetManager
=
app
.
assets
for
(
dir
in
arrayOf
(
"acl"
,
"overture"
))
for
(
dir
in
arrayOf
(
"acl"
,
"overture"
))
...
...
core/src/main/java/com/github/shadowsocks/aidl/ShadowsocksConnection.kt
View file @
3cfc17c2
...
@@ -25,9 +25,9 @@ import android.content.Context
...
@@ -25,9 +25,9 @@ import android.content.Context
import
android.content.Intent
import
android.content.Intent
import
android.content.ServiceConnection
import
android.content.ServiceConnection
import
android.os.DeadObjectException
import
android.os.DeadObjectException
import
android.os.Handler
import
android.os.IBinder
import
android.os.IBinder
import
android.os.RemoteException
import
android.os.RemoteException
import
com.github.shadowsocks.Core
import
com.github.shadowsocks.bg.ProxyService
import
com.github.shadowsocks.bg.ProxyService
import
com.github.shadowsocks.bg.TransproxyService
import
com.github.shadowsocks.bg.TransproxyService
import
com.github.shadowsocks.bg.VpnService
import
com.github.shadowsocks.bg.VpnService
...
@@ -38,7 +38,8 @@ import com.github.shadowsocks.utils.Key
...
@@ -38,7 +38,8 @@ import com.github.shadowsocks.utils.Key
/**
/**
* This object should be compact as it will not get GC-ed.
* This object should be compact as it will not get GC-ed.
*/
*/
class
ShadowsocksConnection
(
private
var
listenForDeath
:
Boolean
=
false
)
:
ServiceConnection
,
IBinder
.
DeathRecipient
{
class
ShadowsocksConnection
(
private
val
handler
:
Handler
=
Handler
(),
private
var
listenForDeath
:
Boolean
=
false
)
:
ServiceConnection
,
IBinder
.
DeathRecipient
{
companion
object
{
companion
object
{
val
serviceClass
get
()
=
when
(
DataStore
.
serviceMode
)
{
val
serviceClass
get
()
=
when
(
DataStore
.
serviceMode
)
{
Key
.
modeProxy
->
ProxyService
::
class
Key
.
modeProxy
->
ProxyService
::
class
...
@@ -66,13 +67,13 @@ class ShadowsocksConnection(private var listenForDeath: Boolean = false) : Servi
...
@@ -66,13 +67,13 @@ class ShadowsocksConnection(private var listenForDeath: Boolean = false) : Servi
private
var
callback
:
Callback
?
=
null
private
var
callback
:
Callback
?
=
null
private
val
serviceCallback
=
object
:
IShadowsocksServiceCallback
.
Stub
()
{
private
val
serviceCallback
=
object
:
IShadowsocksServiceCallback
.
Stub
()
{
override
fun
stateChanged
(
state
:
Int
,
profileName
:
String
?,
msg
:
String
?)
{
override
fun
stateChanged
(
state
:
Int
,
profileName
:
String
?,
msg
:
String
?)
{
Core
.
handler
.
post
{
callback
!!
.
stateChanged
(
state
,
profileName
,
msg
)
}
handler
.
post
{
callback
!!
.
stateChanged
(
state
,
profileName
,
msg
)
}
}
}
override
fun
trafficUpdated
(
profileId
:
Long
,
stats
:
TrafficStats
)
{
override
fun
trafficUpdated
(
profileId
:
Long
,
stats
:
TrafficStats
)
{
Core
.
handler
.
post
{
callback
!!
.
trafficUpdated
(
profileId
,
stats
)
}
handler
.
post
{
callback
!!
.
trafficUpdated
(
profileId
,
stats
)
}
}
}
override
fun
trafficPersisted
(
profileId
:
Long
)
{
override
fun
trafficPersisted
(
profileId
:
Long
)
{
Core
.
handler
.
post
{
callback
!!
.
trafficPersisted
(
profileId
)
}
handler
.
post
{
callback
!!
.
trafficPersisted
(
profileId
)
}
}
}
}
}
private
var
binder
:
IBinder
?
=
null
private
var
binder
:
IBinder
?
=
null
...
@@ -110,7 +111,7 @@ class ShadowsocksConnection(private var listenForDeath: Boolean = false) : Servi
...
@@ -110,7 +111,7 @@ class ShadowsocksConnection(private var listenForDeath: Boolean = false) : Servi
override
fun
binderDied
()
{
override
fun
binderDied
()
{
service
=
null
service
=
null
callback
!!
.
onBinderDied
(
)
handler
.
post
(
callback
!!::
onBinderDied
)
}
}
private
fun
unregisterCallback
()
{
private
fun
unregisterCallback
()
{
...
...
core/src/main/java/com/github/shadowsocks/bg/BaseService.kt
View file @
3cfc17c2
...
@@ -39,8 +39,8 @@ import com.github.shadowsocks.plugin.PluginManager
...
@@ -39,8 +39,8 @@ import com.github.shadowsocks.plugin.PluginManager
import
com.github.shadowsocks.utils.Action
import
com.github.shadowsocks.utils.Action
import
com.github.shadowsocks.utils.broadcastReceiver
import
com.github.shadowsocks.utils.broadcastReceiver
import
com.github.shadowsocks.utils.printLog
import
com.github.shadowsocks.utils.printLog
import
com.github.shadowsocks.utils.thread
import
com.google.firebase.analytics.FirebaseAnalytics
import
com.google.firebase.analytics.FirebaseAnalytics
import
kotlinx.coroutines.*
import
java.io.File
import
java.io.File
import
java.net.UnknownHostException
import
java.net.UnknownHostException
import
java.util.*
import
java.util.*
...
@@ -62,21 +62,22 @@ object BaseService {
...
@@ -62,21 +62,22 @@ object BaseService {
const
val
CONFIG_FILE_UDP
=
"shadowsocks-udp.conf"
const
val
CONFIG_FILE_UDP
=
"shadowsocks-udp.conf"
class
Data
internal
constructor
(
private
val
service
:
Interface
)
{
class
Data
internal
constructor
(
private
val
service
:
Interface
)
{
@Volatile
var
state
=
STOPPED
var
state
=
STOPPED
va
l
processes
=
GuardedProcessPool
()
va
r
processes
:
GuardedProcessPool
?
=
null
@Volatile
var
proxy
:
ProxyInstance
?
=
null
var
proxy
:
ProxyInstance
?
=
null
@Volatile
var
udpFallback
:
ProxyInstance
?
=
null
var
udpFallback
:
ProxyInstance
?
=
null
var
notification
:
ServiceNotification
?
=
null
var
notification
:
ServiceNotification
?
=
null
val
closeReceiver
=
broadcastReceiver
{
_
,
intent
->
val
closeReceiver
=
broadcastReceiver
{
_
,
intent
->
when
(
intent
.
action
)
{
when
(
intent
.
action
)
{
Action
.
RELOAD
->
service
.
forceLoad
()
Action
.
RELOAD
->
service
.
forceLoad
()
else
->
service
.
stopRunner
(
true
)
else
->
service
.
stopRunner
()
}
}
}
}
var
closeReceiverRegistered
=
false
var
closeReceiverRegistered
=
false
val
binder
=
Binder
(
this
)
val
binder
=
Binder
(
this
)
var
connectingJob
:
Job
?
=
null
fun
changeState
(
s
:
Int
,
msg
:
String
?
=
null
)
{
fun
changeState
(
s
:
Int
,
msg
:
String
?
=
null
)
{
if
(
state
==
s
&&
msg
==
null
)
return
if
(
state
==
s
&&
msg
==
null
)
return
...
@@ -92,6 +93,7 @@ object BaseService {
...
@@ -92,6 +93,7 @@ object BaseService {
class
Binder
(
private
var
data
:
Data
?
=
null
)
:
IShadowsocksService
.
Stub
(),
AutoCloseable
{
class
Binder
(
private
var
data
:
Data
?
=
null
)
:
IShadowsocksService
.
Stub
(),
AutoCloseable
{
val
callbacks
=
RemoteCallbackList
<
IShadowsocksServiceCallback
>()
val
callbacks
=
RemoteCallbackList
<
IShadowsocksServiceCallback
>()
private
val
bandwidthListeners
=
HashSet
<
IBinder
>()
// the binder is the real identifier
private
val
bandwidthListeners
=
HashSet
<
IBinder
>()
// the binder is the real identifier
private
val
handler
=
Handler
()
override
fun
getState
():
Int
=
data
!!
.
state
override
fun
getState
():
Int
=
data
!!
.
state
override
fun
getProfileName
():
String
=
data
!!
.
proxy
?.
profile
?.
name
?:
"Idle"
override
fun
getProfileName
():
String
=
data
!!
.
proxy
?.
profile
?.
name
?:
"Idle"
...
@@ -101,17 +103,17 @@ object BaseService {
...
@@ -101,17 +103,17 @@ object BaseService {
}
}
private
fun
broadcast
(
work
:
(
IShadowsocksServiceCallback
)
->
Unit
)
{
private
fun
broadcast
(
work
:
(
IShadowsocksServiceCallback
)
->
Unit
)
{
val
n
=
callbacks
.
beginBroadcast
()
repeat
(
callbacks
.
beginBroadcast
())
{
for
(
i
in
0
until
n
)
try
{
try
{
work
(
callbacks
.
getBroadcastItem
(
i
))
work
(
callbacks
.
getBroadcastItem
(
it
))
}
catch
(
e
:
Exception
)
{
}
catch
(
e
:
Exception
)
{
printLog
(
e
)
printLog
(
e
)
}
}
}
callbacks
.
finishBroadcast
()
callbacks
.
finishBroadcast
()
}
}
private
fun
registerTimeout
()
=
private
fun
registerTimeout
()
=
handler
.
postDelayed
(
this
::
onTimeout
,
1000
)
Core
.
handler
.
postAtTime
(
this
::
onTimeout
,
this
,
SystemClock
.
uptimeMillis
()
+
1000
)
private
fun
onTimeout
()
{
private
fun
onTimeout
()
{
val
proxies
=
listOfNotNull
(
data
!!
.
proxy
,
data
!!
.
udpFallback
)
val
proxies
=
listOfNotNull
(
data
!!
.
proxy
,
data
!!
.
udpFallback
)
val
stats
=
proxies
val
stats
=
proxies
...
@@ -157,7 +159,7 @@ object BaseService {
...
@@ -157,7 +159,7 @@ object BaseService {
override
fun
stopListeningForBandwidth
(
cb
:
IShadowsocksServiceCallback
)
{
override
fun
stopListeningForBandwidth
(
cb
:
IShadowsocksServiceCallback
)
{
if
(
bandwidthListeners
.
remove
(
cb
.
asBinder
())
&&
bandwidthListeners
.
isEmpty
())
{
if
(
bandwidthListeners
.
remove
(
cb
.
asBinder
())
&&
bandwidthListeners
.
isEmpty
())
{
Core
.
handler
.
removeCallbacksAndMessages
(
this
)
handler
.
removeCallbacksAndMessages
(
null
)
}
}
}
}
...
@@ -179,7 +181,7 @@ object BaseService {
...
@@ -179,7 +181,7 @@ object BaseService {
override
fun
close
()
{
override
fun
close
()
{
callbacks
.
kill
()
callbacks
.
kill
()
Core
.
handler
.
removeCallbacksAndMessages
(
this
)
handler
.
removeCallbacksAndMessages
(
null
)
data
=
null
data
=
null
}
}
}
}
...
@@ -193,26 +195,23 @@ object BaseService {
...
@@ -193,26 +195,23 @@ object BaseService {
fun
forceLoad
()
{
fun
forceLoad
()
{
val
(
profile
,
fallback
)
=
Core
.
currentProfile
val
(
profile
,
fallback
)
=
Core
.
currentProfile
?:
return
stopRunner
(
tru
e
,
(
this
as
Context
).
getString
(
R
.
string
.
profile_empty
))
?:
return
stopRunner
(
fals
e
,
(
this
as
Context
).
getString
(
R
.
string
.
profile_empty
))
if
(
profile
.
host
.
isEmpty
()
||
profile
.
password
.
isEmpty
()
||
if
(
profile
.
host
.
isEmpty
()
||
profile
.
password
.
isEmpty
()
||
fallback
!=
null
&&
(
fallback
.
host
.
isEmpty
()
||
fallback
.
password
.
isEmpty
()))
{
fallback
!=
null
&&
(
fallback
.
host
.
isEmpty
()
||
fallback
.
password
.
isEmpty
()))
{
stopRunner
(
tru
e
,
(
this
as
Context
).
getString
(
R
.
string
.
proxy_empty
))
stopRunner
(
fals
e
,
(
this
as
Context
).
getString
(
R
.
string
.
proxy_empty
))
return
return
}
}
val
s
=
data
.
state
val
s
=
data
.
state
when
(
s
)
{
when
(
s
)
{
STOPPED
->
startRunner
()
STOPPED
->
startRunner
()
CONNECTED
->
{
CONNECTED
->
stopRunner
(
true
)
stopRunner
(
false
)
startRunner
()
}
else
->
Crashlytics
.
log
(
Log
.
WARN
,
tag
,
"Illegal state when invoking use: $s"
)
else
->
Crashlytics
.
log
(
Log
.
WARN
,
tag
,
"Illegal state when invoking use: $s"
)
}
}
}
}
fun
buildAdditionalArguments
(
cmd
:
ArrayList
<
String
>):
ArrayList
<
String
>
=
cmd
fun
buildAdditionalArguments
(
cmd
:
ArrayList
<
String
>):
ArrayList
<
String
>
=
cmd
fun
startNative
Processes
()
{
suspend
fun
start
Processes
()
{
val
configRoot
=
(
if
(
Build
.
VERSION
.
SDK_INT
<
24
||
app
.
getSystemService
<
UserManager
>()
val
configRoot
=
(
if
(
Build
.
VERSION
.
SDK_INT
<
24
||
app
.
getSystemService
<
UserManager
>()
?.
isUserUnlocked
!=
false
)
app
else
Core
.
deviceStorage
).
noBackupFilesDir
?.
isUserUnlocked
!=
false
)
app
else
Core
.
deviceStorage
).
noBackupFilesDir
val
udpFallback
=
data
.
udpFallback
val
udpFallback
=
data
.
udpFallback
...
@@ -233,19 +232,25 @@ object BaseService {
...
@@ -233,19 +232,25 @@ object BaseService {
else
startService
(
Intent
(
this
,
javaClass
))
else
startService
(
Intent
(
this
,
javaClass
))
}
}
fun
killProcesses
()
=
data
.
processes
.
killAll
()
suspend
fun
killProcesses
()
{
data
.
processes
?.
run
{
close
()
data
.
processes
=
null
}
}
fun
stopRunner
(
stopService
:
Boolean
,
msg
:
String
?
=
null
)
{
fun
stopRunner
(
restart
:
Boolean
=
false
,
msg
:
String
?
=
null
)
{
if
(
data
.
state
==
STOPPING
)
return
// channge the state
// channge the state
val
data
=
data
data
.
changeState
(
STOPPING
)
data
.
changeState
(
STOPPING
)
GlobalScope
.
launch
(
Dispatchers
.
Main
,
CoroutineStart
.
UNDISPATCHED
)
{
Core
.
analytics
.
logEvent
(
"stop"
,
bundleOf
(
Pair
(
FirebaseAnalytics
.
Param
.
METHOD
,
tag
)))
Core
.
analytics
.
logEvent
(
"stop"
,
bundleOf
(
Pair
(
FirebaseAnalytics
.
Param
.
METHOD
,
tag
)))
killProcesses
()
killProcesses
()
// clean up recevier
// clean up recevier
this
as
Service
this
@Interface
as
Service
val
data
=
data
if
(
data
.
closeReceiverRegistered
)
{
if
(
data
.
closeReceiverRegistered
)
{
unregisterReceiver
(
data
.
closeReceiver
)
unregisterReceiver
(
data
.
closeReceiver
)
data
.
closeReceiverRegistered
=
false
data
.
closeReceiverRegistered
=
false
...
@@ -265,7 +270,8 @@ object BaseService {
...
@@ -265,7 +270,8 @@ object BaseService {
data
.
changeState
(
STOPPED
,
msg
)
data
.
changeState
(
STOPPED
,
msg
)
// stop the service if nothing has bound to it
// stop the service if nothing has bound to it
if
(
stopService
)
stopSelf
()
if
(
restart
)
startRunner
()
else
stopSelf
()
}
}
}
fun
onStartCommand
(
intent
:
Intent
?,
flags
:
Int
,
startId
:
Int
):
Int
{
fun
onStartCommand
(
intent
:
Intent
?,
flags
:
Int
,
startId
:
Int
):
Int
{
...
@@ -276,7 +282,7 @@ object BaseService {
...
@@ -276,7 +282,7 @@ object BaseService {
if
(
profilePair
==
null
)
{
if
(
profilePair
==
null
)
{
// gracefully shutdown: https://stackoverflow.com/q/47337857/2245107
// gracefully shutdown: https://stackoverflow.com/q/47337857/2245107
data
.
notification
=
createNotification
(
""
)
data
.
notification
=
createNotification
(
""
)
stopRunner
(
tru
e
,
getString
(
R
.
string
.
profile_empty
))
stopRunner
(
fals
e
,
getString
(
R
.
string
.
profile_empty
))
return
Service
.
START_NOT_STICKY
return
Service
.
START_NOT_STICKY
}
}
val
(
profile
,
fallback
)
=
profilePair
val
(
profile
,
fallback
)
=
profilePair
...
@@ -298,15 +304,18 @@ object BaseService {
...
@@ -298,15 +304,18 @@ object BaseService {
Core
.
analytics
.
logEvent
(
"start"
,
bundleOf
(
Pair
(
FirebaseAnalytics
.
Param
.
METHOD
,
tag
)))
Core
.
analytics
.
logEvent
(
"start"
,
bundleOf
(
Pair
(
FirebaseAnalytics
.
Param
.
METHOD
,
tag
)))
data
.
changeState
(
CONNECTING
)
data
.
changeState
(
CONNECTING
)
data
.
connectingJob
=
GlobalScope
.
launch
(
Dispatchers
.
Main
)
{
thread
(
"$tag-Connecting"
)
{
try
{
try
{
proxy
.
init
()
proxy
.
init
()
data
.
udpFallback
?.
init
()
data
.
udpFallback
?.
init
()
// Clean up
killProcesses
()
startNativeProcesses
()
killProcesses
()
data
.
processes
=
GuardedProcessPool
{
printLog
(
it
)
data
.
connectingJob
?.
apply
{
runBlocking
{
cancelAndJoin
()
}
}
stopRunner
(
false
,
it
.
localizedMessage
)
}
startProcesses
()
proxy
.
scheduleUpdate
()
proxy
.
scheduleUpdate
()
data
.
udpFallback
?.
scheduleUpdate
()
data
.
udpFallback
?.
scheduleUpdate
()
...
@@ -314,12 +323,14 @@ object BaseService {
...
@@ -314,12 +323,14 @@ object BaseService {
data
.
changeState
(
CONNECTED
)
data
.
changeState
(
CONNECTED
)
}
catch
(
_
:
UnknownHostException
)
{
}
catch
(
_
:
UnknownHostException
)
{
stopRunner
(
tru
e
,
getString
(
R
.
string
.
invalid_server
))
stopRunner
(
fals
e
,
getString
(
R
.
string
.
invalid_server
))
}
catch
(
exc
:
Throwable
)
{
}
catch
(
exc
:
Throwable
)
{
if
(
exc
!
is
PluginManager
.
PluginNotFoundException
&&
exc
!
is
VpnService
.
NullConnectionException
)
{
if
(
exc
!
is
PluginManager
.
PluginNotFoundException
&&
exc
!
is
VpnService
.
NullConnectionException
)
{
printLog
(
exc
)
printLog
(
exc
)
}
}
stopRunner
(
true
,
"${getString(R.string.service_failed)}: ${exc.localizedMessage}"
)
stopRunner
(
false
,
"${getString(R.string.service_failed)}: ${exc.localizedMessage}"
)
}
finally
{
data
.
connectingJob
=
null
}
}
}
}
return
Service
.
START_NOT_STICKY
return
Service
.
START_NOT_STICKY
...
...
core/src/main/java/com/github/shadowsocks/bg/GuardedProcessPool.kt
View file @
3cfc17c2
...
@@ -26,135 +26,109 @@ import android.system.ErrnoException
...
@@ -26,135 +26,109 @@ import android.system.ErrnoException
import
android.system.Os
import
android.system.Os
import
android.system.OsConstants
import
android.system.OsConstants
import
android.util.Log
import
android.util.Log
import
androidx.annotation.MainThread
import
com.crashlytics.android.Crashlytics
import
com.crashlytics.android.Crashlytics
import
com.github.shadowsocks.Core
import
com.github.shadowsocks.Core
import
com.github.shadowsocks.utils.Commandline
import
com.github.shadowsocks.utils.Commandline
import
com.github.shadowsocks.utils.thread
import
kotlinx.coroutines.*
import
kotlinx.coroutines.channels.Channel
import
kotlinx.coroutines.selects.select
import
java.io.File
import
java.io.File
import
java.io.IOException
import
java.io.IOException
import
java.io.InputStream
import
java.io.InputStream
import
java.util.concurrent.ArrayBlockingQueue
import
kotlin.concurrent.thread
import
java.util.concurrent.atomic.AtomicReference
import
java.util.concurrent.TimeUnit
class
GuardedProcessPool
{
class
GuardedProcessPool
(
private
val
onFatal
:
(
IOException
)
->
Unit
)
:
CoroutineScope
{
companion
object
Dummy
:
IOException
(
"Oopsie the developer has made a no-no"
)
{
companion
object
{
private
const
val
TAG
=
"GuardedProcessPool"
private
const
val
TAG
=
"GuardedProcessPool"
private
val
ProcessImpl
by
lazy
{
Class
.
forName
(
"java.lang.ProcessManager\$ProcessImpl"
)
}
private
val
pid
by
lazy
{
private
val
pid
by
lazy
{
ProcessImpl
.
getDeclaredField
(
"pid"
).
apply
{
isAccessible
=
true
}
}
Class
.
forName
(
"java.lang.ProcessManager\$ProcessImpl"
).
getDeclaredField
(
"pid"
).
apply
{
isAccessible
=
true
}
private
val
exitValueMutex
by
lazy
{
ProcessImpl
.
getDeclaredField
(
"exitValueMutex"
).
apply
{
isAccessible
=
true
}
}
}
}
}
private
inner
class
Guard
(
private
val
cmd
:
List
<
String
>
,
private
val
onRestartCallback
:
(()
->
Unit
)?
)
{
private
inner
class
Guard
(
private
val
cmd
:
List
<
String
>)
{
val
cmdName
=
File
(
cmd
.
first
()).
nameWithoutExtension
private
val
abortChannel
=
Channel
<
Unit
>()
val
excQueue
=
ArrayBlockingQueue
<
IOException
>(
1
)
// ArrayBlockingQueue doesn't want null
private
var
startTime
:
Long
=
-
1
private
var
pushed
=
false
private
lateinit
var
process
:
Process
private
fun
streamLogger
(
input
:
InputStream
,
logger
:
(
String
,
String
)
->
Int
)
=
private
fun
streamLogger
(
input
:
InputStream
,
logger
:
(
String
)
->
Unit
)
=
try
{
thread
(
"StreamLogger-$cmdName"
)
{
input
.
bufferedReader
().
forEachLine
(
logger
)
try
{
input
.
bufferedReader
().
forEachLine
{
logger
(
TAG
,
it
)
}
}
catch
(
_
:
IOException
)
{
}
// ignore
}
catch
(
_
:
IOException
)
{
}
// ignore
fun
start
()
{
startTime
=
SystemClock
.
elapsedRealtime
()
process
=
ProcessBuilder
(
cmd
).
directory
(
Core
.
deviceStorage
.
noBackupFilesDir
).
start
()
}
}
private
fun
pushException
(
ioException
:
IOException
?)
{
if
(
pushed
)
return
suspend
fun
abort
()
{
excQueue
.
put
(
ioException
?:
Dummy
)
if
(!
abortChannel
.
isClosedForSend
)
abortChannel
.
send
(
Unit
)
pushed
=
true
}
}
fun
looper
(
host
:
HashSet
<
Thread
>)
{
suspend
fun
looper
(
onRestartCallback
:
(()
->
Unit
)?)
{
var
process
:
Process
?
=
null
var
running
=
true
val
cmdName
=
File
(
cmd
.
first
()).
nameWithoutExtension
val
exitChannel
=
Channel
<
Int
>()
try
{
try
{
var
callback
:
(()
->
Unit
)?
=
null
while
(
true
)
{
while
(
guardThreads
.
get
()
===
host
)
{
thread
(
name
=
"stderr-$cmdName"
)
{
streamLogger
(
process
.
errorStream
)
{
Log
.
e
(
cmdName
,
it
)
}
}
Crashlytics
.
log
(
Log
.
DEBUG
,
TAG
,
"start process: "
+
Commandline
.
toString
(
cmd
))
thread
(
name
=
"stdout-$cmdName"
)
{
val
startTime
=
SystemClock
.
elapsedRealtime
()
runBlocking
{
streamLogger
(
process
.
inputStream
)
{
Log
.
i
(
cmdName
,
it
)
}
process
=
ProcessBuilder
(
cmd
)
exitChannel
.
send
(
process
.
waitFor
())
// this thread also acts as a daemon thread for waitFor
.
redirectErrorStream
(
true
)
}
.
directory
(
Core
.
deviceStorage
.
noBackupFilesDir
)
}
.
start
()
if
(
select
{
abortChannel
.
onReceive
{
true
}
// prefer abort to save work
streamLogger
(
process
.
inputStream
,
Log
::
i
)
exitChannel
.
onReceive
{
false
}
streamLogger
(
process
.
errorStream
,
Log
::
e
)
})
break
running
=
false
if
(
callback
==
null
)
callback
=
onRestartCallback
else
callback
()
if
(
SystemClock
.
elapsedRealtime
()
-
startTime
<
1000
)
throw
IOException
(
"$cmdName exits too fast"
)
Crashlytics
.
log
(
Log
.
DEBUG
,
TAG
,
"restart process: "
+
Commandline
.
toString
(
cmd
))
pushException
(
null
)
start
()
process
.
waitFor
()
running
=
true
onRestartCallback
?.
invoke
()
if
(
SystemClock
.
elapsedRealtime
()
-
startTime
<
1000
)
{
Crashlytics
.
log
(
Log
.
WARN
,
TAG
,
"process exit too fast, stop guard: $cmdName"
)
break
}
}
}
}
catch
(
_
:
InterruptedException
)
{
Crashlytics
.
log
(
Log
.
DEBUG
,
TAG
,
"thread interrupt, destroy process: $cmdName"
)
}
catch
(
e
:
IOException
)
{
}
catch
(
e
:
IOException
)
{
pushException
(
e
)
Crashlytics
.
log
(
Log
.
WARN
,
TAG
,
"error occurred. stop guard: "
+
Commandline
.
toString
(
cmd
))
// calling callback without closing channel first will cause deadlock, therefore we defer it
GlobalScope
.
launch
(
Dispatchers
.
Main
)
{
onFatal
(
e
)
}
}
finally
{
}
finally
{
if
(
process
!=
null
)
{
abortChannel
.
close
()
if
(!
running
)
return
// process already exited, nothing to be done
if
(
Build
.
VERSION
.
SDK_INT
<
24
)
{
if
(
Build
.
VERSION
.
SDK_INT
<
24
)
{
val
pid
=
pid
.
get
(
process
)
as
Int
try
{
try
{
Os
.
kill
(
pid
,
OsConstants
.
SIGTERM
)
Os
.
kill
(
pid
.
get
(
process
)
as
Int
,
OsConstants
.
SIGTERM
)
}
catch
(
e
:
ErrnoException
)
{
}
catch
(
e
:
ErrnoException
)
{
if
(
e
.
errno
!=
OsConstants
.
ESRCH
)
throw
e
if
(
e
.
errno
!=
OsConstants
.
ESRCH
)
throw
e
}
}
val
mutex
=
exitValueMutex
.
get
(
process
)
as
Object
if
(
withTimeoutOrNull
(
500
)
{
exitChannel
.
receive
()
}
!=
null
)
return
synchronized
(
mutex
)
{
try
{
process
.
exitValue
()
}
catch
(
e
:
IllegalThreadStateException
)
{
mutex
.
wait
(
500
)
}
}
}
}
process
.
destroy
()
// kill the process
process
.
destroy
()
// kill the process
if
(
Build
.
VERSION
.
SDK_INT
>=
26
)
{
if
(
Build
.
VERSION
.
SDK_INT
>=
26
)
{
val
isKilled
=
process
.
waitFor
(
1L
,
TimeUnit
.
SECONDS
)
// wait for 1 second
if
(
withTimeoutOrNull
(
1000
)
{
exitChannel
.
receive
()
}
!=
null
)
return
if
(!
isKilled
)
{
process
.
destroyForcibly
()
// Force to kill the process if it's still alive
process
.
destroyForcibly
()
// Force to kill the process if it's still alive
}
}
}
exitChannel
.
receive
()
process
.
waitFor
()
// ensure the process is destroyed
}
pushException
(
null
)
}
}
}
}
}
}
/**
private
val
supervisor
=
SupervisorJob
()
* This is an indication of which thread pool is being active.
override
val
coroutineContext
get
()
=
Dispatchers
.
Main
+
super
visor
* Reading/writing this collection still needs an additional lock to prevent concurrent modification.
private
val
guards
=
ArrayList
<
Guard
>()
*/
private
val
guardThreads
=
AtomicReference
<
HashSet
<
Thread
>>(
HashSet
())
fun
start
(
cmd
:
List
<
String
>,
onRestartCallback
:
(()
->
Unit
)?
=
null
):
GuardedProcessPool
{
@MainThread
val
guard
=
Guard
(
cmd
,
onRestartCallback
)
suspend
fun
start
(
cmd
:
List
<
String
>,
onRestartCallback
:
(()
->
Unit
)?
=
null
)
{
val
guardThreads
=
guardThreads
.
get
()
Crashlytics
.
log
(
Log
.
DEBUG
,
TAG
,
"start process: "
+
Commandline
.
toString
(
cmd
))
synchronized
(
guardThreads
)
{
val
guard
=
Guard
(
cmd
)
guardThreads
.
add
(
thread
(
"GuardThread-${guard.cmdName}"
)
{
guard
.
start
()
guard
.
looper
(
guardThreads
)
guards
+=
guard
})
launch
(
start
=
CoroutineStart
.
UNDISPATCHED
)
{
guard
.
looper
(
onRestartCallback
)
}
}
val
ioException
=
guard
.
excQueue
.
take
()
if
(
ioException
!==
Dummy
)
throw
ioException
return
this
}
}
fun
killAll
()
{
@MainThread
val
guardThreads
=
guardThreads
.
getAndSet
(
HashSet
())
suspend
fun
close
()
{
synchronized
(
guardThreads
)
{
guards
.
forEach
{
it
.
abort
()
}
guardThreads
.
forEach
{
it
.
interrupt
()
}
super
visor
.
children
.
forEach
{
it
.
join
()
}
// we can't cancel the supervisor as we need it to do clean up
try
{
guardThreads
.
forEach
{
it
.
join
()
}
}
catch
(
_
:
InterruptedException
)
{
}
}
}
}
}
}
core/src/main/java/com/github/shadowsocks/bg/LocalDnsService.kt
View file @
3cfc17c2
...
@@ -32,8 +32,8 @@ import org.json.JSONObject
...
@@ -32,8 +32,8 @@ import org.json.JSONObject
object
LocalDnsService
{
object
LocalDnsService
{
interface
Interface
:
BaseService
.
Interface
{
interface
Interface
:
BaseService
.
Interface
{
override
fun
startNative
Processes
()
{
override
suspend
fun
start
Processes
()
{
super
.
start
Native
Processes
()
super
.
startProcesses
()
val
data
=
data
val
data
=
data
val
profile
=
data
.
proxy
!!
.
profile
val
profile
=
data
.
proxy
!!
.
profile
...
@@ -86,7 +86,7 @@ object LocalDnsService {
...
@@ -86,7 +86,7 @@ object LocalDnsService {
})
})
}
}
if
(!
profile
.
udpdns
)
data
.
processes
.
start
(
buildAdditionalArguments
(
arrayListOf
(
if
(!
profile
.
udpdns
)
data
.
processes
!!
.
start
(
buildAdditionalArguments
(
arrayListOf
(
File
(
app
.
applicationInfo
.
nativeLibraryDir
,
Executable
.
OVERTURE
).
absolutePath
,
File
(
app
.
applicationInfo
.
nativeLibraryDir
,
Executable
.
OVERTURE
).
absolutePath
,
"-c"
,
buildOvertureConfig
(
"overture.conf"
))))
"-c"
,
buildOvertureConfig
(
"overture.conf"
))))
}
}
...
...
core/src/main/java/com/github/shadowsocks/bg/LocalSocketListener.kt
View file @
3cfc17c2
...
@@ -23,16 +23,19 @@ package com.github.shadowsocks.bg
...
@@ -23,16 +23,19 @@ package com.github.shadowsocks.bg
import
android.net.LocalServerSocket
import
android.net.LocalServerSocket
import
android.net.LocalSocket
import
android.net.LocalSocket
import
android.net.LocalSocketAddress
import
android.net.LocalSocketAddress
import
android.system.ErrnoException
import
android.system.Os
import
android.system.OsConstants
import
com.github.shadowsocks.utils.printLog
import
com.github.shadowsocks.utils.printLog
import
java.io.File
import
java.io.File
import
java.io.IOException
import
java.io.IOException
abstract
class
LocalSocketListener
(
protected
val
tag
:
String
)
:
Thread
(
tag
)
{
abstract
class
LocalSocketListener
(
name
:
String
,
socketFile
:
File
)
:
Thread
(
name
),
AutoCloseable
{
init
{
private
val
localSocket
=
LocalSocket
().
apply
{
setUncaughtExceptionHandler
{
_
,
t
->
printLog
(
t
)
}
socketFile
.
delete
()
// It's a must-have to close and reuse previous local socket.
bind
(
LocalSocketAddress
(
socketFile
.
absolutePath
,
LocalSocketAddress
.
Namespace
.
FILESYSTEM
))
}
}
private
val
serverSocket
=
LocalServerSocket
(
localSocket
.
fileDescriptor
)
protected
abstract
val
socketFile
:
File
@Volatile
@Volatile
private
var
running
=
true
private
var
running
=
true
...
@@ -40,29 +43,25 @@ abstract class LocalSocketListener(protected val tag: String) : Thread(tag) {
...
@@ -40,29 +43,25 @@ abstract class LocalSocketListener(protected val tag: String) : Thread(tag) {
* Inherited class do not need to close input/output streams as they will be closed automatically.
* Inherited class do not need to close input/output streams as they will be closed automatically.
*/
*/
protected
abstract
fun
accept
(
socket
:
LocalSocket
)
protected
abstract
fun
accept
(
socket
:
LocalSocket
)
final
override
fun
run
()
{
final
override
fun
run
()
=
localSocket
.
use
{
socketFile
.
delete
()
// It's a must-have to close and reuse previous local socket.
LocalSocket
().
use
{
localSocket
->
val
serverSocket
=
try
{
localSocket
.
bind
(
LocalSocketAddress
(
socketFile
.
absolutePath
,
LocalSocketAddress
.
Namespace
.
FILESYSTEM
))
LocalServerSocket
(
localSocket
.
fileDescriptor
)
}
catch
(
e
:
IOException
)
{
printLog
(
e
)
return
}
while
(
running
)
{
while
(
running
)
{
try
{
try
{
serverSocket
.
accept
(
)
accept
(
serverSocket
.
accept
()
)
}
catch
(
e
:
IOException
)
{
}
catch
(
e
:
IOException
)
{
printLog
(
e
)
if
(
running
)
printLog
(
e
)
continue
continue
}
?.
use
(
this
::
accept
)
}
}
}
}
}
}
fun
stopThread
()
{
override
fun
close
()
{
running
=
false
running
=
false
interrupt
()
// see also: https://issuetracker.google.com/issues/36945762#comment15
try
{
Os
.
shutdown
(
localSocket
.
fileDescriptor
,
OsConstants
.
SHUT_RDWR
)
}
catch
(
e
:
ErrnoException
)
{
if
(
e
.
errno
!=
OsConstants
.
EBADF
)
throw
e
// suppress fd already closed
}
join
()
}
}
}
}
core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt
View file @
3cfc17c2
...
@@ -30,16 +30,18 @@ import com.github.shadowsocks.database.ProfileManager
...
@@ -30,16 +30,18 @@ import com.github.shadowsocks.database.ProfileManager
import
com.github.shadowsocks.plugin.PluginConfiguration
import
com.github.shadowsocks.plugin.PluginConfiguration
import
com.github.shadowsocks.plugin.PluginManager
import
com.github.shadowsocks.plugin.PluginManager
import
com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.utils.*
import
com.github.shadowsocks.utils.DirectBoot
import
okhttp3.FormBody
import
com.github.shadowsocks.utils.parseNumericAddress
import
okhttp3.OkHttpClient
import
com.github.shadowsocks.utils.signaturesCompat
import
okhttp3.Request
import
kotlinx.coroutines.Dispatchers
import
kotlinx.coroutines.withContext
import
kotlinx.coroutines.withTimeout
import
java.io.File
import
java.io.File
import
java.io.IOException
import
java.io.IOException
import
java.net.HttpURLConnection
import
java.net.InetAddress
import
java.net.InetAddress
import
java.net.UnknownHostException
import
java.net.UnknownHostException
import
java.security.MessageDigest
import
java.security.MessageDigest
import
java.util.concurrent.TimeUnit
/**
/**
* This class sets up environment for ss-local.
* This class sets up environment for ss-local.
...
@@ -50,25 +52,26 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
...
@@ -50,25 +52,26 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
private
val
plugin
=
PluginConfiguration
(
profile
.
plugin
?:
""
).
selectedOptions
private
val
plugin
=
PluginConfiguration
(
profile
.
plugin
?:
""
).
selectedOptions
val
pluginPath
by
lazy
{
PluginManager
.
init
(
plugin
)
}
val
pluginPath
by
lazy
{
PluginManager
.
init
(
plugin
)
}
fun
init
()
{
suspend
fun
init
()
{
if
(
profile
.
host
==
"198.199.101.152"
)
{
if
(
profile
.
host
==
"198.199.101.152"
)
{
val
client
=
OkHttpClient
.
Builder
()
.
connectTimeout
(
10
,
TimeUnit
.
SECONDS
)
.
writeTimeout
(
10
,
TimeUnit
.
SECONDS
)
.
readTimeout
(
30
,
TimeUnit
.
SECONDS
)
.
build
()
val
mdg
=
MessageDigest
.
getInstance
(
"SHA-1"
)
val
mdg
=
MessageDigest
.
getInstance
(
"SHA-1"
)
mdg
.
update
(
Core
.
packageInfo
.
signaturesCompat
.
first
().
toByteArray
())
mdg
.
update
(
Core
.
packageInfo
.
signaturesCompat
.
first
().
toByteArray
())
val
requestBody
=
FormBody
.
Builder
()
val
conn
=
RemoteConfig
.
proxyUrl
.
openConnection
()
as
HttpURLConnection
.
add
(
"sig"
,
String
(
Base64
.
encode
(
mdg
.
digest
(),
0
)))
conn
.
requestMethod
=
"POST"
.
build
()
conn
.
doOutput
=
true
val
request
=
Request
.
Builder
()
.
url
(
RemoteConfig
.
proxyUrl
)
val
proxies
=
try
{
.
post
(
requestBody
)
withTimeout
(
30_000
)
{
.
build
()
withContext
(
Dispatchers
.
IO
)
{
conn
.
outputStream
.
bufferedWriter
().
use
{
val
proxies
=
client
.
newCall
(
request
).
execute
()
it
.
write
(
"sig="
+
String
(
Base64
.
encode
(
mdg
.
digest
(),
Base64
.
DEFAULT
)))
.
body
()
!!
.
string
().
split
(
'|'
).
toMutableList
()
}
conn
.
inputStream
.
bufferedReader
().
readText
()
}
}
}
finally
{
conn
.
disconnect
()
}.
split
(
'|'
).
toMutableList
()
proxies
.
shuffle
()
proxies
.
shuffle
()
val
proxy
=
proxies
.
first
().
split
(
':'
)
val
proxy
=
proxies
.
first
().
split
(
':'
)
profile
.
host
=
proxy
[
0
].
trim
()
profile
.
host
=
proxy
[
0
].
trim
()
...
@@ -80,22 +83,16 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
...
@@ -80,22 +83,16 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
if
(
route
==
Acl
.
CUSTOM_RULES
)
Acl
.
save
(
Acl
.
CUSTOM_RULES
,
Acl
.
customRules
.
flatten
(
10
))
if
(
route
==
Acl
.
CUSTOM_RULES
)
Acl
.
save
(
Acl
.
CUSTOM_RULES
,
Acl
.
customRules
.
flatten
(
10
))
// it's hard to resolve DNS on a specific interface so we'll do it here
// it's hard to resolve DNS on a specific interface so we'll do it here
if
(
profile
.
host
.
parseNumericAddress
()
==
null
)
{
if
(
profile
.
host
.
parseNumericAddress
()
==
null
)
profile
.
host
=
withTimeout
(
10_000
)
{
thread
(
"ProxyInstance-resolve"
)
{
withContext
(
Dispatchers
.
IO
)
{
InetAddress
.
getByName
(
profile
.
host
).
hostAddress
}
// A WAR fix for Huawei devices that UnknownHostException cannot be caught correctly
}
?:
throw
UnknownHostException
()
try
{
profile
.
host
=
InetAddress
.
getByName
(
profile
.
host
).
hostAddress
?:
""
}
catch
(
_
:
UnknownHostException
)
{
}
}.
join
(
10
*
1000
)
if
(
profile
.
host
.
parseNumericAddress
()
==
null
)
throw
UnknownHostException
()
}
}
}
/**
/**
* Sensitive shadowsocks configuration file requires extra protection. It may be stored in encrypted storage or
* Sensitive shadowsocks configuration file requires extra protection. It may be stored in encrypted storage or
* device storage, depending on which is currently available.
* device storage, depending on which is currently available.
*/
*/
fun
start
(
service
:
BaseService
.
Interface
,
stat
:
File
,
configFile
:
File
,
extraFlag
:
String
?
=
null
)
{
suspend
fun
start
(
service
:
BaseService
.
Interface
,
stat
:
File
,
configFile
:
File
,
extraFlag
:
String
?
=
null
)
{
trafficMonitor
=
TrafficMonitor
(
stat
)
trafficMonitor
=
TrafficMonitor
(
stat
)
this
.
configFile
=
configFile
this
.
configFile
=
configFile
...
@@ -122,7 +119,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
...
@@ -122,7 +119,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
if
(
DataStore
.
tcpFastOpen
)
cmd
+=
"--fast-open"
if
(
DataStore
.
tcpFastOpen
)
cmd
+=
"--fast-open"
service
.
data
.
processes
.
start
(
cmd
)
service
.
data
.
processes
!!
.
start
(
cmd
)
}
}
fun
scheduleUpdate
()
{
fun
scheduleUpdate
()
{
...
...
core/src/main/java/com/github/shadowsocks/bg/RemoteConfig.kt
View file @
3cfc17c2
...
@@ -25,11 +25,12 @@ import androidx.core.os.bundleOf
...
@@ -25,11 +25,12 @@ import androidx.core.os.bundleOf
import
com.github.shadowsocks.Core
import
com.github.shadowsocks.Core
import
com.github.shadowsocks.core.R
import
com.github.shadowsocks.core.R
import
com.google.firebase.remoteconfig.FirebaseRemoteConfig
import
com.google.firebase.remoteconfig.FirebaseRemoteConfig
import
java.net.URL
object
RemoteConfig
{
object
RemoteConfig
{
private
val
config
=
FirebaseRemoteConfig
.
getInstance
().
apply
{
setDefaults
(
R
.
xml
.
default_configs
)
}
private
val
config
=
FirebaseRemoteConfig
.
getInstance
().
apply
{
setDefaults
(
R
.
xml
.
default_configs
)
}
val
proxyUrl
get
()
=
config
.
getString
(
"proxy_url"
)
val
proxyUrl
get
()
=
URL
(
config
.
getString
(
"proxy_url"
)
)
fun
fetch
()
=
config
.
fetch
().
addOnCompleteListener
{
fun
fetch
()
=
config
.
fetch
().
addOnCompleteListener
{
if
(
it
.
isSuccessful
)
config
.
activateFetched
()
else
{
if
(
it
.
isSuccessful
)
config
.
activateFetched
()
else
{
...
...
core/src/main/java/com/github/shadowsocks/bg/TrafficMonitor.kt
View file @
3cfc17c2
...
@@ -23,18 +23,14 @@ package com.github.shadowsocks.bg
...
@@ -23,18 +23,14 @@ package com.github.shadowsocks.bg
import
android.net.LocalSocket
import
android.net.LocalSocket
import
android.os.SystemClock
import
android.os.SystemClock
import
com.github.shadowsocks.aidl.TrafficStats
import
com.github.shadowsocks.aidl.TrafficStats
import
com.github.shadowsocks.utils.printLog
import
java.io.File
import
java.io.File
import
java.io.IOException
import
java.io.IOException
import
java.nio.ByteBuffer
import
java.nio.ByteBuffer
import
java.nio.ByteOrder
import
java.nio.ByteOrder
class
TrafficMonitor
(
statFile
:
File
)
:
AutoCloseable
{
class
TrafficMonitor
(
statFile
:
File
)
:
AutoCloseable
{
private
val
thread
=
object
:
LocalSocketListener
(
"TrafficMonitor"
)
{
private
val
thread
=
object
:
LocalSocketListener
(
"TrafficMonitor"
,
statFile
)
{
override
val
socketFile
=
statFile
override
fun
accept
(
socket
:
LocalSocket
)
=
socket
.
use
{
override
fun
accept
(
socket
:
LocalSocket
)
{
try
{
val
buffer
=
ByteArray
(
16
)
val
buffer
=
ByteArray
(
16
)
if
(
socket
.
inputStream
.
read
(
buffer
)
!=
16
)
throw
IOException
(
"Unexpected traffic stat length"
)
if
(
socket
.
inputStream
.
read
(
buffer
)
!=
16
)
throw
IOException
(
"Unexpected traffic stat length"
)
val
stat
=
ByteBuffer
.
wrap
(
buffer
).
order
(
ByteOrder
.
LITTLE_ENDIAN
)
val
stat
=
ByteBuffer
.
wrap
(
buffer
).
order
(
ByteOrder
.
LITTLE_ENDIAN
)
...
@@ -48,16 +44,12 @@ class TrafficMonitor(statFile: File) : AutoCloseable {
...
@@ -48,16 +44,12 @@ class TrafficMonitor(statFile: File) : AutoCloseable {
current
.
rxTotal
=
rx
current
.
rxTotal
=
rx
dirty
=
true
dirty
=
true
}
}
}
catch
(
e
:
IOException
)
{
printLog
(
e
)
}
}
}
}.
apply
{
start
()
}
}.
apply
{
start
()
}
val
current
=
TrafficStats
()
val
current
=
TrafficStats
()
var
out
=
TrafficStats
()
var
out
=
TrafficStats
()
private
var
timestampLast
=
0L
private
var
timestampLast
=
0L
@Volatile
private
var
dirty
=
false
private
var
dirty
=
false
fun
requestUpdate
():
Pair
<
TrafficStats
,
Boolean
>
{
fun
requestUpdate
():
Pair
<
TrafficStats
,
Boolean
>
{
...
@@ -87,5 +79,5 @@ class TrafficMonitor(statFile: File) : AutoCloseable {
...
@@ -87,5 +79,5 @@ class TrafficMonitor(statFile: File) : AutoCloseable {
return
Pair
(
out
,
updated
)
return
Pair
(
out
,
updated
)
}
}
override
fun
close
()
=
thread
.
stopThread
()
override
fun
close
()
=
thread
.
close
()
}
}
core/src/main/java/com/github/shadowsocks/bg/TransproxyService.kt
View file @
3cfc17c2
...
@@ -36,7 +36,7 @@ class TransproxyService : Service(), LocalDnsService.Interface {
...
@@ -36,7 +36,7 @@ class TransproxyService : Service(), LocalDnsService.Interface {
override
fun
onStartCommand
(
intent
:
Intent
?,
flags
:
Int
,
startId
:
Int
):
Int
=
override
fun
onStartCommand
(
intent
:
Intent
?,
flags
:
Int
,
startId
:
Int
):
Int
=
super
<
LocalDnsService
.
Interface
>.
onStartCommand
(
intent
,
flags
,
startId
)
super
<
LocalDnsService
.
Interface
>.
onStartCommand
(
intent
,
flags
,
startId
)
private
fun
startDNSTunnel
()
{
private
suspend
fun
startDNSTunnel
()
{
val
proxy
=
data
.
proxy
!!
val
proxy
=
data
.
proxy
!!
val
cmd
=
arrayListOf
(
File
(
applicationInfo
.
nativeLibraryDir
,
Executable
.
SS_TUNNEL
).
absolutePath
,
val
cmd
=
arrayListOf
(
File
(
applicationInfo
.
nativeLibraryDir
,
Executable
.
SS_TUNNEL
).
absolutePath
,
"-t"
,
"10"
,
"-t"
,
"10"
,
...
@@ -47,10 +47,10 @@ class TransproxyService : Service(), LocalDnsService.Interface {
...
@@ -47,10 +47,10 @@ class TransproxyService : Service(), LocalDnsService.Interface {
// config is already built by BaseService.Interface
// config is already built by BaseService.Interface
"-c"
,
(
data
.
udpFallback
?:
proxy
).
configFile
!!
.
absolutePath
)
"-c"
,
(
data
.
udpFallback
?:
proxy
).
configFile
!!
.
absolutePath
)
if
(
DataStore
.
tcpFastOpen
)
cmd
+=
"--fast-open"
if
(
DataStore
.
tcpFastOpen
)
cmd
+=
"--fast-open"
data
.
processes
.
start
(
cmd
)
data
.
processes
!!
.
start
(
cmd
)
}
}
private
fun
startRedsocksDaemon
()
{
private
suspend
fun
startRedsocksDaemon
()
{
File
(
Core
.
deviceStorage
.
noBackupFilesDir
,
"redsocks.conf"
).
writeText
(
"""base {
File
(
Core
.
deviceStorage
.
noBackupFilesDir
,
"redsocks.conf"
).
writeText
(
"""base {
log_debug = off;
log_debug = off;
log_info = off;
log_info = off;
...
@@ -66,13 +66,13 @@ redsocks {
...
@@ -66,13 +66,13 @@ redsocks {
type = socks5;
type = socks5;
}
}
"""
)
"""
)
data
.
processes
.
start
(
listOf
(
data
.
processes
!!
.
start
(
listOf
(
File
(
applicationInfo
.
nativeLibraryDir
,
Executable
.
REDSOCKS
).
absolutePath
,
"-c"
,
"redsocks.conf"
))
File
(
applicationInfo
.
nativeLibraryDir
,
Executable
.
REDSOCKS
).
absolutePath
,
"-c"
,
"redsocks.conf"
))
}
}
override
fun
startNative
Processes
()
{
override
suspend
fun
start
Processes
()
{
startRedsocksDaemon
()
startRedsocksDaemon
()
super
.
start
Native
Processes
()
super
.
startProcesses
()
if
(
data
.
proxy
!!
.
profile
.
udpdns
)
startDNSTunnel
()
if
(
data
.
proxy
!!
.
profile
.
udpdns
)
startDNSTunnel
()
}
}
...
...
core/src/main/java/com/github/shadowsocks/bg/VpnService.kt
View file @
3cfc17c2
...
@@ -39,6 +39,7 @@ import com.github.shadowsocks.utils.Key
...
@@ -39,6 +39,7 @@ import com.github.shadowsocks.utils.Key
import
com.github.shadowsocks.utils.Subnet
import
com.github.shadowsocks.utils.Subnet
import
com.github.shadowsocks.utils.parseNumericAddress
import
com.github.shadowsocks.utils.parseNumericAddress
import
com.github.shadowsocks.utils.printLog
import
com.github.shadowsocks.utils.printLog
import
kotlinx.coroutines.*
import
java.io.Closeable
import
java.io.Closeable
import
java.io.File
import
java.io.File
import
java.io.FileDescriptor
import
java.io.FileDescriptor
...
@@ -78,10 +79,15 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
...
@@ -78,10 +79,15 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
override
fun
close
()
=
Os
.
close
(
fd
)
override
fun
close
()
=
Os
.
close
(
fd
)
}
}
private
inner
class
ProtectWorker
:
LocalSocketListener
(
"ShadowsocksVpnThread"
)
{
private
inner
class
ProtectWorker
:
override
val
socketFile
:
File
=
File
(
Core
.
deviceStorage
.
noBackupFilesDir
,
"protect_path"
)
LocalSocketListener
(
"ShadowsocksVpnThread"
,
File
(
Core
.
deviceStorage
.
noBackupFilesDir
,
"protect_path"
)),
CoroutineScope
{
private
val
job
=
SupervisorJob
()
override
val
coroutineContext
get
()
=
Dispatchers
.
IO
+
job
+
CoroutineExceptionHandler
{
_
,
t
->
printLog
(
t
)
}
override
fun
accept
(
socket
:
LocalSocket
)
=
try
{
override
fun
accept
(
socket
:
LocalSocket
)
{
launch
{
socket
.
use
{
socket
.
inputStream
.
read
()
socket
.
inputStream
.
read
()
val
fd
=
socket
.
ancillaryFileDescriptors
!!
.
single
()
!!
val
fd
=
socket
.
ancillaryFileDescriptors
!!
.
single
()
!!
CloseableFd
(
fd
).
use
{
CloseableFd
(
fd
).
use
{
...
@@ -96,8 +102,14 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
...
@@ -96,8 +102,14 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
}
else
protect
(
getInt
.
invoke
(
fd
)
as
Int
)
}
else
protect
(
getInt
.
invoke
(
fd
)
as
Int
)
})
0
else
1
)
})
0
else
1
)
}
}
}
catch
(
e
:
IOException
)
{
}
printLog
(
e
)
}
}
suspend
fun
shutdown
()
{
job
.
cancel
()
close
()
job
.
join
()
}
}
}
}
inner
class
NullConnectionException
:
NullPointerException
()
{
inner
class
NullConnectionException
:
NullPointerException
()
{
...
@@ -139,14 +151,14 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
...
@@ -139,14 +151,14 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
else
->
super
<
LocalDnsService
.
Interface
>.
onBind
(
intent
)
else
->
super
<
LocalDnsService
.
Interface
>.
onBind
(
intent
)
}
}
override
fun
onRevoke
()
=
stopRunner
(
true
)
override
fun
onRevoke
()
=
stopRunner
()
override
fun
killProcesses
()
{
override
suspend
fun
killProcesses
()
{
if
(
listeningForDefaultNetwork
)
{
if
(
listeningForDefaultNetwork
)
{
connectivity
.
unregisterNetworkCallback
(
defaultNetworkCallback
)
connectivity
.
unregisterNetworkCallback
(
defaultNetworkCallback
)
listeningForDefaultNetwork
=
false
listeningForDefaultNetwork
=
false
}
}
worker
?.
s
topThread
()
worker
?.
s
hutdown
()
worker
=
null
worker
=
null
super
.
killProcesses
()
super
.
killProcesses
()
conn
?.
close
()
conn
?.
close
()
...
@@ -159,16 +171,14 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
...
@@ -159,16 +171,14 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
startActivity
(
Intent
(
this
,
VpnRequestActivity
::
class
.
java
)
startActivity
(
Intent
(
this
,
VpnRequestActivity
::
class
.
java
)
.
addFlags
(
Intent
.
FLAG_ACTIVITY_NEW_TASK
))
.
addFlags
(
Intent
.
FLAG_ACTIVITY_NEW_TASK
))
else
return
super
<
LocalDnsService
.
Interface
>.
onStartCommand
(
intent
,
flags
,
startId
)
else
return
super
<
LocalDnsService
.
Interface
>.
onStartCommand
(
intent
,
flags
,
startId
)
stopRunner
(
true
)
stopRunner
()
return
Service
.
START_NOT_STICKY
return
Service
.
START_NOT_STICKY
}
}
override
fun
startNativeProcesses
()
{
override
suspend
fun
startProcesses
()
{
val
worker
=
ProtectWorker
()
worker
=
ProtectWorker
().
apply
{
start
()
}
worker
.
start
()
this
.
worker
=
worker
super
.
start
Native
Processes
()
super
.
startProcesses
()
sendFd
(
startVpn
())
sendFd
(
startVpn
())
}
}
...
@@ -178,7 +188,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
...
@@ -178,7 +188,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
return
cmd
return
cmd
}
}
private
fun
startVpn
():
FileDescriptor
{
private
suspend
fun
startVpn
():
FileDescriptor
{
val
profile
=
data
.
proxy
!!
.
profile
val
profile
=
data
.
proxy
!!
.
profile
val
builder
=
Builder
()
val
builder
=
Builder
()
.
setConfigureIntent
(
Core
.
configureIntent
(
this
))
.
setConfigureIntent
(
Core
.
configureIntent
(
this
))
...
@@ -247,11 +257,11 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
...
@@ -247,11 +257,11 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
cmd
+=
"--dnsgw"
cmd
+=
"--dnsgw"
cmd
+=
"127.0.0.1:${DataStore.portLocalDns}"
cmd
+=
"127.0.0.1:${DataStore.portLocalDns}"
}
}
data
.
processes
.
start
(
cmd
,
onRestartCallback
=
{
data
.
processes
!!
.
start
(
cmd
,
onRestartCallback
=
{
try
{
try
{
sendFd
(
conn
.
fileDescriptor
)
sendFd
(
conn
.
fileDescriptor
)
}
catch
(
e
:
ErrnoException
)
{
}
catch
(
e
:
ErrnoException
)
{
stopRunner
(
tru
e
,
e
.
message
)
stopRunner
(
fals
e
,
e
.
message
)
}
}
})
})
return
conn
.
fileDescriptor
return
conn
.
fileDescriptor
...
...
core/src/main/java/com/github/shadowsocks/utils/HttpsTest.kt
View file @
3cfc17c2
...
@@ -26,9 +26,9 @@ import androidx.lifecycle.ViewModel
...
@@ -26,9 +26,9 @@ import androidx.lifecycle.ViewModel
import
com.github.shadowsocks.Core
import
com.github.shadowsocks.Core
import
com.github.shadowsocks.Core.app
import
com.github.shadowsocks.Core.app
import
com.github.shadowsocks.acl.Acl
import
com.github.shadowsocks.acl.Acl
import
com.github.shadowsocks.bg.BaseService
import
com.github.shadowsocks.core.R
import
com.github.shadowsocks.core.R
import
com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.preference.DataStore
import
kotlinx.coroutines.*
import
java.io.IOException
import
java.io.IOException
import
java.net.HttpURLConnection
import
java.net.HttpURLConnection
import
java.net.InetSocketAddress
import
java.net.InetSocketAddress
...
@@ -72,14 +72,12 @@ class HttpsTest : ViewModel() {
...
@@ -72,14 +72,12 @@ class HttpsTest : ViewModel() {
}
}
}
}
private
var
testCount
=
0
private
var
running
:
Pair
<
HttpURLConnection
,
Job
>?
=
null
val
status
=
MutableLiveData
<
Status
>().
apply
{
value
=
Status
.
Idle
}
val
status
=
MutableLiveData
<
Status
>().
apply
{
value
=
Status
.
Idle
}
fun
testConnection
()
{
fun
testConnection
()
{
++
testCount
cancelTest
()
status
.
value
=
Status
.
Testing
status
.
value
=
Status
.
Testing
val
id
=
testCount
// it would change by other code
thread
(
"ConnectionTest"
)
{
val
url
=
URL
(
"https"
,
when
(
Core
.
currentProfile
!!
.
first
.
route
)
{
val
url
=
URL
(
"https"
,
when
(
Core
.
currentProfile
!!
.
first
.
route
)
{
Acl
.
CHINALIST
->
"www.qualcomm.cn"
Acl
.
CHINALIST
->
"www.qualcomm.cn"
else
->
"www.google.com"
else
->
"www.google.com"
...
@@ -90,7 +88,9 @@ class HttpsTest : ViewModel() {
...
@@ -90,7 +88,9 @@ class HttpsTest : ViewModel() {
conn
.
setRequestProperty
(
"Connection"
,
"close"
)
conn
.
setRequestProperty
(
"Connection"
,
"close"
)
conn
.
instanceFollowRedirects
=
false
conn
.
instanceFollowRedirects
=
false
conn
.
useCaches
=
false
conn
.
useCaches
=
false
val
result
=
try
{
running
=
conn
to
GlobalScope
.
launch
(
Dispatchers
.
Main
,
CoroutineStart
.
UNDISPATCHED
)
{
status
.
value
=
withContext
(
Dispatchers
.
IO
)
{
try
{
val
start
=
SystemClock
.
elapsedRealtime
()
val
start
=
SystemClock
.
elapsedRealtime
()
val
code
=
conn
.
responseCode
val
code
=
conn
.
responseCode
val
elapsed
=
SystemClock
.
elapsedRealtime
()
-
start
val
elapsed
=
SystemClock
.
elapsedRealtime
()
-
start
...
@@ -101,12 +101,18 @@ class HttpsTest : ViewModel() {
...
@@ -101,12 +101,18 @@ class HttpsTest : ViewModel() {
}
finally
{
}
finally
{
conn
.
disconnect
()
conn
.
disconnect
()
}
}
if
(
testCount
==
id
)
status
.
postValue
(
result
)
}
}
}
}
}
private
fun
cancelTest
()
=
running
?.
let
{
(
conn
,
job
)
->
job
.
cancel
()
// ensure job is cancelled before interrupting
conn
.
disconnect
()
running
=
null
}
fun
invalidate
()
{
fun
invalidate
()
{
++
testCount
cancelTest
()
status
.
value
=
Status
.
Idle
status
.
value
=
Status
.
Idle
}
}
}
}
core/src/main/java/com/github/shadowsocks/utils/TcpFastOpen.kt
View file @
3cfc17c2
...
@@ -20,6 +20,8 @@
...
@@ -20,6 +20,8 @@
package
com.github.shadowsocks.utils
package
com.github.shadowsocks.utils
import
kotlinx.coroutines.runBlocking
import
kotlinx.coroutines.withTimeoutOrNull
import
java.io.File
import
java.io.File
import
java.io.IOException
import
java.io.IOException
...
@@ -58,5 +60,5 @@ object TcpFastOpen {
...
@@ -58,5 +60,5 @@ object TcpFastOpen {
e
.
localizedMessage
e
.
localizedMessage
}
}
}
}
fun
enable
Async
()
=
thread
(
"TcpFastOpen"
)
{
enable
()
}.
join
(
1000
)
fun
enable
Timeout
()
=
runBlocking
{
withTimeoutOrNull
(
1000
)
{
enable
()
}
}
}
}
core/src/main/java/com/github/shadowsocks/utils/Utils.kt
View file @
3cfc17c2
...
@@ -54,17 +54,6 @@ fun broadcastReceiver(callback: (Context, Intent) -> Unit): BroadcastReceiver =
...
@@ -54,17 +54,6 @@ fun broadcastReceiver(callback: (Context, Intent) -> Unit): BroadcastReceiver =
override
fun
onReceive
(
context
:
Context
,
intent
:
Intent
)
=
callback
(
context
,
intent
)
override
fun
onReceive
(
context
:
Context
,
intent
:
Intent
)
=
callback
(
context
,
intent
)
}
}
/**
* Wrapper for kotlin.concurrent.thread that tracks uncaught exceptions.
*/
fun
thread
(
name
:
String
?
=
null
,
start
:
Boolean
=
true
,
isDaemon
:
Boolean
=
false
,
contextClassLoader
:
ClassLoader
?
=
null
,
priority
:
Int
=
-
1
,
block
:
()
->
Unit
):
Thread
{
val
thread
=
kotlin
.
concurrent
.
thread
(
false
,
isDaemon
,
contextClassLoader
,
name
,
priority
,
block
)
thread
.
setUncaughtExceptionHandler
{
_
,
t
->
printLog
(
t
)
}
if
(
start
)
thread
.
start
()
return
thread
}
val
URLConnection
.
responseLength
:
Long
val
URLConnection
.
responseLength
:
Long
get
()
=
if
(
Build
.
VERSION
.
SDK_INT
>=
24
)
contentLengthLong
else
contentLength
.
toLong
()
get
()
=
if
(
Build
.
VERSION
.
SDK_INT
>=
24
)
contentLengthLong
else
contentLength
.
toLong
()
...
...
mobile/proguard-rules.pro
View file @
3cfc17c2
...
@@ -22,5 +22,3 @@
...
@@ -22,5 +22,3 @@
#-
renamesourcefileattribute
SourceFile
#-
renamesourcefileattribute
SourceFile
-
dontwarn
com
.
google
.
android
.
gms
.
internal
.
**
-
dontwarn
com
.
google
.
android
.
gms
.
internal
.
**
-
dontwarn
okhttp3
.
**
-
dontwarn
okio
.
**
mobile/src/main/java/com/github/shadowsocks/AppManager.kt
View file @
3cfc17c2
...
@@ -32,10 +32,10 @@ import android.content.pm.PackageInfo
...
@@ -32,10 +32,10 @@ import android.content.pm.PackageInfo
import
android.content.pm.PackageManager
import
android.content.pm.PackageManager
import
android.graphics.drawable.Drawable
import
android.graphics.drawable.Drawable
import
android.os.Bundle
import
android.os.Bundle
import
android.os.Handler
import
android.view.*
import
android.view.*
import
android.widget.ImageView
import
android.widget.ImageView
import
android.widget.Switch
import
android.widget.Switch
import
androidx.annotation.UiThread
import
androidx.appcompat.app.AppCompatActivity
import
androidx.appcompat.app.AppCompatActivity
import
androidx.appcompat.widget.Toolbar
import
androidx.appcompat.widget.Toolbar
import
androidx.core.content.getSystemService
import
androidx.core.content.getSystemService
...
@@ -47,9 +47,8 @@ import com.github.shadowsocks.database.ProfileManager
...
@@ -47,9 +47,8 @@ import com.github.shadowsocks.database.ProfileManager
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
com.github.shadowsocks.utils.Key
import
com.github.shadowsocks.utils.Key
import
com.github.shadowsocks.utils.thread
import
com.google.android.material.snackbar.Snackbar
import
com.google.android.material.snackbar.Snackbar
import
java.util.concurrent.atomic.AtomicBoolean
import
kotlinx.coroutines.*
class
AppManager
:
AppCompatActivity
()
{
class
AppManager
:
AppCompatActivity
()
{
companion
object
{
companion
object
{
...
@@ -58,13 +57,13 @@ class AppManager : AppCompatActivity() {
...
@@ -58,13 +57,13 @@ class AppManager : AppCompatActivity() {
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
)
=
synchronized
(
AppManager
)
{
private
suspend
fun
getApps
(
pm
:
PackageManager
)
=
synchronized
(
AppManager
)
{
if
(
receiver
==
null
)
receiver
=
Core
.
listenForPackageChanges
{
if
(
receiver
==
null
)
receiver
=
Core
.
listenForPackageChanges
{
synchronized
(
AppManager
)
{
synchronized
(
AppManager
)
{
receiver
=
null
receiver
=
null
cachedApps
=
null
cachedApps
=
null
}
}
AppManager
.
instance
?.
re
loadApps
()
AppManager
.
instance
?.
loadApps
()
}
}
// Labels and icons can change on configuration (locale, etc.) changes, therefore they are not cached.
// Labels and icons can change on configuration (locale, etc.) changes, therefore they are not cached.
val
cachedApps
=
cachedApps
?:
pm
.
getInstalledPackages
(
PackageManager
.
GET_PERMISSIONS
)
val
cachedApps
=
cachedApps
?:
pm
.
getInstalledPackages
(
PackageManager
.
GET_PERMISSIONS
)
...
@@ -72,7 +71,10 @@ class AppManager : AppCompatActivity() {
...
@@ -72,7 +71,10 @@ class AppManager : AppCompatActivity() {
it
.
requestedPermissions
?.
contains
(
Manifest
.
permission
.
INTERNET
)
?:
false
}
it
.
requestedPermissions
?.
contains
(
Manifest
.
permission
.
INTERNET
)
?:
false
}
this
.
cachedApps
=
cachedApps
this
.
cachedApps
=
cachedApps
cachedApps
cachedApps
}.
map
{
ProxiedApp
(
pm
,
it
.
applicationInfo
,
it
.
packageName
)
}
}.
map
{
yield
()
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
,
...
@@ -106,17 +108,15 @@ class AppManager : AppCompatActivity() {
...
@@ -106,17 +108,15 @@ class AppManager : AppCompatActivity() {
proxiedApps
.
add
(
item
.
packageName
)
proxiedApps
.
add
(
item
.
packageName
)
check
.
isChecked
=
true
check
.
isChecked
=
true
}
}
if
(!
appsLoading
.
get
())
{
DataStore
.
individual
=
proxiedApps
.
joinToString
(
"\n"
)
DataStore
.
individual
=
proxiedApps
.
joinToString
(
"\n"
)
DataStore
.
dirty
=
true
DataStore
.
dirty
=
true
}
}
}
}
}
private
inner
class
AppsAdapter
:
RecyclerView
.
Adapter
<
AppViewHolder
>()
{
private
inner
class
AppsAdapter
:
RecyclerView
.
Adapter
<
AppViewHolder
>()
{
private
var
apps
=
listOf
<
ProxiedApp
>()
private
var
apps
=
listOf
<
ProxiedApp
>()
fun
reload
()
{
suspend
fun
reload
()
{
apps
=
getApps
(
packageManager
)
apps
=
getApps
(
packageManager
)
.
sortedWith
(
compareBy
({
!
proxiedApps
.
contains
(
it
.
packageName
)
},
{
it
.
name
.
toString
()
}))
.
sortedWith
(
compareBy
({
!
proxiedApps
.
contains
(
it
.
packageName
)
},
{
it
.
name
.
toString
()
}))
}
}
...
@@ -132,38 +132,36 @@ class AppManager : AppCompatActivity() {
...
@@ -132,38 +132,36 @@ class AppManager : AppCompatActivity() {
private
lateinit
var
bypassSwitch
:
Switch
private
lateinit
var
bypassSwitch
:
Switch
private
lateinit
var
appListView
:
RecyclerView
private
lateinit
var
appListView
:
RecyclerView
private
lateinit
var
loadingView
:
View
private
lateinit
var
loadingView
:
View
private
val
appsLoading
=
AtomicBoolean
()
private
val
handler
=
Handler
()
private
val
clipboard
by
lazy
{
getSystemService
<
ClipboardManager
>()
!!
}
private
val
clipboard
by
lazy
{
getSystemService
<
ClipboardManager
>()
!!
}
private
var
loader
:
Job
?
=
null
private
val
shortAnimTime
by
lazy
{
resources
.
getInteger
(
android
.
R
.
integer
.
config_shortAnimTime
).
toLong
()
}
private
fun
View
.
crossFadeFrom
(
other
:
View
)
{
clearAnimation
()
other
.
clearAnimation
()
if
(
visibility
==
View
.
VISIBLE
&&
other
.
visibility
==
View
.
GONE
)
return
alpha
=
0F
visibility
=
View
.
VISIBLE
animate
().
alpha
(
1F
).
duration
=
shortAnimTime
other
.
animate
().
alpha
(
0F
).
setListener
(
object
:
AnimatorListenerAdapter
()
{
override
fun
onAnimationEnd
(
animation
:
Animator
)
{
other
.
visibility
=
View
.
GONE
}
}).
duration
=
shortAnimTime
}
private
fun
initProxiedApps
(
str
:
String
=
DataStore
.
individual
)
{
private
fun
initProxiedApps
(
str
:
String
=
DataStore
.
individual
)
{
proxiedApps
=
str
.
split
(
'\n'
).
toHashSet
()
proxiedApps
=
str
.
split
(
'\n'
).
toHashSet
()
}
}
private
fun
reloadApps
()
{
@UiThread
if
(!
appsLoading
.
compareAndSet
(
true
,
false
))
loadAppsAsync
()
private
fun
loadApps
()
{
}
loader
?.
cancel
()
private
fun
loadAppsAsync
()
{
loader
=
GlobalScope
.
launch
(
Dispatchers
.
Main
,
CoroutineStart
.
UNDISPATCHED
)
{
if
(!
appsLoading
.
compareAndSet
(
false
,
true
))
return
loadingView
.
crossFadeFrom
(
appListView
)
appListView
.
visibility
=
View
.
GONE
loadingView
.
visibility
=
View
.
VISIBLE
thread
(
"AppManager-loader"
)
{
val
adapter
=
appListView
.
adapter
as
AppsAdapter
val
adapter
=
appListView
.
adapter
as
AppsAdapter
do
{
withContext
(
Dispatchers
.
IO
)
{
adapter
.
reload
()
}
appsLoading
.
set
(
true
)
adapter
.
reload
()
}
while
(!
appsLoading
.
compareAndSet
(
true
,
false
))
handler
.
post
{
adapter
.
notifyDataSetChanged
()
adapter
.
notifyDataSetChanged
()
val
shortAnimTime
=
resources
.
getInteger
(
android
.
R
.
integer
.
config_shortAnimTime
)
appListView
.
crossFadeFrom
(
loadingView
)
appListView
.
alpha
=
0F
appListView
.
visibility
=
View
.
VISIBLE
appListView
.
animate
().
alpha
(
1F
).
duration
=
shortAnimTime
.
toLong
()
loadingView
.
animate
().
alpha
(
0F
).
setListener
(
object
:
AnimatorListenerAdapter
()
{
override
fun
onAnimationEnd
(
animation
:
Animator
)
{
loadingView
.
visibility
=
View
.
GONE
}
}).
duration
=
shortAnimTime
.
toLong
()
}
}
}
}
}
...
@@ -199,7 +197,7 @@ class AppManager : AppCompatActivity() {
...
@@ -199,7 +197,7 @@ class AppManager : AppCompatActivity() {
appListView
.
adapter
=
AppsAdapter
()
appListView
.
adapter
=
AppsAdapter
()
instance
=
this
instance
=
this
loadApps
Async
()
loadApps
()
}
}
override
fun
onCreateOptionsMenu
(
menu
:
Menu
?):
Boolean
{
override
fun
onCreateOptionsMenu
(
menu
:
Menu
?):
Boolean
{
...
@@ -239,7 +237,7 @@ class AppManager : AppCompatActivity() {
...
@@ -239,7 +237,7 @@ class AppManager : AppCompatActivity() {
DataStore
.
dirty
=
true
DataStore
.
dirty
=
true
Snackbar
.
make
(
appListView
,
R
.
string
.
action_import_msg
,
Snackbar
.
LENGTH_LONG
).
show
()
Snackbar
.
make
(
appListView
,
R
.
string
.
action_import_msg
,
Snackbar
.
LENGTH_LONG
).
show
()
initProxiedApps
(
apps
)
initProxiedApps
(
apps
)
re
loadApps
()
loadApps
()
return
true
return
true
}
catch
(
_
:
IllegalArgumentException
)
{
}
}
catch
(
_
:
IllegalArgumentException
)
{
}
}
}
...
@@ -255,7 +253,7 @@ class AppManager : AppCompatActivity() {
...
@@ -255,7 +253,7 @@ class AppManager : AppCompatActivity() {
override
fun
onDestroy
()
{
override
fun
onDestroy
()
{
instance
=
null
instance
=
null
handler
.
removeCallbacksAndMessages
(
null
)
loader
?.
cancel
(
)
super
.
onDestroy
()
super
.
onDestroy
()
}
}
}
}
mobile/src/main/java/com/github/shadowsocks/MainActivity.kt
View file @
3cfc17c2
...
@@ -29,6 +29,7 @@ import android.nfc.NdefMessage
...
@@ -29,6 +29,7 @@ import android.nfc.NdefMessage
import
android.nfc.NfcAdapter
import
android.nfc.NfcAdapter
import
android.os.Bundle
import
android.os.Bundle
import
android.os.DeadObjectException
import
android.os.DeadObjectException
import
android.os.Handler
import
android.util.Log
import
android.util.Log
import
android.view.KeyCharacterMap
import
android.view.KeyCharacterMap
import
android.view.KeyEvent
import
android.view.KeyEvent
...
@@ -124,7 +125,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
...
@@ -124,7 +125,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
else
->
Core
.
startService
()
else
->
Core
.
startService
()
}
}
private
val
connection
=
ShadowsocksConnection
(
true
)
private
val
handler
=
Handler
()
private
val
connection
=
ShadowsocksConnection
(
handler
,
true
)
override
fun
onServiceConnected
(
service
:
IShadowsocksService
)
=
changeState
(
try
{
override
fun
onServiceConnected
(
service
:
IShadowsocksService
)
=
changeState
(
try
{
service
.
state
service
.
state
}
catch
(
_
:
DeadObjectException
)
{
}
catch
(
_
:
DeadObjectException
)
{
...
@@ -132,12 +134,10 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
...
@@ -132,12 +134,10 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
})
})
override
fun
onServiceDisconnected
()
=
changeState
(
BaseService
.
IDLE
)
override
fun
onServiceDisconnected
()
=
changeState
(
BaseService
.
IDLE
)
override
fun
onBinderDied
()
{
override
fun
onBinderDied
()
{
Core
.
handler
.
post
{
connection
.
disconnect
(
this
)
connection
.
disconnect
(
this
)
Executable
.
killAll
()
Executable
.
killAll
()
connection
.
connect
(
this
,
this
)
connection
.
connect
(
this
,
this
)
}
}
}
override
fun
onActivityResult
(
requestCode
:
Int
,
resultCode
:
Int
,
data
:
Intent
?)
{
override
fun
onActivityResult
(
requestCode
:
Int
,
resultCode
:
Int
,
data
:
Intent
?)
{
when
{
when
{
...
@@ -167,7 +167,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
...
@@ -167,7 +167,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
fab
.
setOnClickListener
{
toggle
()
}
fab
.
setOnClickListener
{
toggle
()
}
changeState
(
BaseService
.
IDLE
)
// reset everything to init state
changeState
(
BaseService
.
IDLE
)
// reset everything to init state
Core
.
handler
.
post
{
connection
.
connect
(
this
,
this
)
}
connection
.
connect
(
this
,
this
)
DataStore
.
publicStore
.
registerChangeListener
(
this
)
DataStore
.
publicStore
.
registerChangeListener
(
this
)
val
intent
=
this
.
intent
val
intent
=
this
.
intent
...
@@ -205,7 +205,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
...
@@ -205,7 +205,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
override
fun
onPreferenceDataStoreChanged
(
store
:
PreferenceDataStore
,
key
:
String
?)
{
override
fun
onPreferenceDataStoreChanged
(
store
:
PreferenceDataStore
,
key
:
String
?)
{
when
(
key
)
{
when
(
key
)
{
Key
.
serviceMode
->
Core
.
handler
.
post
{
Key
.
serviceMode
->
handler
.
post
{
connection
.
disconnect
(
this
)
connection
.
disconnect
(
this
)
connection
.
connect
(
this
,
this
)
connection
.
connect
(
this
,
this
)
}
}
...
@@ -280,6 +280,6 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
...
@@ -280,6 +280,6 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
DataStore
.
publicStore
.
unregisterChangeListener
(
this
)
DataStore
.
publicStore
.
unregisterChangeListener
(
this
)
connection
.
disconnect
(
this
)
connection
.
disconnect
(
this
)
BackupManager
(
this
).
dataChanged
()
BackupManager
(
this
).
dataChanged
()
Core
.
handler
.
removeCallbacksAndMessages
(
null
)
handler
.
removeCallbacksAndMessages
(
null
)
}
}
}
}
mobile/src/main/res/layout/layout_apps.xml
View file @
3cfc17c2
...
@@ -55,11 +55,13 @@
...
@@ -55,11 +55,13 @@
android:layout_width=
"fill_parent"
android:layout_width=
"fill_parent"
android:layout_height=
"fill_parent"
android:layout_height=
"fill_parent"
android:id=
"@+id/list"
android:id=
"@+id/list"
android:visibility=
"gone"
app:fastScrollEnabled=
"true"
app:fastScrollEnabled=
"true"
app:fastScrollHorizontalThumbDrawable=
"@drawable/fastscroll_thumb"
app:fastScrollHorizontalThumbDrawable=
"@drawable/fastscroll_thumb"
app:fastScrollHorizontalTrackDrawable=
"@drawable/fastscroll_line"
app:fastScrollHorizontalTrackDrawable=
"@drawable/fastscroll_line"
app:fastScrollVerticalThumbDrawable=
"@drawable/fastscroll_thumb"
app:fastScrollVerticalThumbDrawable=
"@drawable/fastscroll_thumb"
app:fastScrollVerticalTrackDrawable=
"@drawable/fastscroll_line"
app:fastScrollVerticalTrackDrawable=
"@drawable/fastscroll_line"
tools:listitem=
"@layout/layout_apps_item"
/>
tools:listitem=
"@layout/layout_apps_item"
tools:visibility=
"visible"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout>
</LinearLayout>
tv/proguard-rules.pro
View file @
3cfc17c2
...
@@ -22,5 +22,3 @@
...
@@ -22,5 +22,3 @@
#-
renamesourcefileattribute
SourceFile
#-
renamesourcefileattribute
SourceFile
-
dontwarn
com
.
google
.
android
.
gms
.
internal
.
**
-
dontwarn
com
.
google
.
android
.
gms
.
internal
.
**
-
dontwarn
okhttp3
.
**
-
dontwarn
okio
.
**
tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt
View file @
3cfc17c2
...
@@ -27,6 +27,7 @@ import android.content.Intent
...
@@ -27,6 +27,7 @@ import android.content.Intent
import
android.net.VpnService
import
android.net.VpnService
import
android.os.Bundle
import
android.os.Bundle
import
android.os.DeadObjectException
import
android.os.DeadObjectException
import
android.os.Handler
import
android.text.format.Formatter
import
android.text.format.Formatter
import
android.util.Log
import
android.util.Log
import
android.widget.Toast
import
android.widget.Toast
...
@@ -135,7 +136,8 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti
...
@@ -135,7 +136,8 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti
}
}
}
}
private
val
connection
=
ShadowsocksConnection
(
true
)
private
val
handler
=
Handler
()
private
val
connection
=
ShadowsocksConnection
(
handler
,
true
)
override
fun
onServiceConnected
(
service
:
IShadowsocksService
)
=
changeState
(
try
{
override
fun
onServiceConnected
(
service
:
IShadowsocksService
)
=
changeState
(
try
{
service
.
state
service
.
state
}
catch
(
_
:
DeadObjectException
)
{
}
catch
(
_
:
DeadObjectException
)
{
...
@@ -143,12 +145,10 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti
...
@@ -143,12 +145,10 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti
})
})
override
fun
onServiceDisconnected
()
=
changeState
(
BaseService
.
IDLE
)
override
fun
onServiceDisconnected
()
=
changeState
(
BaseService
.
IDLE
)
override
fun
onBinderDied
()
{
override
fun
onBinderDied
()
{
Core
.
handler
.
post
{
connection
.
disconnect
(
activity
)
connection
.
disconnect
(
activity
)
Executable
.
killAll
()
Executable
.
killAll
()
connection
.
connect
(
activity
,
this
)
connection
.
connect
(
activity
,
this
)
}
}
}
override
fun
onCreatePreferences
(
savedInstanceState
:
Bundle
?,
rootKey
:
String
?)
{
override
fun
onCreatePreferences
(
savedInstanceState
:
Bundle
?,
rootKey
:
String
?)
{
preferenceManager
.
preferenceDataStore
=
DataStore
.
publicStore
preferenceManager
.
preferenceDataStore
=
DataStore
.
publicStore
...
@@ -232,7 +232,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti
...
@@ -232,7 +232,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti
override
fun
onPreferenceDataStoreChanged
(
store
:
PreferenceDataStore
,
key
:
String
?)
{
override
fun
onPreferenceDataStoreChanged
(
store
:
PreferenceDataStore
,
key
:
String
?)
{
when
(
key
)
{
when
(
key
)
{
Key
.
serviceMode
->
Core
.
handler
.
post
{
Key
.
serviceMode
->
handler
.
post
{
connection
.
disconnect
(
activity
)
connection
.
disconnect
(
activity
)
connection
.
connect
(
activity
,
this
)
connection
.
connect
(
activity
,
this
)
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment