diff --git a/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl b/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl index 94e732e3..17545289 100644 --- a/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl +++ b/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl @@ -7,6 +7,8 @@ interface IShadowsocksService { String getProfileName(); oneway void registerCallback(IShadowsocksServiceCallback cb); + oneway void startListeningForBandwidth(IShadowsocksServiceCallback cb); + oneway void stopListeningForBandwidth(IShadowsocksServiceCallback cb); oneway void unregisterCallback(IShadowsocksServiceCallback cb); oneway void use(in int profileId); diff --git a/src/main/scala/com/github/shadowsocks/BaseService.scala b/src/main/scala/com/github/shadowsocks/BaseService.scala index ee88792f..bc0a1109 100644 --- a/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -29,7 +29,7 @@ import java.util.{Timer, TimerTask} import android.app.Service import android.content.{BroadcastReceiver, Context, Intent, IntentFilter} import android.net.ConnectivityManager -import android.os.{Handler, RemoteCallbackList} +import android.os.{Handler, IBinder, RemoteCallbackList} import android.text.TextUtils import android.util.Log import android.widget.Toast @@ -39,6 +39,7 @@ import com.github.shadowsocks.database.Profile import com.github.shadowsocks.utils._ import okhttp3.{Dns, FormBody, OkHttpClient, Request} +import scala.collection.mutable import scala.util.Random trait BaseService extends Service { @@ -54,7 +55,7 @@ trait BaseService extends Service { var trafficMonitorThread: TrafficMonitorThread = _ final val callbacks = new RemoteCallbackList[IShadowsocksServiceCallback] - var callbacksCount: Int = _ + private final val bandwidthListeners = new mutable.HashSet[IBinder]() // the binder is the real identifier lazy val handler = new Handler(getMainLooper) lazy val restartHanlder = new Handler(getMainLooper) lazy val protectPath: String = getApplicationInfo.dataDir + "/protect_path" @@ -85,31 +86,29 @@ trait BaseService extends Service { override def getProfileName: String = if (profile == null) null else profile.getName - override def unregisterCallback(cb: IShadowsocksServiceCallback) { - if (cb != null && callbacks.unregister(cb)) { - callbacksCount -= 1 - if (callbacksCount == 0 && timer != null) { - timer.cancel() - timer = null - } - } - } + override def registerCallback(cb: IShadowsocksServiceCallback): Unit = callbacks.register(cb) - override def registerCallback(cb: IShadowsocksServiceCallback) { - if (cb != null && callbacks.register(cb)) { - callbacksCount += 1 - if (callbacksCount != 0 && timer == null) { - val task = new TimerTask { - def run() { - if (TrafficMonitor.updateRate()) updateTrafficRate() - } - } + override def startListeningForBandwidth(cb: IShadowsocksServiceCallback): Unit = + if (bandwidthListeners.add(cb.asBinder)){ + if (timer == null) { timer = new Timer(true) - timer.schedule(task, 1000, 1000) + timer.schedule(new TimerTask { + def run(): Unit = if (TrafficMonitor.updateRate()) updateTrafficRate() + }, 1000, 1000) } TrafficMonitor.updateRate() cb.trafficUpdated(TrafficMonitor.txRate, TrafficMonitor.rxRate, TrafficMonitor.txTotal, TrafficMonitor.rxTotal) } + + override def stopListeningForBandwidth(cb: IShadowsocksServiceCallback): Unit = + if (bandwidthListeners.remove(cb.asBinder) && bandwidthListeners.isEmpty) { + timer.cancel() + timer = null + } + + override def unregisterCallback(cb: IShadowsocksServiceCallback) { + stopListeningForBandwidth(cb) // saves an RPC, and safer + callbacks.unregister(cb) } override def use(profileId: Int): Unit = synchronized(if (profileId < 0) stopRunner(stopService = true) else { @@ -258,7 +257,7 @@ trait BaseService extends Service { def updateTrafficRate() { handler.post(() => { - if (callbacksCount > 0) { + if (bandwidthListeners.nonEmpty) { val txRate = TrafficMonitor.txRate val rxRate = TrafficMonitor.rxRate val txTotal = TrafficMonitor.txTotal @@ -266,7 +265,8 @@ trait BaseService extends Service { val n = callbacks.beginBroadcast() for (i <- 0 until n) { try { - callbacks.getBroadcastItem(i).trafficUpdated(txRate, rxRate, txTotal, rxTotal) + val item = callbacks.getBroadcastItem(i) + if (bandwidthListeners.contains(item.asBinder)) item.trafficUpdated(txRate, rxRate, txTotal, rxTotal) } catch { case _: Exception => // Ignore } @@ -289,7 +289,7 @@ trait BaseService extends Service { protected def changeState(s: Int, msg: String = null) { val handler = new Handler(getMainLooper) handler.post(() => if (state != s || msg != null) { - if (callbacksCount > 0) { + if (callbacks.getRegisteredCallbackCount > 0) { val n = callbacks.beginBroadcast() for (i <- 0 until n) { try { diff --git a/src/main/scala/com/github/shadowsocks/MainActivity.scala b/src/main/scala/com/github/shadowsocks/MainActivity.scala index a20d29f9..8ec3c246 100644 --- a/src/main/scala/com/github/shadowsocks/MainActivity.scala +++ b/src/main/scala/com/github/shadowsocks/MainActivity.scala @@ -142,7 +142,9 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe override def onServiceConnected() { // Update the UI if (fab != null) fab.setEnabled(true) - updateState() + Log.d(TAG, "bgService " + bgService.getState) + callback.stateChanged(bgService.getState, null, null) + state = bgService.getState if (Build.VERSION.SDK_INT >= 21 && app.isNatEnabled) { val snackbar = Snackbar.make(findViewById(R.id.snackbar), R.string.nat_deprecated, Snackbar.LENGTH_LONG) addDisableNatToSnackbar(snackbar) @@ -393,25 +395,14 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe } } - private def updateState() { - if (bgService != null) { - Log.d(TAG, "bgService " + bgService.getState) - callback.stateChanged(bgService.getState, null, null) - state = bgService.getState - } - } - protected override def onResume() { super.onResume() - app.refreshContainerHolder() - - updateState() } override def onStart() { super.onStart() - registerCallback() + setListeningForBandwidth(true) } override def onBackPressed(): Unit = @@ -421,8 +412,8 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe } else super.onBackPressed() override def onStop() { + setListeningForBandwidth(false) super.onStop() - unregisterCallback() } override def onDestroy() { diff --git a/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala b/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala index e0907b95..2be868a1 100644 --- a/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala +++ b/src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala @@ -35,7 +35,13 @@ trait ServiceBoundContext extends Context with IBinder.DeathRecipient { binder = service service.linkToDeath(ServiceBoundContext.this, 0) bgService = IShadowsocksService.Stub.asInterface(service) - registerCallback() + if (callback != null && !callbackRegistered) try { + bgService.registerCallback(callback) + callbackRegistered = true + if (listeningForBandwidth) bgService.startListeningForBandwidth(callback) + } catch { + case _: RemoteException => // Nothing + } ServiceBoundContext.this.onServiceConnected() } override def onServiceDisconnected(name: ComponentName) { @@ -46,17 +52,17 @@ trait ServiceBoundContext extends Context with IBinder.DeathRecipient { } } - def registerCallback(): Unit = if (bgService != null && callback != null && !callbackRegistered) try { - bgService.registerCallback(callback) - callbackRegistered = true - } catch { - case _: RemoteException => // Nothing + def setListeningForBandwidth(value: Boolean) { + if (listeningForBandwidth != value && bgService != null && callback != null) + if (value) bgService.startListeningForBandwidth(callback) else bgService.stopListeningForBandwidth(callback) + listeningForBandwidth = value } - def unregisterCallback() { + private def unregisterCallback() { if (bgService != null && callback != null && callbackRegistered) try bgService.unregisterCallback(callback) catch { case _: RemoteException => } + listeningForBandwidth = false callbackRegistered = false } @@ -67,6 +73,7 @@ trait ServiceBoundContext extends Context with IBinder.DeathRecipient { private var callback: IShadowsocksServiceCallback.Stub = _ private var connection: ShadowsocksServiceConnection = _ private var callbackRegistered: Boolean = _ + private var listeningForBandwidth: Boolean = _ // Variables var binder: IBinder = _ diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala index 90aacd08..5cefdbe7 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala @@ -80,6 +80,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str case Intent.ACTION_SCREEN_ON => setVisible(visible && Utils.isLollipopOrAbove && !keyGuard.inKeyguardRestrictedInputMode, forceShow) service.binder.registerCallback(callback) + service.binder.startListeningForBandwidth(callback) callbackRegistered = true case Intent.ACTION_USER_PRESENT => setVisible(visible = true, forceShow = forceShow) }