diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 21d49d7c..513a1396 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -70,17 +70,22 @@
+ android:exported="false">
+
+
+
+
+
diff --git a/src/main/java/com/github/shadowsocks/aidl/Config.aidl b/src/main/java/com/github/shadowsocks/aidl/Config.aidl
new file mode 100644
index 00000000..667e8d16
--- /dev/null
+++ b/src/main/java/com/github/shadowsocks/aidl/Config.aidl
@@ -0,0 +1,3 @@
+package com.github.shadowsocks.aidl;
+
+parcelable Config;
\ No newline at end of file
diff --git a/src/main/java/com/github/shadowsocks/aidl/Config.java b/src/main/java/com/github/shadowsocks/aidl/Config.java
new file mode 100644
index 00000000..cac52c3c
--- /dev/null
+++ b/src/main/java/com/github/shadowsocks/aidl/Config.java
@@ -0,0 +1,86 @@
+package com.github.shadowsocks.aidl;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class Config implements Parcelable {
+
+ public boolean isGlobalProxy = true;
+ public boolean isGFWList = true;
+ public boolean isBypassApps = false;
+ public boolean isTrafficStat = false;
+
+ public String profileName = "Untitled";
+ public String proxy = "127.0.0.1";
+ public String sitekey = "null";
+
+ public String encMethod = "rc4";
+ public String proxiedAppString = "";
+
+ public int remotePort = 1984;
+ public int localPort = 1080;
+
+ public static final Parcelable.Creator CREATOR = new
+ Parcelable.Creator() {
+ public Config createFromParcel(Parcel in) {
+ return new Config(in);
+ }
+
+ public Config[] newArray(int size) {
+ return new Config[size];
+ }
+ };
+
+ public Config(boolean isGlobalProxy, boolean isGFWList, boolean isBypassApps,
+ boolean isTrafficStat, String profileName, String proxy, String sitekey, String encMethod,
+ String proxiedAppString, int remotePort, int localPort) {
+ this.isGlobalProxy = isGlobalProxy;
+ this.isGFWList = isGFWList;
+ this.isBypassApps = isBypassApps;
+ this.isTrafficStat = isTrafficStat;
+ this.profileName = profileName;
+ this.proxy = proxy;
+ this.sitekey = sitekey;
+ this.encMethod = encMethod;
+ this.proxiedAppString = proxiedAppString;
+ this.remotePort = remotePort;
+ this.localPort = localPort;
+ }
+
+ private Config(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public void readFromParcel(Parcel in) {
+ isGlobalProxy = in.readInt() == 1;
+ isGFWList = in.readInt() == 1;
+ isBypassApps = in.readInt() == 1;
+ isTrafficStat = in.readInt() == 1;
+ profileName = in.readString();
+ proxy = in.readString();
+ sitekey = in.readString();
+ encMethod = in.readString();
+ proxiedAppString = in.readString();
+ remotePort = in.readInt();
+ localPort = in.readInt();
+ }
+
+ @Override public int describeContents() {
+ return 0;
+ }
+
+ @Override public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(isGlobalProxy ? 1: 0);
+ out.writeInt(isGFWList ? 1: 0);
+ out.writeInt(isBypassApps ? 1: 0);
+ out.writeInt(isTrafficStat ? 1: 0);
+ out.writeString(profileName);
+ out.writeString(proxy);
+ out.writeString(sitekey);
+ out.writeString(encMethod);
+ out.writeString(proxiedAppString);
+ out.writeInt(remotePort);
+ out.writeInt(localPort);
+
+ }
+}
diff --git a/src/main/java/com/github/shadowsocks/aidl/IShadowsocksService.aidl b/src/main/java/com/github/shadowsocks/aidl/IShadowsocksService.aidl
new file mode 100644
index 00000000..c06de025
--- /dev/null
+++ b/src/main/java/com/github/shadowsocks/aidl/IShadowsocksService.aidl
@@ -0,0 +1,13 @@
+package com.github.shadowsocks.aidl;
+
+import com.github.shadowsocks.aidl.Config;
+import com.github.shadowsocks.aidl.IShadowsocksServiceCallback;
+
+interface IShadowsocksService {
+ int getMode();
+ int getState();
+ void start(in Config config);
+ void stop();
+ void registerCallback(IShadowsocksServiceCallback cb);
+ void unregisterCallback(IShadowsocksServiceCallback cb);
+}
\ No newline at end of file
diff --git a/src/main/java/com/github/shadowsocks/aidl/IShadowsocksServiceCallback.aidl b/src/main/java/com/github/shadowsocks/aidl/IShadowsocksServiceCallback.aidl
new file mode 100644
index 00000000..00ef9b63
--- /dev/null
+++ b/src/main/java/com/github/shadowsocks/aidl/IShadowsocksServiceCallback.aidl
@@ -0,0 +1,5 @@
+package com.github.shadowsocks.aidl;
+
+interface IShadowsocksServiceCallback {
+ void stateChanged(int state, String msg);
+}
\ No newline at end of file
diff --git a/src/main/res/layout/layout_switch.xml b/src/main/res/layout/layout_switch.xml
index 97bfcf70..2be5096b 100644
--- a/src/main/res/layout/layout_switch.xml
+++ b/src/main/res/layout/layout_switch.xml
@@ -17,6 +17,7 @@
+ Log.e(getTag, "Cannot open pid file: " + name)
+ case e: IOException =>
+ Log.e(getTag, "Cannot read pid file: " + name)
+ case e: NumberFormatException =>
+ Log.e(getTag, "Invalid pid", e)
+ }
+ -1
+ }
+
+ def initSoundVibrateLights(notification: Notification) {
+ notification.sound = null
+ }
+}
diff --git a/src/main/scala/com/github/shadowsocks/ShadowVpnActivity.scala b/src/main/scala/com/github/shadowsocks/ShadowVpnActivity.scala
index 25fc9944..a2e9940a 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowVpnActivity.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowVpnActivity.scala
@@ -40,16 +40,28 @@
package com.github.shadowsocks
import android.app.Activity
-import android.os.Bundle
+import android.os.{IBinder, Bundle}
import android.net.VpnService
-import android.content.Intent
+import android.content.{ComponentName, ServiceConnection, Intent}
import android.util.Log
import android.preference.PreferenceManager
-import com.github.shadowsocks.utils.Extra
-import com.actionbarsherlock.app.SherlockActivity
+import com.github.shadowsocks.utils._
+import com.github.shadowsocks.aidl.IShadowsocksService
class ShadowVpnActivity extends Activity {
+ // Services
+ var bgService: IShadowsocksService = null
+ val connection = new ServiceConnection {
+ override def onServiceConnected(name: ComponentName, service: IBinder) {
+ bgService = IShadowsocksService.Stub.asInterface(service)
+ }
+
+ override def onServiceDisconnected(name: ComponentName) {
+ bgService = null
+ }
+ }
+
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
val intent = VpnService.prepare(this)
@@ -62,14 +74,12 @@ class ShadowVpnActivity extends Activity {
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
resultCode match {
- case Activity.RESULT_OK => {
- val intent: Intent = new Intent(this, classOf[ShadowVpnService])
- Extra.put(PreferenceManager.getDefaultSharedPreferences(this), intent)
- startService(intent)
- }
- case _ => {
+ case Activity.RESULT_OK =>
+ if (bgService != null) {
+ bgService.start(ConfigUtils.load(PreferenceManager.getDefaultSharedPreferences(this)))
+ }
+ case _ =>
Log.e(Shadowsocks.TAG, "Failed to start VpnService")
- }
}
finish()
}
diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
index 74a7c8ec..7993f8db 100644
--- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
+++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
@@ -67,12 +67,11 @@ import scala.collection.mutable.ListBuffer
import com.github.shadowsocks.database.Profile
import com.nostra13.universalimageloader.core.download.BaseImageDownloader
import com.github.shadowsocks.preferences.{ProfileEditTextPreference, PasswordEditTextPreference, SummaryEditTextPreference}
-import com.github.shadowsocks.database.Item
-import com.github.shadowsocks.database.Category
import com.github.shadowsocks.utils._
import com.github.shadowsocks.database.Item
import com.github.shadowsocks.database.Category
import com.google.zxing.integration.android.IntentIntegrator
+import com.github.shadowsocks.aidl.{IShadowsocksServiceCallback, IShadowsocksService}
class ProfileIconDownloader(context: Context, connectTimeout: Int, readTimeout: Int)
extends BaseImageDownloader(context, connectTimeout, readTimeout) {
@@ -103,10 +102,9 @@ object Typefaces {
val t: Typeface = Typeface.createFromAsset(c.getAssets, assetPath)
cache.put(assetPath, t)
} catch {
- case e: Exception => {
+ case e: Exception =>
Log.e(TAG, "Could not get typeface '" + assetPath + "' because " + e.getMessage)
return null
- }
}
}
return cache.get(assetPath)
@@ -131,8 +129,7 @@ object Shadowsocks {
// Flags
var vpnEnabled = -1
- // Help functions
-
+ // Helper functions
def updateListPreference(pref: Preference, value: String) {
pref.setSummary(value)
pref.asInstanceOf[ListPreference].setValue(value)
@@ -172,10 +169,6 @@ object Shadowsocks {
}
}
- def isServiceStarted(context: Context): Boolean = {
- ShadowsocksService.isServiceStarted(context) || ShadowVpnService.isServiceStarted(context)
- }
-
class ProxyFragment extends UnifiedPreferenceFragment {
var receiver: BroadcastReceiver = null
@@ -194,8 +187,8 @@ object Shadowsocks {
private def updatePreferenceScreen() {
for (name <- Shadowsocks.PROXY_PREFS) {
val pref = findPreference(name)
- Shadowsocks.updatePreference(pref, name,
- getActivity.asInstanceOf[Shadowsocks].currentProfile)
+ Shadowsocks
+ .updatePreference(pref, name, getActivity.asInstanceOf[Shadowsocks].currentProfile)
}
}
@@ -252,8 +245,8 @@ object Shadowsocks {
private def updatePreferenceScreen() {
for (name <- Shadowsocks.FEATRUE_PREFS) {
val pref = findPreference(name)
- Shadowsocks.updatePreference(pref, name,
- getActivity.asInstanceOf[Shadowsocks].currentProfile)
+ Shadowsocks
+ .updatePreference(pref, name, getActivity.asInstanceOf[Shadowsocks].currentProfile)
}
}
@@ -304,9 +297,61 @@ class Shadowsocks
var prepared = false
var currentProfile = new Profile
+ // Services
+ var currentServiceName = classOf[ShadowsocksNatService].getName
+ var bgService: IShadowsocksService = null
+ val callback = new IShadowsocksServiceCallback.Stub {
+ override def stateChanged(state: Int, msg: String) {
+ onStateChanged(state, msg)
+ }
+ }
+ val connection = new ServiceConnection {
+ override def onServiceConnected(name: ComponentName, service: IBinder) {
+ // Initialize the background service
+ bgService = IShadowsocksService.Stub.asInterface(service)
+ try {
+ bgService.registerCallback(callback)
+ } catch {
+ case ignored: RemoteException => // Nothing
+ }
+ // Update the UI
+ switchButton.setEnabled(true)
+ if (State.isAvailable(bgService.getState)) {
+ if (status.getBoolean(Key.isRunning, false)) {
+ spawn {
+ crash_recovery()
+ handler.sendEmptyMessage(MSG_CRASH_RECOVER)
+ }
+ }
+ Crouton.cancelAllCroutons()
+ onStateChanged(State.STOPPED, null)
+ setPreferenceEnabled(enabled = true)
+ } else {
+ if (bgService.getMode == Mode.VPN) {
+ val style = new Style.Builder().setBackgroundColorValue(Style.holoBlueLight).build()
+ val config = new Configuration.Builder().setDuration(Configuration.DURATION_LONG).build()
+ Crouton
+ .makeText(Shadowsocks.this, R.string.vpn_status, style)
+ .setConfiguration(config)
+ .show()
+ switchButton.setEnabled(false)
+ }
+ switchButton.setChecked(true)
+ onStateChanged(State.CONNECTED, null)
+ setPreferenceEnabled(enabled = false)
+ }
+ // set the listener
+ switchButton.setOnCheckedChangeListener(Shadowsocks.this)
+ }
+
+ override def onServiceDisconnected(name: ComponentName) {
+ switchButton.setEnabled(false)
+ bgService = null
+ }
+ }
+
lazy val settings = PreferenceManager.getDefaultSharedPreferences(this)
lazy val status = getSharedPreferences(Key.status, Context.MODE_PRIVATE)
- lazy val stateReceiver = new StateBroadcastReceiver
lazy val preferenceReceiver = new PreferenceBroadcastReceiver
lazy val drawer = MenuDrawer.attach(this)
lazy val menuAdapter = new MenuAdapter(this, getMenuList)
@@ -341,9 +386,8 @@ class Shadowsocks
try {
files = assetManager.list(path)
} catch {
- case e: IOException => {
+ case e: IOException =>
Log.e(Shadowsocks.TAG, e.getMessage)
- }
}
if (files != null) {
for (file <- files) {
@@ -363,9 +407,8 @@ class Shadowsocks
out.close()
out = null
} catch {
- case ex: Exception => {
+ case ex: Exception =>
Log.e(Shadowsocks.TAG, ex.getMessage)
- }
}
}
}
@@ -409,9 +452,8 @@ class Shadowsocks
val pi: PackageInfo = getPackageManager.getPackageInfo(getPackageName, 0)
version = pi.versionName
} catch {
- case e: PackageManager.NameNotFoundException => {
+ case e: PackageManager.NameNotFoundException =>
version = "Package name not found"
- }
}
version
}
@@ -445,12 +487,10 @@ class Shadowsocks
def onCheckedChanged(compoundButton: CompoundButton, checked: Boolean) {
if (compoundButton eq switchButton) {
checked match {
- case true => {
+ case true =>
prepareStartService()
- }
- case false => {
+ case false =>
serviceStop()
- }
}
}
}
@@ -494,15 +534,26 @@ class Shadowsocks
/** Called when the activity is first created. */
override def onCreate(savedInstanceState: Bundle) {
- // Initialize preference
+ // Initialize the preference
setHeaderRes(R.xml.shadowsocks_headers)
super.onCreate(savedInstanceState)
- // Initialize profile
+ // Initialize the profile
currentProfile = {
profileManager.getProfile(settings.getInt(Key.profileId, -1)) getOrElse currentProfile
}
+ // Update the profile
+ if (!status.getBoolean(getVersionName, false)) {
+ val h = showProgress(getString(R.string.initializing))
+ status.edit.putBoolean(getVersionName, true).apply()
+ spawn {
+ reset()
+ currentProfile = profileManager.create()
+ h.sendEmptyMessage(0)
+ }
+ }
+
// Initialize drawer
menuAdapter.setActiveId(settings.getInt(Key.profileId, -1))
menuAdapter.setListener(this)
@@ -527,23 +578,19 @@ class Shadowsocks
getSupportActionBar.setIcon(R.drawable.ic_stat_shadowsocks)
// Register broadcast receiver
- registerReceiver(stateReceiver, new IntentFilter(Action.UPDATE_STATE))
registerReceiver(preferenceReceiver, new IntentFilter(Action.UPDATE_PREFS))
- // Update status
- if (!Shadowsocks.isServiceStarted(this)) {
- spawn {
- status.edit.putBoolean(Key.isRoot, Utils.getRoot).apply()
- }
- if (!status.getBoolean(getVersionName, false)) {
- val h = showProgress(getString(R.string.initializing))
- status.edit.putBoolean(getVersionName, true).apply()
- spawn {
- reset()
- currentProfile = profileManager.create()
- h.sendEmptyMessage(0)
+ // Bind to the service
+ spawn {
+ status.edit.putBoolean(Key.isRoot, Utils.getRoot).apply()
+ handler.post(new Runnable {
+ override def run() {
+ val isRoot = status.getBoolean(Key.isRoot, false)
+ val s = if (isRoot) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService]
+ bindService(new Intent(s.getName), connection, Context.BIND_AUTO_CREATE)
+ startService(new Intent(Shadowsocks.this, s))
}
- }
+ })
}
}
@@ -571,15 +618,15 @@ class Shadowsocks
drawer.setActiveView(v, pos)
}
-
def newProfile(id: Int) {
val builder = new AlertDialog.Builder(this)
- builder.setTitle(R.string.add_profile)
- .setItems(R.array.add_profile_methods, new DialogInterface.OnClickListener() {
+ builder
+ .setTitle(R.string.add_profile)
+ .setItems(R.array.add_profile_methods, new DialogInterface.OnClickListener() {
def onClick(dialog: DialogInterface, which: Int) {
which match {
- case 0 => {
+ case 0 =>
dialog.dismiss()
val h = showProgress(getString(R.string.loading))
h.postDelayed(new Runnable() {
@@ -589,17 +636,14 @@ class Shadowsocks
h.sendEmptyMessage(0)
}
}, 600)
- }
- case 1 => {
+ case 1 =>
dialog.dismiss()
addProfile(id)
- }
case _ =>
}
}
})
builder.create().show()
-
}
def addProfile(profile: Profile) {
@@ -737,7 +781,7 @@ class Shadowsocks
EasyTracker
.getInstance(this)
.send(
- MapBuilder.createEvent(Shadowsocks.TAG, "flush_dnscache", getVersionName, null).build())
+ MapBuilder.createEvent(Shadowsocks.TAG, "flush_dnscache", getVersionName, null).build())
flushDnsCache()
})
@@ -753,10 +797,9 @@ class Shadowsocks
override def onOptionsItemSelected(item: com.actionbarsherlock.view.MenuItem): Boolean = {
item.getItemId match {
- case android.R.id.home => {
+ case android.R.id.home =>
drawer.toggleMenu()
return true
- }
}
super.onOptionsItemSelected(item)
}
@@ -768,38 +811,7 @@ class Shadowsocks
protected override def onResume() {
super.onResume()
- if (!prepared) {
- if (Shadowsocks.isServiceStarted(this)) {
- switchButton.setChecked(true)
- if (ShadowVpnService.isServiceStarted(this)) {
- val style = new Style.Builder().setBackgroundColorValue(Style.holoBlueLight).build()
- val config = new Configuration.Builder().setDuration(Configuration.DURATION_LONG).build()
- switchButton.setEnabled(false)
- Crouton
- .makeText(Shadowsocks.this, R.string.vpn_status, style)
- .setConfiguration(config)
- .show()
- }
- setPreferenceEnabled(enabled = false)
- onStateChanged(State.CONNECTED, null)
- } else {
- switchButton.setEnabled(true)
- switchButton.setChecked(false)
- Crouton.cancelAllCroutons()
- setPreferenceEnabled(enabled = true)
- if (status.getBoolean(Key.isRunning, false)) {
- spawn {
- crash_recovery()
- handler.sendEmptyMessage(MSG_CRASH_RECOVER)
- }
- }
- onStateChanged(State.STOPPED, null)
- }
- }
-
- switchButton.setOnCheckedChangeListener(this)
-
- Config.refresh(this)
+ ConfigUtils.refresh(this)
}
private def setPreferenceEnabled(enabled: Boolean) {
@@ -848,8 +860,11 @@ class Shadowsocks
override def onDestroy() {
super.onDestroy()
+ if (bgService != null) {
+ bgService.unregisterCallback(callback)
+ unbindService(connection)
+ }
Crouton.cancelAllCroutons()
- unregisterReceiver(stateReceiver)
unregisterReceiver(preferenceReceiver)
new BackupManager(this).dataChanged()
}
@@ -891,16 +906,14 @@ class Shadowsocks
}
} else {
resultCode match {
- case Activity.RESULT_OK => {
+ case Activity.RESULT_OK =>
prepared = true
if (!serviceStart) {
switchButton.setChecked(false)
}
- }
- case _ => {
+ case _ =>
clearDialog()
Log.e(Shadowsocks.TAG, "Failed to start VpnService")
- }
}
}
}
@@ -918,7 +931,7 @@ class Shadowsocks
}
def serviceStop() {
- sendBroadcast(new Intent(Action.CLOSE))
+ if (bgService != null) bgService.stop()
}
/** Called when connect button is clicked. */
@@ -935,26 +948,20 @@ class Shadowsocks
return false
}
} catch {
- case ex: Exception => {
+ case ex: Exception =>
this.showDialog(getString(R.string.port_alert))
return false
- }
}
+ if (bgService == null) return false
+
+ bgService.start(ConfigUtils.load(settings))
+
if (isVpnEnabled) {
- if (ShadowVpnService.isServiceStarted(this)) return false
- val intent: Intent = new Intent(this, classOf[ShadowVpnService])
- Extra.put(settings, intent)
- startService(intent)
val style = new Style.Builder().setBackgroundColorValue(Style.holoBlueLight).build()
val config = new Configuration.Builder().setDuration(Configuration.DURATION_LONG).build()
Crouton.makeText(Shadowsocks.this, R.string.vpn_status, style).setConfiguration(config).show()
switchButton.setEnabled(false)
- } else {
- if (ShadowsocksService.isServiceStarted(this)) return false
- val intent: Intent = new Intent(this, classOf[ShadowsocksService])
- Extra.put(settings, intent)
- startService(intent)
}
true
}
@@ -974,9 +981,8 @@ class Shadowsocks
try {
versionName = getPackageManager.getPackageInfo(getPackageName, 0).versionName
} catch {
- case ex: PackageManager.NameNotFoundException => {
+ case ex: PackageManager.NameNotFoundException =>
versionName = ""
- }
}
new AlertDialog.Builder(this)
@@ -1014,44 +1020,45 @@ class Shadowsocks
}
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)
+ handler.post(new Runnable {
+ override def run() {
+ 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(enabled = false)
+ case State.CONNECTED =>
+ clearDialog()
+ if (!switchButton.isChecked) switchButton.setChecked(true)
+ setPreferenceEnabled(enabled = false)
+ case State.STOPPED =>
+ 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).build()
+ val config = new Configuration.Builder()
+ .setDuration(Configuration.DURATION_LONG)
+ .build()
+ Crouton
+ .makeText(Shadowsocks.this, getString(R.string.vpn_error).format(m), style)
+ .setConfiguration(config)
+ .show()
+ }
+ setPreferenceEnabled(enabled = true)
}
- setPreferenceEnabled(enabled = false)
- }
- case State.CONNECTED => {
- clearDialog()
- if (!switchButton.isChecked) switchButton.setChecked(true)
- setPreferenceEnabled(enabled = false)
- }
- case State.STOPPED => {
- 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).build()
- val config = new Configuration.Builder()
- .setDuration(Configuration.DURATION_LONG)
- .build()
- Crouton
- .makeText(Shadowsocks.this, getString(R.string.vpn_error).format(m), style)
- .setConfiguration(config)
- .show()
- }
- setPreferenceEnabled(enabled = true)
+ if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
}
}
- if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
- }
+ })
}
class PreferenceBroadcastReceiver extends BroadcastReceiver {
@@ -1061,12 +1068,4 @@ class Shadowsocks
}
}
- class StateBroadcastReceiver extends BroadcastReceiver {
- override def onReceive(context: Context, intent: Intent) {
- val state = intent.getIntExtra(Extra.STATE, State.INIT)
- val message = intent.getStringExtra(Extra.MESSAGE)
- onStateChanged(state, message)
- }
- }
-
}
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala
similarity index 76%
rename from src/main/scala/com/github/shadowsocks/ShadowsocksService.scala
rename to src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala
index b0a2943a..7a826ebf 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala
@@ -57,22 +57,16 @@ import org.apache.http.conn.util.InetAddressUtils
import scala.collection._
import java.util.{TimerTask, Timer}
import android.net.TrafficStats
-import scala.concurrent.ops._
import com.github.shadowsocks.utils._
import scala.Some
import android.graphics.Color
+import com.github.shadowsocks.aidl.Config
case class TrafficStat(tx: Long, rx: Long, timestamp: Long)
-object ShadowsocksService {
- def isServiceStarted(context: Context): Boolean = {
- Utils.isServiceStarted("com.github.shadowsocks.ShadowsocksService", context)
- }
-}
+class ShadowsocksNatService extends Service with BaseService {
-class ShadowsocksService extends Service {
-
- val TAG = "ShadowsocksService"
+ val TAG = "ShadowsocksNatService"
val CMD_IPTABLES_RETURN = " -t nat -A OUTPUT -p tcp -d 0.0.0.0 -j RETURN\n"
val CMD_IPTABLES_REDIRECT_ADD_SOCKS = " -t nat -A OUTPUT -p tcp " + "-j REDIRECT --to 8123\n"
@@ -80,12 +74,6 @@ class ShadowsocksService extends Service {
"-j DNAT --to-destination 127.0.0.1:8123\n"
val DNS_PORT = 8153
- 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
-
private val mStartForegroundSignature = Array[Class[_]](classOf[Int], classOf[Notification])
private val mStopForegroundSignature = Array[Class[_]](classOf[Boolean])
private val mSetForegroundSignature = Array[Class[_]](classOf[Boolean])
@@ -104,56 +92,25 @@ class ShadowsocksService extends Service {
private var mStartForegroundArgs = new Array[AnyRef](2)
private var mStopForegroundArgs = new Array[AnyRef](1)
- private var state = State.INIT
private var last: TrafficStat = null
private var lastTxRate = 0
private var lastRxRate = 0
private var timer: Timer = null
private val TIMER_INTERVAL = 2
- private def changeState(s: Int) {
- if (state != s) {
- state = s
- val intent = new Intent(Action.UPDATE_STATE)
- intent.putExtra(Extra.STATE, state)
- sendBroadcast(intent)
- }
- }
-
val handler: Handler = new Handler {
override def handleMessage(msg: Message) {
msg.what match {
- case MSG_CONNECT_SUCCESS =>
+ case Msg.CONNECT_SUCCESS =>
changeState(State.CONNECTED)
- case MSG_CONNECT_FAIL =>
+ case Msg.CONNECT_FAIL =>
changeState(State.STOPPED)
- case MSG_STOP_SELF =>
- stopSelf()
case _ =>
}
super.handleMessage(msg)
}
}
- def getPid(name: String): Int = {
- try {
- val reader: BufferedReader = new BufferedReader(new FileReader(Path.BASE + name + ".pid"))
- val line = reader.readLine
- return Integer.valueOf(line)
- } catch {
- case e: FileNotFoundException => {
- Log.e(TAG, "Cannot open pid file: " + name)
- }
- case e: IOException => {
- Log.e(TAG, "Cannot read pid file: " + name)
- }
- case e: NumberFormatException => {
- Log.e(TAG, "Invalid pid", e)
- }
- }
- -1
- }
-
def startShadowsocksDaemon() {
val cmd: String = (Path.BASE +
"shadowsocks -b 127.0.0.1 -s \"%s\" -p \"%d\" -l \"%d\" -k \"%s\" -m \"%s\" -f " +
@@ -165,8 +122,8 @@ class ShadowsocksService extends Service {
def startDnsDaemon() {
val cmd: String = Path.BASE + "pdnsd -c " + Path.BASE + "pdnsd.conf"
- val conf: String = Config.PDNSD.format("127.0.0.1")
- Config.printToFile(new File(Path.BASE + "pdnsd.conf"))(p => {
+ val conf: String = ConfigUtils.PDNSD.format("127.0.0.1")
+ ConfigUtils.printToFile(new File(Path.BASE + "pdnsd.conf"))(p => {
p.println(conf)
})
Utils.runCommand(cmd)
@@ -178,101 +135,17 @@ class ShadowsocksService extends Service {
val pi: PackageInfo = getPackageManager.getPackageInfo(getPackageName, 0)
version = pi.versionName
} catch {
- case e: PackageManager.NameNotFoundException => {
+ case e: PackageManager.NameNotFoundException =>
version = "Package name not found"
- }
}
version
}
- def handleCommand(intent: Intent) {
- if (intent == null) {
- stopSelf()
- return
- }
-
- changeState(State.CONNECTING)
-
- config = Extra.get(intent)
-
- if (config.isTrafficStat) {
- // initialize timer
- val task = new TimerTask {
- def run() {
- val pm = getSystemService(Context.POWER_SERVICE).asInstanceOf[PowerManager]
- val now = new TrafficStat(TrafficStats.getUidTxBytes(uid),
- TrafficStats.getUidRxBytes(uid), java.lang.System.currentTimeMillis())
- val txRate = ((now.tx - last.tx) / 1024 / TIMER_INTERVAL).toInt
- val rxRate = ((now.rx - last.rx) / 1024 / TIMER_INTERVAL).toInt
- last = now
- if (lastTxRate == txRate && lastRxRate == rxRate) {
- return
- } else {
- lastTxRate = txRate
- lastRxRate = rxRate
- }
- if ((pm.isScreenOn && state == State.CONNECTED) || (txRate == 0 && rxRate == 0)) {
- notifyForegroundAlert(getString(R.string.forward_success),
- getString(R.string.service_status).format(math.max(txRate, rxRate)), math.max(txRate, rxRate))
- }
- }
- }
- last = new TrafficStat(TrafficStats.getUidTxBytes(uid),
- TrafficStats.getUidRxBytes(uid), java.lang.System.currentTimeMillis())
- timer = new Timer(true)
- timer.schedule(task, TIMER_INTERVAL * 1000, TIMER_INTERVAL * 1000)
- }
-
- spawn {
-
- if (config.proxy == "198.199.101.152") {
- val container = getApplication.asInstanceOf[ShadowsocksApplication].tagContainer
- try {
- config = Config.getPublicConfig(getBaseContext, container, config)
- } catch {
- case ex: Exception => {
- notifyAlert(getString(R.string.forward_fail), getString(R.string.service_failed))
- stopSelf()
- handler.sendEmptyMessageDelayed(MSG_CONNECT_FAIL, 500)
- return
- }
- }
- }
-
- killProcesses()
-
- var resolved: Boolean = false
- if (!InetAddressUtils.isIPv4Address(config.proxy) &&
- !InetAddressUtils.isIPv6Address(config.proxy)) {
- Utils.resolve(config.proxy, enableIPv6 = true) match {
- case Some(a) =>
- config.proxy = a
- resolved = true
- case None => resolved = false
- }
- } else {
- resolved = true
- }
-
- hasRedirectSupport = Utils.getHasRedirectSupport
-
- if (resolved && handleConnection) {
- notifyForegroundAlert(getString(R.string.forward_success),
- getString(R.string.service_running).format(config.profileName))
- handler.sendEmptyMessageDelayed(MSG_CONNECT_SUCCESS, 500)
- } else {
- notifyAlert(getString(R.string.forward_fail), getString(R.string.service_failed))
- stopSelf()
- handler.sendEmptyMessageDelayed(MSG_CONNECT_FAIL, 500)
- }
- handler.sendEmptyMessageDelayed(MSG_CONNECT_FINISH, 500)
- }
- }
-
def startRedsocksDaemon() {
- val conf = Config.REDSOCKS.format(config.localPort)
- val cmd = "%sredsocks -p %sredsocks.pid -c %sredsocks.conf".format(Path.BASE, Path.BASE, Path.BASE)
- Config.printToFile(new File(Path.BASE + "redsocks.conf"))(p => {
+ val conf = ConfigUtils.REDSOCKS.format(config.localPort)
+ val cmd = "%sredsocks -p %sredsocks.pid -c %sredsocks.conf"
+ .format(Path.BASE, Path.BASE, Path.BASE)
+ ConfigUtils.printToFile(new File(Path.BASE + "redsocks.conf"))(p => {
p.println(conf)
})
Utils.runRootCommand(cmd)
@@ -291,14 +164,14 @@ class ShadowsocksService extends Service {
try {
t.join(300)
} catch {
- case ignored: InterruptedException => {
- }
+ case ignored: InterruptedException =>
}
!t.isAlive
}
/** Called when the activity is first created. */
def handleConnection: Boolean = {
+
startShadowsocksDaemon()
startDnsDaemon()
startRedsocksDaemon()
@@ -308,21 +181,14 @@ class ShadowsocksService extends Service {
true
}
- def initSoundVibrateLights(notification: Notification) {
- notification.sound = null
- notification.defaults |= Notification.DEFAULT_LIGHTS
- }
-
def invokeMethod(method: Method, args: Array[AnyRef]) {
try {
method.invoke(this, mStartForegroundArgs: _*)
} catch {
- case e: InvocationTargetException => {
+ case e: InvocationTargetException =>
Log.w(TAG, "Unable to invoke method", e)
- }
- case e: IllegalAccessException => {
+ case e: IllegalAccessException =>
Log.w(TAG, "Unable to invoke method", e)
- }
}
}
@@ -340,8 +206,9 @@ class ShadowsocksService extends Service {
val icon = getResources.getDrawable(R.drawable.ic_stat_shadowsocks)
if (rate >= 0) {
- val bitmap =Utils.getBitmap(rate.toString, icon.getIntrinsicWidth * 4,
- icon.getIntrinsicHeight * 4, Color.TRANSPARENT)
+ val bitmap = Utils
+ .getBitmap(rate.toString, icon.getIntrinsicWidth * 4, icon.getIntrinsicHeight * 4,
+ Color.TRANSPARENT)
builder.setLargeIcon(bitmap)
if (rate < 1000) {
@@ -352,7 +219,6 @@ class ShadowsocksService extends Service {
} else {
builder.setSmallIcon(R.drawable.ic_stat_speed, 1091)
}
-
} else {
builder.setSmallIcon(R.drawable.ic_stat_shadowsocks)
}
@@ -364,7 +230,7 @@ class ShadowsocksService extends Service {
.setContentText(info)
.setContentIntent(contentIntent)
.addAction(android.R.drawable.ic_menu_close_clear_cancel, getString(R.string.stop),
- actionIntent)
+ actionIntent)
startForegroundCompat(1, builder.build)
}
@@ -386,20 +252,17 @@ class ShadowsocksService extends Service {
}
def onBind(intent: Intent): IBinder = {
- null
+ if (classOf[ShadowsocksNatService].getName equals intent.getAction) {
+ binder
+ } else {
+ null
+ }
}
override def onCreate() {
super.onCreate()
- Config.refresh(this)
-
- EasyTracker
- .getInstance(this)
- .send(MapBuilder
- .createEvent(TAG, "start", getVersionName, 0L)
- .set(Fields.SESSION_CONTROL, "start")
- .build())
+ ConfigUtils.refresh(this)
notificationManager = this
.getSystemService(Context.NOTIFICATION_SERVICE)
@@ -408,63 +271,19 @@ class ShadowsocksService extends Service {
mStartForeground = getClass.getMethod("startForeground", mStartForegroundSignature: _*)
mStopForeground = getClass.getMethod("stopForeground", mStopForegroundSignature: _*)
} catch {
- case e: NoSuchMethodException => {
+ case e: NoSuchMethodException =>
mStartForeground = {
mStopForeground = null
mStopForeground
}
- }
}
try {
mSetForeground = getClass.getMethod("setForeground", mSetForegroundSignature: _*)
} catch {
- case e: NoSuchMethodException => {
+ case e: NoSuchMethodException =>
throw new IllegalStateException(
"OS doesn't have Service.startForeground OR Service.setForeground!")
- }
}
-
- // register close receiver
- val filter = new IntentFilter()
- filter.addAction(Intent.ACTION_SHUTDOWN)
- filter.addAction(Action.CLOSE)
- receiver = new BroadcastReceiver() {
- def onReceive(p1: Context, p2: Intent) {
- stopSelf()
- }
- }
- registerReceiver(receiver, filter)
- }
-
- /** Called when the activity is closed. */
- override def onDestroy() {
-
- // reset timer
- if (timer != null) {
- timer.cancel()
- timer = null
- }
-
- // clean up context
- changeState(State.STOPPED)
-
- EasyTracker
- .getInstance(this)
- .send(MapBuilder
- .createEvent(TAG, "stop", getVersionName, 0L)
- .set(Fields.SESSION_CONTROL, "stop")
- .build())
-
- stopForegroundCompat(1)
- if (receiver != null) {
- unregisterReceiver(receiver)
- receiver = null
- }
-
- // reset NAT
- killProcesses()
-
- super.onDestroy()
}
def killProcesses() {
@@ -489,12 +308,7 @@ class ShadowsocksService extends Service {
Utils.runCommand(sb.toString())
}
- override def onStart(intent: Intent, startId: Int) {
- handleCommand(intent)
- }
-
override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = {
- handleCommand(intent)
Service.START_STICKY
}
@@ -593,12 +407,10 @@ class ShadowsocksService extends Service {
try {
mStopForeground.invoke(this, mStopForegroundArgs: _*)
} catch {
- case e: InvocationTargetException => {
+ case e: InvocationTargetException =>
Log.w(TAG, "Unable to invoke stopForeground", e)
- }
- case e: IllegalAccessException => {
+ case e: IllegalAccessException =>
Log.w(TAG, "Unable to invoke stopForeground", e)
- }
}
return
}
@@ -606,4 +418,135 @@ class ShadowsocksService extends Service {
mSetForegroundArgs(0) = boolean2Boolean(x = false)
invokeMethod(mSetForeground, mSetForegroundArgs)
}
+
+ override def startRunner(c: Config) {
+
+ var config = c
+
+ // register close receiver
+ val filter = new IntentFilter()
+ filter.addAction(Intent.ACTION_SHUTDOWN)
+ filter.addAction(Action.CLOSE)
+ receiver = new BroadcastReceiver() {
+ def onReceive(p1: Context, p2: Intent) {
+ stopRunner()
+ }
+ }
+ registerReceiver(receiver, filter)
+
+ // start tracker
+ EasyTracker
+ .getInstance(this)
+ .send(MapBuilder
+ .createEvent(TAG, "start", getVersionName, 0L)
+ .set(Fields.SESSION_CONTROL, "start")
+ .build())
+
+ changeState(State.CONNECTING)
+
+ if (config.isTrafficStat) {
+ // initialize timer
+ val task = new TimerTask {
+ def run() {
+ val pm = getSystemService(Context.POWER_SERVICE).asInstanceOf[PowerManager]
+ val now = new
+ TrafficStat(TrafficStats.getUidTxBytes(uid), TrafficStats.getUidRxBytes(uid),
+ java.lang.System.currentTimeMillis())
+ val txRate = ((now.tx - last.tx) / 1024 / TIMER_INTERVAL).toInt
+ val rxRate = ((now.rx - last.rx) / 1024 / TIMER_INTERVAL).toInt
+ last = now
+ if (lastTxRate == txRate && lastRxRate == rxRate) {
+ return
+ } else {
+ lastTxRate = txRate
+ lastRxRate = rxRate
+ }
+ if ((pm.isScreenOn && state == State.CONNECTED) || (txRate == 0 && rxRate == 0)) {
+ notifyForegroundAlert(getString(R.string.forward_success),
+ getString(R.string.service_status).format(math.max(txRate, rxRate)),
+ math.max(txRate, rxRate))
+ }
+ }
+ }
+ last = new TrafficStat(TrafficStats.getUidTxBytes(uid), TrafficStats.getUidRxBytes(uid),
+ java.lang.System.currentTimeMillis())
+ timer = new Timer(true)
+ timer.schedule(task, TIMER_INTERVAL * 1000, TIMER_INTERVAL * 1000)
+ }
+
+ if (config.proxy == "198.199.101.152") {
+ val container = getApplication.asInstanceOf[ShadowsocksApplication].tagContainer
+ try {
+ config = ConfigUtils.getPublicConfig(getBaseContext, container, config)
+ } catch {
+ case ex: Exception =>
+ notifyAlert(getString(R.string.forward_fail), getString(R.string.service_failed))
+ stopRunner()
+ handler.sendEmptyMessageDelayed(Msg.CONNECT_FAIL, 500)
+ return
+ }
+ }
+
+ killProcesses()
+
+ var resolved: Boolean = false
+ if (!InetAddressUtils.isIPv4Address(config.proxy) &&
+ !InetAddressUtils.isIPv6Address(config.proxy)) {
+ Utils.resolve(config.proxy, enableIPv6 = true) match {
+ case Some(a) =>
+ config.proxy = a
+ resolved = true
+ case None => resolved = false
+ }
+ } else {
+ resolved = true
+ }
+
+ hasRedirectSupport = Utils.getHasRedirectSupport
+
+ if (resolved && handleConnection) {
+ notifyForegroundAlert(getString(R.string.forward_success),
+ getString(R.string.service_running).format(config.profileName))
+ handler.sendEmptyMessageDelayed(Msg.CONNECT_SUCCESS, 500)
+ } else {
+ notifyAlert(getString(R.string.forward_fail), getString(R.string.service_failed))
+ stopRunner()
+ handler.sendEmptyMessageDelayed(Msg.CONNECT_FAIL, 500)
+ }
+ handler.sendEmptyMessageDelayed(Msg.CONNECT_FINISH, 500)
+ }
+
+ override def stopRunner() {
+
+ // change the state
+ changeState(State.STOPPED)
+
+ // stop the tracker
+ EasyTracker
+ .getInstance(this)
+ .send(MapBuilder
+ .createEvent(TAG, "stop", getVersionName, 0L)
+ .set(Fields.SESSION_CONTROL, "stop")
+ .build())
+
+ // reset timer
+ if (timer != null) {
+ timer.cancel()
+ timer = null
+ }
+
+ // clean up context
+ stopForegroundCompat(1)
+ if (receiver != null) {
+ unregisterReceiver(receiver)
+ receiver = null
+ }
+
+ // reset NAT
+ killProcesses()
+ }
+
+ override def getTag = TAG
+
+ override def getServiceMode = Mode.NAT
}
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala
index 9e743bde..841c406b 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala
@@ -54,33 +54,17 @@ class ShadowsocksReceiver extends BroadcastReceiver {
val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val status = context.getSharedPreferences(Key.status, Context.MODE_PRIVATE)
- if (intent.getAction == Action.UPDATE_STATE) {
- val state = intent.getIntExtra(Extra.STATE, State.INIT)
- val running = state match {
- case State.CONNECTING => true
- case State.CONNECTED => true
- case _ => false
- }
- status.edit.putBoolean(Key.isRunning, running).commit()
- return
- }
-
var versionName: String = null
try {
versionName = context.getPackageManager.getPackageInfo(context.getPackageName, 0).versionName
} catch {
- case e: PackageManager.NameNotFoundException => {
+ case e: PackageManager.NameNotFoundException =>
versionName = "NONE"
- }
}
val isAutoConnect: Boolean = settings.getBoolean(Key.isAutoConnect, false)
val isInstalled: Boolean = status.getBoolean(versionName, false)
if (isAutoConnect && isInstalled) {
if (Utils.getRoot) {
- if (ShadowsocksService.isServiceStarted(context)) return
- val intent: Intent = new Intent(context, classOf[ShadowsocksService])
- Extra.put(settings, intent)
- context.startService(intent)
}
}
}
diff --git a/src/main/scala/com/github/shadowsocks/ShadowVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
similarity index 71%
rename from src/main/scala/com/github/shadowsocks/ShadowVpnService.scala
rename to src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
index 93d777e6..89271595 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowVpnService.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
@@ -51,27 +51,15 @@ import java.io._
import android.net.VpnService
import org.apache.http.conn.util.InetAddressUtils
import android.os.Message
-import scala.concurrent.ops._
import org.apache.commons.net.util.SubnetUtils
import java.net.InetAddress
import com.github.shadowsocks.utils._
import scala.Some
+import com.github.shadowsocks.aidl.Config
-object ShadowVpnService {
- def isServiceStarted(context: Context): Boolean = {
- Utils.isServiceStarted("com.github.shadowsocks.ShadowVpnService", context)
- }
-}
+class ShadowsocksVpnService extends VpnService with BaseService {
-class ShadowVpnService extends VpnService {
-
- val TAG = "ShadowVpnService"
-
- 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 TAG = "ShadowsocksVpnService"
val VPN_MTU = 1500
@@ -83,60 +71,21 @@ class ShadowVpnService extends VpnService {
var apps: Array[ProxiedApp] = null
var config: Config = 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
- val intent = new Intent(Action.UPDATE_STATE)
- intent.putExtra(Extra.STATE, state)
- intent.putExtra(Extra.MESSAGE, message)
- sendBroadcast(intent)
- }
- }
-
val handler: Handler = new Handler {
override def handleMessage(msg: Message) {
msg.what match {
- case MSG_CONNECT_SUCCESS =>
+ case Msg.CONNECT_SUCCESS =>
changeState(State.CONNECTED)
- case MSG_CONNECT_FAIL =>
+ case Msg.CONNECT_FAIL =>
changeState(State.STOPPED)
- case MSG_VPN_ERROR =>
+ case Msg.VPN_ERROR =>
if (msg.obj != null) changeState(State.STOPPED, msg.obj.asInstanceOf[String])
- case MSG_STOP_SELF =>
- stopSelf()
case _ =>
}
super.handleMessage(msg)
}
}
- def getPid(name: String): Int = {
- try {
- val reader: BufferedReader = new BufferedReader(new FileReader(Path.BASE + name + ".pid"))
- val line = reader.readLine
- return Integer.valueOf(line)
- } catch {
- case e: FileNotFoundException => {
- Log.e(TAG, "Cannot open pid file: " + name)
- }
- case e: IOException => {
- Log.e(TAG, "Cannot read pid file: " + name)
- }
- case e: NumberFormatException => {
- Log.e(TAG, "Invalid pid", e)
- }
- }
- -1
- }
-
def startShadowsocksDaemon() {
val cmd: String = (Path.BASE +
"shadowsocks -b 127.0.0.1 -s \"%s\" -p \"%d\" -l \"%d\" -k \"%s\" -m \"%s\" -f " +
@@ -148,8 +97,8 @@ class ShadowVpnService extends VpnService {
def startDnsDaemon() {
val cmd: String = Path.BASE + "pdnsd -c " + Path.BASE + "pdnsd.conf"
- val conf: String = Config.PDNSD.format("0.0.0.0")
- Config.printToFile(new File(Path.BASE + "pdnsd.conf"))(p => {
+ val conf: String = ConfigUtils.PDNSD.format("0.0.0.0")
+ ConfigUtils.printToFile(new File(Path.BASE + "pdnsd.conf"))(p => {
p.println(conf)
})
Utils.runCommand(cmd)
@@ -161,74 +110,12 @@ class ShadowVpnService extends VpnService {
val pi: PackageInfo = getPackageManager.getPackageInfo(getPackageName, 0)
version = pi.versionName
} catch {
- case e: PackageManager.NameNotFoundException => {
+ case e: PackageManager.NameNotFoundException =>
version = "Package name not found"
- }
}
version
}
- def handleCommand(intent: Intent) {
-
- if (intent == null) {
- stopSelf()
- return
- }
-
- if (VpnService.prepare(this) != null) {
- val i = new Intent(this, classOf[ShadowVpnActivity])
- i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- startActivity(i)
- stopSelf()
- return
- }
-
- changeState(State.CONNECTING)
-
- config = Extra.get(intent)
-
- spawn {
- if (config.proxy == "198.199.101.152") {
- val container = getApplication.asInstanceOf[ShadowsocksApplication].tagContainer
- try {
- config = Config.getPublicConfig(getBaseContext, container, config)
- } catch {
- case ex: Exception => {
- notifyAlert(getString(R.string.forward_fail), getString(R.string.service_failed))
- stopSelf()
- handler.sendEmptyMessageDelayed(MSG_CONNECT_FAIL, 500)
- return
- }
- }
- }
-
- killProcesses()
-
- // Resolve server address
- var resolved: Boolean = false
- if (!InetAddressUtils.isIPv4Address(config.proxy) &&
- !InetAddressUtils.isIPv6Address(config.proxy)) {
- Utils.resolve(config.proxy, enableIPv6 = true) match {
- case Some(addr) =>
- config.proxy = addr
- resolved = true
- case None => resolved = false
- }
- } else {
- resolved = true
- }
-
- if (resolved && handleConnection) {
- handler.sendEmptyMessageDelayed(MSG_CONNECT_SUCCESS, 300)
- } else {
- notifyAlert(getString(R.string.forward_fail), getString(R.string.service_failed))
- handler.sendEmptyMessageDelayed(MSG_CONNECT_FAIL, 300)
- handler.sendEmptyMessageDelayed(MSG_STOP_SELF, 500)
- }
- handler.sendEmptyMessageDelayed(MSG_CONNECT_FINISH, 300)
- }
- }
-
def waitForProcess(name: String): Boolean = {
val pid: Int = getPid(name)
if (pid == -1) return false
@@ -242,8 +129,7 @@ class ShadowVpnService extends VpnService {
try {
t.join(300)
} catch {
- case ignored: InterruptedException => {
- }
+ case ignored: InterruptedException =>
}
!t.isAlive
}
@@ -308,18 +194,17 @@ class ShadowVpnService extends VpnService {
try {
conn = builder.establish()
} catch {
- case ex: IllegalStateException => {
+ case ex: IllegalStateException =>
val msg = new Message()
- msg.what = MSG_VPN_ERROR
+ msg.what = Msg.VPN_ERROR
msg.obj = ex.getMessage
handler.sendMessage(msg)
conn = null
- }
case ex: Exception => conn = null
}
if (conn == null) {
- stopSelf()
+ stopRunner()
return
}
@@ -335,7 +220,7 @@ class ShadowVpnService extends VpnService {
+ "--loglevel 3 "
+ "--pid %stun2socks.pid")
.format(PRIVATE_VLAN.format("2"), PRIVATE_VLAN.format("1"), config.localPort, fd, VPN_MTU,
- Path.BASE)
+ Path.BASE)
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
System.exec(cmd)
}
@@ -348,11 +233,6 @@ class ShadowVpnService extends VpnService {
true
}
- def initSoundVibrateLights(notification: Notification) {
- notification.sound = null
- notification.defaults |= Notification.DEFAULT_LIGHTS
- }
-
def notifyAlert(title: String, info: String) {
val openIntent: Intent = new Intent(this, classOf[Shadowsocks])
openIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
@@ -373,6 +253,8 @@ class ShadowVpnService extends VpnService {
val action = intent.getAction
if (VpnService.SERVICE_INTERFACE == action) {
return super.onBind(intent)
+ } else if (classOf[ShadowsocksVpnService].getName == action) {
+ return binder
}
null
}
@@ -380,55 +262,10 @@ class ShadowVpnService extends VpnService {
override def onCreate() {
super.onCreate()
- Config.refresh(this)
-
- EasyTracker
- .getInstance(this)
- .send(MapBuilder
- .createEvent(TAG, "start", getVersionName, 0L)
- .set(Fields.SESSION_CONTROL, "start")
- .build())
+ ConfigUtils.refresh(this)
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE)
.asInstanceOf[NotificationManager]
-
- // register close receiver
- val filter = new IntentFilter()
- filter.addAction(Intent.ACTION_SHUTDOWN)
- filter.addAction(Action.CLOSE)
- receiver = new BroadcastReceiver {
- def onReceive(p1: Context, p2: Intent) {
- stopSelf()
- }
- }
- registerReceiver(receiver, filter)
- }
-
- override def onDestroy() {
- killProcesses()
-
- changeState(State.STOPPED)
-
- EasyTracker
- .getInstance(this)
- .send(MapBuilder
- .createEvent(TAG, "stop", getVersionName, 0L)
- .set(Fields.SESSION_CONTROL, "stop")
- .build())
-
- if (receiver != null) {
- unregisterReceiver(receiver)
- receiver = null
- }
-
- if (conn != null) {
- conn.close()
- conn = null
- }
-
- notificationManager.cancel(1)
-
- super.onDestroy()
}
def killProcesses() {
@@ -448,13 +285,115 @@ class ShadowVpnService extends VpnService {
Utils.runCommand(sb.toString())
}
- override def onStart(intent: Intent, startId: Int) {
- handleCommand(intent)
- }
-
override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = {
- handleCommand(intent)
Service.START_STICKY
}
+ override def startRunner(c: Config) {
+
+ var config = c
+
+ // ensure the VPNService is prepared
+ if (VpnService.prepare(this) != null) {
+ val i = new Intent(this, classOf[ShadowVpnActivity])
+ i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ startActivity(i)
+ return
+ }
+
+ // start the tracker
+ EasyTracker
+ .getInstance(this)
+ .send(MapBuilder
+ .createEvent(TAG, "start", getVersionName, 0L)
+ .set(Fields.SESSION_CONTROL, "start")
+ .build())
+
+ // register close receiver
+ val filter = new IntentFilter()
+ filter.addAction(Intent.ACTION_SHUTDOWN)
+ receiver = new BroadcastReceiver {
+ def onReceive(p1: Context, p2: Intent) {
+ stopRunner()
+ }
+ }
+ registerReceiver(receiver, filter)
+
+ changeState(State.CONNECTING)
+
+ if (config.proxy == "198.199.101.152") {
+ val container = getApplication.asInstanceOf[ShadowsocksApplication].tagContainer
+ try {
+ config = ConfigUtils.getPublicConfig(getBaseContext, container, config)
+ } catch {
+ case ex: Exception =>
+ notifyAlert(getString(R.string.forward_fail), getString(R.string.service_failed))
+ stopRunner()
+ handler.sendEmptyMessageDelayed(Msg.CONNECT_FAIL, 500)
+ return
+ }
+ }
+
+ // reset the context
+ killProcesses()
+
+ // Resolve the server address
+ var resolved: Boolean = false
+ if (!InetAddressUtils.isIPv4Address(config.proxy) &&
+ !InetAddressUtils.isIPv6Address(config.proxy)) {
+ Utils.resolve(config.proxy, enableIPv6 = true) match {
+ case Some(addr) =>
+ config.proxy = addr
+ resolved = true
+ case None => resolved = false
+ }
+ } else {
+ resolved = true
+ }
+
+ if (resolved && handleConnection) {
+ handler.sendEmptyMessageDelayed(Msg.CONNECT_SUCCESS, 300)
+ } else {
+ notifyAlert(getString(R.string.forward_fail), getString(R.string.service_failed))
+ handler.sendEmptyMessageDelayed(Msg.CONNECT_FAIL, 300)
+ stopRunner()
+ }
+ handler.sendEmptyMessageDelayed(Msg.CONNECT_FINISH, 300)
+ }
+
+ override def stopRunner() {
+
+ // channge the state
+ changeState(State.STOPPED)
+
+ // stop the tracker
+ EasyTracker
+ .getInstance(this)
+ .send(MapBuilder
+ .createEvent(TAG, "stop", getVersionName, 0L)
+ .set(Fields.SESSION_CONTROL, "stop")
+ .build())
+
+ // clean up the context
+ if (receiver != null) {
+ unregisterReceiver(receiver)
+ receiver = null
+ }
+
+ // reset VPN
+ killProcesses()
+
+ // close connections
+ if (conn != null) {
+ conn.close()
+ conn = null
+ }
+
+ // reset notifications
+ notificationManager.cancel(1)
+ }
+
+ override def getTag = TAG
+
+ override def getServiceMode = Mode.VPN
}
diff --git a/src/main/scala/com/github/shadowsocks/utils/Config.scala b/src/main/scala/com/github/shadowsocks/utils/ConfigUtils.scala
similarity index 64%
rename from src/main/scala/com/github/shadowsocks/utils/Config.scala
rename to src/main/scala/com/github/shadowsocks/utils/ConfigUtils.scala
index c5d6ff2f..5090cd2f 100644
--- a/src/main/scala/com/github/shadowsocks/utils/Config.scala
+++ b/src/main/scala/com/github/shadowsocks/utils/ConfigUtils.scala
@@ -1,11 +1,12 @@
package com.github.shadowsocks.utils
-import android.content.Context
+import android.content.{Intent, SharedPreferences, Context}
import com.github.shadowsocks.ShadowsocksApplication
import com.google.tagmanager.Container
import scalaj.http.{HttpOptions, Http}
+import com.github.shadowsocks.aidl.Config
-object Config {
+object ConfigUtils {
val SHADOWSOCKS = "{\"server\": [%s], \"server_port\": %d, \"local_port\": %d, \"password\": %s, \"timeout\": %d}"
val REDSOCKS = "base {" +
" log_debug = off;" +
@@ -83,10 +84,35 @@ object Config {
val method = proxy(3).trim
new Config(config.isGlobalProxy, config.isGFWList, config.isBypassApps, config.isTrafficStat,
- config.profileName, host, password, method, port, config.localPort, config.proxiedAppString)
+ config.profileName, host, password, method, config.proxiedAppString, port, config.localPort)
}
-}
-case class Config(isGlobalProxy: Boolean, isGFWList: Boolean, isBypassApps: Boolean,
- isTrafficStat: Boolean, profileName: String, var proxy: String, sitekey: String,
- encMethod: String, remotePort: Int, localPort: Int, proxiedAppString: String)
+ def load(settings: SharedPreferences): Config = {
+ val isGlobalProxy = settings.getBoolean(Key.isGlobalProxy, false)
+ val isGFWList = settings.getBoolean(Key.isGFWList, false)
+ val isBypassApps = settings.getBoolean(Key.isBypassApps, false)
+ val isTrafficStat = settings.getBoolean(Key.isTrafficStat, false)
+
+ val profileName = settings.getString(Key.profileName, "default")
+ val proxy = settings.getString(Key.proxy, "127.0.0.1")
+ val sitekey = settings.getString(Key.sitekey, "default")
+ val encMethod = settings.getString(Key.encMethod, "table")
+ val remotePort: Int = try {
+ settings.getString(Key.remotePort, "1984").toInt
+ } catch {
+ case ex: NumberFormatException =>
+ 1984
+ }
+ val localPort: Int = try {
+ settings.getString(Key.localPort, "1984").toInt
+ } catch {
+ case ex: NumberFormatException =>
+ 1984
+ }
+ val proxiedAppString = settings.getString(Key.proxied, "")
+
+ new Config(isGlobalProxy, isGFWList, isBypassApps, isTrafficStat, profileName, proxy, sitekey,
+ encMethod, proxiedAppString, remotePort, localPort)
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/src/main/scala/com/github/shadowsocks/utils/Constants.scala
index cf113971..2b4df64e 100644
--- a/src/main/scala/com/github/shadowsocks/utils/Constants.scala
+++ b/src/main/scala/com/github/shadowsocks/utils/Constants.scala
@@ -1,6 +1,14 @@
package com.github.shadowsocks.utils
import android.content.{Intent, SharedPreferences}
+import com.github.shadowsocks.aidl.Config
+
+object Msg {
+ val CONNECT_FINISH = 1
+ val CONNECT_SUCCESS = 2
+ val CONNECT_FAIL = 3
+ val VPN_ERROR = 6
+}
object Path {
val BASE = "/data/data/com.github.shadowsocks/"
@@ -37,100 +45,21 @@ object Scheme {
val SS = "ss"
}
+object Mode {
+ val NAT = 0
+ val VPN = 1
+}
+
object State {
val INIT = 0
val CONNECTING = 1
val CONNECTED = 2
val STOPPED = 3
+ def isAvailable(state: Int): Boolean = state != CONNECTED && state != CONNECTING
}
object Action {
- val CLOSE = "com.github.shadowsocks.ACTION_SHUTDOWN"
- val UPDATE_STATE = "com.github.shadowsocks.ACTION_UPDATE_STATE"
+ val CLOSE = "com.github.shadowsocks.CLOSE"
val UPDATE_FRAGMENT = "com.github.shadowsocks.ACTION_UPDATE_FRAGMENT"
val UPDATE_PREFS = "com.github.shadowsocks.ACTION_UPDATE_PREFS"
-}
-
-object Extra {
- val STATE = "state"
- val MESSAGE = "message"
-
- def save(settings: SharedPreferences, config: Config) {
- val edit = settings.edit()
-
- edit.putBoolean(Key.isGlobalProxy, config.isGlobalProxy)
- edit.putBoolean(Key.isGFWList, config.isGFWList)
- edit.putBoolean(Key.isBypassApps, config.isBypassApps)
- edit.putBoolean(Key.isTrafficStat, config.isTrafficStat)
-
- edit.putString(Key.profileName, config.profileName)
- edit.putString(Key.proxy, config.proxy)
- edit.putString(Key.sitekey, config.sitekey)
- edit.putString(Key.encMethod, config.encMethod)
- edit.putString(Key.remotePort, config.remotePort.toString)
- edit.putString(Key.localPort, config.localPort.toString)
-
- edit.apply()
- }
-
- def get(intent: Intent): Config = {
- val isGlobalProxy = intent.getBooleanExtra(Key.isGlobalProxy, false)
- val isGFWList = intent.getBooleanExtra(Key.isGFWList, false)
- val isBypassApps = intent.getBooleanExtra(Key.isBypassApps, false)
- val isTrafficStat = intent.getBooleanExtra(Key.isTrafficStat, false)
-
- val profileName = intent.getStringExtra(Key.profileName)
- val proxy = intent.getStringExtra(Key.proxy)
- val sitekey = intent.getStringExtra(Key.sitekey)
- val encMethod = intent.getStringExtra(Key.encMethod)
- val remotePort = intent.getIntExtra(Key.remotePort, 1984)
- val localPort = intent.getIntExtra(Key.localPort, 1984)
- val proxiedString = intent.getStringExtra(Key.proxied)
-
- new Config(isGlobalProxy, isGFWList, isBypassApps, isTrafficStat, profileName, proxy, sitekey,
- encMethod, remotePort, localPort, proxiedString)
- }
-
- def put(settings: SharedPreferences, intent: Intent) {
- val isGlobalProxy = settings.getBoolean(Key.isGlobalProxy, false)
- val isGFWList = settings.getBoolean(Key.isGFWList, false)
- val isBypassApps = settings.getBoolean(Key.isBypassApps, false)
- val isTrafficStat = settings.getBoolean(Key.isTrafficStat, false)
-
-
- val profileName = settings.getString(Key.profileName, "default")
- val proxy = settings.getString(Key.proxy, "127.0.0.1")
- val sitekey = settings.getString(Key.sitekey, "default")
- val encMethod = settings.getString(Key.encMethod, "table")
- val remotePort: Int = try {
- settings.getString(Key.remotePort, "1984").toInt
- } catch {
- case ex: NumberFormatException => {
- 1984
- }
- }
- val localProt: Int = try {
- settings.getString(Key.localPort, "1984").toInt
- } catch {
- case ex: NumberFormatException => {
- 1984
- }
- }
- val proxiedAppString = settings.getString(Key.proxied, "")
-
- intent.putExtra(Key.isGlobalProxy, isGlobalProxy)
- intent.putExtra(Key.isGFWList, isGFWList)
- intent.putExtra(Key.isBypassApps, isBypassApps)
- intent.putExtra(Key.isTrafficStat, isTrafficStat)
-
- intent.putExtra(Key.profileName, profileName)
- intent.putExtra(Key.proxy, proxy)
- intent.putExtra(Key.sitekey, sitekey)
- intent.putExtra(Key.encMethod, encMethod)
- intent.putExtra(Key.remotePort, remotePort)
- intent.putExtra(Key.localPort, localProt)
-
- intent.putExtra(Key.proxied, proxiedAppString)
- }
-}
-
+}
\ No newline at end of file