From c480137decf3c593fb5cad0909d58c459b082c3b Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 5 Jul 2016 14:07:09 +0800 Subject: [PATCH] Remove .aidl.Config and persisting SharedPreferences (#742) Previously a profile can exist in the following forms: * `.database.Profile` used for everything backend; * `.ShadowsocksSettings` which uses Preferences to provide UI to tweak settings; * `.aidl.Config` used for IPC; * `/shared_pref/com.github.shadowsocks_preferences.xml` which exists for no apparent reason. Thus long code block can be seen whose purpose is simply converting the data from one form to another. This commit intends to remove the latter two by: 1. Using profile ID and take advantage of fs r/w lock SQLite uses for IPC and better extensibility; 2. Stop persisting redundant fields in the preferences file and update database directly. --- .../com/github/shadowsocks/aidl/Config.aidl | 3 - .../com/github/shadowsocks/aidl/Config.java | 97 -------- .../shadowsocks/aidl/IShadowsocksService.aidl | 3 +- src/main/res/xml/pref_all.xml | 11 + .../com/github/shadowsocks/AppManager.scala | 26 ++- .../com/github/shadowsocks/BaseService.scala | 95 ++++++-- .../shadowsocks/ProfileManagerActivity.scala | 10 +- .../com/github/shadowsocks/Shadowsocks.scala | 50 ++--- .../shadowsocks/ShadowsocksApplication.scala | 12 +- .../shadowsocks/ShadowsocksNatService.scala | 141 +++++------- .../ShadowsocksRunnerActivity.scala | 4 +- .../ShadowsocksRunnerService.scala | 4 +- .../shadowsocks/ShadowsocksSettings.scala | 69 ++++-- .../shadowsocks/ShadowsocksVpnService.scala | 139 +++++------- .../shadowsocks/database/ProfileManager.scala | 65 +----- .../shadowsocks/utils/ConfigUtils.scala | 211 ------------------ .../github/shadowsocks/utils/Constants.scala | 95 ++++++++ .../com/github/shadowsocks/utils/Utils.scala | 9 + 18 files changed, 414 insertions(+), 630 deletions(-) delete mode 100644 src/main/aidl/com/github/shadowsocks/aidl/Config.aidl delete mode 100644 src/main/aidl/com/github/shadowsocks/aidl/Config.java delete mode 100644 src/main/scala/com/github/shadowsocks/utils/ConfigUtils.scala diff --git a/src/main/aidl/com/github/shadowsocks/aidl/Config.aidl b/src/main/aidl/com/github/shadowsocks/aidl/Config.aidl deleted file mode 100644 index 667e8d16..00000000 --- a/src/main/aidl/com/github/shadowsocks/aidl/Config.aidl +++ /dev/null @@ -1,3 +0,0 @@ -package com.github.shadowsocks.aidl; - -parcelable Config; \ No newline at end of file diff --git a/src/main/aidl/com/github/shadowsocks/aidl/Config.java b/src/main/aidl/com/github/shadowsocks/aidl/Config.java deleted file mode 100644 index 13ec1ff8..00000000 --- a/src/main/aidl/com/github/shadowsocks/aidl/Config.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.github.shadowsocks.aidl; - -import android.os.Parcel; -import android.os.Parcelable; - -public class Config implements Parcelable { - - public boolean isProxyApps = false; - public boolean isBypassApps = false; - public boolean isUdpDns = false; - public boolean isAuth = false; - public boolean isIpv6 = false; - - public String profileName = "Untitled"; - public String proxy = "127.0.0.1"; - public String sitekey = "null"; - public String route = "all"; - - public String encMethod = "rc4"; - public String proxiedAppString = ""; - - public int remotePort = 1984; - public int localPort = 1080; - - public int profileId = 0; - - 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 isProxyApps, boolean isBypassApps, - boolean isUdpDns, boolean isAuth, boolean isIpv6, String profileName, String proxy, String sitekey, - String encMethod, String proxiedAppString, String route, int remotePort, int localPort, int profileId) { - this.isProxyApps = isProxyApps; - this.isBypassApps = isBypassApps; - this.isUdpDns = isUdpDns; - this.isAuth = isAuth; - this.isIpv6 = isIpv6; - this.profileName = profileName; - this.proxy = proxy; - this.sitekey = sitekey; - this.encMethod = encMethod; - this.proxiedAppString = proxiedAppString; - this.route = route; - this.remotePort = remotePort; - this.localPort = localPort; - this.profileId = profileId; - } - - private Config(Parcel in) { - readFromParcel(in); - } - - public void readFromParcel(Parcel in) { - isProxyApps = in.readInt() == 1; - isBypassApps = in.readInt() == 1; - isUdpDns = in.readInt() == 1; - isAuth = in.readInt() == 1; - isIpv6 = in.readInt() == 1; - profileName = in.readString(); - proxy = in.readString(); - sitekey = in.readString(); - encMethod = in.readString(); - proxiedAppString = in.readString(); - route = in.readString(); - remotePort = in.readInt(); - localPort = in.readInt(); - profileId = in.readInt(); - } - - @Override public int describeContents() { - return 0; - } - - @Override public void writeToParcel(Parcel out, int flags) { - out.writeInt(isProxyApps ? 1 : 0); - out.writeInt(isBypassApps ? 1 : 0); - out.writeInt(isUdpDns ? 1 : 0); - out.writeInt(isAuth ? 1 : 0); - out.writeInt(isIpv6 ? 1 : 0); - out.writeString(profileName); - out.writeString(proxy); - out.writeString(sitekey); - out.writeString(encMethod); - out.writeString(proxiedAppString); - out.writeString(route); - out.writeInt(remotePort); - out.writeInt(localPort); - out.writeInt(profileId); - } -} diff --git a/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl b/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl index 7b46ad9a..7245721e 100644 --- a/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl +++ b/src/main/aidl/com/github/shadowsocks/aidl/IShadowsocksService.aidl @@ -1,6 +1,5 @@ package com.github.shadowsocks.aidl; -import com.github.shadowsocks.aidl.Config; import com.github.shadowsocks.aidl.IShadowsocksServiceCallback; interface IShadowsocksService { @@ -9,5 +8,5 @@ interface IShadowsocksService { oneway void registerCallback(IShadowsocksServiceCallback cb); oneway void unregisterCallback(IShadowsocksServiceCallback cb); - oneway void use(in Config config); + oneway void use(in int profileId); } diff --git a/src/main/res/xml/pref_all.xml b/src/main/res/xml/pref_all.xml index e29c44ec..fb1246ba 100644 --- a/src/main/res/xml/pref_all.xml +++ b/src/main/res/xml/pref_all.xml @@ -7,36 +7,43 @@ @@ -47,20 +54,24 @@ diff --git a/src/main/scala/com/github/shadowsocks/AppManager.scala b/src/main/scala/com/github/shadowsocks/AppManager.scala index 9eb02b36..54d9ea33 100644 --- a/src/main/scala/com/github/shadowsocks/AppManager.scala +++ b/src/main/scala/com/github/shadowsocks/AppManager.scala @@ -117,8 +117,10 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { proxiedApps.add(item.packageName) check.setChecked(true) } - if (!appsLoading.get) - app.editor.putString(Key.proxied, proxiedApps.mkString("\n")).apply + if (!appsLoading.get) { + profile.individual = proxiedApps.mkString("\n") + app.profileManager.updateProfile(profile) + } } } @@ -141,9 +143,9 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { private var loadingView: View = _ private val appsLoading = new AtomicBoolean private var handler: Handler = _ + private val profile = app.currentProfile.get - private def initProxiedApps(str: String = app.settings.getString(Key.proxied, "")) = - proxiedApps = str.split('\n').to[mutable.HashSet] + private def initProxiedApps(str: String = profile.individual) = proxiedApps = str.split('\n').to[mutable.HashSet] override def onDestroy() { instance = null @@ -225,17 +227,23 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { toolbar.inflateMenu(R.menu.app_manager_menu) toolbar.setOnMenuItemClickListener(this) - app.editor.putBoolean(Key.isProxyApps, true).apply + if (!profile.proxyApps) { + profile.proxyApps = true + app.profileManager.updateProfile(profile) + } findViewById(R.id.onSwitch).asInstanceOf[Switch] .setOnCheckedChangeListener((_, checked) => { - app.editor.putBoolean(Key.isProxyApps, checked).apply + profile.proxyApps = checked + app.profileManager.updateProfile(profile) finish() }) bypassSwitch = findViewById(R.id.bypassSwitch).asInstanceOf[Switch] - bypassSwitch.setChecked(app.settings.getBoolean(Key.isBypassApps, false)) - bypassSwitch.setOnCheckedChangeListener((_, checked) => - app.editor.putBoolean(Key.isBypassApps, checked).apply()) + bypassSwitch.setChecked(profile.bypass) + bypassSwitch.setOnCheckedChangeListener((_, checked) => { + profile.bypass = checked + app.profileManager.updateProfile(profile) + }) initProxiedApps() loadingView = findViewById(R.id.loading) diff --git a/src/main/scala/com/github/shadowsocks/BaseService.scala b/src/main/scala/com/github/shadowsocks/BaseService.scala index 709d7a55..12c61eb4 100644 --- a/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -47,17 +47,19 @@ import android.os.{Handler, RemoteCallbackList} import android.text.TextUtils import android.util.Log import android.widget.Toast -import com.github.shadowsocks.aidl.{Config, IShadowsocksService, IShadowsocksServiceCallback} +import com.github.kevinsawicki.http.HttpRequest +import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback} import com.github.shadowsocks.utils._ import com.github.shadowsocks.ShadowsocksApplication.app +import com.github.shadowsocks.database.Profile trait BaseService extends Service { @volatile private var state = State.STOPPED - @volatile protected var config: Config = null + @volatile protected var profile: Profile = _ - var timer: Timer = null - var trafficMonitorThread: TrafficMonitorThread = null + var timer: Timer = _ + var trafficMonitorThread: TrafficMonitorThread = _ final val callbacks = new RemoteCallbackList[IShadowsocksServiceCallback] var callbacksCount: Int = _ @@ -101,26 +103,50 @@ trait BaseService extends Service { } } - override def use(config: Config) = synchronized(state match { - case State.STOPPED => if (config != null && checkConfig(config)) startRunner(config) - case State.CONNECTED => - if (config == null) stopRunner(true) - else if (config.profileId != BaseService.this.config.profileId && checkConfig(config)) { + override def use(profileId: Int) = synchronized(if (profileId < 0) stopRunner(true) else { + val profile = app.profileManager.getProfile(profileId).orNull + if (profile == null) stopRunner(true) else state match { + case State.STOPPED => if (checkProfile(profile)) startRunner(profile) + case State.CONNECTED => if (profileId != BaseService.this.profile.id && checkProfile(profile)) { stopRunner(false) - startRunner(config) + startRunner(profile) } - case _ => Log.w(BaseService.this.getClass.getSimpleName, "Illegal state when invoking use: " + state) + case _ => Log.w(BaseService.this.getClass.getSimpleName, "Illegal state when invoking use: " + state) + } }) } - def checkConfig(config: Config) = if (TextUtils.isEmpty(config.proxy) || TextUtils.isEmpty(config.sitekey)) { + def checkProfile(profile: Profile) = if (TextUtils.isEmpty(profile.host) || TextUtils.isEmpty(profile.password)) { changeState(State.STOPPED) stopRunner(true) false } else true - def startRunner(config: Config) { - this.config = config + def connect() = if (profile.host == "198.199.101.152") try { + val holder = app.containerHolder + val container = holder.getContainer + val url = container.getString("proxy_url") + val sig = Utils.getSignature(this) + val list = HttpRequest + .post(url) + .connectTimeout(2000) + .readTimeout(2000) + .send("sig="+sig) + .body + val proxies = util.Random.shuffle(list.split('|').toSeq) + val proxy = proxies.head.split(':') + profile.host = proxy(0).trim + profile.remotePort = proxy(1).trim.toInt + profile.password = proxy(2).trim + profile.method = proxy(3).trim + } catch { + case ex: Exception => + changeState(State.STOPPED, getString(R.string.service_failed)) + stopRunner(true) + } + + def startRunner(profile: Profile) { + this.profile = profile startService(new Intent(this, getClass)) TrafficMonitor.reset() @@ -135,6 +161,12 @@ trait BaseService extends Service { registerReceiver(closeReceiver, filter) closeReceiverRegistered = true } + + app.track(getClass.getSimpleName, "start") + + changeState(State.CONNECTING) + + Utils.ThrowableFuture(connect) } def stopRunner(stopService: Boolean) { @@ -158,17 +190,19 @@ trait BaseService extends Service { // stop the service if nothing has bound to it if (stopService) stopSelf() + + profile = null } def updateTrafficTotal(tx: Long, rx: Long) { - val config = this.config // avoid race conditions without locking - if (config != null) { - app.profileManager.getProfile(config.profileId) match { - case Some(profile) => - profile.tx += tx - profile.rx += rx - app.profileManager.updateProfile(profile) - case None => // Ignore + val profile = this.profile // avoid race conditions without locking + if (profile != null) { + app.profileManager.getProfile(profile.id) match { + case Some(p) => // default profile may have host, etc. modified + p.tx += tx + p.rx += rx + app.profileManager.updateProfile(p) + case None => } } } @@ -197,6 +231,12 @@ trait BaseService extends Service { }) } + + override def onCreate() { + super.onCreate() + app.refreshContainerHolder + } + // Service of shadowsocks should always be started explicitly override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = Service.START_NOT_STICKY @@ -217,4 +257,15 @@ trait BaseService extends Service { state = s }) } + + def getBlackList = { + val default = getString(R.string.black_list) + try { + val container = app.containerHolder.getContainer + val update = container.getString("black_list") + if (update == null || update.isEmpty) default else update + } catch { + case ex: Exception => default + } + } } diff --git a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala index d26f0fc3..63476a32 100644 --- a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala +++ b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala @@ -11,20 +11,19 @@ import android.provider.Settings import android.support.v7.app.{AlertDialog, AppCompatActivity} import android.support.v7.widget.RecyclerView.ViewHolder import android.support.v7.widget.Toolbar.OnMenuItemClickListener +import android.support.v7.widget._ import android.support.v7.widget.helper.ItemTouchHelper import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback -import android.support.v7.widget._ import android.text.style.TextAppearanceSpan import android.text.{SpannableStringBuilder, Spanned, TextUtils} -import android.util.Log import android.view._ import android.widget.{CheckedTextView, ImageView, LinearLayout, Toast} import com.github.clans.fab.{FloatingActionButton, FloatingActionMenu} +import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.database.Profile import com.github.shadowsocks.utils.{Key, Parser, TrafficMonitor, Utils} import com.github.shadowsocks.widget.UndoSnackbarManager -import com.github.shadowsocks.ShadowsocksApplication.app import net.glxn.qrgen.android.QRCode import scala.collection.mutable.ArrayBuffer @@ -281,8 +280,9 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic v.getId match { case R.id.fab_manual_add => menu.toggle(true) - app.profileManager.reload(-1) - app.switchProfile(app.profileManager.save.id) + val profile = app.profileManager.createProfile() + app.profileManager.updateProfile(profile) + app.switchProfile(profile.id) finish case R.id.fab_qrcode_add => menu.toggle(false) diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index f5f9725f..61c410c6 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -100,7 +100,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { import Shadowsocks._ // Variables - var serviceStarted = false + var serviceStarted: Boolean = _ var fab: FloatingActionButton = _ var fabProgressCircle: FABProgressCircle = _ var progressDialog: ProgressDialog = _ @@ -186,23 +186,6 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { if (!app.settings.getBoolean(app.getVersionName, false)) { app.editor.putBoolean(app.getVersionName, true).apply() - try { - // Workaround that convert port(String) to port(Int) - val oldLocalPort = app.settings.getString(Key.localPort, "") - val oldRemotePort = app.settings.getString(Key.remotePort, "") - - if (oldLocalPort != "") { - app.editor.putInt(Key.localPort, oldLocalPort.toInt).apply() - } - if (oldRemotePort != "") { - app.editor.putInt(Key.remotePort, oldRemotePort.toInt).apply() - } - } catch { - case ex: Exception => // Ignore - } - val oldProxiedApps = app.settings.getString(Key.proxied, "") - if (oldProxiedApps.contains('|')) - app.editor.putString(Key.proxied, DBHelper.updateProxiedApps(this, oldProxiedApps)).apply() recovery() @@ -364,7 +347,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { val title = field.get(toolbar).asInstanceOf[TextView] title.setFocusable(true) title.setGravity(0x10) - title.getLayoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; + title.getLayoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT title.setOnClickListener(_ => startActivity(new Intent(this, classOf[ProfileManagerActivity]))) val typedArray = obtainStyledAttributes(Array(R.attr.selectableItemBackgroundBorderless)) title.setBackgroundResource(typedArray.getResourceId(0, 0)) @@ -434,11 +417,6 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { handler.post(() => attachService) } - protected override def onPause() { - super.onPause() - app.profileManager.save - } - private def hideCircle() { try { fabProgressCircle.hide() @@ -489,34 +467,34 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { private def updateCurrentProfile() = { // Check if current profile changed - if (app.profileId != currentProfile.id) { - currentProfile = app.currentProfile match { + if (preferences.profile == null || app.profileId != preferences.profile.id) { + updatePreferenceScreen(app.currentProfile match { case Some(profile) => profile // updated case None => // removed app.switchProfile((app.profileManager.getFirstProfile match { case Some(first) => first case None => app.profileManager.createDefault() }).id) - } - - updatePreferenceScreen() + }) if (serviceStarted) serviceLoad() true - } else false + } else { + preferences.refreshProfile() + false + } } protected override def onResume() { super.onResume() - ConfigUtils.refresh(this) + app.refreshContainerHolder updateState(updateCurrentProfile()) } - private def updatePreferenceScreen() { - val profile = currentProfile + private def updatePreferenceScreen(profile: Profile) { if (profile.host == "198.199.101.152") if (adView == null) { adView = new AdView(this) adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322") @@ -525,7 +503,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { adView.loadAd(new AdRequest.Builder().build()) } else adView.setVisibility(View.VISIBLE) else if (adView != null) adView.setVisibility(View.GONE) - preferences.update(profile) + preferences.setProfile(profile) } override def onStart() { @@ -587,7 +565,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { } def serviceStop() { - if (bgService != null) bgService.use(null) + if (bgService != null) bgService.use(-1) } def checkText(key: String): Boolean = { @@ -599,7 +577,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { /** Called when connect button is clicked. */ def serviceLoad() { - bgService.use(ConfigUtils.loadFromSharedPreferences) + bgService.use(app.profileId) if (app.isVpnEnabled) { changeSwitch(checked = false) diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index f10c607c..200606dc 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -60,13 +60,12 @@ object ShadowsocksApplication { class ShadowsocksApplication extends Application { import ShadowsocksApplication._ - lazy val dbHelper = new DBHelper(this) final val SIG_FUNC = "getSignature" var containerHolder: ContainerHolder = _ lazy val tracker = GoogleAnalytics.getInstance(this).newTracker(R.xml.tracker) lazy val settings = PreferenceManager.getDefaultSharedPreferences(this) lazy val editor = settings.edit - lazy val profileManager = new ProfileManager(settings, dbHelper) + lazy val profileManager = new ProfileManager(new DBHelper(this)) def isNatEnabled = settings.getBoolean(Key.isNAT, false) def isVpnEnabled = !isNatEnabled @@ -85,12 +84,12 @@ class ShadowsocksApplication extends Application { .build()) def profileId = settings.getInt(Key.profileId, -1) - def profileId(i: Int) = settings.edit.putInt(Key.profileId, i).apply + def profileId(i: Int) = editor.putInt(Key.profileId, i).apply def currentProfile = profileManager.getProfile(profileId) def switchProfile(id: Int) = { profileId(id) - profileManager.load(id) + profileManager.getProfile(id) getOrElse profileManager.createProfile() } override def onCreate() { @@ -117,4 +116,9 @@ class ShadowsocksApplication extends Application { } pending.setResultCallback(callback, 2, TimeUnit.SECONDS) } + + def refreshContainerHolder { + val holder = app.containerHolder + if (holder != null) holder.refresh() + } } diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index 4774996a..90390661 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -48,9 +48,9 @@ import android.content._ import android.net.{ConnectivityManager, Network} import android.os._ import android.util.{Log, SparseArray} -import com.github.shadowsocks.aidl.Config import com.github.shadowsocks.utils._ import com.github.shadowsocks.ShadowsocksApplication.app +import com.github.shadowsocks.database.Profile import scala.collection.JavaConversions._ import scala.collection.mutable.ArrayBuffer @@ -144,22 +144,22 @@ class ShadowsocksNatService extends BaseService { } def startShadowsocksDaemon() { - if (config.route != Route.ALL) { - val acl: Array[Array[String]] = config.route match { + if (profile.route != Route.ALL) { + val acl: Array[Array[String]] = profile.route match { case Route.BYPASS_LAN => Array(getResources.getStringArray(R.array.private_route)) case Route.BYPASS_CHN => Array(getResources.getStringArray(R.array.chn_route)) case Route.BYPASS_LAN_CHN => Array(getResources.getStringArray(R.array.private_route), getResources.getStringArray(R.array.chn_route)) } - ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/acl.list"))(p => { + Utils.printToFile(new File(getApplicationInfo.dataDir + "/acl.list"))(p => { acl.flatten.foreach(p.println) }) } val conf = ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, config.localPort, - config.sitekey, config.encMethod, 600) - ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/ss-local-nat.conf"))(p => { + .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort, + profile.password, profile.method, 600) + Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-local-nat.conf"))(p => { p.println(conf) }) @@ -170,9 +170,9 @@ class ShadowsocksNatService extends BaseService { , "-P", getApplicationInfo.dataDir , "-c" , getApplicationInfo.dataDir + "/ss-local-nat.conf") - if (config.isAuth) cmd += "-A" + if (profile.auth) cmd += "-A" - if (config.route != Route.ALL) { + if (profile.route != Route.ALL) { cmd += "--acl" cmd += (getApplicationInfo.dataDir + "/acl.list") } @@ -182,11 +182,11 @@ class ShadowsocksNatService extends BaseService { } def startTunnel() { - if (config.isUdpDns) { + if (profile.udpdns) { val conf = ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, 8153, - config.sitekey, config.encMethod, 10) - ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => { + .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, 8153, + profile.password, profile.method, 10) + Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => { p.println(conf) }) val cmd = new ArrayBuffer[String] @@ -200,7 +200,7 @@ class ShadowsocksNatService extends BaseService { cmd += ("-l" , "8153") - if (config.isAuth) cmd += "-A" + if (profile.auth) cmd += "-A" if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) @@ -208,9 +208,9 @@ class ShadowsocksNatService extends BaseService { } else { val conf = ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, 8163, - config.sitekey, config.encMethod, 10) - ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => { + .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, 8163, + profile.password, profile.method, 10) + Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => { p.println(conf) }) val cmdBuf = new ArrayBuffer[String] @@ -223,7 +223,7 @@ class ShadowsocksNatService extends BaseService { , "-P", getApplicationInfo.dataDir , "-c" , getApplicationInfo.dataDir + "/ss-tunnel-nat.conf") - if (config.isAuth) cmdBuf += "-A" + if (profile.auth) cmdBuf += "-A" if (BuildConfig.DEBUG) Log.d(TAG, cmdBuf.mkString(" ")) @@ -233,16 +233,15 @@ class ShadowsocksNatService extends BaseService { def startDnsDaemon() { - val conf = if (config.route == Route.BYPASS_CHN || config.route == Route.BYPASS_LAN_CHN) { - val blackList = ConfigUtils.getBlackList(this) + val conf = if (profile.route == Route.BYPASS_CHN || profile.route == Route.BYPASS_LAN_CHN) { ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, - "127.0.0.1", 8153, blackList, 8163, "") + "127.0.0.1", 8153, getBlackList, 8163, "") } else { ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, "127.0.0.1", 8153, 8163, "") } - ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-nat.conf"))(p => { + Utils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-nat.conf"))(p => { p.println(conf) }) val cmd = getApplicationInfo.dataDir + "/pdnsd -c " + getApplicationInfo.dataDir + "/pdnsd-nat.conf" @@ -253,10 +252,10 @@ class ShadowsocksNatService extends BaseService { } def startRedsocksDaemon() { - val conf = ConfigUtils.REDSOCKS.formatLocal(Locale.ENGLISH, config.localPort) + val conf = ConfigUtils.REDSOCKS.formatLocal(Locale.ENGLISH, profile.localPort) val cmd = "%s/redsocks -c %s/redsocks-nat.conf" .formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, getApplicationInfo.dataDir) - ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/redsocks-nat.conf"))(p => { + Utils.printToFile(new File(getApplicationInfo.dataDir + "/redsocks-nat.conf"))(p => { p.println(conf) }) @@ -268,7 +267,7 @@ class ShadowsocksNatService extends BaseService { def handleConnection: Boolean = { startTunnel() - if (!config.isUdpDns) startDnsDaemon() + if (!profile.udpdns) startDnsDaemon() startRedsocksDaemon() startShadowsocksDaemon() setupIptables() @@ -285,11 +284,6 @@ class ShadowsocksNatService extends BaseService { } } - override def onCreate() { - super.onCreate() - ConfigUtils.refresh(this) - } - def killProcesses() { if (sslocalProcess != null) { sslocalProcess.destroy() @@ -319,8 +313,8 @@ class ShadowsocksNatService extends BaseService { init_sb.append(Utils.iptables + " -t nat -F OUTPUT") val cmd_bypass = Utils.iptables + CMD_IPTABLES_RETURN - if (!InetAddress.getByName(config.proxy.toUpperCase).isInstanceOf[Inet6Address]) { - init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d " + config.proxy)) + if (!InetAddress.getByName(profile.host.toUpperCase).isInstanceOf[Inet6Address]) { + init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d " + profile.host)) } init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d 127.0.0.1")) init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-m owner --uid-owner " + myUid)) @@ -329,14 +323,14 @@ class ShadowsocksNatService extends BaseService { init_sb.append(Utils.iptables + " -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:8153") - if (!config.isProxyApps || config.isBypassApps) { + if (!profile.proxyApps || profile.bypass) { http_sb.append(Utils.iptables + CMD_IPTABLES_DNAT_ADD_SOCKS) } - if (config.isProxyApps) { + if (profile.proxyApps) { val uidMap = getPackageManager.getInstalledApplications(0).map(ai => ai.packageName -> ai.uid).toMap - for (pn <- config.proxiedAppString.split('\n')) uidMap.get(pn) match { + for (pn <- profile.individual.split('\n')) uidMap.get(pn) match { case Some(uid) => - if (!config.isBypassApps) { + if (!profile.bypass) { http_sb.append((Utils.iptables + CMD_IPTABLES_DNAT_ADD_SOCKS) .replace("-t nat", "-t nat -m owner --uid-owner " + uid)) } else { @@ -348,59 +342,46 @@ class ShadowsocksNatService extends BaseService { Console.runRootCommand((init_sb ++ http_sb).toArray) } - override def startRunner(config: Config) { + override def startRunner(profile: Profile) { if (!Console.isRoot) { changeState(State.STOPPED, getString(R.string.nat_no_root)) + stopRunner(true) return } - super.startRunner(config) + super.startRunner(profile) + } - app.track(TAG, "start") - - changeState(State.CONNECTING) + override def connect() { + super.connect() - Utils.ThrowableFuture { - if (config.proxy == "198.199.101.152") { - val holder = app.containerHolder - try { - this.config = ConfigUtils.getPublicConfig(getBaseContext, holder.getContainer, config) - } catch { - case ex: Exception => - changeState(State.STOPPED, getString(R.string.service_failed)) - stopRunner(true) - this.config = null + if (this.profile != null) { + + // Clean up + killProcesses() + + var resolved: Boolean = false + if (!Utils.isNumeric(this.profile.host)) { + Utils.resolve(this.profile.host, enableIPv6 = true) match { + case Some(a) => + this.profile.host = a + resolved = true + case None => resolved = false } + } else { + resolved = true } - if (this.config != null) { - - // Clean up - killProcesses() - - var resolved: Boolean = false - if (!Utils.isNumeric(config.proxy)) { - Utils.resolve(config.proxy, enableIPv6 = true) match { - case Some(a) => - config.proxy = a - resolved = true - case None => resolved = false - } - } else { - resolved = true - } - - if (!resolved) { - changeState(State.STOPPED, getString(R.string.invalid_server)) - stopRunner(true) - } else if (handleConnection) { - // Set DNS - flushDns() - changeState(State.CONNECTED) - notification = new ShadowsocksNotification(this, config.profileName, true) - } else { - changeState(State.STOPPED, getString(R.string.service_failed)) - stopRunner(true) - } + if (!resolved) { + changeState(State.STOPPED, getString(R.string.invalid_server)) + stopRunner(true) + } else if (handleConnection) { + // Set DNS + flushDns() + changeState(State.CONNECTED) + notification = new ShadowsocksNotification(this, profile.name, true) + } else { + changeState(State.STOPPED, getString(R.string.service_failed)) + stopRunner(true) } } } diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala index bc71eb50..7d52e9df 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerActivity.scala @@ -66,7 +66,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext { def startBackgroundService() { if (app.isNatEnabled) { - bgService.use(ConfigUtils.loadFromSharedPreferences) + bgService.use(app.profileId) finish() } else { val intent = VpnService.prepare(ShadowsocksRunnerActivity.this) @@ -109,7 +109,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext { resultCode match { case Activity.RESULT_OK => if (bgService != null) { - bgService.use(ConfigUtils.loadFromSharedPreferences) + bgService.use(app.profileId) } case _ => Log.e(TAG, "Failed to start VpnService") diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala index 929b6161..7613556e 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksRunnerService.scala @@ -58,11 +58,11 @@ class ShadowsocksRunnerService extends Service with ServiceBoundContext { } def startBackgroundService() { - if (app.isNatEnabled) bgService.use(ConfigUtils.loadFromSharedPreferences) else { + if (app.isNatEnabled) bgService.use(app.profileId) else { val intent = VpnService.prepare(ShadowsocksRunnerService.this) if (intent == null) { if (bgService != null) { - bgService.use(ConfigUtils.loadFromSharedPreferences) + bgService.use(app.profileId) } } } diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala index 61ef14c3..4f50faaa 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala @@ -9,10 +9,10 @@ import android.os.{Build, Bundle} import android.preference.{Preference, PreferenceFragment, SwitchPreference} import android.support.v7.app.AlertDialog import android.webkit.{WebView, WebViewClient} +import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.database.Profile import com.github.shadowsocks.preferences._ import com.github.shadowsocks.utils.{Key, Utils} -import com.github.shadowsocks.ShadowsocksApplication.app object ShadowsocksSettings { // Constants @@ -74,12 +74,58 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan addPreferencesFromResource(R.xml.pref_all) getPreferenceManager.getSharedPreferences.registerOnSharedPreferenceChangeListener(this) + findPreference(Key.profileName).setOnPreferenceChangeListener((_, value) => { + profile.name = value.asInstanceOf[String] + app.profileManager.updateProfile(profile) + }) + findPreference(Key.proxy).setOnPreferenceChangeListener((_, value) => { + profile.host = value.asInstanceOf[String] + app.profileManager.updateProfile(profile) + }) + findPreference(Key.remotePort).setOnPreferenceChangeListener((_, value) => { + profile.remotePort = value.asInstanceOf[Int] + app.profileManager.updateProfile(profile) + }) + findPreference(Key.localPort).setOnPreferenceChangeListener((_, value) => { + profile.localPort = value.asInstanceOf[Int] + app.profileManager.updateProfile(profile) + }) + findPreference(Key.sitekey).setOnPreferenceChangeListener((_, value) => { + profile.password = value.asInstanceOf[String] + app.profileManager.updateProfile(profile) + }) + findPreference(Key.encMethod).setOnPreferenceChangeListener((_, value) => { + profile.method = value.asInstanceOf[String] + app.profileManager.updateProfile(profile) + }) + findPreference(Key.route).setOnPreferenceChangeListener((_, value) => { + profile.route = value.asInstanceOf[String] + app.profileManager.updateProfile(profile) + }) + isProxyApps = findPreference(Key.isProxyApps).asInstanceOf[SwitchPreference] - isProxyApps.setOnPreferenceClickListener((preference: Preference) => { + isProxyApps.setOnPreferenceClickListener(_ => { startActivity(new Intent(activity, classOf[AppManager])) isProxyApps.setChecked(true) false }) + isProxyApps.setOnPreferenceChangeListener((_, value) => { + profile.proxyApps = value.asInstanceOf[Boolean] + app.profileManager.updateProfile(profile) + }) + + findPreference(Key.isUdpDns).setOnPreferenceChangeListener((_, value) => { + profile.udpdns = value.asInstanceOf[Boolean] + app.profileManager.updateProfile(profile) + }) + findPreference(Key.isAuth).setOnPreferenceChangeListener((_, value) => { + profile.auth = value.asInstanceOf[Boolean] + app.profileManager.updateProfile(profile) + }) + findPreference(Key.isIpv6).setOnPreferenceChangeListener((_, value) => { + profile.ipv6 = value.asInstanceOf[Boolean] + app.profileManager.updateProfile(profile) + }) val switch = findPreference(Key.isAutoConnect).asInstanceOf[SwitchPreference] switch.setOnPreferenceChangeListener((_, value) => { @@ -131,9 +177,9 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan }) } - override def onResume { - super.onResume() - isProxyApps.setChecked(app.settings.getBoolean(Key.isProxyApps, false)) // update + def refreshProfile() { + profile = app.currentProfile.get + isProxyApps.setChecked(profile.proxyApps) } override def onDestroy { @@ -160,14 +206,9 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan } } - def update(profile: Profile) { - for (name <- PROXY_PREFS) { - val pref = findPreference(name) - updatePreference(pref, name, profile) - } - for (name <- FEATURE_PREFS) { - val pref = findPreference(name) - updatePreference(pref, name, profile) - } + var profile: Profile = _ + def setProfile(profile: Profile) { + this.profile = profile + for (name <- Array(PROXY_PREFS, FEATURE_PREFS).flatten) updatePreference(findPreference(name), name, profile) } } diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index a0359a94..b7a38c51 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -48,9 +48,9 @@ import android.content.pm.PackageManager.NameNotFoundException import android.net.VpnService import android.os._ import android.util.Log -import com.github.shadowsocks.aidl.Config import com.github.shadowsocks.utils._ import com.github.shadowsocks.ShadowsocksApplication.app +import com.github.shadowsocks.database.Profile import scala.collection.mutable.ArrayBuffer @@ -78,11 +78,6 @@ class ShadowsocksVpnService extends VpnService with BaseService { null } - override def onCreate() { - super.onCreate() - ConfigUtils.refresh(this) - } - override def onRevoke() { stopRunner(true) } @@ -132,66 +127,53 @@ class ShadowsocksVpnService extends VpnService with BaseService { } } - override def startRunner(config: Config) { - - super.startRunner(config) - - vpnThread = new ShadowsocksVpnThread(this) - vpnThread.start() + override def startRunner(profile: Profile) { // 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) + stopRunner(true) return } - app.track(TAG, "start") + super.startRunner(profile) + } - changeState(State.CONNECTING) + override def connect() = { + super.connect() - Utils.ThrowableFuture { - if (config.proxy == "198.199.101.152") { - val holder = app.containerHolder - try { - this.config = ConfigUtils.getPublicConfig(getBaseContext, holder.getContainer, config) - } catch { - case ex: Exception => - changeState(State.STOPPED, getString(R.string.service_failed)) - stopRunner(true) - this.config = null + if (profile != null) { + + vpnThread = new ShadowsocksVpnThread(this) + vpnThread.start() + + // reset the context + killProcesses() + + // Resolve the server address + var resolved: Boolean = false + if (!Utils.isNumeric(profile.host)) { + Utils.resolve(profile.host, enableIPv6 = true) match { + case Some(addr) => + profile.host = addr + resolved = true + case None => resolved = false } + } else { + resolved = true } - if (config != null) { - - // reset the context - killProcesses() - - // Resolve the server address - var resolved: Boolean = false - if (!Utils.isNumeric(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) { - changeState(State.STOPPED, getString(R.string.invalid_server)) - stopRunner(true) - } else if (handleConnection) { - changeState(State.CONNECTED) - notification = new ShadowsocksNotification(this, config.profileName) - } else { - changeState(State.STOPPED, getString(R.string.service_failed)) - stopRunner(true) - } + if (!resolved) { + changeState(State.STOPPED, getString(R.string.invalid_server)) + stopRunner(true) + } else if (handleConnection) { + changeState(State.CONNECTED) + notification = new ShadowsocksNotification(this, profile.name) + } else { + changeState(State.STOPPED, getString(R.string.service_failed)) + stopRunner(true) } } } @@ -199,7 +181,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { /** Called when the activity is first created. */ def handleConnection: Boolean = { startShadowsocksDaemon() - if (!config.isUdpDns) { + if (!profile.udpdns) { startDnsDaemon() startDnsTunnel() } @@ -210,22 +192,22 @@ class ShadowsocksVpnService extends VpnService with BaseService { def startShadowsocksDaemon() { - if (config.route != Route.ALL) { - val acl: Array[Array[String]] = config.route match { + if (profile.route != Route.ALL) { + val acl: Array[Array[String]] = profile.route match { case Route.BYPASS_LAN => Array(getResources.getStringArray(R.array.private_route)) case Route.BYPASS_CHN => Array(getResources.getStringArray(R.array.chn_route)) case Route.BYPASS_LAN_CHN => Array(getResources.getStringArray(R.array.private_route), getResources.getStringArray(R.array.chn_route)) } - ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/acl.list"))(p => { + Utils.printToFile(new File(getApplicationInfo.dataDir + "/acl.list"))(p => { acl.flatten.foreach(p.println) }) } val conf = ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, config.localPort, - config.sitekey, config.encMethod, 600) - ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/ss-local-vpn.conf"))(p => { + .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort, + profile.password, profile.method, 600) + Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-local-vpn.conf"))(p => { p.println(conf) }) @@ -236,9 +218,9 @@ class ShadowsocksVpnService extends VpnService with BaseService { , "-P", getApplicationInfo.dataDir , "-c", getApplicationInfo.dataDir + "/ss-local-vpn.conf") - if (config.isAuth) cmd += "-A" + if (profile.auth) cmd += "-A" - if (config.route != Route.ALL) { + if (profile.route != Route.ALL) { cmd += "--acl" cmd += (getApplicationInfo.dataDir + "/acl.list") } @@ -250,9 +232,9 @@ class ShadowsocksVpnService extends VpnService with BaseService { def startDnsTunnel() = { val conf = ConfigUtils - .SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, 8163, - config.sitekey, config.encMethod, 10) - ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf"))(p => { + .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, 8163, + profile.password, profile.method, 10) + Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf"))(p => { p.println(conf) }) val cmd = new ArrayBuffer[String] @@ -266,7 +248,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { , "-P", getApplicationInfo.dataDir , "-c", getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf") - if (config.isAuth) cmd += "-A" + if (profile.auth) cmd += "-A" if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) @@ -274,18 +256,17 @@ class ShadowsocksVpnService extends VpnService with BaseService { } def startDnsDaemon() { - val ipv6 = if (config.isIpv6) "" else "reject = ::/0;" + val ipv6 = if (profile.ipv6) "" else "reject = ::/0;" val conf = { - if (config.route == Route.BYPASS_CHN || config.route == Route.BYPASS_LAN_CHN) { - val blackList = ConfigUtils.getBlackList(this) + if (profile.route == Route.BYPASS_CHN || profile.route == Route.BYPASS_LAN_CHN) { ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, - "0.0.0.0", 8153, blackList, 8163, ipv6) + "0.0.0.0", 8153, getBlackList, 8163, ipv6) } else { ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, "0.0.0.0", 8153, 8163, ipv6) } } - ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-vpn.conf"))(p => { + Utils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-vpn.conf"))(p => { p.println(conf) }) val cmd = getApplicationInfo.dataDir + "/pdnsd -c " + getApplicationInfo.dataDir + "/pdnsd-vpn.conf" @@ -299,22 +280,22 @@ class ShadowsocksVpnService extends VpnService with BaseService { val builder = new Builder() builder - .setSession(config.profileName) + .setSession(profile.name) .setMtu(VPN_MTU) .addAddress(PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), 24) .addDnsServer("8.8.8.8") - if (config.isIpv6) { + if (profile.ipv6) { builder.addAddress(PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "1"), 126) builder.addRoute("::", 0) } if (Utils.isLollipopOrAbove) { - if (config.isProxyApps) { - for (pkg <- config.proxiedAppString.split('\n')) { + if (profile.proxyApps) { + for (pkg <- profile.individual.split('\n')) { try { - if (!config.isBypassApps) { + if (!profile.bypass) { builder.addAllowedApplication(pkg) } else { builder.addDisallowedApplication(pkg) @@ -327,7 +308,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { } } - if (config.route == Route.ALL || config.route == Route.BYPASS_CHN) { + if (profile.route == Route.ALL || profile.route == Route.BYPASS_CHN) { builder.addRoute("0.0.0.0", 0) } else { val privateList = getResources.getStringArray(R.array.bypass_private_route) @@ -368,12 +349,12 @@ class ShadowsocksVpnService extends VpnService with BaseService { + "--loglevel 3") .formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "2"), - config.localPort, fd, VPN_MTU, getApplicationInfo.dataDir + "/sock_path") + profile.localPort, fd, VPN_MTU, getApplicationInfo.dataDir + "/sock_path") - if (config.isIpv6) + if (profile.ipv6) cmd += " --netif-ip6addr " + PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "2") - if (config.isUdpDns) + if (profile.udpdns) cmd += " --enable-udprelay" else cmd += " --dnsgw %s:8153".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1")) diff --git a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala index 9e178e5c..76a59d57 100644 --- a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala +++ b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala @@ -39,16 +39,14 @@ package com.github.shadowsocks.database -import android.content.SharedPreferences import android.util.Log import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.utils.Key object ProfileManager { private final val TAG = "ProfileManager" } -class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { +class ProfileManager(dbHelper: DBHelper) { import ProfileManager._ var profileAddedListener: Profile => Any = _ @@ -139,67 +137,6 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { } } - def reload(id: Int): Profile = { - save() - load(id) - } - - def load(id: Int): Profile = { - - val profile = getProfile(id) getOrElse createProfile() - - val edit = settings.edit() - edit.putBoolean(Key.isProxyApps, profile.proxyApps) - edit.putBoolean(Key.isBypassApps, profile.bypass) - edit.putBoolean(Key.isUdpDns, profile.udpdns) - edit.putBoolean(Key.isAuth, profile.auth) - edit.putBoolean(Key.isIpv6, profile.ipv6) - edit.putString(Key.profileName, profile.name) - edit.putString(Key.proxy, profile.host) - edit.putString(Key.sitekey, profile.password) - edit.putString(Key.encMethod, profile.method) - edit.putInt(Key.remotePort, profile.remotePort) - edit.putInt(Key.localPort, profile.localPort) - edit.putString(Key.proxied, profile.individual) - edit.putInt(Key.profileId, profile.id) - edit.putString(Key.route, profile.route) - edit.apply() - - profile - } - - private def loadFromPreferences: Profile = { - - val id = settings.getInt(Key.profileId, -1) - - val profile: Profile = getProfile(id) match { - case Some(p) => p - case _ => new Profile() - } - - profile.proxyApps = settings.getBoolean(Key.isProxyApps, false) - profile.bypass = settings.getBoolean(Key.isBypassApps, false) - profile.udpdns = settings.getBoolean(Key.isUdpDns, false) - profile.auth = settings.getBoolean(Key.isAuth, false) - profile.ipv6 = settings.getBoolean(Key.isIpv6, false) - profile.name = settings.getString(Key.profileName, "default") - profile.host = settings.getString(Key.proxy, "127.0.0.1") - profile.password = settings.getString(Key.sitekey, "default") - profile.method = settings.getString(Key.encMethod, "table") - profile.route = settings.getString(Key.route, "all") - profile.remotePort = settings.getInt(Key.remotePort, 1984) - profile.localPort = settings.getInt(Key.localPort, 1984) - profile.individual = settings.getString(Key.proxied, "") - - profile - } - - def save(): Profile = { - val profile = loadFromPreferences - updateProfile(profile) - profile - } - def createDefault(): Profile = { val profile = new Profile { name = "Default" diff --git a/src/main/scala/com/github/shadowsocks/utils/ConfigUtils.scala b/src/main/scala/com/github/shadowsocks/utils/ConfigUtils.scala deleted file mode 100644 index ddcbb243..00000000 --- a/src/main/scala/com/github/shadowsocks/utils/ConfigUtils.scala +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Shadowsocks - A shadowsocks client for Android - * Copyright (C) 2014 - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * - * ___====-_ _-====___ - * _--^^^#####// \\#####^^^--_ - * _-^##########// ( ) \\##########^-_ - * -############// |\^^/| \\############- - * _/############// (@::@) \\############\_ - * /#############(( \\// ))#############\ - * -###############\\ (oo) //###############- - * -#################\\ / VV \ //#################- - * -###################\\/ \//###################- - * _#/|##########/\######( /\ )######/\##########|\#_ - * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \| - * ` |/ V V ` V \#\| | | |/#/ V ' V V \| ' - * ` ` ` ` / | | | | \ ' ' ' ' - * ( | | | | ) - * __\ | | | | /__ - * (vvv(VVV)(VVV)vvv) - * - * HERE BE DRAGONS - * - */ - -package com.github.shadowsocks.utils - -import android.content.Context -import com.github.kevinsawicki.http.HttpRequest -import com.github.shadowsocks.R -import com.github.shadowsocks.ShadowsocksApplication.app -import com.github.shadowsocks.aidl.Config -import com.google.android.gms.tagmanager.Container - -object ConfigUtils { - val SHADOWSOCKS = "{\"server\": \"%s\", \"server_port\": %d, \"local_port\": %d, \"password\": \"%s\", \"method\":\"%s\", \"timeout\": %d}" - val REDSOCKS = "base {\n" + - " log_debug = off;\n" + - " log_info = off;\n" + - " log = stderr;\n" + - " daemon = off;\n" + - " redirector = iptables;\n" + - "}\n" + - "redsocks {\n" + - " local_ip = 127.0.0.1;\n" + - " local_port = 8123;\n" + - " ip = 127.0.0.1;\n" + - " port = %d;\n" + - " type = socks5;\n" + - "}\n" - - val PDNSD_LOCAL = - """ - |global { - | perm_cache = 2048; - | cache_dir = "%s"; - | server_ip = %s; - | server_port = %d; - | query_method = tcp_only; - | run_ipv4 = on; - | min_ttl = 15m; - | max_ttl = 1w; - | timeout = 10; - | daemon = off; - |} - | - |server { - | label = "local"; - | ip = 127.0.0.1; - | port = %d; - | %s - | reject_policy = negate; - | reject_recursively = on; - | timeout = 5; - |} - | - |rr { - | name=localhost; - | reverse=on; - | a=127.0.0.1; - | owner=localhost; - | soa=localhost,root.localhost,42,86400,900,86400,86400; - |} - """.stripMargin - - val PDNSD_DIRECT = - """ - |global { - | perm_cache = 2048; - | cache_dir = "%s"; - | server_ip = %s; - | server_port = %d; - | query_method = tcp_only; - | run_ipv4 = on; - | min_ttl = 15m; - | max_ttl = 1w; - | timeout = 10; - | daemon = off; - |} - | - |server { - | label = "china-servers"; - | ip = 114.114.114.114, 112.124.47.27; - | timeout = 4; - | exclude = %s; - | policy = included; - | uptest = none; - | preset = on; - |} - | - |server { - | label = "local-server"; - | ip = 127.0.0.1; - | port = %d; - | %s - | reject_policy = negate; - | reject_recursively = on; - |} - | - |rr { - | name=localhost; - | reverse=on; - | a=127.0.0.1; - | owner=localhost; - | soa=localhost,root.localhost,42,86400,900,86400,86400; - |} - """.stripMargin - - def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) { - val p = new java.io.PrintWriter(f) - try { - op(p) - } finally { - p.close() - } - } - - def refresh(context: Context) { - val holder = app.containerHolder - if (holder != null) holder.refresh() - } - - def getBlackList(context: Context): String = { - val default = context.getString(R.string.black_list) - try { - val container = app.containerHolder.getContainer - val update = container.getString("black_list") - if (update == null || update.isEmpty) default else update - } catch { - case ex: Exception => default - } - } - - def getPublicConfig(context: Context, container: Container, config: Config): Config = { - val url = container.getString("proxy_url") - val sig = Utils.getSignature(context) - val list = HttpRequest - .post(url) - .connectTimeout(2000) - .readTimeout(2000) - .send("sig="+sig) - .body - val proxies = util.Random.shuffle(list.split('|').toSeq) - val proxy = proxies.head.split(':') - - val host = proxy(0).trim - val port = proxy(1).trim.toInt - val password = proxy(2).trim - val method = proxy(3).trim - - new Config(config.isProxyApps, config.isBypassApps, config.isUdpDns, config.isAuth, config.isIpv6, - config.profileName, host, password, method, config.proxiedAppString, config.route, port, config.localPort, 0) - } - - def loadFromSharedPreferences = { - val isProxyApps = app.settings.getBoolean(Key.isProxyApps, false) - val isBypassApps = app.settings.getBoolean(Key.isBypassApps, false) - val isUdpDns = app.settings.getBoolean(Key.isUdpDns, false) - val isAuth = app.settings.getBoolean(Key.isAuth, false) - val isIpv6 = app.settings.getBoolean(Key.isIpv6, false) - - val profileName = app.settings.getString(Key.profileName, "default") - val proxy = app.settings.getString(Key.proxy, "127.0.0.1") - val sitekey = app.settings.getString(Key.sitekey, "default") - val encMethod = app.settings.getString(Key.encMethod, "table") - val route = app.settings.getString(Key.route, "all") - - val remotePort = app.settings.getInt(Key.remotePort, 1984) - val localPort = app.settings.getInt(Key.localPort, 1984) - val proxiedAppString = app.settings.getString(Key.proxied, "") - - val profileId = app.settings.getInt(Key.profileId, -1) - - new Config(isProxyApps, isBypassApps, isUdpDns, isAuth, isIpv6, profileName, proxy, sitekey, encMethod, - proxiedAppString, route, remotePort, localPort, profileId) - } -} diff --git a/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/src/main/scala/com/github/shadowsocks/utils/Constants.scala index ec82513f..808dab55 100644 --- a/src/main/scala/com/github/shadowsocks/utils/Constants.scala +++ b/src/main/scala/com/github/shadowsocks/utils/Constants.scala @@ -47,6 +47,101 @@ object Executable { val TUN2SOCKS = "tun2socks" } +object ConfigUtils { + val SHADOWSOCKS = "{\"server\": \"%s\", \"server_port\": %d, \"local_port\": %d, \"password\": \"%s\", \"method\":\"%s\", \"timeout\": %d}" + val REDSOCKS = "base {\n" + + " log_debug = off;\n" + + " log_info = off;\n" + + " log = stderr;\n" + + " daemon = off;\n" + + " redirector = iptables;\n" + + "}\n" + + "redsocks {\n" + + " local_ip = 127.0.0.1;\n" + + " local_port = 8123;\n" + + " ip = 127.0.0.1;\n" + + " port = %d;\n" + + " type = socks5;\n" + + "}\n" + + val PDNSD_LOCAL = + """ + |global { + | perm_cache = 2048; + | cache_dir = "%s"; + | server_ip = %s; + | server_port = %d; + | query_method = tcp_only; + | run_ipv4 = on; + | min_ttl = 15m; + | max_ttl = 1w; + | timeout = 10; + | daemon = off; + |} + | + |server { + | label = "local"; + | ip = 127.0.0.1; + | port = %d; + | %s + | reject_policy = negate; + | reject_recursively = on; + | timeout = 5; + |} + | + |rr { + | name=localhost; + | reverse=on; + | a=127.0.0.1; + | owner=localhost; + | soa=localhost,root.localhost,42,86400,900,86400,86400; + |} + """.stripMargin + + val PDNSD_DIRECT = + """ + |global { + | perm_cache = 2048; + | cache_dir = "%s"; + | server_ip = %s; + | server_port = %d; + | query_method = tcp_only; + | run_ipv4 = on; + | min_ttl = 15m; + | max_ttl = 1w; + | timeout = 10; + | daemon = off; + |} + | + |server { + | label = "china-servers"; + | ip = 114.114.114.114, 112.124.47.27; + | timeout = 4; + | exclude = %s; + | policy = included; + | uptest = none; + | preset = on; + |} + | + |server { + | label = "local-server"; + | ip = 127.0.0.1; + | port = %d; + | %s + | reject_policy = negate; + | reject_recursively = on; + |} + | + |rr { + | name=localhost; + | reverse=on; + | a=127.0.0.1; + | owner=localhost; + | soa=localhost,root.localhost,42,86400,900,86400,86400; + |} + """.stripMargin +} + object Key { val profileId = "profileId" val profileName = "profileName" diff --git a/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/src/main/scala/com/github/shadowsocks/utils/Utils.scala index 7305988a..c5c79121 100644 --- a/src/main/scala/com/github/shadowsocks/utils/Utils.scala +++ b/src/main/scala/com/github/shadowsocks/utils/Utils.scala @@ -120,6 +120,15 @@ object Utils { }) } + def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) { + val p = new java.io.PrintWriter(f) + try { + op(p) + } finally { + p.close() + } + } + // Blocked > 3 seconds def toggleAirplaneMode(context: Context) = { if (Console.isRoot) {