From 4d27c8c02337dd153e4aeb52c889dea475427786 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Sun, 26 May 2013 15:41:39 +0800 Subject: [PATCH] fix #15 --- src/main/AndroidManifest.xml | 4 +- .../com/github/shadowsocks/IStateService.aidl | 6 + src/main/res/drawable-hdpi/ic_launcher.png | Bin 1711 -> 2019 bytes src/main/res/drawable-mdpi/ic_launcher.png | Bin 1299 -> 1546 bytes src/main/res/drawable-xhdpi/ic_launcher.png | Bin 2094 -> 3540 bytes src/main/res/drawable-xxhdpi/ic_launcher.png | Bin 4052 -> 4930 bytes .../github/shadowsocks/ShadowVpnService.scala | 81 +++--- .../com/github/shadowsocks/Shadowsocks.scala | 257 +++++++++++------- .../shadowsocks/ShadowsocksReceiver.scala | 2 +- .../shadowsocks/ShadowsocksService.scala | 116 ++++---- .../scala/com/github/shadowsocks/Utils.scala | 29 +- 11 files changed, 283 insertions(+), 212 deletions(-) create mode 100644 src/main/java/com/github/shadowsocks/IStateService.aidl 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 810bd7c1ef1eae087164131fa42b114ff419ed3f..4a7bd7f1b9d4091a4da4ff8e79544cb1cff6889e 100755 GIT binary patch delta 1846 zcmZ`&X*iqd8rDS`gO-|BT}L;ER$H{lOfAzML8n9YbkG@2%~B>^?IckWzS@Z;Bx8#u zN{~r(l8CJ&Ld6mlGBT$UB-YrMP}R~i>oZVzmnd83Y2EM2d`vKu1PKgomf3 zq-14gGMP+1pD&e4W1^$eQd4(#cVE4FC6~*kLqm>^j!K21j>91k2%rj-V9;p5*4o+% zc%DK~6c!5}Bf_EZCs9!e@$rQP1!OWAkHf{q#ugP7b$53I5KsUb11P}!OeTLM0%adX z{6|M}G8TgY0HCI^u@Te)Dxd>&ijIy3Y@iCa1I&cLS6BG4c-Ki-lYPEWGb+w8{1DXT^L4SXL^phto5fmOT;y;# zr6na(Q&S)~;O)f31Q?56Q!_U=$6zqxpFRafHZ?VIxm<}vGCe&FoDj6NvDxh0oE(r; zM+ej)5{Z(Nl8Pym5xJbt2#0<2zzE`MyXV)*VhXQ3Z8>gTl=E9xwEq~G$dqJrJA0a>S%AT zWipqSm)qLfa)?A|U|^t2DD3L$s;{pXc6K&4G(d52qobpEJifHF6vRfQQmd+}z-EC} z;MUcZQ>kLHn3S8_)Yu4?27|$5XJzH*<+ZoBCnYBK_4Q?DWPlBbzvHWUAVEe z)?|P9vXhgOtu8awEE5p#3Al0H)lmC&AyGqPU#^=g^0sfMO7_m%0QuCWG!#pGWZ?M@)LXMxP&37V}?0J6e_K$qzq!F4G|cPCUT^%oR$0;7e0R%2mfL})N%Vq zH5?J#LOpPPZFt&T_wKX!q=R>=cuL?Or&?m zhJ07-g+#K0wh5<1Hxl!h$2`8(jv*!tO9FFI55I>eS|4n%_n*|y#cE?T_pa=iCs@n% zf@1YP4+%gN9}m*BhVN~h_dP&{9{TO^dz&P*8<{cDv&gKn?_uj&9O>@j3WB20z9laH zTIq$_a{t^vunetM$$mFIN=t@%U^VPdp{Ozg-WqmHI4K`qE7{)tB7t5oncg4E+dc>e4?+Z7aE^ z&EU?ur?8IEcrlw4`{7Vp@riGY5Him?vakU5+9&J{>YyXRIm8~N4*sBj3j6T4;|Y=6 z3S-Bp2lCK;*1e&Im9|hd=OSU!WSMs6bMtZR;dluj5inNnv+(xa$uCd_0c3>qkB!h9 z9P5qwPpUBvr8^uJDQm0otweIm%S;FIJ=%HPi`0uTD!Xdy;~EhK`^uC$i33dIAwh@Mm_U53gU}gCp$i*UUvFe7#*diQ0v%7G~&f(If Xb3J$-`6let|GxTCgc{^HnDxH_rT)x= delta 1504 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 delta 1107 zcmZ8eeK6a17#Ee>(4Cdu&BFC)^=`#BIx7vmVl>)m*Lq#Io}EtH8=4nHgy=7kXoHeS zLKZJsyQH*)hSSpqsRTu(lZ`tKk`N+ZB9UNSRAt}YZvX7L?|olB&*%F*&*yo{_p@D? z*$`~xVti7pgM-5Zp|HKZ{Z?^t35Ak_!{z4WkVqsl1T{1?2n2$Tjt*)`NnUO)aDly) zMpKN90VE%f2MAb%{Cofc*S{R_okAw#@p!-y3WeYa@XpRoz{Fv(_`EzU21BRQSx|K~ zgTWXb9ldk=c5Q9#+}xZ*A_1{L;$Qgu#l^*?rKP(S6~ux9P#g&Hu(^48dD&{U%49N9 zQIT9G8yp-2MRbV8Yiny88yhPtD{K}E2E#KeSFtDT*l)$8@m zO-+9-REY{4-j94rNt+w0k`wikVV4O$t5-ucy z^FP}cnwpXl?0DL+_f8X(nVgx4GM|8FWkLJ$ccJru1iSh*Du%m+_IeENJ7;5}lH!Kd zHJ1}Tq7I(H$v%K*T#Af2Nx}qUX`#^-;~?l%2X0-45U)o30G-6@TLz9rg~>hH?c1e9IbJ7DFhc@9j{;l6_rH|>_XjMci*_?QKYumTN2bbkCxruL$DCR>i#T;_Ec+eSB}5a`(n5jNOX+>r<$2z0|`*D^YNt zJhuwH?mu1iCi{9vBoig_;2Y&FZa1xv_A{GnfZi3!qN|k;a%T~dI{|3!LQ-%Nl diff --git a/src/main/res/drawable-xhdpi/ic_launcher.png b/src/main/res/drawable-xhdpi/ic_launcher.png index 4529102eb2f573afd5fde58f139bd14be1e96d38..7b5db8e2505e684386d7cfdd3ae08954647d927d 100755 GIT binary patch delta 3394 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 TX+r`E|9k3^(*vaa7SQ@X8Z1;K delta 1947 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 AIsgCw 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")