@@ -72,6 +72,9 @@
|
||||
<string name="ipv6">IPv6 路由</string>
|
||||
<string name="ipv6_summary">向远程服务器转发 IPv6 流量</string>
|
||||
|
||||
<string name="stat">网络流量</string>
|
||||
<string name="stat_summary">发送:\t%1$s\t|\t接收:\t%2$s\n上传:\t%3$s\t|\t下载:\t%4$s</string>
|
||||
|
||||
<!-- profile -->
|
||||
<string name="add_profile_dialog">为影梭添加此配置文件?</string>
|
||||
<string name="add_profile_methods_scan_qr_code">扫描二维码</string>
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
<string name="nat">NAT mode (deprecated)</string>
|
||||
<string name="nat_summary">Use NAT mode instead of VPN mode</string>
|
||||
<string name="nat_summary_no_root">NAT mode is disabled without root</string>
|
||||
<string name="stat">Network Traffic</string>
|
||||
<string name="stat_summary">Send:\t%1$s\t|\tReceive:\t%2$s\nUpload:\t%3$s\t|\tDownload:\t%4$s</string>
|
||||
|
||||
<!-- proxy category -->
|
||||
<string name="proxy_cat">Server Settings</string>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
<intent android:action="com.github.shadowsocks.ProfileManagerActivity"/>
|
||||
</Preference>
|
||||
|
||||
<Preference android:key="stat" android:title="@string/stat"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
|
||||
@@ -40,7 +40,7 @@ package com.github.shadowsocks
|
||||
|
||||
import java.io.{FileOutputStream, IOException, InputStream, OutputStream}
|
||||
import java.util
|
||||
import java.util.Locale
|
||||
import java.util.{Locale, Timer, TimerTask}
|
||||
|
||||
import android.app.backup.BackupManager
|
||||
import android.app.{Activity, ProgressDialog}
|
||||
@@ -147,7 +147,7 @@ class Shadowsocks
|
||||
extends AppCompatActivity {
|
||||
|
||||
// Variables
|
||||
private var serviceStarted = false
|
||||
var serviceStarted = false
|
||||
var fab: FloatingActionButton = _
|
||||
var fabProgressCircle: FABProgressCircle = _
|
||||
var progressDialog: ProgressDialog = _
|
||||
@@ -156,6 +156,8 @@ 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
|
||||
@@ -465,6 +467,24 @@ class Shadowsocks
|
||||
|
||||
// Check if current profile changed
|
||||
if (ShadowsocksApplication.profileId != currentProfile.id) reloadProfile()
|
||||
|
||||
// initialize timer
|
||||
val task = new TimerTask {
|
||||
def run() {
|
||||
TrafficMonitor.update()
|
||||
val pm = getSystemService(Context.POWER_SERVICE).asInstanceOf[PowerManager]
|
||||
if (pm.isScreenOn) {
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
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() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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;")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ object Key {
|
||||
val profiles = "profiles"
|
||||
val isNAT = "isNAT"
|
||||
val route = "route"
|
||||
val stat = "stat"
|
||||
|
||||
val isRunning = "isRunning"
|
||||
val isAutoConnect = "isAutoConnect"
|
||||
|
||||
@@ -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 ()
|
||||
Reference in New Issue
Block a user