diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index f17450ae..ad614eae 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -108,14 +108,6 @@
-
-
-
-
-
-
-
-
diff --git a/src/main/jni/shadowsocks-libev b/src/main/jni/shadowsocks-libev
index 48a063ef..68eaf3b1 160000
--- a/src/main/jni/shadowsocks-libev
+++ b/src/main/jni/shadowsocks-libev
@@ -1 +1 @@
-Subproject commit 48a063ef1e1a886e274a0fbb797e88358deca35b
+Subproject commit 68eaf3b12d385381130c046547163b81e1807ba4
diff --git a/src/main/scala/com/github/shadowsocks/AppManager.scala b/src/main/scala/com/github/shadowsocks/AppManager.scala
index 7e86eb9f..9f0d9bba 100644
--- a/src/main/scala/com/github/shadowsocks/AppManager.scala
+++ b/src/main/scala/com/github/shadowsocks/AppManager.scala
@@ -39,8 +39,11 @@
package com.github.shadowsocks
+import java.util.concurrent.atomic.AtomicBoolean
+
+import android.Manifest.permission
+import android.content._
import android.content.pm.PackageManager
-import android.content.{ClipData, ClipboardManager, Context}
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
import android.os.{Bundle, Handler}
@@ -54,7 +57,6 @@ import android.view._
import android.widget.AbsListView.OnScrollListener
import android.widget.CompoundButton.OnCheckedChangeListener
import android.widget._
-import android.Manifest.permission
import com.github.shadowsocks.utils.{Key, Utils}
import scala.collection.JavaConversions._
@@ -65,13 +67,31 @@ object AppManager {
case class ProxiedApp(name: String, packageName: String, icon: Drawable)
private case class ListEntry(switch: Switch, text: TextView, icon: ImageView)
- var cachedApps: Array[ProxiedApp] = _
+ private var instance: AppManager = _
+
+ private var receiverRegistered: Boolean = _
+ private var cachedApps: Array[ProxiedApp] = _
private def getApps(pm: PackageManager) = {
- if (cachedApps == null) cachedApps = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS)
- .filter(p => p.requestedPermissions != null && p.requestedPermissions.contains(permission.INTERNET))
- .map(p => new ProxiedApp(pm.getApplicationLabel(p.applicationInfo).toString, p.packageName,
- p.applicationInfo.loadIcon(pm))).toArray
- cachedApps
+ if (!receiverRegistered) {
+ val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED)
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED)
+ filter.addDataScheme("package")
+ ShadowsocksApplication.instance.registerReceiver((context: Context, intent: Intent) =>
+ if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED ||
+ !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ synchronized(cachedApps = null)
+ val instance = AppManager.instance
+ if (instance != null) instance.reloadApps()
+ }, filter)
+ receiverRegistered = true
+ }
+ synchronized {
+ if (cachedApps == null) cachedApps = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS)
+ .filter(p => p.requestedPermissions != null && p.requestedPermissions.contains(permission.INTERNET))
+ .map(p => new ProxiedApp(pm.getApplicationLabel(p.applicationInfo).toString, p.packageName,
+ p.applicationInfo.loadIcon(pm))).toArray
+ cachedApps
+ }
}
}
@@ -86,43 +106,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
private var loadingView: View = _
private var overlay: TextView = _
private var adapter: ListAdapter = _
- @volatile private var appsLoading: Boolean = _
-
- def loadApps() {
- appsLoading = true
- proxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "").split('\n').to[mutable.HashSet]
- apps = getApps(getPackageManager).sortWith((a, b) => {
- val aProxied = proxiedApps.contains(a.packageName)
- if (aProxied ^ proxiedApps.contains(b.packageName)) aProxied else a.name.compareToIgnoreCase(b.name) < 0
- })
- adapter = new ArrayAdapter[ProxiedApp](this, R.layout.layout_apps_item, R.id.itemtext, apps) {
- override def getView(position: Int, view: View, parent: ViewGroup): View = {
- var convertView = view
- var entry: ListEntry = null
- if (convertView == null) {
- convertView = getLayoutInflater.inflate(R.layout.layout_apps_item, parent, false)
- entry = new ListEntry(convertView.findViewById(R.id.itemcheck).asInstanceOf[Switch],
- convertView.findViewById(R.id.itemtext).asInstanceOf[TextView],
- convertView.findViewById(R.id.itemicon).asInstanceOf[ImageView])
- convertView.setOnClickListener(AppManager.this)
- convertView.setTag(entry)
- entry.switch.setOnCheckedChangeListener(AppManager.this)
- } else {
- entry = convertView.getTag.asInstanceOf[ListEntry]
- }
-
- val app: ProxiedApp = apps(position)
-
- entry.text.setText(app.name)
- entry.icon.setImageDrawable(app.icon)
- val switch = entry.switch
- switch.setTag(app)
- switch.setChecked(proxiedApps.contains(app.packageName))
- entry.text.setTag(switch)
- convertView
- }
- }
- }
+ private val appsLoading = new AtomicBoolean
private def setProxied(pn: String, proxied: Boolean) = if (proxied) proxiedApps.add(pn) else proxiedApps.remove(pn)
@@ -145,6 +129,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
}
override def onDestroy() {
+ instance = null
super.onDestroy()
if (handler != null) {
handler.removeCallbacksAndMessages(null)
@@ -186,7 +171,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
// Restart activity
appListView.setVisibility(View.GONE)
loadingView.setVisibility(View.VISIBLE)
- if (appsLoading) appsLoading = false else loadAppsAsync()
+ reloadApps()
return true
}
}
@@ -252,13 +237,49 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
}
}
})
+ instance = this
loadAppsAsync()
}
+ def reloadApps() = if (!appsLoading.compareAndSet(true, false)) loadAppsAsync()
def loadAppsAsync() {
+ if (!appsLoading.compareAndSet(false, true)) return
ThrowableFuture {
- while (!appsLoading) loadApps()
- appsLoading = false
+ do {
+ proxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "").split('\n').to[mutable.HashSet]
+ appsLoading.set(true)
+ apps = getApps(getPackageManager).sortWith((a, b) => {
+ val aProxied = proxiedApps.contains(a.packageName)
+ if (aProxied ^ proxiedApps.contains(b.packageName)) aProxied else a.name.compareToIgnoreCase(b.name) < 0
+ })
+ adapter = new ArrayAdapter[ProxiedApp](this, R.layout.layout_apps_item, R.id.itemtext, apps) {
+ override def getView(position: Int, view: View, parent: ViewGroup): View = {
+ var convertView = view
+ var entry: ListEntry = null
+ if (convertView == null) {
+ convertView = getLayoutInflater.inflate(R.layout.layout_apps_item, parent, false)
+ entry = new ListEntry(convertView.findViewById(R.id.itemcheck).asInstanceOf[Switch],
+ convertView.findViewById(R.id.itemtext).asInstanceOf[TextView],
+ convertView.findViewById(R.id.itemicon).asInstanceOf[ImageView])
+ convertView.setOnClickListener(AppManager.this)
+ convertView.setTag(entry)
+ entry.switch.setOnCheckedChangeListener(AppManager.this)
+ } else {
+ entry = convertView.getTag.asInstanceOf[ListEntry]
+ }
+
+ val app: ProxiedApp = apps(position)
+
+ entry.text.setText(app.name)
+ entry.icon.setImageDrawable(app.icon)
+ val switch = entry.switch
+ switch.setTag(app)
+ switch.setChecked(proxiedApps.contains(app.packageName))
+ entry.text.setTag(switch)
+ convertView
+ }
+ }
+ } while (!appsLoading.compareAndSet(true, false))
handler.post(() => {
appListView.setAdapter(adapter)
Utils.crossFade(AppManager.this, loadingView, appListView)
@@ -267,7 +288,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
}
def saveAppSettings(context: Context) {
- if (!appsLoading) ShadowsocksApplication.settings.edit.putString(Key.proxied, proxiedApps.mkString("\n")).apply
+ if (!appsLoading.get) ShadowsocksApplication.settings.edit.putString(Key.proxied, proxiedApps.mkString("\n")).apply
}
var handler: Handler = null
diff --git a/src/main/scala/com/github/shadowsocks/AppManagerReceiver.scala b/src/main/scala/com/github/shadowsocks/AppManagerReceiver.scala
deleted file mode 100644
index f3170f21..00000000
--- a/src/main/scala/com/github/shadowsocks/AppManagerReceiver.scala
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.github.shadowsocks
-
-import android.content.{Intent, Context, BroadcastReceiver}
-
-/**
- * @author Mygod
- */
-class AppManagerReceiver extends BroadcastReceiver {
- override def onReceive(context: Context, intent: Intent) = if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED ||
- !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) AppManager.synchronized(AppManager.cachedApps = null)
-}
diff --git a/src/main/scala/com/github/shadowsocks/BaseService.scala b/src/main/scala/com/github/shadowsocks/BaseService.scala
index 0c81975a..67ea792d 100644
--- a/src/main/scala/com/github/shadowsocks/BaseService.scala
+++ b/src/main/scala/com/github/shadowsocks/BaseService.scala
@@ -42,7 +42,7 @@ package com.github.shadowsocks
import java.util.{Timer, TimerTask}
import android.app.Service
-import android.content.Context
+import android.content.{Intent, Context}
import android.os.{Handler, RemoteCallbackList}
import com.github.shadowsocks.aidl.{Config, IShadowsocksService, IShadowsocksServiceCallback}
import com.github.shadowsocks.utils.{State, TrafficMonitor, TrafficMonitorThread}
@@ -50,7 +50,6 @@ import com.github.shadowsocks.utils.{State, TrafficMonitor, TrafficMonitorThread
trait BaseService extends Service {
@volatile private var state = State.STOPPED
- @volatile private var callbackCount = 0
@volatile protected var config: Config = null
var timer: Timer = null
@@ -70,32 +69,28 @@ trait BaseService extends Service {
override def unregisterCallback(cb: IShadowsocksServiceCallback) {
if (cb != null ) {
callbacks.unregister(cb)
- callbackCount -= 1
}
- if (callbackCount == 0 && timer != null) {
+ if (callbacks.getRegisteredCallbackCount == 0 && timer != null) {
timer.cancel()
timer = null
}
- if (callbackCount == 0 && state == State.STOPPED) {
- stopBackgroundService()
- }
}
override def registerCallback(cb: IShadowsocksServiceCallback) {
if (cb != null) {
- if (callbackCount == 0 && timer == null) {
+ callbacks.register(cb)
+ if (callbacks.getRegisteredCallbackCount != 0 && timer == null) {
val task = new TimerTask {
def run {
- TrafficMonitor.updateRate()
- updateTrafficRate(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate,
- TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal)
+ if (TrafficMonitor.updateRate()) updateTrafficRate()
}
}
timer = new Timer(true)
timer.schedule(task, 1000, 1000)
}
- callbacks.register(cb)
- callbackCount += 1
+ TrafficMonitor.updateRate()
+ cb.trafficUpdated(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate,
+ TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal)
}
}
@@ -115,6 +110,7 @@ trait BaseService extends Service {
def startRunner(config: Config) {
this.config = config
+ startService(new Intent(getContext, getClass))
TrafficMonitor.reset()
trafficMonitorThread = new TrafficMonitorThread()
trafficMonitorThread.start()
@@ -122,39 +118,38 @@ trait BaseService extends Service {
def stopRunner() {
// Make sure update total traffic when stopping the runner
- updateTrafficTotal(TrafficMonitor.getDeltaTx, TrafficMonitor.getDeltaRx)
+ updateTrafficTotal(TrafficMonitor.txTotal, TrafficMonitor.rxTotal)
TrafficMonitor.reset()
if (trafficMonitorThread != null) {
trafficMonitorThread.stopThread()
trafficMonitorThread = null
}
+
+ // change the state
+ changeState(State.STOPPED)
+
+ // stop the service if nothing has bound to it
+ stopSelf()
}
def updateTrafficTotal(tx: Long, rx: Long) {
- val handler = new Handler(getContext.getMainLooper)
- handler.post(() => {
- val config = this.config // avoid race conditions without locking
- if (config != null) {
- ShadowsocksApplication.profileManager.getProfile(config.profileId) match {
- case Some(profile) =>
- profile.tx += tx
- profile.rx += rx
- ShadowsocksApplication.profileManager.updateProfile(profile)
- case None => // Ignore
- }
+ val config = this.config // avoid race conditions without locking
+ if (config != null) {
+ ShadowsocksApplication.profileManager.getProfile(config.profileId) match {
+ case Some(profile) =>
+ profile.tx += tx
+ profile.rx += rx
+ ShadowsocksApplication.profileManager.updateProfile(profile)
+ case None => // Ignore
}
- })
+ }
}
- def stopBackgroundService()
def getServiceMode: Int
def getTag: String
def getContext: Context
- def getCallbackCount: Int = {
- callbackCount
- }
def getState: Int = {
state
}
@@ -162,10 +157,14 @@ trait BaseService extends Service {
changeState(s, null)
}
- def updateTrafficRate(txRate: String, rxRate: String, txTotal: String, rxTotal: String) {
+ def updateTrafficRate() {
val handler = new Handler(getContext.getMainLooper)
handler.post(() => {
- if (callbackCount > 0) {
+ if (callbacks.getRegisteredCallbackCount > 0) {
+ val txRate = TrafficMonitor.getTxRate
+ val rxRate = TrafficMonitor.getRxRate
+ val txTotal = TrafficMonitor.getTxTotal
+ val rxTotal = TrafficMonitor.getRxTotal
val n = callbacks.beginBroadcast()
for (i <- 0 until n) {
try {
@@ -182,7 +181,7 @@ trait BaseService extends Service {
protected def changeState(s: Int, msg: String) {
val handler = new Handler(getContext.getMainLooper)
handler.post(() => if (state != s) {
- if (callbackCount > 0) {
+ if (callbacks.getRegisteredCallbackCount > 0) {
val n = callbacks.beginBroadcast()
for (i <- 0 until n) {
try {
diff --git a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala
index 3eef2882..401cbd6d 100644
--- a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala
+++ b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala
@@ -218,6 +218,15 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
}
}
+ override def onStart() {
+ super.onStart()
+ registerCallback
+ }
+ override def onStop() {
+ super.onStop()
+ unregisterCallback
+ }
+
override def onDestroy {
deattachService()
super.onDestroy
diff --git a/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala b/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala
index c304f559..f50cd606 100644
--- a/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala
+++ b/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala
@@ -13,20 +13,12 @@ trait ServiceBoundContext extends Context {
class ShadowsocksServiceConnection extends ServiceConnection {
override def onServiceConnected(name: ComponentName, service: IBinder) {
bgService = IShadowsocksService.Stub.asInterface(service)
- if (callback != null) try {
- bgService.registerCallback(callback)
- } catch {
- case ignored: RemoteException => // Nothing
- }
+ registerCallback
ServiceBoundContext.this.onServiceConnected()
}
override def onServiceDisconnected(name: ComponentName) {
if (callback != null) {
- try {
- if (bgService != null) bgService.unregisterCallback(callback)
- } catch {
- case ignored: RemoteException => // Nothing
- }
+ unregisterCallback
callback = null
}
ServiceBoundContext.this.onServiceDisconnected()
@@ -34,11 +26,25 @@ trait ServiceBoundContext extends Context {
}
}
+ def registerCallback = if (bgService != null && callback != null && !callbackRegistered) try {
+ bgService.registerCallback(callback)
+ callbackRegistered = true
+ } catch {
+ case ignored: RemoteException => // Nothing
+ }
+ def unregisterCallback = if (bgService != null && callback != null && callbackRegistered) try {
+ bgService.unregisterCallback(callback)
+ callbackRegistered = false
+ } catch {
+ case ignored: RemoteException => // Nothing
+ }
+
def onServiceConnected() = ()
def onServiceDisconnected() = ()
private var callback: IShadowsocksServiceCallback.Stub = _
private var connection: ShadowsocksServiceConnection = _
+ private var callbackRegistered: Boolean = _
// Variables
var bgService: IShadowsocksService = _
@@ -54,19 +60,13 @@ trait ServiceBoundContext extends Context {
connection = new ShadowsocksServiceConnection()
bindService(intent, connection, Context.BIND_AUTO_CREATE)
-
- startService(new Intent(this, s))
}
}
def deattachService() {
if (bgService != null) {
if (callback != null) {
- try {
- bgService.unregisterCallback(callback)
- } catch {
- case ignored: RemoteException => // Nothing
- }
+ unregisterCallback
callback = null
}
if (connection != null) {
diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
index 2c739d72..2f949e41 100644
--- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
+++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
@@ -461,8 +461,13 @@ class Shadowsocks
preferences.update(profile)
}
+ override def onStart() {
+ super.onStart()
+ registerCallback
+ }
override def onStop() {
super.onStop()
+ unregisterCallback
clearDialog()
}
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala
index f25becb5..45f93c87 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala
@@ -458,8 +458,6 @@ class ShadowsocksNatService extends BaseService {
override def stopRunner() {
- super.stopRunner()
-
// channge the state
changeState(State.STOPPING)
@@ -476,17 +474,7 @@ class ShadowsocksNatService extends BaseService {
// reset NAT
killProcesses()
- // stop the service if no callback registered
- if (getCallbackCount == 0) {
- stopSelf()
- }
-
- // change the state
- changeState(State.STOPPED)
- }
-
- override def stopBackgroundService() {
- stopSelf()
+ super.stopRunner()
}
override def getTag = TAG
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala
index f8f8b06e..4c609f53 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala
@@ -2,7 +2,7 @@ package com.github.shadowsocks
import java.util.Locale
-import android.app.{KeyguardManager, PendingIntent}
+import android.app.{KeyguardManager, NotificationManager, PendingIntent}
import android.content.{BroadcastReceiver, Context, Intent, IntentFilter}
import android.os.PowerManager
import android.support.v4.app.NotificationCompat
@@ -16,6 +16,7 @@ import com.github.shadowsocks.utils.{Action, State, Utils}
*/
class ShadowsocksNotification(private val service: BaseService, profileName: String, visible: Boolean = false) {
private lazy val keyGuard = service.getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager]
+ private lazy val nm = service.getSystemService(Context.NOTIFICATION_SERVICE).asInstanceOf[NotificationManager]
private lazy val callback = new Stub {
override def stateChanged(state: Int, msg: String) = () // ignore
override def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) {
@@ -27,6 +28,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
}
private var lockReceiver: BroadcastReceiver = _
private var callbackRegistered: Boolean = _
+ private var started: Boolean = _
private val builder = new NotificationCompat.Builder(service)
.setWhen(0)
@@ -73,8 +75,10 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
show()
} else if (forceShow) show()
- def notification = builder.build()
- def show() = service.startForeground(1, notification)
+ def show() = if (started) nm.notify(1, builder.build) else {
+ service.startForeground(1, builder.build)
+ started = true
+ }
def destroy() {
if (lockReceiver != null) {
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
index 849af214..d0ecfeaf 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
@@ -113,8 +113,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
override def stopRunner() {
- super.stopRunner()
-
if (vpnThread != null) {
vpnThread.stopThread()
vpnThread = null
@@ -136,19 +134,13 @@ class ShadowsocksVpnService extends VpnService with BaseService {
conn = null
}
- // stop the service if no callback registered
- if (getCallbackCount == 0) {
- stopSelf()
- }
-
// clean up recevier
if (closeReceiver != null) {
unregisterReceiver(closeReceiver)
closeReceiver = null
}
- // channge the state
- changeState(State.STOPPED)
+ super.stopRunner()
}
def getVersionName: String = {
@@ -458,10 +450,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
fd
}
- override def stopBackgroundService() {
- stopSelf()
- }
-
override def getTag = TAG
override def getServiceMode = Mode.VPN
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala
index 6946f8cd..f3fbb3e2 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala
@@ -45,7 +45,12 @@ import java.util.concurrent.Executors
import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress}
import android.util.Log
+object ShadowsocksVpnThread {
+ val getInt = classOf[FileDescriptor].getDeclaredMethod("getInt$")
+}
+
class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread {
+ import ShadowsocksVpnThread._
val TAG = "ShadowsocksVpnService"
val PATH = "/data/data/com.github.shadowsocks/protect_path"
@@ -103,11 +108,8 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread {
val fds = socket.getAncillaryFileDescriptors
if (fds.nonEmpty) {
- var ret = false
-
- val getInt = classOf[FileDescriptor].getDeclaredMethod("getInt$")
val fd = getInt.invoke(fds(0)).asInstanceOf[Int]
- ret = vpnService.protect(fd)
+ val ret = vpnService.protect(fd)
// Trick to close file decriptor
System.jniclose(fd)
diff --git a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala
index d34b526f..168fd6ed 100644
--- a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala
+++ b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala
@@ -5,26 +5,20 @@ import java.text.DecimalFormat
import com.github.shadowsocks.{R, ShadowsocksApplication}
-case class Traffic(tx: Long, rx: Long, timestamp: Long)
-
object TrafficMonitor {
- var last: Traffic = getTraffic(0, 0)
-
// Bytes per second
- var txRate: Long = 0
- var rxRate: Long = 0
+ var txRate: Long = _
+ var rxRate: Long = _
// Bytes for the current session
- var txTotal: Long = 0
- var rxTotal: Long = 0
+ var txTotal: Long = _
+ var rxTotal: Long = _
// Bytes for the last query
- var txLast: Long = 0
- var rxLast: Long = 0
-
- def getTraffic(tx: Long, rx: Long): Traffic = {
- new Traffic(tx, rx, System.currentTimeMillis())
- }
+ var txLast: Long = _
+ var rxLast: Long = _
+ var timestampLast: Long = _
+ @volatile var dirty = true
private val units = Array("KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "NB", "DB", "CB")
private val numberFormat = new DecimalFormat("@@@")
@@ -39,21 +33,42 @@ object TrafficMonitor {
else numberFormat.format(n) + ' ' + units(i)
}
- def updateRate() {
- val now = getTraffic(txTotal, rxTotal)
- val delta = now.timestamp - last.timestamp
- val deltaTx = now.tx - last.tx
- val deltaRx = now.rx - last.rx
+ def updateRate() = {
+ val now = System.currentTimeMillis()
+ val delta = now - timestampLast
+ var updated = false
if (delta != 0) {
- txRate = deltaTx * 1000 / delta
- rxRate = deltaRx * 1000 / delta
+ if (dirty) {
+ txRate = (txTotal - txLast) * 1000 / delta
+ rxRate = (rxTotal - rxLast) * 1000 / delta
+ txLast = txTotal
+ rxLast = rxTotal
+ dirty = false
+ updated = true
+ } else {
+ if (txRate != 0) {
+ txRate = 0
+ updated = true
+ }
+ if (rxRate != 0) {
+ rxRate = 0
+ updated = true
+ }
+ }
+ timestampLast = now
}
- last = now
+ updated
}
def update(tx: Long, rx: Long) {
- txTotal = tx
- rxTotal = rx
+ if (txTotal != tx) {
+ txTotal = tx
+ dirty = true
+ }
+ if (rxTotal != rx) {
+ rxTotal = rx
+ dirty = true
+ }
}
def reset() {
@@ -63,7 +78,7 @@ object TrafficMonitor {
rxTotal = 0
txLast = 0
rxLast = 0
- last = getTraffic(0, 0)
+ dirty = true
}
def getTxTotal(): String = {
@@ -89,17 +104,5 @@ object TrafficMonitor {
def getRate(): String = {
formatTraffic(txRate + rxRate)
}
-
- def getDeltaTx(): Long = {
- val last = txLast
- txLast = txTotal
- txTotal - last
- }
-
- def getDeltaRx(): Long = {
- val last = rxLast
- rxLast = rxTotal
- rxTotal - last
- }
}
diff --git a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala
index 15e9a43c..ec4b890e 100644
--- a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala
+++ b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala
@@ -40,11 +40,11 @@
package com.github.shadowsocks.utils
import java.io.{File, IOException}
+import java.nio.{ByteBuffer, ByteOrder}
import java.util.concurrent.Executors
import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress}
import android.util.Log
-import com.github.shadowsocks.BaseService
class TrafficMonitorThread() extends Thread {
@@ -99,16 +99,10 @@ class TrafficMonitorThread() extends Thread {
val input = socket.getInputStream
val output = socket.getOutputStream
- val buffer = new Array[Byte](256)
- val size = input.read(buffer)
-
- val array = new Array[Byte](size)
- Array.copy(buffer, 0, array, 0, size)
-
- val stat = new String(array, "UTF-8").split("\\|")
- if (stat.length == 2) {
- TrafficMonitor.update(stat(0).toLong, stat(1).toLong)
- }
+ val buffer = new Array[Byte](16)
+ if (input.read(buffer) != 16) throw new IOException("Unexpected traffic stat length")
+ val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN)
+ TrafficMonitor.update(stat.getLong(0), stat.getLong(8))
output.write(0)