Commit 0b8114ad authored by Max Lv's avatar Max Lv

Merge pull request #64 from shadowsocks/aidl

AIDL
parents abf050a4 59d304ea
......@@ -54,7 +54,7 @@
</activity>
<activity
android:name=".ShadowVpnActivity"
android:name=".ShadowsocksRunnerActivity"
android:theme="@style/PopupTheme"
android:launchMode="singleTask">
</activity>
......@@ -70,17 +70,22 @@
</activity>
<service
android:name=".ShadowsocksService"
android:name=".ShadowsocksNatService"
android:process=":proxy"
android:exported="false"/>
android:exported="false">
<intent-filter>
<action android:name="com.github.shadowsocks.ShadowsocksNatService"/>
</intent-filter>
</service>
<service
android:name=".ShadowVpnService"
android:name=".ShadowsocksVpnService"
android:label="@string/app_name"
android:process=":vpn"
android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="false">
<intent-filter>
<action android:name="com.github.shadowsocks.ShadowsocksVpnService"/>
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>
......
package com.github.shadowsocks.aidl;
parcelable Config;
\ No newline at end of file
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<Config> CREATOR = new
Parcelable.Creator<Config>() {
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);
}
}
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
package com.github.shadowsocks.aidl;
interface IShadowsocksServiceCallback {
void stateChanged(int state, String msg);
}
\ No newline at end of file
......@@ -17,6 +17,7 @@
<org.jraf.android.backport.switchwidget.Switch
android:id="@+id/switchButton"
android:enabled="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
......
package com.github.shadowsocks
import android.os.{Handler, RemoteCallbackList}
import com.github.shadowsocks.aidl.{Config, IShadowsocksService, IShadowsocksServiceCallback}
import com.github.shadowsocks.utils.{Path, State}
import java.io.{IOException, FileNotFoundException, FileReader, BufferedReader}
import android.util.Log
import android.app.Notification
import android.content.Context
trait BaseService {
var state = State.INIT
final val callbacks = new RemoteCallbackList[IShadowsocksServiceCallback]
protected val binder = new IShadowsocksService.Stub {
override def getMode: Int = {
getServiceMode
}
override def getState: Int = {
state
}
override def unregisterCallback(cb: IShadowsocksServiceCallback) {
if (cb != null ) callbacks.unregister(cb)
}
override def registerCallback(cb: IShadowsocksServiceCallback) {
if (cb != null) callbacks.register(cb)
}
override def stop() {
stopRunner()
}
override def start(config: Config) {
startRunner(config)
}
}
def startRunner(config: Config)
def stopRunner()
def getServiceMode: Int
def getTag: String
def getContext: Context
def changeState(s: Int) {
changeState(s, null)
}
protected def changeState(s: Int, msg: String) {
val handler = new Handler(getContext.getMainLooper)
handler.post(new Runnable {
override def run() {
if (state != s) {
val n = callbacks.beginBroadcast()
for (i <- 0 to n -1) {
callbacks.getBroadcastItem(i).stateChanged(s, msg)
}
callbacks.finishBroadcast()
state = s
}
}
})
}
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(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
}
}
......@@ -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,49 @@ 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()
setPreferenceEnabled(enabled = true)
} else {
changeSwitch(checked = true)
setPreferenceEnabled(enabled = false)
}
state = bgService.getState
}
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)
......@@ -325,6 +358,18 @@ class Shadowsocks
}
}
private def changeSwitch (checked: Boolean) {
switchButton.setOnCheckedChangeListener(null)
switchButton.setChecked(checked)
if (switchButton.isEnabled) {
switchButton.setEnabled(false)
handler.postDelayed(new Runnable {
override def run() { switchButton.setEnabled(true) }
}, 1000)
}
switchButton.setOnCheckedChangeListener(this)
}
private def showProgress(msg: String): Handler = {
clearDialog()
progressDialog = ProgressDialog.show(this, "", msg, true, false)
......@@ -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,21 +452,29 @@ 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
}
private def isTextEmpty(s: String, msg: String): Boolean = {
def isTextEmpty(s: String, msg: String): Boolean = {
if (s == null || s.length <= 0) {
showDialog(msg)
Crouton.makeText(this, msg, Style.ALERT).show()
return true
}
false
}
def cancelStart() {
handler.postDelayed(new Runnable {
override def run() {
clearDialog()
changeSwitch(checked = false)
}
}, 1000)
}
def prepareStartService() {
showProgress(getString(R.string.connecting))
spawn {
......@@ -436,7 +487,7 @@ class Shadowsocks
}
} else {
if (!serviceStart) {
switchButton.setChecked(false)
cancelStart()
}
}
}
......@@ -445,12 +496,16 @@ class Shadowsocks
def onCheckedChanged(compoundButton: CompoundButton, checked: Boolean) {
if (compoundButton eq switchButton) {
checked match {
case true => {
case true =>
prepareStartService()
}
case false => {
case false =>
serviceStop()
}
}
if (switchButton.isEnabled) {
switchButton.setEnabled(false)
handler.postDelayed(new Runnable {
override def run() { switchButton.setEnabled(true) }
}, 1000)
}
}
}
......@@ -491,18 +546,28 @@ class Shadowsocks
onContentChanged()
}
/** 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 +592,34 @@ 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 {
val isRoot = Utils.getRoot
handler.post(new Runnable {
override def run() {
status.edit.putBoolean(Key.isRoot, isRoot).commit()
attachService()
}
}
})
}
}
def attachService() {
if (bgService == null) {
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))
}
}
def deattachService() {
if (bgService != null) {
bgService.unregisterCallback(callback)
unbindService(connection)
bgService = null
}
}
......@@ -571,15 +647,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 +665,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 +810,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,53 +826,35 @@ 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)
}
protected override def onPause() {
super.onPause()
switchButton.setOnCheckedChangeListener(null)
prepared = false
}
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)
if (bgService != null) {
bgService.getState match {
case State.CONNECTED =>
changeSwitch(checked = true)
case State.CONNECTING =>
changeSwitch(checked = true)
case _ =>
changeSwitch(checked = false)
}
state = bgService.getState
}
switchButton.setOnCheckedChangeListener(this)
Config.refresh(this)
// set the listener
switchButton.setOnCheckedChangeListener(Shadowsocks.this)
ConfigUtils.refresh(this)
}
private def setPreferenceEnabled(enabled: Boolean) {
......@@ -848,8 +903,8 @@ class Shadowsocks
override def onDestroy() {
super.onDestroy()
deattachService()
Crouton.cancelAllCroutons()
unregisterReceiver(stateReceiver)
unregisterReceiver(preferenceReceiver)
new BackupManager(this).dataChanged()
}
......@@ -866,7 +921,6 @@ class Shadowsocks
private def recovery() {
val h = showProgress(getString(R.string.recovering))
serviceStop()
spawn {
reset()
......@@ -891,16 +945,14 @@ class Shadowsocks
}
} else {
resultCode match {
case Activity.RESULT_OK => {
case Activity.RESULT_OK =>
prepared = true
if (!serviceStart) {
switchButton.setChecked(false)
cancelStart()
}
}
case _ => {
clearDialog()
case _ =>
cancelStart()
Log.e(Shadowsocks.TAG, "Failed to start VpnService")
}
}
}
}
......@@ -908,7 +960,7 @@ class Shadowsocks
def isVpnEnabled: Boolean = {
if (Shadowsocks.vpnEnabled < 0) {
Shadowsocks.vpnEnabled = if (Build.VERSION.SDK_INT
>= Build.VERSION_CODES.ICE_CREAM_SANDWICH && !Utils.getRoot) {
>= Build.VERSION_CODES.ICE_CREAM_SANDWICH && !status.getBoolean(Key.isRoot, false)) {
1
} else {
0
......@@ -918,43 +970,48 @@ class Shadowsocks
}
def serviceStop() {
sendBroadcast(new Intent(Action.CLOSE))
if (bgService != null) bgService.stop()
}
/** Called when connect button is clicked. */
def serviceStart: Boolean = {
def checkText(key: String): Boolean = {
val text = settings.getString(key, "")
!isTextEmpty(text, getString(R.string.proxy_empty))
}
val proxy = settings.getString(Key.proxy, "")
if (isTextEmpty(proxy, getString(R.string.proxy_empty))) return false
val portText = settings.getString(Key.localPort, "")
if (isTextEmpty(portText, getString(R.string.port_empty))) return false
def checkNumber(key: String): Boolean = {
val text = settings.getString(key, "")
if (isTextEmpty(text, getString(R.string.port_empty))) return false
try {
val port: Int = Integer.valueOf(portText)
val port: Int = Integer.valueOf(text)
if (port <= 1024) {
this.showDialog(getString(R.string.port_alert))
Crouton.makeText(this, R.string.port_alert, Style.ALERT).show()
return false
}
} catch {
case ex: Exception => {
this.showDialog(getString(R.string.port_alert))
case ex: Exception =>
Crouton.makeText(this, R.string.port_alert, Style.ALERT).show()
return false
}
}
true
}
/** Called when connect button is clicked. */
def serviceStart: Boolean = {
if (!checkText(Key.proxy)) return false
if (!checkText(Key.sitekey)) return false
if (!checkNumber(Key.localPort)) return false
if (!checkNumber(Key.remotePort)) 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)
changeSwitch(checked = false)
}
true
}
......@@ -974,9 +1031,8 @@ class Shadowsocks
try {
versionName = getPackageManager.getPackageInfo(getPackageName, 0).versionName
} catch {
case ex: PackageManager.NameNotFoundException => {
case ex: PackageManager.NameNotFoundException =>
versionName = ""
}
}
new AlertDialog.Builder(this)
......@@ -992,20 +1048,6 @@ class Shadowsocks
.show()
}
private def showDialog(msg: String) {
val builder: AlertDialog.Builder = new AlertDialog.Builder(this)
builder
.setMessage(msg)
.setCancelable(false)
.setNegativeButton(getString(R.string.ok_iknow), new DialogInterface.OnClickListener {
def onClick(dialog: DialogInterface, id: Int) {
dialog.cancel()
}
})
val alert: AlertDialog = builder.create
alert.show()
}
def clearDialog() {
if (progressDialog != null) {
progressDialog.dismiss()
......@@ -1014,44 +1056,42 @@ 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)
}
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()
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()
changeSwitch(checked = true)
setPreferenceEnabled(enabled = false)
case State.STOPPED =>
clearDialog()
changeSwitch(checked = 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 = true)
if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
}
}
if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
}
})
}
class PreferenceBroadcastReceiver extends BroadcastReceiver {
......@@ -1061,12 +1101,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)
}
}
}
......@@ -61,18 +61,13 @@ 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 +75,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 +93,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 +123,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 +136,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 +165,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 +182,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 +207,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 +220,6 @@ class ShadowsocksService extends Service {
} else {
builder.setSmallIcon(R.drawable.ic_stat_speed, 1091)
}
} else {
builder.setSmallIcon(R.drawable.ic_stat_shadowsocks)
}
......@@ -364,7 +231,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 +253,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 +272,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 +309,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 +408,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 +419,137 @@ class ShadowsocksService extends Service {
mSetForegroundArgs(0) = boolean2Boolean(x = false)
invokeMethod(mSetForeground, mSetForegroundArgs)
}
override def startRunner(c: Config) {
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)
}
spawn {
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
override def getContext = getBaseContext
}
......@@ -54,34 +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)
}
context.startActivity(new Intent(context, classOf[ShadowsocksRunnerActivity]))
}
}
}
......@@ -40,36 +40,73 @@
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.{Context, 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 {
class ShadowsocksRunnerActivity extends Activity {
lazy val settings = PreferenceManager.getDefaultSharedPreferences(this)
lazy val isRoot = Utils.getRoot
// Services
var bgService: IShadowsocksService = null
val connection = new ServiceConnection {
override def onServiceConnected(name: ComponentName, service: IBinder) {
bgService = IShadowsocksService.Stub.asInterface(service)
if (!isRoot) {
val intent = VpnService.prepare(ShadowsocksRunnerActivity.this)
if (intent != null) {
startActivityForResult(intent, Shadowsocks.REQUEST_CONNECT)
} else {
onActivityResult(Shadowsocks.REQUEST_CONNECT, Activity.RESULT_OK, null)
}
} else {
bgService.start(ConfigUtils.load(settings))
}
}
override def onServiceDisconnected(name: ComponentName) {
bgService = null
}
}
def attachService() {
if (bgService == null) {
val s = if (isRoot) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService]
bindService(new Intent(s.getName), connection, Context.BIND_AUTO_CREATE)
startService(new Intent(this, s))
}
}
def deattachService() {
if (bgService != null) {
unbindService(connection)
bgService = null
}
}
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
val intent = VpnService.prepare(this)
if (intent != null) {
startActivityForResult(intent, Shadowsocks.REQUEST_CONNECT)
} else {
onActivityResult(Shadowsocks.REQUEST_CONNECT, Activity.RESULT_OK, null)
}
attachService()
}
override def onDestroy() {
super.onDestroy()
deattachService()
}
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(settings))
}
case _ =>
Log.e(Shadowsocks.TAG, "Failed to start VpnService")
}
}
finish()
}
......
......@@ -56,22 +56,11 @@ import org.apache.commons.net.util.SubnetUtils
import java.net.InetAddress
import com.github.shadowsocks.utils._
import scala.Some
import com.github.shadowsocks.aidl.{IShadowsocksService, Config}
object ShadowVpnService {
def isServiceStarted(context: Context): Boolean = {
Utils.isServiceStarted("com.github.shadowsocks.ShadowVpnService", context)
}
}
class ShadowVpnService extends VpnService {
class ShadowsocksVpnService extends VpnService with BaseService {
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 +72,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 +98,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 +111,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 +130,7 @@ class ShadowVpnService extends VpnService {
try {
t.join(300)
} catch {
case ignored: InterruptedException => {
}
case ignored: InterruptedException =>
}
!t.isAlive
}
......@@ -308,18 +195,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 +221,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 +234,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 +254,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,8 +263,47 @@ class ShadowVpnService extends VpnService {
override def onCreate() {
super.onCreate()
Config.refresh(this)
ConfigUtils.refresh(this)
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE)
.asInstanceOf[NotificationManager]
}
def killProcesses() {
val sb = new StringBuilder
if (!waitForProcess("shadowsocks")) {
sb ++= "kill -9 `cat /data/data/com.github.shadowsocks/shadowsocks.pid`" ++= "\n"
sb ++= "killall -9 shadowsocks" ++= "\n"
}
if (!waitForProcess("tun2socks")) {
sb ++= "kill -9 `cat /data/data/com.github.shadowsocks/tun2socks.pid`" ++= "\n"
sb ++= "killall -9 tun2socks" ++= "\n"
}
if (!waitForProcess("pdnsd")) {
sb ++= "kill -9 `cat /data/data/com.github.shadowsocks/pdnsd.pid`" ++= "\n"
sb ++= "killall -9 pdnsd" ++= "\n"
}
Utils.runCommand(sb.toString())
}
override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = {
Service.START_STICKY
}
override def startRunner(c: Config) {
config = c
// ensure the VPNService is prepared
if (VpnService.prepare(this) != null) {
val i = new Intent(this, classOf[ShadowsocksRunnerActivity])
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(i)
return
}
// start the tracker
EasyTracker
.getInstance(this)
.send(MapBuilder
......@@ -389,26 +311,66 @@ class ShadowVpnService extends VpnService {
.set(Fields.SESSION_CONTROL, "start")
.build())
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()
stopRunner()
}
}
registerReceiver(receiver, filter)
changeState(State.CONNECTING)
spawn {
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 onDestroy() {
killProcesses()
override def stopRunner() {
// channge the state
changeState(State.STOPPED)
// stop the tracker
EasyTracker
.getInstance(this)
.send(MapBuilder
......@@ -416,45 +378,26 @@ class ShadowVpnService extends VpnService {
.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)
super.onDestroy()
}
def killProcesses() {
val sb = new StringBuilder
if (!waitForProcess("shadowsocks")) {
sb ++= "kill -9 `cat /data/data/com.github.shadowsocks/shadowsocks.pid`" ++= "\n"
sb ++= "killall -9 shadowsocks" ++= "\n"
}
if (!waitForProcess("tun2socks")) {
sb ++= "kill -9 `cat /data/data/com.github.shadowsocks/tun2socks.pid`" ++= "\n"
sb ++= "killall -9 tun2socks" ++= "\n"
}
if (!waitForProcess("pdnsd")) {
sb ++= "kill -9 `cat /data/data/com.github.shadowsocks/pdnsd.pid`" ++= "\n"
sb ++= "killall -9 pdnsd" ++= "\n"
}
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 getTag = TAG
override def getServiceMode = Mode.VPN
override def getContext = getBaseContext
}
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
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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment