fix #15
@@ -50,11 +50,13 @@
|
||||
|
||||
<service
|
||||
android:name=".ShadowsocksService"
|
||||
android:enabled="true"/>
|
||||
android:process=":proxy"
|
||||
android:exported="false"/>
|
||||
|
||||
<service
|
||||
android:name=".ShadowVpnService"
|
||||
android:label="@string/app_name"
|
||||
android:process=":vpn"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.github.shadowsocks;
|
||||
|
||||
interface IStateService {
|
||||
int getState();
|
||||
String getMessage();
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.8 KiB |
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||