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")