Commit 2641de43 authored by Max Lv's avatar Max Lv

Merge pull request #494 from Mygod/master

Reduce battery usage
parents 6a0955c0 4d7bf8d7
...@@ -108,14 +108,6 @@ ...@@ -108,14 +108,6 @@
</intent-filter> </intent-filter>
</service> </service>
<receiver android:name=".AppManagerReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED" />
<action android:name="android.intent.action.PACKAGE_REMOVED" />
<data android:scheme="package"/>
</intent-filter>
</receiver>
<receiver android:name=".ShadowsocksReceiver"> <receiver android:name=".ShadowsocksReceiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.BOOT_COMPLETED"/>
......
Subproject commit 48a063ef1e1a886e274a0fbb797e88358deca35b Subproject commit 68eaf3b12d385381130c046547163b81e1807ba4
...@@ -39,8 +39,11 @@ ...@@ -39,8 +39,11 @@
package com.github.shadowsocks package com.github.shadowsocks
import java.util.concurrent.atomic.AtomicBoolean
import android.Manifest.permission
import android.content._
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.{ClipData, ClipboardManager, Context}
import android.graphics.PixelFormat import android.graphics.PixelFormat
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.{Bundle, Handler} import android.os.{Bundle, Handler}
...@@ -54,7 +57,6 @@ import android.view._ ...@@ -54,7 +57,6 @@ import android.view._
import android.widget.AbsListView.OnScrollListener import android.widget.AbsListView.OnScrollListener
import android.widget.CompoundButton.OnCheckedChangeListener import android.widget.CompoundButton.OnCheckedChangeListener
import android.widget._ import android.widget._
import android.Manifest.permission
import com.github.shadowsocks.utils.{Key, Utils} import com.github.shadowsocks.utils.{Key, Utils}
import scala.collection.JavaConversions._ import scala.collection.JavaConversions._
...@@ -65,13 +67,31 @@ object AppManager { ...@@ -65,13 +67,31 @@ object AppManager {
case class ProxiedApp(name: String, packageName: String, icon: Drawable) case class ProxiedApp(name: String, packageName: String, icon: Drawable)
private case class ListEntry(switch: Switch, text: TextView, icon: ImageView) private case class ListEntry(switch: Switch, text: TextView, icon: ImageView)
var cachedApps: Array[ProxiedApp] = _ private var instance: AppManager = _
private var receiverRegistered: Boolean = _
private var cachedApps: Array[ProxiedApp] = _
private def getApps(pm: PackageManager) = { private def getApps(pm: PackageManager) = {
if (cachedApps == null) cachedApps = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS) if (!receiverRegistered) {
.filter(p => p.requestedPermissions != null && p.requestedPermissions.contains(permission.INTERNET)) val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED)
.map(p => new ProxiedApp(pm.getApplicationLabel(p.applicationInfo).toString, p.packageName, filter.addAction(Intent.ACTION_PACKAGE_REMOVED)
p.applicationInfo.loadIcon(pm))).toArray filter.addDataScheme("package")
cachedApps ShadowsocksApplication.instance.registerReceiver((context: Context, intent: Intent) =>
if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED ||
!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
synchronized(cachedApps = null)
val instance = AppManager.instance
if (instance != null) instance.reloadApps()
}, filter)
receiverRegistered = true
}
synchronized {
if (cachedApps == null) cachedApps = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS)
.filter(p => p.requestedPermissions != null && p.requestedPermissions.contains(permission.INTERNET))
.map(p => new ProxiedApp(pm.getApplicationLabel(p.applicationInfo).toString, p.packageName,
p.applicationInfo.loadIcon(pm))).toArray
cachedApps
}
} }
} }
...@@ -86,43 +106,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC ...@@ -86,43 +106,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
private var loadingView: View = _ private var loadingView: View = _
private var overlay: TextView = _ private var overlay: TextView = _
private var adapter: ListAdapter = _ private var adapter: ListAdapter = _
@volatile private var appsLoading: Boolean = _ private val appsLoading = new AtomicBoolean
def loadApps() {
appsLoading = true
proxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "").split('\n').to[mutable.HashSet]
apps = getApps(getPackageManager).sortWith((a, b) => {
val aProxied = proxiedApps.contains(a.packageName)
if (aProxied ^ proxiedApps.contains(b.packageName)) aProxied else a.name.compareToIgnoreCase(b.name) < 0
})
adapter = new ArrayAdapter[ProxiedApp](this, R.layout.layout_apps_item, R.id.itemtext, apps) {
override def getView(position: Int, view: View, parent: ViewGroup): View = {
var convertView = view
var entry: ListEntry = null
if (convertView == null) {
convertView = getLayoutInflater.inflate(R.layout.layout_apps_item, parent, false)
entry = new ListEntry(convertView.findViewById(R.id.itemcheck).asInstanceOf[Switch],
convertView.findViewById(R.id.itemtext).asInstanceOf[TextView],
convertView.findViewById(R.id.itemicon).asInstanceOf[ImageView])
convertView.setOnClickListener(AppManager.this)
convertView.setTag(entry)
entry.switch.setOnCheckedChangeListener(AppManager.this)
} else {
entry = convertView.getTag.asInstanceOf[ListEntry]
}
val app: ProxiedApp = apps(position)
entry.text.setText(app.name)
entry.icon.setImageDrawable(app.icon)
val switch = entry.switch
switch.setTag(app)
switch.setChecked(proxiedApps.contains(app.packageName))
entry.text.setTag(switch)
convertView
}
}
}
private def setProxied(pn: String, proxied: Boolean) = if (proxied) proxiedApps.add(pn) else proxiedApps.remove(pn) private def setProxied(pn: String, proxied: Boolean) = if (proxied) proxiedApps.add(pn) else proxiedApps.remove(pn)
...@@ -145,6 +129,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC ...@@ -145,6 +129,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
} }
override def onDestroy() { override def onDestroy() {
instance = null
super.onDestroy() super.onDestroy()
if (handler != null) { if (handler != null) {
handler.removeCallbacksAndMessages(null) handler.removeCallbacksAndMessages(null)
...@@ -186,7 +171,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC ...@@ -186,7 +171,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
// Restart activity // Restart activity
appListView.setVisibility(View.GONE) appListView.setVisibility(View.GONE)
loadingView.setVisibility(View.VISIBLE) loadingView.setVisibility(View.VISIBLE)
if (appsLoading) appsLoading = false else loadAppsAsync() reloadApps()
return true return true
} }
} }
...@@ -252,13 +237,49 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC ...@@ -252,13 +237,49 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
} }
} }
}) })
instance = this
loadAppsAsync() loadAppsAsync()
} }
def reloadApps() = if (!appsLoading.compareAndSet(true, false)) loadAppsAsync()
def loadAppsAsync() { def loadAppsAsync() {
if (!appsLoading.compareAndSet(false, true)) return
ThrowableFuture { ThrowableFuture {
while (!appsLoading) loadApps() do {
appsLoading = false proxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "").split('\n').to[mutable.HashSet]
appsLoading.set(true)
apps = getApps(getPackageManager).sortWith((a, b) => {
val aProxied = proxiedApps.contains(a.packageName)
if (aProxied ^ proxiedApps.contains(b.packageName)) aProxied else a.name.compareToIgnoreCase(b.name) < 0
})
adapter = new ArrayAdapter[ProxiedApp](this, R.layout.layout_apps_item, R.id.itemtext, apps) {
override def getView(position: Int, view: View, parent: ViewGroup): View = {
var convertView = view
var entry: ListEntry = null
if (convertView == null) {
convertView = getLayoutInflater.inflate(R.layout.layout_apps_item, parent, false)
entry = new ListEntry(convertView.findViewById(R.id.itemcheck).asInstanceOf[Switch],
convertView.findViewById(R.id.itemtext).asInstanceOf[TextView],
convertView.findViewById(R.id.itemicon).asInstanceOf[ImageView])
convertView.setOnClickListener(AppManager.this)
convertView.setTag(entry)
entry.switch.setOnCheckedChangeListener(AppManager.this)
} else {
entry = convertView.getTag.asInstanceOf[ListEntry]
}
val app: ProxiedApp = apps(position)
entry.text.setText(app.name)
entry.icon.setImageDrawable(app.icon)
val switch = entry.switch
switch.setTag(app)
switch.setChecked(proxiedApps.contains(app.packageName))
entry.text.setTag(switch)
convertView
}
}
} while (!appsLoading.compareAndSet(true, false))
handler.post(() => { handler.post(() => {
appListView.setAdapter(adapter) appListView.setAdapter(adapter)
Utils.crossFade(AppManager.this, loadingView, appListView) Utils.crossFade(AppManager.this, loadingView, appListView)
...@@ -267,7 +288,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC ...@@ -267,7 +288,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
} }
def saveAppSettings(context: Context) { def saveAppSettings(context: Context) {
if (!appsLoading) ShadowsocksApplication.settings.edit.putString(Key.proxied, proxiedApps.mkString("\n")).apply if (!appsLoading.get) ShadowsocksApplication.settings.edit.putString(Key.proxied, proxiedApps.mkString("\n")).apply
} }
var handler: Handler = null var handler: Handler = null
......
package com.github.shadowsocks
import android.content.{Intent, Context, BroadcastReceiver}
/**
* @author Mygod
*/
class AppManagerReceiver extends BroadcastReceiver {
override def onReceive(context: Context, intent: Intent) = if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED ||
!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) AppManager.synchronized(AppManager.cachedApps = null)
}
...@@ -42,7 +42,7 @@ package com.github.shadowsocks ...@@ -42,7 +42,7 @@ package com.github.shadowsocks
import java.util.{Timer, TimerTask} import java.util.{Timer, TimerTask}
import android.app.Service import android.app.Service
import android.content.Context import android.content.{Intent, Context}
import android.os.{Handler, RemoteCallbackList} import android.os.{Handler, RemoteCallbackList}
import com.github.shadowsocks.aidl.{Config, IShadowsocksService, IShadowsocksServiceCallback} import com.github.shadowsocks.aidl.{Config, IShadowsocksService, IShadowsocksServiceCallback}
import com.github.shadowsocks.utils.{State, TrafficMonitor, TrafficMonitorThread} import com.github.shadowsocks.utils.{State, TrafficMonitor, TrafficMonitorThread}
...@@ -50,7 +50,6 @@ import com.github.shadowsocks.utils.{State, TrafficMonitor, TrafficMonitorThread ...@@ -50,7 +50,6 @@ import com.github.shadowsocks.utils.{State, TrafficMonitor, TrafficMonitorThread
trait BaseService extends Service { trait BaseService extends Service {
@volatile private var state = State.STOPPED @volatile private var state = State.STOPPED
@volatile private var callbackCount = 0
@volatile protected var config: Config = null @volatile protected var config: Config = null
var timer: Timer = null var timer: Timer = null
...@@ -70,32 +69,28 @@ trait BaseService extends Service { ...@@ -70,32 +69,28 @@ trait BaseService extends Service {
override def unregisterCallback(cb: IShadowsocksServiceCallback) { override def unregisterCallback(cb: IShadowsocksServiceCallback) {
if (cb != null ) { if (cb != null ) {
callbacks.unregister(cb) callbacks.unregister(cb)
callbackCount -= 1
} }
if (callbackCount == 0 && timer != null) { if (callbacks.getRegisteredCallbackCount == 0 && timer != null) {
timer.cancel() timer.cancel()
timer = null timer = null
} }
if (callbackCount == 0 && state == State.STOPPED) {
stopBackgroundService()
}
} }
override def registerCallback(cb: IShadowsocksServiceCallback) { override def registerCallback(cb: IShadowsocksServiceCallback) {
if (cb != null) { if (cb != null) {
if (callbackCount == 0 && timer == null) { callbacks.register(cb)
if (callbacks.getRegisteredCallbackCount != 0 && timer == null) {
val task = new TimerTask { val task = new TimerTask {
def run { def run {
TrafficMonitor.updateRate() if (TrafficMonitor.updateRate()) updateTrafficRate()
updateTrafficRate(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate,
TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal)
} }
} }
timer = new Timer(true) timer = new Timer(true)
timer.schedule(task, 1000, 1000) timer.schedule(task, 1000, 1000)
} }
callbacks.register(cb) TrafficMonitor.updateRate()
callbackCount += 1 cb.trafficUpdated(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate,
TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal)
} }
} }
...@@ -115,6 +110,7 @@ trait BaseService extends Service { ...@@ -115,6 +110,7 @@ trait BaseService extends Service {
def startRunner(config: Config) { def startRunner(config: Config) {
this.config = config this.config = config
startService(new Intent(getContext, getClass))
TrafficMonitor.reset() TrafficMonitor.reset()
trafficMonitorThread = new TrafficMonitorThread() trafficMonitorThread = new TrafficMonitorThread()
trafficMonitorThread.start() trafficMonitorThread.start()
...@@ -122,39 +118,38 @@ trait BaseService extends Service { ...@@ -122,39 +118,38 @@ trait BaseService extends Service {
def stopRunner() { def stopRunner() {
// Make sure update total traffic when stopping the runner // Make sure update total traffic when stopping the runner
updateTrafficTotal(TrafficMonitor.getDeltaTx, TrafficMonitor.getDeltaRx) updateTrafficTotal(TrafficMonitor.txTotal, TrafficMonitor.rxTotal)
TrafficMonitor.reset() TrafficMonitor.reset()
if (trafficMonitorThread != null) { if (trafficMonitorThread != null) {
trafficMonitorThread.stopThread() trafficMonitorThread.stopThread()
trafficMonitorThread = null trafficMonitorThread = null
} }
// change the state
changeState(State.STOPPED)
// stop the service if nothing has bound to it
stopSelf()
} }
def updateTrafficTotal(tx: Long, rx: Long) { def updateTrafficTotal(tx: Long, rx: Long) {
val handler = new Handler(getContext.getMainLooper) val config = this.config // avoid race conditions without locking
handler.post(() => { if (config != null) {
val config = this.config // avoid race conditions without locking ShadowsocksApplication.profileManager.getProfile(config.profileId) match {
if (config != null) { case Some(profile) =>
ShadowsocksApplication.profileManager.getProfile(config.profileId) match { profile.tx += tx
case Some(profile) => profile.rx += rx
profile.tx += tx ShadowsocksApplication.profileManager.updateProfile(profile)
profile.rx += rx case None => // Ignore
ShadowsocksApplication.profileManager.updateProfile(profile)
case None => // Ignore
}
} }
}) }
} }
def stopBackgroundService()
def getServiceMode: Int def getServiceMode: Int
def getTag: String def getTag: String
def getContext: Context def getContext: Context
def getCallbackCount: Int = {
callbackCount
}
def getState: Int = { def getState: Int = {
state state
} }
...@@ -162,10 +157,14 @@ trait BaseService extends Service { ...@@ -162,10 +157,14 @@ trait BaseService extends Service {
changeState(s, null) changeState(s, null)
} }
def updateTrafficRate(txRate: String, rxRate: String, txTotal: String, rxTotal: String) { def updateTrafficRate() {
val handler = new Handler(getContext.getMainLooper) val handler = new Handler(getContext.getMainLooper)
handler.post(() => { handler.post(() => {
if (callbackCount > 0) { if (callbacks.getRegisteredCallbackCount > 0) {
val txRate = TrafficMonitor.getTxRate
val rxRate = TrafficMonitor.getRxRate
val txTotal = TrafficMonitor.getTxTotal
val rxTotal = TrafficMonitor.getRxTotal
val n = callbacks.beginBroadcast() val n = callbacks.beginBroadcast()
for (i <- 0 until n) { for (i <- 0 until n) {
try { try {
...@@ -182,7 +181,7 @@ trait BaseService extends Service { ...@@ -182,7 +181,7 @@ trait BaseService extends Service {
protected def changeState(s: Int, msg: String) { protected def changeState(s: Int, msg: String) {
val handler = new Handler(getContext.getMainLooper) val handler = new Handler(getContext.getMainLooper)
handler.post(() => if (state != s) { handler.post(() => if (state != s) {
if (callbackCount > 0) { if (callbacks.getRegisteredCallbackCount > 0) {
val n = callbacks.beginBroadcast() val n = callbacks.beginBroadcast()
for (i <- 0 until n) { for (i <- 0 until n) {
try { try {
......
...@@ -218,6 +218,15 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe ...@@ -218,6 +218,15 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
} }
} }
override def onStart() {
super.onStart()
registerCallback
}
override def onStop() {
super.onStop()
unregisterCallback
}
override def onDestroy { override def onDestroy {
deattachService() deattachService()
super.onDestroy super.onDestroy
......
...@@ -13,20 +13,12 @@ trait ServiceBoundContext extends Context { ...@@ -13,20 +13,12 @@ trait ServiceBoundContext extends Context {
class ShadowsocksServiceConnection extends ServiceConnection { class ShadowsocksServiceConnection extends ServiceConnection {
override def onServiceConnected(name: ComponentName, service: IBinder) { override def onServiceConnected(name: ComponentName, service: IBinder) {
bgService = IShadowsocksService.Stub.asInterface(service) bgService = IShadowsocksService.Stub.asInterface(service)
if (callback != null) try { registerCallback
bgService.registerCallback(callback)
} catch {
case ignored: RemoteException => // Nothing
}
ServiceBoundContext.this.onServiceConnected() ServiceBoundContext.this.onServiceConnected()
} }
override def onServiceDisconnected(name: ComponentName) { override def onServiceDisconnected(name: ComponentName) {
if (callback != null) { if (callback != null) {
try { unregisterCallback
if (bgService != null) bgService.unregisterCallback(callback)
} catch {
case ignored: RemoteException => // Nothing
}
callback = null callback = null
} }
ServiceBoundContext.this.onServiceDisconnected() ServiceBoundContext.this.onServiceDisconnected()
...@@ -34,11 +26,25 @@ trait ServiceBoundContext extends Context { ...@@ -34,11 +26,25 @@ trait ServiceBoundContext extends Context {
} }
} }
def registerCallback = if (bgService != null && callback != null && !callbackRegistered) try {
bgService.registerCallback(callback)
callbackRegistered = true
} catch {
case ignored: RemoteException => // Nothing
}
def unregisterCallback = if (bgService != null && callback != null && callbackRegistered) try {
bgService.unregisterCallback(callback)
callbackRegistered = false
} catch {
case ignored: RemoteException => // Nothing
}
def onServiceConnected() = () def onServiceConnected() = ()
def onServiceDisconnected() = () def onServiceDisconnected() = ()
private var callback: IShadowsocksServiceCallback.Stub = _ private var callback: IShadowsocksServiceCallback.Stub = _
private var connection: ShadowsocksServiceConnection = _ private var connection: ShadowsocksServiceConnection = _
private var callbackRegistered: Boolean = _
// Variables // Variables
var bgService: IShadowsocksService = _ var bgService: IShadowsocksService = _
...@@ -54,19 +60,13 @@ trait ServiceBoundContext extends Context { ...@@ -54,19 +60,13 @@ trait ServiceBoundContext extends Context {
connection = new ShadowsocksServiceConnection() connection = new ShadowsocksServiceConnection()
bindService(intent, connection, Context.BIND_AUTO_CREATE) bindService(intent, connection, Context.BIND_AUTO_CREATE)
startService(new Intent(this, s))
} }
} }
def deattachService() { def deattachService() {
if (bgService != null) { if (bgService != null) {
if (callback != null) { if (callback != null) {
try { unregisterCallback
bgService.unregisterCallback(callback)
} catch {
case ignored: RemoteException => // Nothing
}
callback = null callback = null
} }
if (connection != null) { if (connection != null) {
......
...@@ -461,8 +461,13 @@ class Shadowsocks ...@@ -461,8 +461,13 @@ class Shadowsocks
preferences.update(profile) preferences.update(profile)
} }
override def onStart() {
super.onStart()
registerCallback
}
override def onStop() { override def onStop() {
super.onStop() super.onStop()
unregisterCallback
clearDialog() clearDialog()
} }
......
...@@ -458,8 +458,6 @@ class ShadowsocksNatService extends BaseService { ...@@ -458,8 +458,6 @@ class ShadowsocksNatService extends BaseService {
override def stopRunner() { override def stopRunner() {
super.stopRunner()
// channge the state // channge the state
changeState(State.STOPPING) changeState(State.STOPPING)
...@@ -476,17 +474,7 @@ class ShadowsocksNatService extends BaseService { ...@@ -476,17 +474,7 @@ class ShadowsocksNatService extends BaseService {
// reset NAT // reset NAT
killProcesses() killProcesses()
// stop the service if no callback registered super.stopRunner()
if (getCallbackCount == 0) {
stopSelf()
}
// change the state
changeState(State.STOPPED)
}
override def stopBackgroundService() {
stopSelf()
} }
override def getTag = TAG override def getTag = TAG
......
...@@ -2,7 +2,7 @@ package com.github.shadowsocks ...@@ -2,7 +2,7 @@ package com.github.shadowsocks
import java.util.Locale import java.util.Locale
import android.app.{KeyguardManager, PendingIntent} import android.app.{KeyguardManager, NotificationManager, PendingIntent}
import android.content.{BroadcastReceiver, Context, Intent, IntentFilter} import android.content.{BroadcastReceiver, Context, Intent, IntentFilter}
import android.os.PowerManager import android.os.PowerManager
import android.support.v4.app.NotificationCompat import android.support.v4.app.NotificationCompat
...@@ -16,6 +16,7 @@ import com.github.shadowsocks.utils.{Action, State, Utils} ...@@ -16,6 +16,7 @@ import com.github.shadowsocks.utils.{Action, State, Utils}
*/ */
class ShadowsocksNotification(private val service: BaseService, profileName: String, visible: Boolean = false) { class ShadowsocksNotification(private val service: BaseService, profileName: String, visible: Boolean = false) {
private lazy val keyGuard = service.getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager] private lazy val keyGuard = service.getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager]
private lazy val nm = service.getSystemService(Context.NOTIFICATION_SERVICE).asInstanceOf[NotificationManager]
private lazy val callback = new Stub { private lazy val callback = new Stub {
override def stateChanged(state: Int, msg: String) = () // ignore override def stateChanged(state: Int, msg: String) = () // ignore
override def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) { override def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) {
...@@ -27,6 +28,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str ...@@ -27,6 +28,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
} }
private var lockReceiver: BroadcastReceiver = _ private var lockReceiver: BroadcastReceiver = _
private var callbackRegistered: Boolean = _ private var callbackRegistered: Boolean = _
private var started: Boolean = _
private val builder = new NotificationCompat.Builder(service) private val builder = new NotificationCompat.Builder(service)
.setWhen(0) .setWhen(0)
...@@ -73,8 +75,10 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str ...@@ -73,8 +75,10 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
show() show()
} else if (forceShow) show() } else if (forceShow) show()
def notification = builder.build() def show() = if (started) nm.notify(1, builder.build) else {
def show() = service.startForeground(1, notification) service.startForeground(1, builder.build)
started = true
}
def destroy() { def destroy() {
if (lockReceiver != null) { if (lockReceiver != null) {
......
...@@ -113,8 +113,6 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -113,8 +113,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
override def stopRunner() { override def stopRunner() {
super.stopRunner()
if (vpnThread != null) { if (vpnThread != null) {
vpnThread.stopThread() vpnThread.stopThread()
vpnThread = null vpnThread = null
...@@ -136,19 +134,13 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -136,19 +134,13 @@ class ShadowsocksVpnService extends VpnService with BaseService {
conn = null conn = null
} }
// stop the service if no callback registered
if (getCallbackCount == 0) {
stopSelf()
}
// clean up recevier // clean up recevier
if (closeReceiver != null) { if (closeReceiver != null) {
unregisterReceiver(closeReceiver) unregisterReceiver(closeReceiver)
closeReceiver = null closeReceiver = null
} }
// channge the state super.stopRunner()
changeState(State.STOPPED)
} }
def getVersionName: String = { def getVersionName: String = {
...@@ -458,10 +450,6 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -458,10 +450,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
fd fd
} }
override def stopBackgroundService() {
stopSelf()
}
override def getTag = TAG override def getTag = TAG
override def getServiceMode = Mode.VPN override def getServiceMode = Mode.VPN
......
...@@ -45,7 +45,12 @@ import java.util.concurrent.Executors ...@@ -45,7 +45,12 @@ import java.util.concurrent.Executors
import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress} import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress}
import android.util.Log import android.util.Log
object ShadowsocksVpnThread {
val getInt = classOf[FileDescriptor].getDeclaredMethod("getInt$")
}
class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread { class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread {
import ShadowsocksVpnThread._
val TAG = "ShadowsocksVpnService" val TAG = "ShadowsocksVpnService"
val PATH = "/data/data/com.github.shadowsocks/protect_path" val PATH = "/data/data/com.github.shadowsocks/protect_path"
...@@ -103,11 +108,8 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread { ...@@ -103,11 +108,8 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread {
val fds = socket.getAncillaryFileDescriptors val fds = socket.getAncillaryFileDescriptors
if (fds.nonEmpty) { if (fds.nonEmpty) {
var ret = false
val getInt = classOf[FileDescriptor].getDeclaredMethod("getInt$")
val fd = getInt.invoke(fds(0)).asInstanceOf[Int] val fd = getInt.invoke(fds(0)).asInstanceOf[Int]
ret = vpnService.protect(fd) val ret = vpnService.protect(fd)
// Trick to close file decriptor // Trick to close file decriptor
System.jniclose(fd) System.jniclose(fd)
......
...@@ -5,26 +5,20 @@ import java.text.DecimalFormat ...@@ -5,26 +5,20 @@ import java.text.DecimalFormat
import com.github.shadowsocks.{R, ShadowsocksApplication} import com.github.shadowsocks.{R, ShadowsocksApplication}
case class Traffic(tx: Long, rx: Long, timestamp: Long)
object TrafficMonitor { object TrafficMonitor {
var last: Traffic = getTraffic(0, 0)
// Bytes per second // Bytes per second
var txRate: Long = 0 var txRate: Long = _
var rxRate: Long = 0 var rxRate: Long = _
// Bytes for the current session // Bytes for the current session
var txTotal: Long = 0 var txTotal: Long = _
var rxTotal: Long = 0 var rxTotal: Long = _
// Bytes for the last query // Bytes for the last query
var txLast: Long = 0 var txLast: Long = _
var rxLast: Long = 0 var rxLast: Long = _
var timestampLast: Long = _
def getTraffic(tx: Long, rx: Long): Traffic = { @volatile var dirty = true
new Traffic(tx, rx, System.currentTimeMillis())
}
private val units = Array("KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "NB", "DB", "CB") private val units = Array("KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "NB", "DB", "CB")
private val numberFormat = new DecimalFormat("@@@") private val numberFormat = new DecimalFormat("@@@")
...@@ -39,21 +33,42 @@ object TrafficMonitor { ...@@ -39,21 +33,42 @@ object TrafficMonitor {
else numberFormat.format(n) + ' ' + units(i) else numberFormat.format(n) + ' ' + units(i)
} }
def updateRate() { def updateRate() = {
val now = getTraffic(txTotal, rxTotal) val now = System.currentTimeMillis()
val delta = now.timestamp - last.timestamp val delta = now - timestampLast
val deltaTx = now.tx - last.tx var updated = false
val deltaRx = now.rx - last.rx
if (delta != 0) { if (delta != 0) {
txRate = deltaTx * 1000 / delta if (dirty) {
rxRate = deltaRx * 1000 / delta txRate = (txTotal - txLast) * 1000 / delta
rxRate = (rxTotal - rxLast) * 1000 / delta
txLast = txTotal
rxLast = rxTotal
dirty = false
updated = true
} else {
if (txRate != 0) {
txRate = 0
updated = true
}
if (rxRate != 0) {
rxRate = 0
updated = true
}
}
timestampLast = now
} }
last = now updated
} }
def update(tx: Long, rx: Long) { def update(tx: Long, rx: Long) {
txTotal = tx if (txTotal != tx) {
rxTotal = rx txTotal = tx
dirty = true
}
if (rxTotal != rx) {
rxTotal = rx
dirty = true
}
} }
def reset() { def reset() {
...@@ -63,7 +78,7 @@ object TrafficMonitor { ...@@ -63,7 +78,7 @@ object TrafficMonitor {
rxTotal = 0 rxTotal = 0
txLast = 0 txLast = 0
rxLast = 0 rxLast = 0
last = getTraffic(0, 0) dirty = true
} }
def getTxTotal(): String = { def getTxTotal(): String = {
...@@ -89,17 +104,5 @@ object TrafficMonitor { ...@@ -89,17 +104,5 @@ object TrafficMonitor {
def getRate(): String = { def getRate(): String = {
formatTraffic(txRate + rxRate) formatTraffic(txRate + rxRate)
} }
def getDeltaTx(): Long = {
val last = txLast
txLast = txTotal
txTotal - last
}
def getDeltaRx(): Long = {
val last = rxLast
rxLast = rxTotal
rxTotal - last
}
} }
...@@ -40,11 +40,11 @@ ...@@ -40,11 +40,11 @@
package com.github.shadowsocks.utils package com.github.shadowsocks.utils
import java.io.{File, IOException} import java.io.{File, IOException}
import java.nio.{ByteBuffer, ByteOrder}
import java.util.concurrent.Executors import java.util.concurrent.Executors
import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress} import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress}
import android.util.Log import android.util.Log
import com.github.shadowsocks.BaseService
class TrafficMonitorThread() extends Thread { class TrafficMonitorThread() extends Thread {
...@@ -99,16 +99,10 @@ class TrafficMonitorThread() extends Thread { ...@@ -99,16 +99,10 @@ class TrafficMonitorThread() extends Thread {
val input = socket.getInputStream val input = socket.getInputStream
val output = socket.getOutputStream val output = socket.getOutputStream
val buffer = new Array[Byte](256) val buffer = new Array[Byte](16)
val size = input.read(buffer) if (input.read(buffer) != 16) throw new IOException("Unexpected traffic stat length")
val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN)
val array = new Array[Byte](size) TrafficMonitor.update(stat.getLong(0), stat.getLong(8))
Array.copy(buffer, 0, array, 0, size)
val stat = new String(array, "UTF-8").split("\\|")
if (stat.length == 2) {
TrafficMonitor.update(stat(0).toLong, stat(1).toLong)
}
output.write(0) output.write(0)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment