diff --git a/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksServiceCallback.aidl b/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksServiceCallback.aidl index d079a20c..ff9aa551 100644 --- a/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksServiceCallback.aidl +++ b/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksServiceCallback.aidl @@ -2,4 +2,6 @@ package com.github.shadowsocks.aidl; interface IShadowsocksServiceCallback { oneway void stateChanged(int state, String msg); + oneway void trafficUpdated(String txRate, String rxRate, + String txTotal, String rxTotal); } diff --git a/src/main/jni/shadowsocks-libev b/src/main/jni/shadowsocks-libev index 772de6fd..f226d47e 160000 --- a/src/main/jni/shadowsocks-libev +++ b/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit 772de6fdeb15fd7d2b7853b4d3e0372713f16b81 +Subproject commit f226d47ea43fe4705e80d2b922f5ecdaf9d8cc24 diff --git a/src/main/scala/com/github/shadowsocks/BaseService.scala b/src/main/scala/com/github/shadowsocks/BaseService.scala index 00ed8c49..d2e27d4e 100644 --- a/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -39,16 +39,20 @@ package com.github.shadowsocks +import java.util.Locale + import android.app.Notification import android.content.Context import android.os.{Handler, RemoteCallbackList} import com.github.shadowsocks.aidl.{Config, IShadowsocksService, IShadowsocksServiceCallback} -import com.github.shadowsocks.utils.State +import com.github.shadowsocks.utils.{State, TrafficMonitor, TrafficMonitorThread} trait BaseService { @volatile private var state = State.INIT @volatile private var callbackCount = 0 + @volatile private var trafficMonitorThread: TrafficMonitorThread = null + var config: Config = null final val callbacks = new RemoteCallbackList[IShadowsocksServiceCallback] @@ -91,9 +95,21 @@ trait BaseService { } } + def startRunner(config: Config) { + this.config = config; + + TrafficMonitor.reset() + trafficMonitorThread = new TrafficMonitorThread(this) + trafficMonitorThread.start() + } + def stopRunner() { + TrafficMonitor.reset() + if (trafficMonitorThread != null) { + trafficMonitorThread.stopThread() + trafficMonitorThread = null + } + } def stopBackgroundService() - def startRunner(config: Config) - def stopRunner() def getServiceMode: Int def getTag: String def getContext: Context @@ -108,6 +124,23 @@ trait BaseService { changeState(s, null) } + def updateTraffic(txRate: String, rxRate: String, txTotal: String, rxTotal: String) { + val handler = new Handler(getContext.getMainLooper) + handler.post(() => { + if (callbackCount > 0) { + val n = callbacks.beginBroadcast() + for (i <- 0 to n - 1) { + try { + callbacks.getBroadcastItem(i).trafficUpdated(txRate, rxRate, txTotal, rxTotal) + } catch { + case _: Exception => // Ignore + } + } + callbacks.finishBroadcast() + } + }) + } + protected def changeState(s: Int, msg: String) { val handler = new Handler(getContext.getMainLooper) handler.post(() => if (state != s) { diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index a93a2813..a6648ea7 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -155,8 +155,6 @@ class Shadowsocks var prepared = false var currentProfile = new Profile var vpnEnabled = -1 - var timer: Timer = null - val TIMER_INTERVAL = 1 // sec // Services var currentServiceName = classOf[ShadowsocksNatService].getName @@ -165,6 +163,13 @@ class Shadowsocks override def stateChanged(state: Int, msg: String) { onStateChanged(state, msg) } + override def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) { + val trafficStat = getString(R.string.stat_summary) + .formatLocal(Locale.ENGLISH, txRate, rxRate, txTotal, rxTotal) + handler.post(() => { + preferences.findPreference(Key.stat).setSummary(trafficStat) + }) + } } val connection = new ServiceConnection { override def onServiceConnected(name: ComponentName, service: IBinder) { @@ -466,26 +471,6 @@ class Shadowsocks // Check if current profile changed if (ShadowsocksApplication.profileId != currentProfile.id) reloadProfile() - - // initialize timer - val task = new TimerTask { - def run() { - if (state == State.CONNECTED) { - TrafficMonitor.update() - } else { - TrafficMonitor.reset() - } - val trafficStat = getString(R.string.stat_summary).formatLocal(Locale.ENGLISH, - TrafficMonitor.getTxRate, TrafficMonitor.getRxRate, - TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal) - handler.post(() => { - preferences.findPreference(Key.stat).setSummary(trafficStat) - }) - } - } - task.run() - timer = new Timer(true) - timer.schedule(task, TIMER_INTERVAL * 1000, TIMER_INTERVAL * 1000) } private def setPreferenceEnabled(enabled: Boolean) { @@ -531,12 +516,6 @@ class Shadowsocks override def onStop() { super.onStop() clearDialog() - - // reset timer - if (timer != null) { - timer.cancel() - timer = null - } } override def onDestroy() { diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index 72bf9d7b..f755c919 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -71,7 +71,6 @@ class ShadowsocksNatService extends Service with BaseService { var lockReceiver: BroadcastReceiver = null var closeReceiver: BroadcastReceiver = null var connReceiver: BroadcastReceiver = null - var config: Config = null var apps: Array[ProxiedApp] = null val myUid = Process.myUid() @@ -418,11 +417,9 @@ class ShadowsocksNatService extends Service with BaseService { Console.runRootCommand(http_sb.toArray) } - override def startRunner(c: Config) { + override def startRunner(config: Config) { - TrafficMonitor.reset() - - config = c + super.startRunner(config) // register close receiver val filter = new IntentFilter() @@ -467,16 +464,16 @@ class ShadowsocksNatService extends Service with BaseService { if (config.proxy == "198.199.101.152") { val holder = ShadowsocksApplication.containerHolder try { - config = ConfigUtils.getPublicConfig(getBaseContext, holder.getContainer, config) + this.config = ConfigUtils.getPublicConfig(getBaseContext, holder.getContainer, config) } catch { case ex: Exception => changeState(State.STOPPED, getString(R.string.service_failed)) stopRunner() - config = null + this.config = null } } - if (config != null) { + if (this.config != null) { // Clean up killProcesses() @@ -511,7 +508,7 @@ class ShadowsocksNatService extends Service with BaseService { override def stopRunner() { - TrafficMonitor.reset() + super.stopRunner() // channge the state changeState(State.STOPPING) diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 082cec64..4bb30450 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -67,7 +67,6 @@ class ShadowsocksVpnService extends VpnService with BaseService { val PRIVATE_VLAN6 = "fdfe:dcba:9876::%s" var conn: ParcelFileDescriptor = null var apps: Array[ProxiedApp] = null - var config: Config = null var vpnThread: ShadowsocksVpnThread = null var closeReceiver: BroadcastReceiver = null @@ -144,7 +143,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { override def stopRunner() { - TrafficMonitor.reset() + super.stopRunner() if (vpnThread != null) { vpnThread.stopThread() @@ -217,15 +216,13 @@ class ShadowsocksVpnService extends VpnService with BaseService { } } - override def startRunner(c: Config) { + override def startRunner(config: Config) { - TrafficMonitor.reset() + super.startRunner(config) vpnThread = new ShadowsocksVpnThread(this) vpnThread.start() - config = c - // register close receiver val filter = new IntentFilter() filter.addAction(Intent.ACTION_SHUTDOWN) @@ -252,12 +249,12 @@ class ShadowsocksVpnService extends VpnService with BaseService { if (config.proxy == "198.199.101.152") { val holder = ShadowsocksApplication.containerHolder try { - config = ConfigUtils.getPublicConfig(getBaseContext, holder.getContainer, config) + this.config = ConfigUtils.getPublicConfig(getBaseContext, holder.getContainer, config) } catch { case ex: Exception => changeState(State.STOPPED, getString(R.string.service_failed)) stopRunner() - config = null + this.config = null } } diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala index 559caade..b9c212b8 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala @@ -117,10 +117,11 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread { } else { output.write(1) } - - input.close() - output.close() } + + input.close() + output.close() + } catch { case e: Exception => Log.e(TAG, "Error when protect socket", e) diff --git a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala index 3236a026..d1f6e56f 100644 --- a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala +++ b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala @@ -3,15 +3,12 @@ package com.github.shadowsocks.utils import java.lang.{Math, System} import java.text.DecimalFormat -import android.net.TrafficStats -import android.os.Process import com.github.shadowsocks.{R, ShadowsocksApplication} case class Traffic(tx: Long, rx: Long, timestamp: Long) object TrafficMonitor { - val uid = Process.myUid - var last: Traffic = getTraffic + var last: Traffic = getTraffic(0, 0) // Kilo bytes per second var txRate: Long = 0 @@ -21,9 +18,8 @@ object TrafficMonitor { var txTotal: Long = 0 var rxTotal: Long = 0 - def getTraffic: Traffic = { - new Traffic(Math.max(TrafficStats.getTotalTxBytes - TrafficStats.getUidTxBytes(uid), 0), - Math.max(TrafficStats.getTotalRxBytes - TrafficStats.getUidRxBytes(uid), 0), System.currentTimeMillis()) + def getTraffic(tx: Long, rx: Long): Traffic = { + new Traffic(tx, rx, System.currentTimeMillis()) } private val units = Array("KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "NB", "DB", "CB") @@ -39,8 +35,8 @@ object TrafficMonitor { else numberFormat.format(n) + ' ' + units(i) } - def update() { - val now = getTraffic + def update(tx: Long, rx: Long) { + val now = getTraffic(tx, rx) val delta = now.timestamp - last.timestamp if (delta != 0) { txRate = (now.tx - last.tx) / delta @@ -56,7 +52,7 @@ object TrafficMonitor { rxRate = 0 txTotal = 0 rxTotal = 0 - last = getTraffic + last = getTraffic(0, 0) } def getTxTotal(): String = { diff --git a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala new file mode 100644 index 00000000..31423355 --- /dev/null +++ b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala @@ -0,0 +1,140 @@ +/* + * Shadowsocks - A shadowsocks client for Android + * Copyright (C) 2015 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * + * ___====-_ _-====___ + * _--^^^#####// \\#####^^^--_ + * _-^##########// ( ) \\##########^-_ + * -############// |\^^/| \\############- + * _/############// (@::@) \\############\_ + * /#############(( \\// ))#############\ + * -###############\\ (oo) //###############- + * -#################\\ / VV \ //#################- + * -###################\\/ \//###################- + * _#/|##########/\######( /\ )######/\##########|\#_ + * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| + * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' + * ` ` ` ` / | | | | \ ' ' ' ' + * ( | | | | ) + * __\ | | | | /__ + * (vvv(VVV)(VVV)vvv) + * + * HERE BE DRAGONS + * + */ + +package com.github.shadowsocks.utils + +import java.io.{File, FileDescriptor, IOException} +import java.util.concurrent.Executors + +import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress} +import android.util.Log +import com.github.shadowsocks.BaseService + +class TrafficMonitorThread(service: BaseService) extends Thread { + + val TAG = "TrafficMonitorThread" + val PATH = "/data/data/com.github.shadowsocks/stat_path" + + @volatile var serverSocket: LocalServerSocket = null + @volatile var isRunning: Boolean = true + + def closeServerSocket() { + if (serverSocket != null) { + try { + serverSocket.close() + } catch { + case _: Exception => // ignore + } + serverSocket = null + } + } + + def stopThread() { + isRunning = false + closeServerSocket() + } + + override def run(): Unit = { + + try { + new File(PATH).delete() + } catch { + case _: Exception => // ignore + } + + try { + val localSocket = new LocalSocket + localSocket.bind(new LocalSocketAddress(PATH, LocalSocketAddress.Namespace.FILESYSTEM)) + serverSocket = new LocalServerSocket(localSocket.getFileDescriptor) + } catch { + case e: IOException => + Log.e(TAG, "unable to bind", e) + return + } + + val pool = Executors.newFixedThreadPool(1) + + while (isRunning) { + try { + val socket = serverSocket.accept() + + pool.execute(() => { + try { + 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) + service.updateTraffic(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate, + TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal) + } + + output.write(0) + + input.close() + output.close() + + } catch { + case e: Exception => + Log.e(TAG, "Error when recv traffic stat", e) + } + + // close socket + try { + socket.close() + } catch { + case _: Exception => // ignore + } + + }) + } catch { + case e: IOException => + Log.e(TAG, "Error when accept socket", e) + return + } + } + } +}