diff --git a/build.sbt b/build.sbt
index 45c762f1..5a83ff5c 100644
--- a/build.sbt
+++ b/build.sbt
@@ -32,10 +32,10 @@ libraryDependencies ++= Seq(
"commons-net" % "commons-net" % "3.3",
"com.google.zxing" % "android-integration" % "3.2.1",
"net.glxn.qrgen" % "android" % "2.0",
- "com.google.android.gms" % "play-services-base" % "8.1.0",
- "com.google.android.gms" % "play-services-ads" % "8.1.0",
- "com.google.android.gms" % "play-services-analytics" % "8.1.0",
- "com.android.support" % "design" % "23.1.0",
+ "com.google.android.gms" % "play-services-base" % "8.3.0",
+ "com.google.android.gms" % "play-services-ads" % "8.3.0",
+ "com.google.android.gms" % "play-services-analytics" % "8.3.0",
+ "com.android.support" % "design" % "23.1.1",
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
"com.j256.ormlite" % "ormlite-core" % "4.48",
"com.j256.ormlite" % "ormlite-android" % "4.48"
diff --git a/src/main/res/values-v21/styles.xml b/src/main/res/values-v21/styles.xml
index efa07058..3d6dc203 100644
--- a/src/main/res/values-v21/styles.xml
+++ b/src/main/res/values-v21/styles.xml
@@ -2,39 +2,7 @@
-
-
-
-
-
-
+
+
diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml
index 02d6f29d..75a7070a 100755
--- a/src/main/res/values-zh/strings.xml
+++ b/src/main/res/values-zh/strings.xml
@@ -74,6 +74,10 @@
网络流量
发送:\t%1$s\t|\t接收:\t%2$s\n上传:\t%3$s\t|\t下载:\t%4$s
+ \n发送:\t%1$s\t|\t接收:\t%2$s
+
+ - 字节
+
为影梭添加此配置文件?
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index ba72d186..bc12046f 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -13,7 +13,12 @@
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
+ Sent: \t%1$s\t|\tReceived: \t%2$s\nUpload: \t%3$s\t|\tDownload: \t%4$s
+ \nSent: \t%1$s\t|\tReceived: \t%2$s
+
+ - Byte
+ - Bytes
+
Server Settings
diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml
index 78c52a46..dfde6612 100644
--- a/src/main/res/values/styles.xml
+++ b/src/main/res/values/styles.xml
@@ -14,16 +14,20 @@
- @color/material_accent_500
- @color/material_accent_700
-
+
+
-
diff --git a/src/main/scala/com/github/shadowsocks/ParserActivity.scala b/src/main/scala/com/github/shadowsocks/ParserActivity.scala
index 6d24af45..f1265ccd 100644
--- a/src/main/scala/com/github/shadowsocks/ParserActivity.scala
+++ b/src/main/scala/com/github/shadowsocks/ParserActivity.scala
@@ -61,7 +61,7 @@ class ParserActivity extends Activity {
.setTitle(R.string.add_profile_dialog)
.setCancelable(false)
.setPositiveButton(android.R.string.yes, ((dialog: DialogInterface, id: Int) => {
- ShadowsocksApplication.profileManager.createOrUpdateProfile(profile.get)
+ ShadowsocksApplication.profileManager.createProfile(profile.get)
dialog.dismiss()
}): DialogInterface.OnClickListener)
.setNegativeButton(android.R.string.no, ((dialog: DialogInterface, id: Int) => {
diff --git a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala
index 8bbebe0c..ce69db3c 100644
--- a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala
+++ b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala
@@ -10,11 +10,13 @@ import android.support.v7.widget.Toolbar.OnMenuItemClickListener
import android.support.v7.widget.helper.ItemTouchHelper
import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback
import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar}
+import android.text.style.TextAppearanceSpan
+import android.text.{Spanned, SpannableStringBuilder}
import android.view.View.{OnAttachStateChangeListener, OnClickListener}
import android.view.{LayoutInflater, MenuItem, View, ViewGroup}
import android.widget.{LinearLayout, ImageView, CheckedTextView, Toast}
import com.github.shadowsocks.database.Profile
-import com.github.shadowsocks.utils.{Parser, Utils}
+import com.github.shadowsocks.utils.{TrafficMonitor, Parser, Utils}
import com.google.zxing.integration.android.IntentIntegrator
import net.glxn.qrgen.android.QRCode
@@ -54,10 +56,23 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
})
}
+ def updateText() {
+ val builder = new SpannableStringBuilder
+ builder.append(item.name)
+ if (item.tx != 0 || item.rx != 0) {
+ val start = builder.length
+ builder.append(getString(R.string.stat_profiles,
+ TrafficMonitor.formatTraffic(item.tx), TrafficMonitor.formatTraffic(item.rx)))
+ builder.setSpan(new TextAppearanceSpan(ProfileManagerActivity.this, android.R.style.TextAppearance_Small),
+ start + 1, builder.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
+ }
+ text.setText(builder)
+ }
+
def bind(item: Profile) {
- text.setText(item.name)
- text.setChecked(item.id == ShadowsocksApplication.profileId)
this.item = item
+ updateText()
+ text.setChecked(item.id == ShadowsocksApplication.profileId)
}
def onClick(v: View) = {
@@ -153,7 +168,7 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
val scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
if (scanResult != null) Parser.parse(scanResult.getContents) match {
- case Some(profile) => ShadowsocksApplication.profileManager.createOrUpdateProfile(profile)
+ case Some(profile) => ShadowsocksApplication.profileManager.createProfile(profile)
case _ => // ignore
}
}
diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
index 3ce10ab7..a93a2813 100644
--- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
+++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
@@ -138,7 +138,6 @@ object Shadowsocks {
case Key.isUdpDns => updateSwitchPreference(pref, profile.udpdns)
case Key.isAuth => updateSwitchPreference(pref, profile.auth)
case Key.isIpv6 => updateSwitchPreference(pref, profile.ipv6)
- case _ =>
}
}
}
@@ -476,17 +475,15 @@ class Shadowsocks
} else {
TrafficMonitor.reset()
}
- 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)
- })
- }
+ 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)
}
diff --git a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala
index 627d6b48..3df2e5d5 100644
--- a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala
+++ b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala
@@ -65,35 +65,36 @@ class DBHelper(val context: Context)
if (oldVersion < 7) {
profileDao.executeRawNoArgs("DROP TABLE IF EXISTS 'profile';")
onCreate(database, connectionSource)
- } else {
- if (oldVersion < 8) {
- profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;")
- profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR;")
- }
- if (oldVersion < 9) {
- profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR;")
- }
- if (oldVersion < 10) {
- profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN auth SMALLINT;")
- }
- if (oldVersion < 11) {
- profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
- }
- if (oldVersion < 12) {
- profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
- onCreate(database, connectionSource)
- profileDao.executeRawNoArgs(
- "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," +
- " udpdns, auth, ipv6, individual) " +
- "SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, auth," +
- " 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;")
- }
+ return
+ }
+ if (oldVersion < 8) {
+ profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;")
+ profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR;")
+ }
+ if (oldVersion < 9) {
+ profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR;")
+ }
+ if (oldVersion < 10) {
+ profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN auth SMALLINT;")
+ }
+ if (oldVersion < 11) {
+ profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
+ }
+ if (oldVersion < 12) {
+ profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
+ onCreate(database, connectionSource)
+ profileDao.executeRawNoArgs(
+ "INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," +
+ " udpdns, auth, ipv6, individual) " +
+ "SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, auth," +
+ " ipv6, individual FROM `tmp`;")
+ profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
+ return
+ }
+ 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/ProfileManager.scala b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala
index fd2cda35..b83fb597 100644
--- a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala
+++ b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala
@@ -49,15 +49,27 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
var profileAddedListener: Profile => Any = _
def setProfileAddedListener(listener: Profile => Any) = this.profileAddedListener = listener
- def createOrUpdateProfile(profile: Profile): Boolean = {
+ def createProfile(p: Profile = null): Profile = {
try {
+ val profile = if (p == null) new Profile else p
+ profile.id = 0
+ ShadowsocksApplication.currentProfile match {
+ case Some(oldProfile) =>
+ // Copy Feature Settings from old profile
+ profile.route = oldProfile.route
+ profile.ipv6 = oldProfile.ipv6
+ profile.proxyApps = oldProfile.proxyApps
+ profile.individual = oldProfile.individual
+ profile.udpdns = oldProfile.udpdns
+ case _ =>
+ }
dbHelper.profileDao.createOrUpdate(profile)
if (profileAddedListener != null) profileAddedListener(profile)
- true
+ profile
} catch {
case ex: Exception =>
Log.e(Shadowsocks.TAG, "addProfile", ex)
- false
+ p
}
}
@@ -114,11 +126,7 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
def load(id: Int): Profile = {
- val profile = getProfile(id) getOrElse {
- val p = new Profile()
- createOrUpdateProfile(p)
- p
- }
+ val profile = getProfile(id) getOrElse createProfile()
val edit = settings.edit()
edit.putBoolean(Key.isProxyApps, profile.proxyApps)
@@ -175,7 +183,7 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
remotePort = 443
password = "u1rRWTssNv0p"
}
- createOrUpdateProfile(profile)
+ createProfile(profile)
profile
}
}
diff --git a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala
index b762eb2e..3236a026 100644
--- a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala
+++ b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala
@@ -1,9 +1,11 @@
package com.github.shadowsocks.utils
+import java.lang.{Math, System}
+import java.text.DecimalFormat
+
import android.net.TrafficStats
import android.os.Process
-import java.lang.{String, System, Math}
-import java.util.Locale
+import com.github.shadowsocks.{R, ShadowsocksApplication}
case class Traffic(tx: Long, rx: Long, timestamp: Long)
@@ -19,31 +21,33 @@ object TrafficMonitor {
var txTotal: Long = 0
var rxTotal: Long = 0
- def getTraffic(): Traffic = {
+ def getTraffic: Traffic = {
new Traffic(Math.max(TrafficStats.getTotalTxBytes - TrafficStats.getUidTxBytes(uid), 0),
Math.max(TrafficStats.getTotalRxBytes - TrafficStats.getUidRxBytes(uid), 0), 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"
+ private val units = Array("KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "NB", "DB", "CB")
+ private val numberFormat = new DecimalFormat("0.00")
+ def formatTraffic(size: Long): String = {
+ var n: Double = size
+ var i = -1
+ while (n >= 1024) {
+ n /= 1024
+ i = i + 1
}
+ if (i < 0) size + " " + ShadowsocksApplication.instance.getResources.getQuantityString(R.plurals.bytes, size.toInt)
+ else numberFormat.format(n) + ' ' + units(i)
}
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
+ val delta = now.timestamp - last.timestamp
+ if (delta != 0) {
+ txRate = (now.tx - last.tx) / delta
+ rxRate = (now.rx - last.rx) / delta
+ }
+ txTotal += now.tx - last.tx
+ rxTotal += now.rx - last.rx
last = now
}
@@ -80,4 +84,3 @@ object TrafficMonitor {
}
}
-class TrafficMonitor ()