diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml
index da320a87..02d6f29d 100755
--- a/src/main/res/values-zh/strings.xml
+++ b/src/main/res/values-zh/strings.xml
@@ -72,6 +72,9 @@
IPv6 路由
向远程服务器转发 IPv6 流量
+ 网络流量
+ 发送:\t%1$s\t|\t接收:\t%2$s\n上传:\t%3$s\t|\t下载:\t%4$s
+
为影梭添加此配置文件?
扫描二维码
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 597957e6..ba72d186 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -12,6 +12,8 @@
NAT mode (deprecated)
Use NAT mode instead of VPN mode
NAT mode is disabled without root
+ Network Traffic
+ Send:\t%1$s\t|\tReceive:\t%2$s\nUpload:\t%3$s\t|\tDownload:\t%4$s
Server Settings
diff --git a/src/main/res/xml/pref_all.xml b/src/main/res/xml/pref_all.xml
index 637eebaa..82412e04 100644
--- a/src/main/res/xml/pref_all.xml
+++ b/src/main/res/xml/pref_all.xml
@@ -8,6 +8,8 @@
+
+
{
+ preferences.findPreference(Key.stat).setSummary(trafficStat)
+ })
+ }
+ }
+ }
+ timer = new Timer(true)
+ timer.schedule(task, TIMER_INTERVAL * 1000, TIMER_INTERVAL * 1000)
}
private def setPreferenceEnabled(enabled: Boolean) {
@@ -510,6 +530,12 @@ 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 52e11c9d..72bf9d7b 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala
@@ -420,6 +420,8 @@ class ShadowsocksNatService extends Service with BaseService {
override def startRunner(c: Config) {
+ TrafficMonitor.reset()
+
config = c
// register close receiver
@@ -509,6 +511,8 @@ class ShadowsocksNatService extends Service with BaseService {
override def stopRunner() {
+ TrafficMonitor.reset()
+
// 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 8b78ba7c..082cec64 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
@@ -144,6 +144,8 @@ class ShadowsocksVpnService extends VpnService with BaseService {
override def stopRunner() {
+ TrafficMonitor.reset()
+
if (vpnThread != null) {
vpnThread.stopThread()
vpnThread = null
@@ -217,6 +219,8 @@ class ShadowsocksVpnService extends VpnService with BaseService {
override def startRunner(c: Config) {
+ TrafficMonitor.reset()
+
vpnThread = new ShadowsocksVpnThread(this)
vpnThread.start()
diff --git a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala
index d2ed5a4c..627d6b48 100644
--- a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala
+++ b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala
@@ -51,7 +51,7 @@ object DBHelper {
}
class DBHelper(val context: Context)
- extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 12) {
+ extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 13) {
lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile])
@@ -89,6 +89,11 @@ class DBHelper(val context: Context)
" ipv6, individual FROM `tmp`;")
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
}
+ if (oldVersion < 13) {
+ profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN tx LONG;")
+ profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN rx LONG;")
+ profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN date DATE;")
+ }
}
}
}
diff --git a/src/main/scala/com/github/shadowsocks/database/Profile.scala b/src/main/scala/com/github/shadowsocks/database/Profile.scala
index 8be48264..66744357 100644
--- a/src/main/scala/com/github/shadowsocks/database/Profile.scala
+++ b/src/main/scala/com/github/shadowsocks/database/Profile.scala
@@ -83,4 +83,13 @@ class Profile {
@DatabaseField(dataType = DataType.LONG_STRING)
var individual: String = ""
+
+ @DatabaseField
+ var tx: Long = 0
+
+ @DatabaseField
+ var rx: Long = 0
+
+ @DatabaseField
+ val date: java.util.Date = new java.util.Date()
}
diff --git a/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/src/main/scala/com/github/shadowsocks/utils/Constants.scala
index 07b645fd..c207e08d 100644
--- a/src/main/scala/com/github/shadowsocks/utils/Constants.scala
+++ b/src/main/scala/com/github/shadowsocks/utils/Constants.scala
@@ -68,6 +68,7 @@ object Key {
val profiles = "profiles"
val isNAT = "isNAT"
val route = "route"
+ val stat = "stat"
val isRunning = "isRunning"
val isAutoConnect = "isAutoConnect"
diff --git a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala
new file mode 100644
index 00000000..d3b7c478
--- /dev/null
+++ b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala
@@ -0,0 +1,83 @@
+package com.github.shadowsocks.utils
+
+import android.net.TrafficStats
+import android.os.Process
+import java.lang.{String, System}
+import java.util.Locale
+
+case class Traffic(tx: Long, rx: Long, timestamp: Long)
+
+object TrafficMonitor {
+ val uid = Process.myUid
+ var last: Traffic = getTraffic
+
+ // Kilo bytes per second
+ var txRate: Long = 0
+ var rxRate: Long = 0
+
+ // Kilo bytes for the current session
+ var txTotal: Long = 0
+ var rxTotal: Long = 0
+
+ def getTraffic(): Traffic = {
+ new Traffic(TrafficStats.getUidTxBytes(uid),
+ TrafficStats.getUidRxBytes(uid), System.currentTimeMillis())
+ }
+
+ def formatTraffic(n: Long): String = {
+ if (n <= 1024) {
+ "%d KB".formatLocal(Locale.ENGLISH, n)
+ } else if (n > 1024) {
+ "%d MB".formatLocal(Locale.ENGLISH, n / 1024)
+ } else if (n > 1024 * 1024) {
+ "%d GB".formatLocal(Locale.ENGLISH, n / 1024 / 1024)
+ } else if (n > 1024 * 1024 * 1024) {
+ "%d TB".formatLocal(Locale.ENGLISH, n / 1024 / 1024 / 1024)
+ } else {
+ ">1024 TB"
+ }
+ }
+
+ def update() {
+ val now = getTraffic
+ txRate = (now.tx - last.tx) / (now.timestamp - last.timestamp)
+ rxRate = (now.rx - last.rx) / (now.timestamp - last.timestamp)
+ txTotal += (now.tx - last.tx) / 1024
+ rxTotal += (now.rx - last.rx) / 1024
+ last = now
+ }
+
+ def reset() {
+ txRate = 0
+ rxRate = 0
+ txTotal = 0
+ rxTotal = 0
+ last = getTraffic
+ }
+
+ def getTxTotal(): String = {
+ formatTraffic(txTotal)
+ }
+
+ def getRxTotal(): String = {
+ formatTraffic(rxTotal)
+ }
+
+ def getTotal(): String = {
+ formatTraffic(txTotal + rxTotal)
+ }
+
+ def getTxRate(): String = {
+ formatTraffic(txRate) + "/s"
+ }
+
+ def getRxRate(): String = {
+ formatTraffic(rxRate) + "/s"
+ }
+
+ def getRate(): String = {
+ formatTraffic(txRate + rxRate) + "/s"
+ }
+}
+
+class TrafficMonitor ()