Skip to content

Commit 6e974da

Browse files
committed
Merge pull request shadowsocks#439 from Mygod/master
Add realtime traffic in profiles selector
2 parents c3638b9 + 407ba8b commit 6e974da

12 files changed

+152
-163
lines changed

src/main/res/values-zh/strings.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@
7474
<string name="ipv6_summary">向远程服务器转发 IPv6 流量</string>
7575

7676
<string name="stat">网络流量</string>
77-
<string name="stat_summary">发送:\t%1$s\t|\t接收:\t%2$s\n上传:\t%3$s\t|\t下载:\t%4$s</string>
78-
<string name="stat_profiles">\n已发送: \t%1$s\n已接收: \t%2$s</string>
77+
<string name="stat_summary">发送: \t%3$s\t↑\t%1$s/s\n接收: \t%4$s\t↓\t%2$s/s</string>
7978
<plurals name="bytes">
8079
<item quantity="other">字节</item>
8180
</plurals>

src/main/res/values/strings.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
<string name="nat_summary">Use NAT mode instead of VPN mode</string>
1414
<string name="nat_summary_no_root">NAT mode is disabled without root</string>
1515
<string name="stat">Network Traffic</string>
16-
<string name="stat_summary">Sent: \t%1$s\t|\tReceived: \t%2$s\nUpload: \t%3$s\t|\tDownload: \t%4$s</string>
17-
<string name="stat_profiles">\nTotal Sent: \t%1$s\nTotal Received: \t%2$s</string>
16+
<string name="stat_summary">Sent: \t\t\t\t\t%3$s\t↑\t%1$s/s\nReceived: \t%4$s\t↓\t%2$s/s</string>
17+
<string name="stat_profiles">\n%1$s↑\t%2$s</string>
1818
<plurals name="bytes">
1919
<item quantity="one">Byte</item>
2020
<item quantity="other">Bytes</item>

src/main/scala/com/github/shadowsocks/BaseService.scala

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ import android.app.Notification
4545
import android.content.Context
4646
import android.os.{Handler, RemoteCallbackList}
4747
import android.util.Log
48-
49-
import com.github.shadowsocks.database.{Profile, ProfileManager}
5048
import com.github.shadowsocks.aidl.{Config, IShadowsocksService, IShadowsocksServiceCallback}
5149
import com.github.shadowsocks.utils.{State, TrafficMonitor, TrafficMonitorThread}
5250

@@ -135,12 +133,11 @@ trait BaseService {
135133
handler.post(() => {
136134
if (config != null) {
137135
ShadowsocksApplication.profileManager.getProfile(config.profileId) match {
138-
case Some(profile) => {
139-
profile.tx += tx;
140-
profile.rx += rx;
136+
case Some(profile) =>
137+
profile.tx += tx
138+
profile.rx += rx
141139
Log.d("test", profile.tx + " " + profile.rx)
142140
ShadowsocksApplication.profileManager.updateProfile(profile)
143-
}
144141
case None => // Ignore
145142
}
146143
}

src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import android.text.{SpannableStringBuilder, Spanned}
1414
import android.view.View.{OnAttachStateChangeListener, OnClickListener}
1515
import android.view.{LayoutInflater, MenuItem, View, ViewGroup}
1616
import android.widget.{CheckedTextView, ImageView, LinearLayout, Toast}
17+
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
1718
import com.github.shadowsocks.database.Profile
1819
import com.github.shadowsocks.utils.{Parser, TrafficMonitor, Utils}
1920
import com.google.zxing.integration.android.IntentIntegrator
@@ -24,7 +25,7 @@ import scala.collection.mutable.ArrayBuffer
2425
/**
2526
* @author Mygod
2627
*/
27-
class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListener {
28+
class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListener with ServiceBoundContext {
2829
private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener {
2930
private var item: Profile = _
3031
private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView]
@@ -54,8 +55,12 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
5455
})
5556
}
5657

57-
def updateText() {
58+
def updateText(refetch: Boolean = false) {
5859
val builder = new SpannableStringBuilder
60+
val item = if (refetch) ShadowsocksApplication.profileManager.getProfile(this.item.id) match {
61+
case Some(profile) => profile
62+
case None => return
63+
} else this.item
5964
builder.append(item.name)
6065
if (item.tx != 0 || item.rx != 0) {
6166
val start = builder.length
@@ -70,7 +75,10 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
7075
def bind(item: Profile) {
7176
this.item = item
7277
updateText()
73-
text.setChecked(item.id == ShadowsocksApplication.profileId)
78+
if (item.id == ShadowsocksApplication.profileId) {
79+
text.setChecked(true)
80+
selectedItem = this
81+
}
7482
}
7583

7684
def onClick(v: View) = {
@@ -120,6 +128,8 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
120128
}
121129
}
122130

131+
private var selectedItem: ProfileViewHolder = _
132+
123133
private lazy val profilesAdapter = new ProfilesAdapter
124134
private var removedSnackbar: Snackbar = _
125135

@@ -155,9 +165,17 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
155165
}
156166
def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = false // TODO?
157167
}).attachToRecyclerView(profilesList)
168+
169+
attachService(new IShadowsocksServiceCallback.Stub {
170+
def stateChanged(state: Int, msg: String) = () // ignore
171+
def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) {
172+
if (selectedItem != null) selectedItem.updateText(true)
173+
}
174+
})
158175
}
159176

160177
override def onDestroy {
178+
deattachService()
161179
super.onDestroy
162180
ShadowsocksApplication.profileManager.setProfileAddedListener(null)
163181
profilesAdapter.commitRemoves
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.github.shadowsocks
2+
3+
import android.content.{ComponentName, Context, Intent, ServiceConnection}
4+
import android.os.{RemoteException, IBinder}
5+
import com.github.shadowsocks.aidl.{IShadowsocksServiceCallback, IShadowsocksService}
6+
import com.github.shadowsocks.utils.Action
7+
8+
/**
9+
* @author Mygod
10+
*/
11+
trait ServiceBoundContext extends Context {
12+
val connection = new ServiceConnection {
13+
override def onServiceConnected(name: ComponentName, service: IBinder) {
14+
bgService = IShadowsocksService.Stub.asInterface(service)
15+
if (callback != null) try {
16+
bgService.registerCallback(callback)
17+
} catch {
18+
case ignored: RemoteException => // Nothing
19+
}
20+
ServiceBoundContext.this.onServiceConnected()
21+
}
22+
override def onServiceDisconnected(name: ComponentName) {
23+
if (callback != null) {
24+
try {
25+
if (bgService != null) bgService.unregisterCallback(callback)
26+
} catch {
27+
case ignored: RemoteException => // Nothing
28+
}
29+
callback = null
30+
}
31+
ServiceBoundContext.this.onServiceDisconnected()
32+
bgService = null
33+
}
34+
}
35+
36+
def onServiceConnected() = ()
37+
def onServiceDisconnected() = ()
38+
39+
private var callback: IShadowsocksServiceCallback.Stub = _
40+
41+
// Variables
42+
var bgService: IShadowsocksService = _
43+
44+
def attachService(callback: IShadowsocksServiceCallback.Stub = null) {
45+
this.callback = callback
46+
if (bgService == null) {
47+
val s =
48+
if (ShadowsocksApplication.isVpnEnabled) classOf[ShadowsocksVpnService] else classOf[ShadowsocksNatService]
49+
val intent = new Intent(this, s)
50+
intent.setAction(Action.SERVICE)
51+
bindService(intent, connection, Context.BIND_AUTO_CREATE)
52+
startService(new Intent(this, s))
53+
}
54+
}
55+
56+
def deattachService() {
57+
if (bgService != null) {
58+
unbindService(connection)
59+
bgService = null
60+
}
61+
}
62+
}

src/main/scala/com/github/shadowsocks/Shadowsocks.scala

Lines changed: 30 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ import android.util.Log
5858
import android.view.{View, ViewGroup, ViewParent}
5959
import android.widget._
6060
import com.github.jorgecastilloprz.FABProgressCircle
61-
import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
61+
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
6262
import com.github.shadowsocks.database._
6363
import com.github.shadowsocks.preferences.{DropDownPreference, NumberPickerPreference, PasswordEditTextPreference, SummaryEditTextPreference}
6464
import com.github.shadowsocks.utils._
@@ -111,7 +111,7 @@ object Shadowsocks {
111111
pref.asInstanceOf[PasswordEditTextPreference].setText(value)
112112
}
113113

114-
def updateNumberPickerPreference(pref: Preference, value: Int): Unit = {
114+
def updateNumberPickerPreference(pref: Preference, value: Int) {
115115
pref.asInstanceOf[NumberPickerPreference].setValue(value)
116116
}
117117

@@ -142,7 +142,7 @@ object Shadowsocks {
142142
}
143143

144144
class Shadowsocks
145-
extends AppCompatActivity {
145+
extends AppCompatActivity with ServiceBoundContext {
146146

147147
// Variables
148148
var serviceStarted = false
@@ -157,47 +157,28 @@ class Shadowsocks
157157

158158
// Services
159159
var currentServiceName = classOf[ShadowsocksNatService].getName
160-
var bgService: IShadowsocksService = null
161-
val callback = new IShadowsocksServiceCallback.Stub {
162-
override def stateChanged(state: Int, msg: String) {
163-
onStateChanged(state, msg)
164-
}
165-
override def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) {
166-
val trafficStat = getString(R.string.stat_summary)
167-
.formatLocal(Locale.ENGLISH, txRate, rxRate, txTotal, rxTotal)
168-
handler.post(() => {
169-
preferences.findPreference(Key.stat).setSummary(trafficStat)
170-
})
160+
161+
override def onServiceConnected() {
162+
// Update the UI
163+
if (fab != null) fab.setEnabled(true)
164+
stateUpdate()
165+
166+
if (!ShadowsocksApplication.settings.getBoolean(ShadowsocksApplication.getVersionName, false)) {
167+
ShadowsocksApplication.settings.edit.putBoolean(ShadowsocksApplication.getVersionName, true).apply()
168+
recovery()
171169
}
172170
}
173-
val connection = new ServiceConnection {
174-
override def onServiceConnected(name: ComponentName, service: IBinder) {
175-
// Initialize the background service
176-
bgService = IShadowsocksService.Stub.asInterface(service)
177-
try {
178-
bgService.registerCallback(callback)
179-
} catch {
180-
case ignored: RemoteException => // Nothing
181-
}
182-
// Update the UI
183-
if (fab != null) fab.setEnabled(true)
184-
stateUpdate()
185171

186-
if (!ShadowsocksApplication.settings.getBoolean(ShadowsocksApplication.getVersionName, false)) {
187-
ShadowsocksApplication.settings.edit.putBoolean(ShadowsocksApplication.getVersionName, true).apply()
188-
recovery()
189-
}
190-
}
172+
override def onServiceDisconnected() {
173+
if (fab != null) fab.setEnabled(false)
174+
}
191175

192-
override def onServiceDisconnected(name: ComponentName) {
193-
if (fab != null) fab.setEnabled(false)
194-
try {
195-
if (bgService != null) bgService.unregisterCallback(callback)
196-
} catch {
197-
case ignored: RemoteException => // Nothing
198-
}
199-
bgService = null
200-
}
176+
def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) {
177+
val trafficStat = getString(R.string.stat_summary)
178+
.formatLocal(Locale.ENGLISH, txRate, rxRate, txTotal, rxTotal)
179+
handler.post(() => {
180+
preferences.findPreference(Key.stat).setSummary(trafficStat)
181+
})
201182
}
202183

203184
private lazy val preferences =
@@ -388,33 +369,17 @@ class Shadowsocks
388369

389370
// Bind to the service
390371
handler.post(() => {
391-
attachService()
372+
attachService(new IShadowsocksServiceCallback.Stub {
373+
override def stateChanged(state: Int, msg: String) {
374+
onStateChanged(state, msg)
375+
}
376+
override def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) {
377+
Shadowsocks.this.trafficUpdated(txRate, rxRate, txTotal, rxTotal)
378+
}
379+
})
392380
})
393381
}
394382

395-
def attachService() {
396-
if (bgService == null) {
397-
val s = if (ShadowsocksApplication.isVpnEnabled) classOf[ShadowsocksVpnService]
398-
else classOf[ShadowsocksNatService]
399-
val intent = new Intent(this, s)
400-
intent.setAction(Action.SERVICE)
401-
bindService(intent, connection, Context.BIND_AUTO_CREATE)
402-
startService(new Intent(this, s))
403-
}
404-
}
405-
406-
def deattachService() {
407-
if (bgService != null) {
408-
try {
409-
bgService.unregisterCallback(callback)
410-
} catch {
411-
case ignored: RemoteException => // Nothing
412-
}
413-
bgService = null
414-
unbindService(connection)
415-
}
416-
}
417-
418383
def reloadProfile() {
419384
currentProfile = ShadowsocksApplication.currentProfile match {
420385
case Some(profile) => profile // updated
@@ -467,7 +432,7 @@ class Shadowsocks
467432
// Check if current profile changed
468433
if (ShadowsocksApplication.profileId != currentProfile.id) reloadProfile()
469434

470-
callback.trafficUpdated(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate,
435+
trafficUpdated(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate,
471436
TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal)
472437
}
473438

src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ class ShadowsocksApplication extends Application {
9595
val tm = TagManager.getInstance(this)
9696
val pending = tm.loadContainerPreferNonDefault("GTM-NT8WS8", R.raw.gtm_default_container)
9797
val callback = new ResultCallback[ContainerHolder] {
98-
override def onResult(holder: ContainerHolder): Unit = {
98+
override def onResult(holder: ContainerHolder) {
9999
if (!holder.getStatus.isSuccess) {
100100
return
101101
}

src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala

Lines changed: 9 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -40,30 +40,22 @@
4040
package com.github.shadowsocks
4141

4242
import android.app.{Activity, KeyguardManager}
43-
import android.content._
43+
import android.content.{Intent, IntentFilter, Context, BroadcastReceiver}
4444
import android.net.VpnService
45-
import android.os._
45+
import android.os.{Bundle, Handler}
46+
import android.support.v7.app.AppCompatActivity
4647
import android.util.Log
47-
import com.github.shadowsocks.aidl.IShadowsocksService
48-
import com.github.shadowsocks.utils._
49-
50-
class ShadowsocksRunnerActivity extends Activity {
48+
import com.github.shadowsocks.utils.ConfigUtils
5149

50+
class ShadowsocksRunnerActivity extends AppCompatActivity with ServiceBoundContext {
5251
val handler = new Handler()
53-
val connection = new ServiceConnection {
54-
override def onServiceConnected(name: ComponentName, service: IBinder) {
55-
bgService = IShadowsocksService.Stub.asInterface(service)
56-
handler.postDelayed(() => if (bgService != null) startBackgroundService(), 1000)
57-
}
58-
override def onServiceDisconnected(name: ComponentName) {
59-
bgService = null
60-
}
61-
}
6252

6353
// Variables
64-
var bgService: IShadowsocksService = _
65-
var receiver:BroadcastReceiver = _
54+
var receiver: BroadcastReceiver = _
6655

56+
override def onServiceConnected() {
57+
handler.postDelayed(() => if (bgService != null) startBackgroundService(), 1000)
58+
}
6759

6860
def startBackgroundService() {
6961
if (ShadowsocksApplication.isVpnEnabled) {
@@ -79,24 +71,6 @@ class ShadowsocksRunnerActivity extends Activity {
7971
}
8072
}
8173

82-
def attachService() {
83-
if (bgService == null) {
84-
val s = if (ShadowsocksApplication.isVpnEnabled) classOf[ShadowsocksVpnService]
85-
else classOf[ShadowsocksNatService]
86-
val intent = new Intent(this, s)
87-
intent.setAction(Action.SERVICE)
88-
bindService(intent, connection, Context.BIND_AUTO_CREATE)
89-
startService(new Intent(this, s))
90-
}
91-
}
92-
93-
def deattachService() {
94-
if (bgService != null) {
95-
unbindService(connection)
96-
bgService = null
97-
}
98-
}
99-
10074
override def onCreate(savedInstanceState: Bundle) {
10175
super.onCreate(savedInstanceState)
10276
val km = getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager]

0 commit comments

Comments
 (0)