diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 8e1804f7..0defed9e 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -50,11 +50,13 @@
+ android:process=":proxy"
+ android:exported="false"/>
diff --git a/src/main/java/com/github/shadowsocks/IStateService.aidl b/src/main/java/com/github/shadowsocks/IStateService.aidl
new file mode 100644
index 00000000..f2b336f8
--- /dev/null
+++ b/src/main/java/com/github/shadowsocks/IStateService.aidl
@@ -0,0 +1,6 @@
+package com.github.shadowsocks;
+
+interface IStateService {
+ int getState();
+ String getMessage();
+}
diff --git a/src/main/res/drawable-hdpi/ic_launcher.png b/src/main/res/drawable-hdpi/ic_launcher.png
index 810bd7c1..4a7bd7f1 100755
Binary files a/src/main/res/drawable-hdpi/ic_launcher.png and b/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/src/main/res/drawable-mdpi/ic_launcher.png b/src/main/res/drawable-mdpi/ic_launcher.png
index 0927af4b..5ce8d4b5 100755
Binary files a/src/main/res/drawable-mdpi/ic_launcher.png and b/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/src/main/res/drawable-xhdpi/ic_launcher.png b/src/main/res/drawable-xhdpi/ic_launcher.png
index 4529102e..7b5db8e2 100755
Binary files a/src/main/res/drawable-xhdpi/ic_launcher.png and b/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/src/main/res/drawable-xxhdpi/ic_launcher.png b/src/main/res/drawable-xxhdpi/ic_launcher.png
index 45b16668..c5c0ea13 100755
Binary files a/src/main/res/drawable-xxhdpi/ic_launcher.png and b/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/src/main/scala/com/github/shadowsocks/ShadowVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowVpnService.scala
index 4bebbebc..d7359996 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowVpnService.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowVpnService.scala
@@ -47,25 +47,15 @@ import android.support.v4.app.NotificationCompat
import android.util.Log
import com.google.analytics.tracking.android.EasyTracker
import java.io._
-import java.lang.ref.WeakReference
import android.net.VpnService
import org.apache.http.conn.util.InetAddressUtils
import android.os.Message
import scala.Some
object ShadowVpnService {
- def isServiceStarted: Boolean = {
- if (sRunningInstance == null) {
- false
- } else if (sRunningInstance.get == null) {
- sRunningInstance = null
- false
- } else {
- true
- }
+ def isServiceStarted(context: Context): Boolean = {
+ Utils.isServiceStarted("com.github.shadowsocks.ShadowVpnService", context)
}
-
- var sRunningInstance: WeakReference[ShadowVpnService] = null
}
class ShadowVpnService extends VpnService {
@@ -73,13 +63,12 @@ class ShadowVpnService extends VpnService {
val TAG = "ShadowVpnService"
val BASE = "/data/data/com.github.shadowsocks/"
val SHADOWSOCKS_CONF = "{\"server\": [%s], \"server_port\": %d, \"local_port\": %d, \"password\": %s, \"timeout\": %d}"
- val MSG_CONNECT_START: Int = 0
- val MSG_CONNECT_FINISH: Int = 1
- val MSG_CONNECT_SUCCESS: Int = 2
- val MSG_CONNECT_FAIL: Int = 3
- val MSG_HOST_CHANGE: Int = 4
- val MSG_STOP_SELF: Int = 5
- val MSG_VPN_ERROR: Int = 6
+ val MSG_CONNECT_START = 0
+ val MSG_CONNECT_FINISH = 1
+ val MSG_CONNECT_SUCCESS = 2
+ val MSG_CONNECT_FAIL = 3
+ val MSG_STOP_SELF = 5
+ val MSG_VPN_ERROR = 6
val VPN_MTU = 1500
@@ -103,26 +92,39 @@ class ShadowVpnService extends VpnService {
var encMethod: String = null
var apps: Array[ProxiedApp] = null
+ private var state = State.INIT
+ private var message: String = null
+
+ def changeState(s: Int) {
+ changeState(s, null)
+ }
+
+ def changeState(s: Int, m: String) {
+ if (state != s) {
+ state = s
+ if (m != null) message = m
+ sendBroadcast(new Intent(Utils.ACTION_UPDATE_STATE))
+ }
+ }
+
val handler: Handler = new Handler {
override def handleMessage(msg: Message) {
val ed: SharedPreferences.Editor = settings.edit
msg.what match {
case MSG_CONNECT_START =>
- ed.putBoolean("isConnecting", true)
- ed.remove("vpnError")
- case MSG_CONNECT_FINISH =>
- ed.putBoolean("isConnecting", false)
+ changeState(State.CONNECTING)
case MSG_CONNECT_SUCCESS =>
+ changeState(State.CONNECTED)
ed.putBoolean("isRunning", true)
case MSG_CONNECT_FAIL =>
+ changeState(State.FAILED)
ed.putBoolean("isRunning", false)
- case MSG_HOST_CHANGE =>
- ed.putString("appHost", appHost)
case MSG_VPN_ERROR =>
- if (msg.obj != null) ed.putString("vpnError", msg.obj.asInstanceOf[String])
+ if (msg.obj != null) changeState(State.FAILED, msg.obj.asInstanceOf[String])
case MSG_STOP_SELF =>
destroy()
stopSelf()
+ case _ =>
}
ed.commit
super.handleMessage(msg)
@@ -248,7 +250,6 @@ class ShadowVpnService extends VpnService {
handler.sendEmptyMessageDelayed(MSG_CONNECT_FINISH, 300)
}
}).start()
- markServiceStarted()
}
def waitForProcess(name: String): Boolean = {
@@ -362,14 +363,6 @@ class ShadowVpnService extends VpnService {
notification.defaults |= Notification.DEFAULT_LIGHTS
}
- def markServiceStarted() {
- ShadowVpnService.sRunningInstance = new WeakReference[ShadowVpnService](this)
- }
-
- def markServiceStopped() {
- ShadowVpnService.sRunningInstance = null
- }
-
def notifyAlert(title: String, info: String) {
val openIntent: Intent = new Intent(this, classOf[Shadowsocks])
openIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
@@ -391,7 +384,16 @@ class ShadowVpnService extends VpnService {
if (VpnService.SERVICE_INTERFACE == action) {
return super.onBind(intent)
}
- null
+
+ new IStateService.Stub {
+ def getMessage: String = {
+ val m = message
+ message = null
+ m
+ }
+
+ def getState: Int = state
+ }
}
override def onCreate() {
@@ -405,7 +407,7 @@ class ShadowVpnService extends VpnService {
// register close receiver
val filter = new IntentFilter()
filter.addAction(Intent.ACTION_SHUTDOWN)
- filter.addAction(Utils.CLOSE_ACTION)
+ filter.addAction(Utils.ACTION_CLOSE)
receiver = new BroadcastReceiver {
def onReceive(p1: Context, p2: Intent) {
destroy()
@@ -416,6 +418,7 @@ class ShadowVpnService extends VpnService {
}
def destroy() {
+ changeState(State.STOPPED)
EasyTracker.getTracker.sendEvent(TAG, "stop", getVersionName, 0L)
if (receiver != null) {
unregisterReceiver(receiver)
@@ -428,22 +431,20 @@ class ShadowVpnService extends VpnService {
}.start()
val ed: SharedPreferences.Editor = settings.edit
ed.putBoolean("isRunning", false)
- ed.putBoolean("isConnecting", false)
ed.commit
if (conn != null) {
conn.close()
conn = null
}
notificationManager.cancel(1)
- markServiceStopped()
}
/** Called when the activity is closed. */
override def onDestroy() {
- super.onDestroy()
EasyTracker.getTracker.setStartSession(false)
EasyTracker.getTracker.sendEvent(TAG, "stop", getVersionName, 0L)
destroy()
+ super.onDestroy()
}
def killProcesses() {
diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
index cca32fc2..93cf4e96 100644
--- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
+++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
@@ -38,14 +38,10 @@
package com.github.shadowsocks
import android.app.{Activity, AlertDialog, ProgressDialog}
-import android.content.Context
-import android.content.DialogInterface
-import android.content.Intent
-import android.content.SharedPreferences
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener
+import android.content._
import android.content.res.AssetManager
import android.graphics.Typeface
-import android.os.{Build, Bundle, Handler, Message}
+import android.os._
import android.preference.Preference
import android.preference.PreferenceManager
import android.util.Log
@@ -79,11 +75,17 @@ object Shadowsocks {
val TAG = "Shadowsocks"
val REQUEST_CONNECT = 1
- class ProxyFragment extends UnifiedPreferenceFragment with OnSharedPreferenceChangeListener {
+ def isServiceStarted(context: Context): Boolean = {
+ ShadowsocksService.isServiceStarted(context) || ShadowVpnService.isServiceStarted(context)
+ }
+
+ class ProxyFragment extends UnifiedPreferenceFragment {
+
+ var receiver: BroadcastReceiver = null
+
private def setPreferenceEnabled() {
- val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity)
- val enabled: Boolean = !settings.getBoolean("isRunning", false) &&
- !settings.getBoolean("isConnecting", false)
+ val state = getActivity.asInstanceOf[Shadowsocks].state
+ val enabled: Boolean = state != State.CONNECTED && state != State.CONNECTING
for (name <- PROXY_PREFS) {
val pref: Preference = findPreference(name)
if (pref != null) {
@@ -92,32 +94,44 @@ object Shadowsocks {
}
}
+ override def onStart() {
+ super.onStart()
+ val filter = new IntentFilter()
+ filter.addAction(Utils.ACTION_UPDATE_FRAGMENT)
+ receiver = new BroadcastReceiver {
+ def onReceive(p1: Context, p2: Intent) {
+ setPreferenceEnabled()
+ }
+ }
+ getActivity.getApplicationContext.registerReceiver(receiver, filter)
+ }
+
+ override def onStop() {
+ super.onStop()
+ getActivity.getApplicationContext.unregisterReceiver(receiver)
+ }
+
override def onResume() {
super.onResume()
setPreferenceEnabled()
- getPreferenceScreen.getSharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
override def onPause() {
super.onPause()
- getPreferenceScreen.getSharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
- }
-
- def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
- if ((key == "isRunning") || (key == "isGlobalProxy")) {
- setPreferenceEnabled()
- }
}
}
- class FeatureFragment extends UnifiedPreferenceFragment with OnSharedPreferenceChangeListener {
+ class FeatureFragment extends UnifiedPreferenceFragment {
+
+ var receiver: BroadcastReceiver = null
+
private def setPreferenceEnabled() {
- val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity)
- val enabled: Boolean = !settings.getBoolean("isRunning", false) &&
- !settings.getBoolean("isConnecting", false)
+ val state = getActivity.asInstanceOf[Shadowsocks].state
+ val enabled: Boolean = state != State.CONNECTED && state != State.CONNECTING
for (name <- Shadowsocks.FEATRUE_PREFS) {
val pref: Preference = findPreference(name)
if (pref != null) {
+ val settings = PreferenceManager.getDefaultSharedPreferences(getActivity)
if ((name == "isBypassApps") || (name == "proxyedApps")) {
val isGlobalProxy: Boolean = settings.getBoolean("isGlobalProxy", false)
pref.setEnabled(enabled && !isGlobalProxy)
@@ -130,21 +144,30 @@ object Shadowsocks {
}
}
+ override def onStart() {
+ super.onStart()
+ val filter = new IntentFilter()
+ filter.addAction(Utils.ACTION_UPDATE_FRAGMENT)
+ receiver = new BroadcastReceiver {
+ def onReceive(p1: Context, p2: Intent) {
+ setPreferenceEnabled()
+ }
+ }
+ getActivity.getApplicationContext.registerReceiver(receiver, filter)
+ }
+
+ override def onStop() {
+ super.onStop()
+ getActivity.getApplicationContext.unregisterReceiver(receiver)
+ }
+
override def onResume() {
super.onResume()
setPreferenceEnabled()
- getPreferenceScreen.getSharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
override def onPause() {
super.onPause()
- getPreferenceScreen.getSharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
- }
-
- def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
- if ((key == "isRunning") || (key == "isGlobalProxy")) {
- setPreferenceEnabled()
- }
}
}
@@ -173,20 +196,34 @@ object Typefaces {
}
class Shadowsocks
- extends UnifiedSherlockPreferenceActivity with CompoundButton.OnCheckedChangeListener with
- OnSharedPreferenceChangeListener {
+ extends UnifiedSherlockPreferenceActivity
+ with CompoundButton.OnCheckedChangeListener {
private val MSG_CRASH_RECOVER: Int = 1
private val MSG_INITIAL_FINISH: Int = 2
private var switchButton: Switch = null
private var progressDialog: ProgressDialog = null
+ private var settings: SharedPreferences = null
private var prepared = false
+ private var state = State.INIT
+ private var binder: IStateService = null
+ private var receiver: StateBroadcastReceiver = null
+
+ private val connection = new ServiceConnection {
+ def onServiceDisconnected(name: ComponentName) {
+ onStateChanged(binder.getState, binder.getMessage)
+ binder = null
+ }
+
+ def onServiceConnected(name: ComponentName, s: IBinder) {
+ binder = IStateService.Stub.asInterface(s)
+ onStateChanged(binder.getState, binder.getMessage)
+ }
+ }
private val handler: Handler = new Handler {
override def handleMessage(msg: Message) {
- val settings: SharedPreferences = PreferenceManager
- .getDefaultSharedPreferences(Shadowsocks.this)
val ed: SharedPreferences.Editor = settings.edit
msg.what match {
case MSG_CRASH_RECOVER =>
@@ -326,13 +363,14 @@ class Shadowsocks
getSupportActionBar.setDisplayShowTitleEnabled(false)
getSupportActionBar.setDisplayShowCustomEnabled(true)
getSupportActionBar.setDisplayShowHomeEnabled(false)
+
switchButton = switchLayout.findViewById(R.id.switchButton).asInstanceOf[Switch]
val title: TextView = switchLayout.findViewById(R.id.title).asInstanceOf[TextView]
val tf: Typeface = Typefaces.get(this, "fonts/Iceland.ttf")
if (tf != null) title.setTypeface(tf)
title.setText(R.string.app_name)
- val settings: SharedPreferences = PreferenceManager
- .getDefaultSharedPreferences(Shadowsocks.this)
+ settings = PreferenceManager.getDefaultSharedPreferences(this)
+
val init: Boolean = !settings.getBoolean("isRunning", false) &&
!settings.getBoolean("isConnecting", false)
if (init) {
@@ -412,29 +450,30 @@ class Shadowsocks
protected override def onPause() {
super.onPause()
prepared = false
- PreferenceManager
- .getDefaultSharedPreferences(this)
- .unregisterOnSharedPreferenceChangeListener(this)
}
protected override def onResume() {
super.onResume()
if (!prepared) {
- val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
- if (isServiceStarted) {
+ if (Shadowsocks.isServiceStarted(this)) {
switchButton.setChecked(true)
- if (ShadowVpnService.isServiceStarted) {
+ if (ShadowVpnService.isServiceStarted(this)) {
val style = new Style.Builder()
.setBackgroundColorValue(Style.holoBlueLight)
.setDuration(Style.DURATION_INFINITE)
.build()
switchButton.setEnabled(false)
Crouton.makeText(Shadowsocks.this, R.string.vpn_status, style).show()
+ if (binder == null) bindService(new Intent(this, classOf[ShadowVpnService]), connection, 0)
+ } else {
+ if (binder == null) bindService(new Intent(this, classOf[ShadowsocksService]), connection, 0)
}
+ setPreferenceEnabled(false)
} else {
switchButton.setEnabled(true)
switchButton.setChecked(false)
Crouton.cancelAllCroutons()
+ setPreferenceEnabled(true)
if (settings.getBoolean("isRunning", false)) {
new Thread {
override def run() {
@@ -445,18 +484,11 @@ class Shadowsocks
}
}
}
- setPreferenceEnabled()
+
switchButton.setOnCheckedChangeListener(this)
- PreferenceManager
- .getDefaultSharedPreferences(this)
- .registerOnSharedPreferenceChangeListener(this)
}
- private def setPreferenceEnabled() {
- val settings: SharedPreferences = PreferenceManager
- .getDefaultSharedPreferences(Shadowsocks.this)
- val enabled: Boolean = !settings.getBoolean("isRunning", false) &&
- !settings.getBoolean("isConnecting", false)
+ private def setPreferenceEnabled(enabled: Boolean) {
for (name <- Shadowsocks.PROXY_PREFS) {
val pref: Preference = findPreference(name)
if (pref != null) {
@@ -478,54 +510,16 @@ class Shadowsocks
}
}
- def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
- val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
- if ((key == "isConnecting" || key == "isRunning")
- && !settings.getBoolean("isConnecting", false)) {
- if (settings.getBoolean("isRunning", false)) {
- if (!switchButton.isChecked) switchButton.setChecked(true)
- } else {
- if (switchButton.isChecked) {
- switchButton.setEnabled(true)
- switchButton.setChecked(false)
- Crouton.cancelAllCroutons()
- }
-
- val msg = settings.getString("vpnError", null)
- if (msg != null) {
- Crouton.cancelAllCroutons()
- val style = new Style.Builder()
- .setBackgroundColorValue(Style.holoRedLight)
- .setDuration(Style.DURATION_INFINITE)
- .build()
- Crouton.makeText(this, getString(R.string.vpn_error).format(msg), style).show()
- }
- }
- }
- if (key == "isConnecting") {
- if (settings.getBoolean("isConnecting", false)) {
- if (progressDialog == null) {
- progressDialog = ProgressDialog.show(this, "", getString(R.string.connecting), true, true)
- }
- } else {
- if (progressDialog != null) {
- progressDialog.dismiss()
- progressDialog = null
- }
- }
- }
- if ((key == "isRunning") || (key == "isGlobalProxy")) {
- setPreferenceEnabled()
- }
- }
-
override def onStart() {
super.onStart()
+ receiver = new StateBroadcastReceiver()
+ registerReceiver(receiver, new IntentFilter(Utils.ACTION_UPDATE_STATE))
EasyTracker.getInstance.activityStart(this)
}
override def onStop() {
super.onStop()
+ unregisterReceiver(receiver)
EasyTracker.getInstance.activityStop(this)
if (progressDialog != null) {
progressDialog.dismiss()
@@ -536,6 +530,7 @@ class Shadowsocks
override def onDestroy() {
super.onDestroy()
Crouton.cancelAllCroutons()
+ if (binder != null) unbindService(connection)
}
def reset() {
@@ -589,23 +584,18 @@ class Shadowsocks
Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH && !Utils.getRoot
}
- def isServiceStarted: Boolean = {
- ShadowsocksService.isServiceStarted || ShadowVpnService.isServiceStarted
- }
-
def serviceStop() {
- if (ShadowVpnService.isServiceStarted) {
+ if (ShadowVpnService.isServiceStarted(this)) {
stopService(new Intent(this, classOf[ShadowVpnService]))
}
- if (ShadowsocksService.isServiceStarted) {
- sendBroadcast(new Intent(Utils.CLOSE_ACTION))
+ if (ShadowsocksService.isServiceStarted(this)) {
+ sendBroadcast(new Intent(Utils.ACTION_CLOSE))
}
}
/** Called when connect button is clicked. */
def serviceStart: Boolean = {
- val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
val proxy = settings.getString("proxy", "")
if (isTextEmpty(proxy, getString(R.string.proxy_empty))) return false
val portText = settings.getString("port", "")
@@ -624,9 +614,10 @@ class Shadowsocks
}
if (isVpnEnabled) {
- if (ShadowVpnService.isServiceStarted) return false
+ if (ShadowVpnService.isServiceStarted(this)) return false
val it: Intent = new Intent(this, classOf[ShadowVpnService])
startService(it)
+ bindService(it, connection, 0)
val style = new Style.Builder()
.setBackgroundColorValue(Style.holoBlueLight)
.setDuration(Style.DURATION_INFINITE)
@@ -634,11 +625,11 @@ class Shadowsocks
Crouton.makeText(Shadowsocks.this, R.string.vpn_status, style).show()
switchButton.setEnabled(false)
} else {
- if (ShadowsocksService.isServiceStarted) return false
+ if (ShadowsocksService.isServiceStarted(this)) return false
val it: Intent = new Intent(this, classOf[ShadowsocksService])
startService(it)
+ bindService(it, connection, 0)
}
-
true
}
@@ -688,4 +679,64 @@ class Shadowsocks
val alert: AlertDialog = builder.create
alert.show()
}
+
+ def clearDialog() {
+ if (progressDialog != null) {
+ progressDialog.dismiss()
+ progressDialog = null
+ }
+ }
+
+ def onStateChanged(s: Int, m: String) {
+ if (state != s) {
+ state = s
+ state match {
+ case State.CONNECTING => {
+ if (progressDialog == null) {
+ progressDialog = ProgressDialog.show(Shadowsocks.this, "", getString(R.string.connecting), true, true)
+ }
+ setPreferenceEnabled(false)
+ }
+ case State.CONNECTED => {
+ clearDialog()
+ if (!switchButton.isChecked) switchButton.setChecked(true)
+ setPreferenceEnabled(false)
+ }
+ case State.FAILED => {
+ clearDialog()
+ if (switchButton.isChecked) {
+ switchButton.setEnabled(true)
+ switchButton.setChecked(false)
+ Crouton.cancelAllCroutons()
+ }
+ if (m != null) {
+ Crouton.cancelAllCroutons()
+ val style = new Style.Builder()
+ .setBackgroundColorValue(Style.holoRedLight)
+ .setDuration(Style.DURATION_INFINITE)
+ .build()
+ Crouton.makeText(Shadowsocks.this, getString(R.string.vpn_error).format(m), style).show()
+ }
+ setPreferenceEnabled(true)
+ }
+ case State.STOPPED => {
+ clearDialog()
+ Crouton.cancelAllCroutons()
+ if (switchButton.isChecked) switchButton.setChecked(false)
+ setPreferenceEnabled(true)
+ }
+ }
+ sendBroadcast(new Intent(Utils.ACTION_UPDATE_FRAGMENT))
+ }
+ }
+
+ class StateBroadcastReceiver extends BroadcastReceiver {
+ override def onReceive(context: Context, intent: Intent) {
+ if (binder != null) {
+ val s = binder.getState
+ val m = binder.getMessage
+ onStateChanged(s, m)
+ }
+ }
+ }
}
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala
index dc2c335b..fb76545e 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala
@@ -77,7 +77,7 @@ class ShadowsocksReceiver extends BroadcastReceiver {
}
if (Utils.getRoot) {
- if (ShadowsocksService.isServiceStarted) return
+ if (ShadowsocksService.isServiceStarted(context)) return
val it: Intent = new Intent(context, classOf[ShadowsocksService])
context.startService(it)
}
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala
index 5d0a53f7..84ab5ca4 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala
@@ -44,10 +44,7 @@ import android.app.Service
import android.content._
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
-import android.os.Handler
-import android.os.IBinder
-import android.os.Message
-import android.os.PowerManager
+import android.os._
import android.preference.PreferenceManager
import android.support.v4.app.NotificationCompat
import android.util.Log
@@ -56,7 +53,6 @@ import java.io.BufferedReader
import java.io.FileNotFoundException
import java.io.FileReader
import java.io.IOException
-import java.lang.ref.WeakReference
import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
import org.apache.http.conn.util.InetAddressUtils
@@ -64,18 +60,9 @@ import scala.collection._
import scala.Some
object ShadowsocksService {
- def isServiceStarted: Boolean = {
- if (sRunningInstance == null) {
- false
- } else if (sRunningInstance.get == null) {
- sRunningInstance = null
- false
- } else {
- true
- }
+ def isServiceStarted(context: Context): Boolean = {
+ Utils.isServiceStarted("com.github.shadowsocks.ShadowsocksService", context)
}
-
- var sRunningInstance: WeakReference[ShadowsocksService] = null
}
class ShadowsocksService extends Service {
@@ -101,13 +88,14 @@ class ShadowsocksService extends Service {
val CMD_IPTABLES_REDIRECT_ADD_SOCKS = " -t nat -A OUTPUT -p tcp " + "-j REDIRECT --to 8123\n"
val CMD_IPTABLES_DNAT_ADD_SOCKS = " -t nat -A OUTPUT -p tcp " +
"-j DNAT --to-destination 127.0.0.1:8123\n"
- val MSG_CONNECT_START: Int = 0
- val MSG_CONNECT_FINISH: Int = 1
- val MSG_CONNECT_SUCCESS: Int = 2
- val MSG_CONNECT_FAIL: Int = 3
- val MSG_HOST_CHANGE: Int = 4
- val MSG_STOP_SELF: Int = 5
- val DNS_PORT: Int = 8153
+ val DNS_PORT = 8153
+
+ val MSG_CONNECT_START = 0
+ val MSG_CONNECT_FINISH = 1
+ val MSG_CONNECT_SUCCESS = 2
+ val MSG_CONNECT_FAIL = 3
+ val MSG_STOP_SELF = 4
+ val MSG_VPN_ERROR = 5
val mStartForegroundSignature = Array[Class[_]](classOf[Int], classOf[Notification])
val mStopForegroundSignature = Array[Class[_]](classOf[Boolean])
@@ -115,7 +103,6 @@ class ShadowsocksService extends Service {
var receiver: BroadcastReceiver = null
var notificationManager: NotificationManager = null
- var mWakeLock: PowerManager#WakeLock = null
var appHost: String = null
var remotePort: Int = 0
var localPort: Int = 0
@@ -136,6 +123,36 @@ class ShadowsocksService extends Service {
var mStartForegroundArgs = new Array[AnyRef](2)
var mStopForegroundArgs = new Array[AnyRef](1)
+ private var state = State.INIT
+
+ private def changeState(s: Int) {
+ if (state != s) {
+ state = s
+ sendBroadcast(new Intent(Utils.ACTION_UPDATE_STATE))
+ }
+ }
+
+ val handler: Handler = new Handler {
+ override def handleMessage(msg: Message) {
+ val ed: SharedPreferences.Editor = settings.edit
+ msg.what match {
+ case MSG_CONNECT_START =>
+ changeState(State.CONNECTING)
+ case MSG_CONNECT_SUCCESS =>
+ changeState(State.CONNECTED)
+ ed.putBoolean("isRunning", true)
+ case MSG_CONNECT_FAIL =>
+ changeState(State.FAILED)
+ ed.putBoolean("isRunning", false)
+ case MSG_STOP_SELF =>
+ stopSelf()
+ case _ =>
+ }
+ ed.commit
+ super.handleMessage(msg)
+ }
+ }
+
def getPid(name: String): Int = {
try {
val reader: BufferedReader = new BufferedReader(new FileReader(BASE + name + ".pid"))
@@ -263,7 +280,6 @@ class ShadowsocksService extends Service {
handler.sendEmptyMessageDelayed(MSG_CONNECT_FINISH, 500)
}
}).start()
- markServiceStarted()
}
def startRedsocksDaemon() {
@@ -321,19 +337,11 @@ class ShadowsocksService extends Service {
}
}
- def markServiceStarted() {
- ShadowsocksService.sRunningInstance = new WeakReference[ShadowsocksService](this)
- }
-
- def markServiceStopped() {
- ShadowsocksService.sRunningInstance = null
- }
-
def notifyForegroundAlert(title: String, info: String) {
val openIntent: Intent = new Intent(this, classOf[Shadowsocks])
openIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
val contentIntent: PendingIntent = PendingIntent.getActivity(this, 0, openIntent, 0)
- val closeIntent: Intent = new Intent(Utils.CLOSE_ACTION)
+ val closeIntent: Intent = new Intent(Utils.ACTION_CLOSE)
val actionIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, closeIntent, 0)
val builder: NotificationCompat.Builder = new NotificationCompat.Builder(this)
builder
@@ -365,7 +373,10 @@ class ShadowsocksService extends Service {
}
def onBind(intent: Intent): IBinder = {
- null
+ new IStateService.Stub {
+ def getMessage: String = null
+ def getState: Int = state
+ }
}
override def onCreate() {
@@ -399,7 +410,7 @@ class ShadowsocksService extends Service {
// register close receiver
val filter = new IntentFilter()
filter.addAction(Intent.ACTION_SHUTDOWN)
- filter.addAction(Utils.CLOSE_ACTION)
+ filter.addAction(Utils.ACTION_CLOSE)
receiver = new BroadcastReceiver() {
def onReceive(p1: Context, p2: Intent) {
stopSelf()
@@ -410,7 +421,7 @@ class ShadowsocksService extends Service {
/** Called when the activity is closed. */
override def onDestroy() {
- super.onDestroy()
+ changeState(State.STOPPED)
EasyTracker.getTracker.setStartSession(false)
EasyTracker.getTracker.sendEvent(TAG, "stop", getVersionName, 0L)
stopForegroundCompat(1)
@@ -421,13 +432,12 @@ class ShadowsocksService extends Service {
}.start()
val ed: SharedPreferences.Editor = settings.edit
ed.putBoolean("isRunning", false)
- ed.putBoolean("isConnecting", false)
ed.commit
- markServiceStopped()
if (receiver != null) {
unregisterReceiver(receiver)
receiver = null
}
+ super.onDestroy()
}
def killProcesses() {
@@ -572,31 +582,5 @@ class ShadowsocksService extends Service {
invokeMethod(mSetForeground, mSetForegroundArgs)
}
- val handler: Handler = new Handler {
- override def handleMessage(msg: Message) {
- val ed: SharedPreferences.Editor = settings.edit
- msg.what match {
- case MSG_CONNECT_START =>
- ed.putBoolean("isConnecting", true)
- val pm: PowerManager = getSystemService(Context.POWER_SERVICE).asInstanceOf[PowerManager]
- mWakeLock = pm
- .newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
- "GAEProxy")
- mWakeLock.acquire()
- case MSG_CONNECT_FINISH =>
- ed.putBoolean("isConnecting", false)
- if (mWakeLock != null && mWakeLock.isHeld) mWakeLock.release()
- case MSG_CONNECT_SUCCESS =>
- ed.putBoolean("isRunning", true)
- case MSG_CONNECT_FAIL =>
- ed.putBoolean("isRunning", false)
- case MSG_HOST_CHANGE =>
- ed.putString("appHost", appHost)
- case MSG_STOP_SELF =>
- stopSelf()
- }
- ed.commit
- super.handleMessage(msg)
- }
- }
+
}
diff --git a/src/main/scala/com/github/shadowsocks/Utils.scala b/src/main/scala/com/github/shadowsocks/Utils.scala
index 3cffe1e4..f30218b5 100644
--- a/src/main/scala/com/github/shadowsocks/Utils.scala
+++ b/src/main/scala/com/github/shadowsocks/Utils.scala
@@ -50,10 +50,22 @@ import scala.collection.mutable.ArrayBuffer
import org.xbill.DNS._
import scala.Some
import android.graphics.{Canvas, Bitmap}
+import android.app.ActivityManager
+
+object State {
+ val INIT = 0
+ val CONNECTED = 1
+ val CONNECTING = 2
+ val FAILED = 3
+ val STOPPED = 4
+}
object Utils {
- val CLOSE_ACTION = "com.github.shadowsocks.ACTION_SHUTDOWN"
+ val ACTION_CLOSE = "com.github.shadowsocks.ACTION_SHUTDOWN"
+ val ACTION_UPDATE_STATE = "com.github.shadowsocks.ACTION_UPDATE_STATE"
+ val ACTION_UPDATE_FRAGMENT = "com.github.shadowsocks.ACTION_UPDATE_FRAGMENT"
+
val TAG: String = "Shadowsocks"
val ABI_PROP: String = "ro.product.cpu.abi"
val ABI2_PROP: String = "ro.product.cpu.abi2"
@@ -75,6 +87,21 @@ object Utils {
var data_path: String = null
var rootTries = 0
+ def isServiceStarted(name: String, context: Context): Boolean = {
+ import scala.collection.JavaConversions._
+
+ val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE).asInstanceOf[ActivityManager]
+ val services = activityManager.getRunningServices(Integer.MAX_VALUE)
+
+ for (service <- services) {
+ if (service.service.getClassName == name) {
+ return true
+ }
+ }
+
+ false
+ }
+
def resolve(host: String, addrType: Int): Option[String] = {
val lookup = new Lookup(host, addrType)
val resolver = new SimpleResolver("8.8.8.8")