diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml
index 6ab8edc8..9db0cf6c 100755
--- a/src/main/res/values-zh/strings.xml
+++ b/src/main/res/values-zh/strings.xml
@@ -74,8 +74,7 @@
向远程服务器转发 IPv6 流量
网络流量
- 发送:\t%1$s\t|\t接收:\t%2$s\n上传:\t%3$s\t|\t下载:\t%4$s
- \n已发送: \t%1$s\n已接收: \t%2$s
+ 发送: \t%3$s\t↑\t%1$s/s\n接收: \t%4$s\t↓\t%2$s/s
- 字节
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 83eb0ead..02f32d08 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -13,8 +13,8 @@
Use NAT mode instead of VPN mode
NAT mode is disabled without root
Network Traffic
- Sent: \t%1$s\t|\tReceived: \t%2$s\nUpload: \t%3$s\t|\tDownload: \t%4$s
- \nTotal Sent: \t%1$s\nTotal Received: \t%2$s
+ Sent: \t\t\t\t\t%3$s\t↑\t%1$s/s\nReceived: \t%4$s\t↓\t%2$s/s
+ \n%1$s↑\t%2$s↓
- Byte
- Bytes
diff --git a/src/main/scala/com/github/shadowsocks/BaseService.scala b/src/main/scala/com/github/shadowsocks/BaseService.scala
index 496ffbd0..b21b5b67 100644
--- a/src/main/scala/com/github/shadowsocks/BaseService.scala
+++ b/src/main/scala/com/github/shadowsocks/BaseService.scala
@@ -45,8 +45,6 @@ import android.app.Notification
import android.content.Context
import android.os.{Handler, RemoteCallbackList}
import android.util.Log
-
-import com.github.shadowsocks.database.{Profile, ProfileManager}
import com.github.shadowsocks.aidl.{Config, IShadowsocksService, IShadowsocksServiceCallback}
import com.github.shadowsocks.utils.{State, TrafficMonitor, TrafficMonitorThread}
@@ -135,12 +133,11 @@ trait BaseService {
handler.post(() => {
if (config != null) {
ShadowsocksApplication.profileManager.getProfile(config.profileId) match {
- case Some(profile) => {
- profile.tx += tx;
- profile.rx += rx;
+ case Some(profile) =>
+ profile.tx += tx
+ profile.rx += rx
Log.d("test", profile.tx + " " + profile.rx)
ShadowsocksApplication.profileManager.updateProfile(profile)
- }
case None => // Ignore
}
}
diff --git a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala
index a817a446..96ba5106 100644
--- a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala
+++ b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala
@@ -14,6 +14,7 @@ import android.text.{SpannableStringBuilder, Spanned}
import android.view.View.{OnAttachStateChangeListener, OnClickListener}
import android.view.{LayoutInflater, MenuItem, View, ViewGroup}
import android.widget.{CheckedTextView, ImageView, LinearLayout, Toast}
+import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils.{Parser, TrafficMonitor, Utils}
import com.google.zxing.integration.android.IntentIntegrator
@@ -24,7 +25,7 @@ import scala.collection.mutable.ArrayBuffer
/**
* @author Mygod
*/
-class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListener {
+class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListener with ServiceBoundContext {
private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener {
private var item: Profile = _
private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView]
@@ -54,8 +55,12 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
})
}
- def updateText() {
+ def updateText(refetch: Boolean = false) {
val builder = new SpannableStringBuilder
+ val item = if (refetch) ShadowsocksApplication.profileManager.getProfile(this.item.id) match {
+ case Some(profile) => profile
+ case None => return
+ } else this.item
builder.append(item.name)
if (item.tx != 0 || item.rx != 0) {
val start = builder.length
@@ -70,7 +75,10 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
def bind(item: Profile) {
this.item = item
updateText()
- text.setChecked(item.id == ShadowsocksApplication.profileId)
+ if (item.id == ShadowsocksApplication.profileId) {
+ text.setChecked(true)
+ selectedItem = this
+ }
}
def onClick(v: View) = {
@@ -120,6 +128,8 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
}
}
+ private var selectedItem: ProfileViewHolder = _
+
private lazy val profilesAdapter = new ProfilesAdapter
private var removedSnackbar: Snackbar = _
@@ -155,9 +165,17 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
}
def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = false // TODO?
}).attachToRecyclerView(profilesList)
+
+ attachService(new IShadowsocksServiceCallback.Stub {
+ def stateChanged(state: Int, msg: String) = () // ignore
+ def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) {
+ if (selectedItem != null) selectedItem.updateText(true)
+ }
+ })
}
override def onDestroy {
+ deattachService()
super.onDestroy
ShadowsocksApplication.profileManager.setProfileAddedListener(null)
profilesAdapter.commitRemoves
diff --git a/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala b/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala
new file mode 100644
index 00000000..069fad5c
--- /dev/null
+++ b/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala
@@ -0,0 +1,62 @@
+package com.github.shadowsocks
+
+import android.content.{ComponentName, Context, Intent, ServiceConnection}
+import android.os.{RemoteException, IBinder}
+import com.github.shadowsocks.aidl.{IShadowsocksServiceCallback, IShadowsocksService}
+import com.github.shadowsocks.utils.Action
+
+/**
+ * @author Mygod
+ */
+trait ServiceBoundContext extends Context {
+ val connection = new 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
+ }
+ ServiceBoundContext.this.onServiceConnected()
+ }
+ override def onServiceDisconnected(name: ComponentName) {
+ if (callback != null) {
+ try {
+ if (bgService != null) bgService.unregisterCallback(callback)
+ } catch {
+ case ignored: RemoteException => // Nothing
+ }
+ callback = null
+ }
+ ServiceBoundContext.this.onServiceDisconnected()
+ bgService = null
+ }
+ }
+
+ def onServiceConnected() = ()
+ def onServiceDisconnected() = ()
+
+ private var callback: IShadowsocksServiceCallback.Stub = _
+
+ // Variables
+ var bgService: IShadowsocksService = _
+
+ def attachService(callback: IShadowsocksServiceCallback.Stub = null) {
+ this.callback = callback
+ if (bgService == null) {
+ val s =
+ if (ShadowsocksApplication.isVpnEnabled) classOf[ShadowsocksVpnService] else classOf[ShadowsocksNatService]
+ val intent = new Intent(this, s)
+ intent.setAction(Action.SERVICE)
+ bindService(intent, connection, Context.BIND_AUTO_CREATE)
+ startService(new Intent(this, s))
+ }
+ }
+
+ def deattachService() {
+ if (bgService != null) {
+ unbindService(connection)
+ bgService = null
+ }
+ }
+}
diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
index 46a15cf0..fe56e82f 100644
--- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
+++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
@@ -58,7 +58,7 @@ import android.util.Log
import android.view.{View, ViewGroup, ViewParent}
import android.widget._
import com.github.jorgecastilloprz.FABProgressCircle
-import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
+import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.database._
import com.github.shadowsocks.preferences.{DropDownPreference, NumberPickerPreference, PasswordEditTextPreference, SummaryEditTextPreference}
import com.github.shadowsocks.utils._
@@ -111,7 +111,7 @@ object Shadowsocks {
pref.asInstanceOf[PasswordEditTextPreference].setText(value)
}
- def updateNumberPickerPreference(pref: Preference, value: Int): Unit = {
+ def updateNumberPickerPreference(pref: Preference, value: Int) {
pref.asInstanceOf[NumberPickerPreference].setValue(value)
}
@@ -142,7 +142,7 @@ object Shadowsocks {
}
class Shadowsocks
- extends AppCompatActivity {
+ extends AppCompatActivity with ServiceBoundContext {
// Variables
var serviceStarted = false
@@ -157,47 +157,28 @@ class Shadowsocks
// Services
var currentServiceName = classOf[ShadowsocksNatService].getName
- var bgService: IShadowsocksService = null
- val callback = new IShadowsocksServiceCallback.Stub {
- 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)
- })
+
+ override def onServiceConnected() {
+ // Update the UI
+ if (fab != null) fab.setEnabled(true)
+ stateUpdate()
+
+ if (!ShadowsocksApplication.settings.getBoolean(ShadowsocksApplication.getVersionName, false)) {
+ ShadowsocksApplication.settings.edit.putBoolean(ShadowsocksApplication.getVersionName, true).apply()
+ recovery()
}
}
- val connection = new ServiceConnection {
- override def onServiceConnected(name: ComponentName, service: IBinder) {
- // Initialize the background service
- bgService = IShadowsocksService.Stub.asInterface(service)
- try {
- bgService.registerCallback(callback)
- } catch {
- case ignored: RemoteException => // Nothing
- }
- // Update the UI
- if (fab != null) fab.setEnabled(true)
- stateUpdate()
- if (!ShadowsocksApplication.settings.getBoolean(ShadowsocksApplication.getVersionName, false)) {
- ShadowsocksApplication.settings.edit.putBoolean(ShadowsocksApplication.getVersionName, true).apply()
- recovery()
- }
- }
+ override def onServiceDisconnected() {
+ if (fab != null) fab.setEnabled(false)
+ }
- override def onServiceDisconnected(name: ComponentName) {
- if (fab != null) fab.setEnabled(false)
- try {
- if (bgService != null) bgService.unregisterCallback(callback)
- } catch {
- case ignored: RemoteException => // Nothing
- }
- bgService = null
- }
+ 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)
+ })
}
private lazy val preferences =
@@ -388,33 +369,17 @@ class Shadowsocks
// Bind to the service
handler.post(() => {
- attachService()
+ attachService(new IShadowsocksServiceCallback.Stub {
+ override def stateChanged(state: Int, msg: String) {
+ onStateChanged(state, msg)
+ }
+ override def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) {
+ Shadowsocks.this.trafficUpdated(txRate, rxRate, txTotal, rxTotal)
+ }
+ })
})
}
- def attachService() {
- if (bgService == null) {
- val s = if (ShadowsocksApplication.isVpnEnabled) classOf[ShadowsocksVpnService]
- else classOf[ShadowsocksNatService]
- val intent = new Intent(this, s)
- intent.setAction(Action.SERVICE)
- bindService(intent, connection, Context.BIND_AUTO_CREATE)
- startService(new Intent(this, s))
- }
- }
-
- def deattachService() {
- if (bgService != null) {
- try {
- bgService.unregisterCallback(callback)
- } catch {
- case ignored: RemoteException => // Nothing
- }
- bgService = null
- unbindService(connection)
- }
- }
-
def reloadProfile() {
currentProfile = ShadowsocksApplication.currentProfile match {
case Some(profile) => profile // updated
@@ -467,7 +432,7 @@ class Shadowsocks
// Check if current profile changed
if (ShadowsocksApplication.profileId != currentProfile.id) reloadProfile()
- callback.trafficUpdated(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate,
+ trafficUpdated(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate,
TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal)
}
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala
index a267eb1b..eff4d808 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala
@@ -95,7 +95,7 @@ class ShadowsocksApplication extends Application {
val tm = TagManager.getInstance(this)
val pending = tm.loadContainerPreferNonDefault("GTM-NT8WS8", R.raw.gtm_default_container)
val callback = new ResultCallback[ContainerHolder] {
- override def onResult(holder: ContainerHolder): Unit = {
+ override def onResult(holder: ContainerHolder) {
if (!holder.getStatus.isSuccess) {
return
}
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala
index 137912f7..a306909d 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala
@@ -40,30 +40,22 @@
package com.github.shadowsocks
import android.app.{Activity, KeyguardManager}
-import android.content._
+import android.content.{Intent, IntentFilter, Context, BroadcastReceiver}
import android.net.VpnService
-import android.os._
+import android.os.{Bundle, Handler}
+import android.support.v7.app.AppCompatActivity
import android.util.Log
-import com.github.shadowsocks.aidl.IShadowsocksService
-import com.github.shadowsocks.utils._
-
-class ShadowsocksRunnerActivity extends Activity {
+import com.github.shadowsocks.utils.ConfigUtils
+class ShadowsocksRunnerActivity extends AppCompatActivity with ServiceBoundContext {
val handler = new Handler()
- val connection = new ServiceConnection {
- override def onServiceConnected(name: ComponentName, service: IBinder) {
- bgService = IShadowsocksService.Stub.asInterface(service)
- handler.postDelayed(() => if (bgService != null) startBackgroundService(), 1000)
- }
- override def onServiceDisconnected(name: ComponentName) {
- bgService = null
- }
- }
// Variables
- var bgService: IShadowsocksService = _
- var receiver:BroadcastReceiver = _
+ var receiver: BroadcastReceiver = _
+ override def onServiceConnected() {
+ handler.postDelayed(() => if (bgService != null) startBackgroundService(), 1000)
+ }
def startBackgroundService() {
if (ShadowsocksApplication.isVpnEnabled) {
@@ -79,24 +71,6 @@ class ShadowsocksRunnerActivity extends Activity {
}
}
- def attachService() {
- if (bgService == null) {
- val s = if (ShadowsocksApplication.isVpnEnabled) classOf[ShadowsocksVpnService]
- else classOf[ShadowsocksNatService]
- val intent = new Intent(this, s)
- intent.setAction(Action.SERVICE)
- bindService(intent, connection, Context.BIND_AUTO_CREATE)
- startService(new Intent(this, s))
- }
- }
-
- def deattachService() {
- if (bgService != null) {
- unbindService(connection)
- bgService = null
- }
- }
-
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
val km = getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager]
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala
index 8a271746..e37ec801 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala
@@ -40,31 +40,22 @@
package com.github.shadowsocks
import android.app.Service
-import android.content._
+import android.content.Intent
import android.net.VpnService
-import android.os._
-import com.github.shadowsocks.aidl.IShadowsocksService
-import com.github.shadowsocks.utils._
+import android.os.{IBinder, Handler}
+import com.github.shadowsocks.utils.ConfigUtils
-class ShadowsocksRunnerService extends Service {
+class ShadowsocksRunnerService extends Service with ServiceBoundContext {
val handler = new Handler()
- val connection = new ServiceConnection {
- override def onServiceConnected(name: ComponentName, service: IBinder) {
- bgService = IShadowsocksService.Stub.asInterface(service)
- handler.postDelayed(() => if (bgService != null) startBackgroundService(), 1000)
- }
- override def onServiceDisconnected(name: ComponentName) {
- bgService = null
- }
- }
-
- // Variables
- var bgService: IShadowsocksService = _
override def onBind(intent: Intent): IBinder = {
null
}
+ override def onServiceConnected() {
+ handler.postDelayed(() => if (bgService != null) startBackgroundService(), 1000)
+ }
+
def startBackgroundService() {
if (ShadowsocksApplication.isVpnEnabled) {
val intent = VpnService.prepare(ShadowsocksRunnerService.this)
@@ -79,23 +70,6 @@ class ShadowsocksRunnerService extends Service {
stopSelf()
}
- def attachService() {
- if (bgService == null) {
- val s = if (!ShadowsocksApplication.isVpnEnabled) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService]
- val intent = new Intent(this, s)
- intent.setAction(Action.SERVICE)
- bindService(intent, connection, Context.BIND_AUTO_CREATE)
- startService(new Intent(this, s))
- }
- }
-
- def deattachService() {
- if (bgService != null) {
- unbindService(connection)
- bgService = null
- }
- }
-
override def onCreate() {
super.onCreate()
attachService()
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala
index b9c212b8..6946f8cd 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnThread.scala
@@ -69,7 +69,7 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread {
closeServerSocket()
}
- override def run(): Unit = {
+ override def run() {
try {
new File(PATH).delete()
diff --git a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala
index 9058c470..3c1682bf 100644
--- a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala
+++ b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala
@@ -10,15 +10,15 @@ case class Traffic(tx: Long, rx: Long, timestamp: Long)
object TrafficMonitor {
var last: Traffic = getTraffic(0, 0)
- // Kilo bytes per second
+ // Bytes per second
var txRate: Long = 0
var rxRate: Long = 0
- // Kilo bytes for the current session
+ // Bytes for the current session
var txTotal: Long = 0
var rxTotal: Long = 0
- // Kilo bytes for the last query
+ // Bytes for the last query
var txLast: Long = 0
var rxLast: Long = 0
@@ -42,14 +42,14 @@ object TrafficMonitor {
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) * 1000 / delta
- rxRate = (now.rx - last.rx) * 1000 / delta
- }
txLast = now.tx - last.tx
rxLast = now.rx - last.rx
- txTotal += now.tx - last.tx
- rxTotal += now.rx - last.rx
+ if (delta != 0) {
+ txRate = txLast * 1000 / delta
+ rxRate = rxLast * 1000 / delta
+ }
+ txTotal += txLast
+ rxTotal += rxLast
last = now
}
@@ -74,15 +74,15 @@ object TrafficMonitor {
}
def getTxRate(): String = {
- formatTraffic(txRate) + "/s"
+ formatTraffic(txRate)
}
def getRxRate(): String = {
- formatTraffic(rxRate) + "/s"
+ formatTraffic(rxRate)
}
def getRate(): String = {
- formatTraffic(txRate + rxRate) + "/s"
+ formatTraffic(txRate + rxRate)
}
}
diff --git a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala
index ca60eeb5..632f6029 100644
--- a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala
+++ b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitorThread.scala
@@ -70,7 +70,7 @@ class TrafficMonitorThread(service: BaseService) extends Thread {
closeServerSocket()
}
- override def run(): Unit = {
+ override def run() {
try {
new File(PATH).delete()
@@ -108,9 +108,9 @@ class TrafficMonitorThread(service: BaseService) extends Thread {
val stat = new String(array, "UTF-8").split("\\|")
if (stat.length == 2) {
TrafficMonitor.update(stat(0).toLong, stat(1).toLong)
+ service.updateTrafficTotal(TrafficMonitor.txLast, TrafficMonitor.rxLast)
service.updateTrafficRate(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate,
TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal)
- service.updateTrafficTotal(TrafficMonitor.txLast, TrafficMonitor.rxLast)
}
output.write(0)