Add realtime traffic in profiles selector
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -69,7 +69,7 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread {
|
||||
closeServerSocket()
|
||||
}
|
||||
|
||||
override def run(): Unit = {
|
||||
override def run() {
|
||||
|
||||
try {
|
||||
new File(PATH).delete()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user