From 407ba8beaae6ebdb3e87c3748094a1eaa45331a0 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 21 Nov 2015 16:04:55 +0800 Subject: [PATCH] Add realtime traffic in profiles selector --- .../com/github/shadowsocks/BaseService.scala | 9 +- .../shadowsocks/ProfileManagerActivity.scala | 24 ++++- .../shadowsocks/ServiceBoundContext.scala | 62 ++++++++++++ .../com/github/shadowsocks/Shadowsocks.scala | 95 ++++++------------- .../shadowsocks/ShadowsocksApplication.scala | 2 +- .../ShadowsocksRunnerActivity.scala | 44 ++------- .../ShadowsocksRunnerService.scala | 42 ++------ .../shadowsocks/ShadowsocksVpnThread.scala | 2 +- .../shadowsocks/utils/TrafficMonitor.scala | 18 ++-- .../utils/TrafficMonitorThread.scala | 4 +- 10 files changed, 146 insertions(+), 156 deletions(-) create mode 100644 src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala 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 7d413a5e..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 } 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)