diff --git a/.travis.yml b/.travis.yml index 9f0acdbc..15fd35ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,13 +27,11 @@ install: - gem install travis-artifacts script: - ./build-ndk.sh -- wget https://raw.github.com/paulp/sbt-extras/master/sbt -- chmod u+x ./sbt - cp local.sbt.example local.sbt -- ./sbt clean -- ./sbt apk +- sbt clean +- sbt apk after_success: -- ./sbt deploy +- sbt deploy env: global: - "ARTIFACTS_AWS_REGION=ap-northeast-1" diff --git a/src/main/scala/com/github/shadowsocks/AppManager.scala b/src/main/scala/com/github/shadowsocks/AppManager.scala index ae532824..84d59df7 100644 --- a/src/main/scala/com/github/shadowsocks/AppManager.scala +++ b/src/main/scala/com/github/shadowsocks/AppManager.scala @@ -70,6 +70,7 @@ import com.nostra13.universalimageloader.core.{DisplayImageOptions, ImageLoader, import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer import com.google.analytics.tracking.android.EasyTracker import org.jraf.android.backport.switchwidget.Switch +import com.github.shadowsocks.utils.{Utils, Scheme, Key} case class ProxiedApp(uid: Int, name: String, var proxied: Boolean) diff --git a/src/main/scala/com/github/shadowsocks/ShadowVpnActivity.scala b/src/main/scala/com/github/shadowsocks/ShadowVpnActivity.scala index e2a8022b..32c6ad44 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowVpnActivity.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowVpnActivity.scala @@ -45,6 +45,7 @@ import android.net.VpnService import android.content.Intent import android.util.Log import android.preference.PreferenceManager +import com.github.shadowsocks.utils.Extra class ShadowVpnActivity extends Activity { diff --git a/src/main/scala/com/github/shadowsocks/ShadowVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowVpnService.scala index b7e3200f..e32efbc6 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowVpnService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowVpnService.scala @@ -55,6 +55,9 @@ import scala.Some import scala.concurrent.ops._ import org.apache.commons.net.util.SubnetUtils import java.net.InetAddress +import com.github.shadowsocks.utils._ +import scala.Some +import com.github.shadowsocks.ProxiedApp object ShadowVpnService { def isServiceStarted(context: Context): Boolean = { diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 086c851f..bb82c9c7 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -69,6 +69,9 @@ 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 class ProfileIconDownloader(context: Context, connectTimeout: Int, readTimeout: Int) extends BaseImageDownloader(context, connectTimeout, readTimeout) { @@ -290,7 +293,6 @@ class Shadowsocks // Flags val MSG_CRASH_RECOVER: Int = 1 - val MSG_INITIAL_FINISH: Int = 2 val STATE_MENUDRAWER = "com.github.shadowsocks.menuDrawer" val STATE_ACTIVE_VIEW_ID = "com.github.shadowsocks.activeViewId" @@ -317,8 +319,6 @@ class Shadowsocks case MSG_CRASH_RECOVER => Crouton.makeText(Shadowsocks.this, R.string.crash_alert, Style.ALERT).show() status.edit().putBoolean(Key.isRunning, false).commit() - case MSG_INITIAL_FINISH => - clearDialog() } super.handleMessage(msg) } diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala index d9cdb026..9e743bde 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala @@ -44,6 +44,7 @@ import android.content.Intent import android.content.SharedPreferences import android.content.pm.PackageManager import android.preference.PreferenceManager +import com.github.shadowsocks.utils._ class ShadowsocksReceiver extends BroadcastReceiver { diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala index db05b328..669f6ffe 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala @@ -60,6 +60,10 @@ import android.net.TrafficStats import android.graphics._ import scala.concurrent.ops._ import scala.Some +import com.github.shadowsocks.utils._ +import scala.Some +import com.github.shadowsocks.ProxiedApp +import com.github.shadowsocks.TrafficStat case class TrafficStat(tx: Long, rx: Long, timestamp: Long) diff --git a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala index 7a4f7a29..381c757a 100644 --- a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala +++ b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala @@ -42,6 +42,7 @@ package com.github.shadowsocks.database import android.util.Log import com.github.shadowsocks._ import android.content.{SharedPreferences, Context} +import com.github.shadowsocks.utils.Key class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { diff --git a/src/main/scala/com/github/shadowsocks/preferences/ProfileEditTextPreference.scala b/src/main/scala/com/github/shadowsocks/preferences/ProfileEditTextPreference.scala index 9028c600..01d295e7 100644 --- a/src/main/scala/com/github/shadowsocks/preferences/ProfileEditTextPreference.scala +++ b/src/main/scala/com/github/shadowsocks/preferences/ProfileEditTextPreference.scala @@ -42,7 +42,7 @@ package com.github.shadowsocks.preferences import android.content.{Intent, Context} import android.preference.EditTextPreference import android.util.AttributeSet -import com.github.shadowsocks.Action +import com.github.shadowsocks.utils.Action class ProfileEditTextPreference(context: Context, attrs: AttributeSet, defStyle: Int) extends EditTextPreference(context, attrs, defStyle) { diff --git a/src/main/scala/com/github/shadowsocks/utils/Config.scala b/src/main/scala/com/github/shadowsocks/utils/Config.scala new file mode 100644 index 00000000..c5d6ff2f --- /dev/null +++ b/src/main/scala/com/github/shadowsocks/utils/Config.scala @@ -0,0 +1,92 @@ +package com.github.shadowsocks.utils + +import android.content.Context +import com.github.shadowsocks.ShadowsocksApplication +import com.google.tagmanager.Container +import scalaj.http.{HttpOptions, Http} + +object Config { + val SHADOWSOCKS = "{\"server\": [%s], \"server_port\": %d, \"local_port\": %d, \"password\": %s, \"timeout\": %d}" + val REDSOCKS = "base {" + + " log_debug = off;" + + " log_info = off;" + + " log = stderr;" + + " daemon = on;" + + " redirector = iptables;" + + "}" + + "redsocks {" + + " local_ip = 127.0.0.1;" + + " local_port = 8123;" + + " ip = 127.0.0.1;" + + " port = %d;" + + " type = socks5;" + + "}" + val PDNSD = + """ + |global { + | perm_cache = 2048; + | cache_dir = "/data/data/com.github.shadowsocks"; + | server_ip = %s; + | server_port = 8153; + | query_method = tcp_only; + | run_ipv4 = on; + | min_ttl = 15m; + | max_ttl = 1w; + | timeout = 10; + | daemon = on; + | pid_file = "/data/data/com.github.shadowsocks/pdnsd.pid"; + |} + | + |server { + | label = "root-servers"; + | ip = 8.8.8.8, 8.8.4.4, 208.67.222.222, 208.67.220.220; + | timeout = 5; + |} + | + |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 container = context.getApplicationContext.asInstanceOf[ShadowsocksApplication].tagContainer + if (container != null) container.refresh() + } + + def getPublicConfig(context: Context, container: Container, config: Config): Config = { + val url = container.getString("proxy_url") + val sig = Utils.getSignature(context) + val list = Http.post(url) + .params("sig" -> sig) + .option(HttpOptions.connTimeout(1000)) + .option(HttpOptions.readTimeout(5000)) + .asString + val proxies = util.Random.shuffle(list.split('|').toSeq).toSeq + val proxy = proxies(0).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.isGlobalProxy, config.isGFWList, config.isBypassApps, config.isTrafficStat, + config.profileName, host, password, method, port, config.localPort, config.proxiedAppString) + } +} + +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) diff --git a/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/src/main/scala/com/github/shadowsocks/utils/Constants.scala new file mode 100644 index 00000000..2e8bdc9d --- /dev/null +++ b/src/main/scala/com/github/shadowsocks/utils/Constants.scala @@ -0,0 +1,132 @@ +package com.github.shadowsocks.utils + +import android.content.{Intent, SharedPreferences} + +object Key { + val profileId = "profileId" + val profileName = "profileName" + + val proxied = "Proxyed" + + val isRoot = "isRoot" + val status = "status" + val proxyedApps = "proxyedApps" + + val isRunning = "isRunning" + val isAutoConnect = "isAutoConnect" + + val isGlobalProxy = "isGlobalProxy" + val isGFWList = "isGFWList" + val isBypassApps = "isBypassApps" + val isTrafficStat = "isTrafficStat" + + val proxy = "proxy" + val sitekey = "sitekey" + val encMethod = "encMethod" + val remotePort = "remotePort" + val localPort = "port" +} + +object Scheme { + val APP = "app://" + val PROFILE = "profile://" + val SHADOW = "shadow:" +} + +object State { + val INIT = 0 + val CONNECTING = 1 + val CONNECTED = 2 + val STOPPED = 3 +} + +object Action { + val CLOSE = "com.github.shadowsocks.ACTION_SHUTDOWN" + val UPDATE_STATE = "com.github.shadowsocks.ACTION_UPDATE_STATE" + 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) + } +} + diff --git a/src/main/scala/com/github/shadowsocks/Utils.scala b/src/main/scala/com/github/shadowsocks/utils/Utils.scala similarity index 72% rename from src/main/scala/com/github/shadowsocks/Utils.scala rename to src/main/scala/com/github/shadowsocks/utils/Utils.scala index b7edae54..a76dafc3 100644 --- a/src/main/scala/com/github/shadowsocks/Utils.scala +++ b/src/main/scala/com/github/shadowsocks/utils/Utils.scala @@ -36,7 +36,7 @@ * HERE BE DRAGONS * */ -package com.github.shadowsocks +package com.github.shadowsocks.utils import android.content.{Intent, SharedPreferences, Context} import android.content.pm.ApplicationInfo @@ -56,219 +56,8 @@ import scala.Some import scalaj.http.{Http, HttpOptions} import com.google.tagmanager.Container import java.security.MessageDigest +import com.github.shadowsocks.{BuildConfig, Exec, ShadowsocksApplication} -object Config { - val SHADOWSOCKS = "{\"server\": [%s], \"server_port\": %d, \"local_port\": %d, \"password\": %s, \"timeout\": %d}" - val REDSOCKS = "base {" + - " log_debug = off;" + - " log_info = off;" + - " log = stderr;" + - " daemon = on;" + - " redirector = iptables;" + - "}" + - "redsocks {" + - " local_ip = 127.0.0.1;" + - " local_port = 8123;" + - " ip = 127.0.0.1;" + - " port = %d;" + - " type = socks5;" + - "}" - val PDNSD = - """ - |global { - | perm_cache = 2048; - | cache_dir = "/data/data/com.github.shadowsocks"; - | server_ip = %s; - | server_port = 8153; - | query_method = tcp_only; - | run_ipv4 = on; - | min_ttl = 15m; - | max_ttl = 1w; - | timeout = 10; - | daemon = on; - | pid_file = "/data/data/com.github.shadowsocks/pdnsd.pid"; - |} - | - |server { - | label = "root-servers"; - | ip = 8.8.8.8, 8.8.4.4, 208.67.222.222, 208.67.220.220; - | timeout = 5; - |} - | - |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 container = context.getApplicationContext.asInstanceOf[ShadowsocksApplication].tagContainer - if (container != null) container.refresh() - } - - def getPublicConfig(context: Context, container: Container, config: Config): Config = { - val url = container.getString("proxy_url") - val sig = Utils.getSignature(context) - val list = Http.post(url) - .params("sig" -> sig) - .option(HttpOptions.connTimeout(1000)) - .option(HttpOptions.readTimeout(5000)) - .asString - val proxies = util.Random.shuffle(list.split('|').toSeq).toSeq - val proxy = proxies(0).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.isGlobalProxy, config.isGFWList, config.isBypassApps, config.isTrafficStat, - config.profileName, host, password, method, port, config.localPort, config.proxiedAppString) - } -} - -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) - -object Key { - val profileId = "profileId" - val profileName = "profileName" - - val proxied = "Proxyed" - - val isRoot = "isRoot" - val status = "status" - val proxyedApps = "proxyedApps" - - val isRunning = "isRunning" - val isAutoConnect = "isAutoConnect" - - val isGlobalProxy = "isGlobalProxy" - val isGFWList = "isGFWList" - val isBypassApps = "isBypassApps" - val isTrafficStat = "isTrafficStat" - - val proxy = "proxy" - val sitekey = "sitekey" - val encMethod = "encMethod" - val remotePort = "remotePort" - val localPort = "port" -} - -object Scheme { - val APP = "app://" - val PROFILE = "profile://" -} - -object State { - val INIT = 0 - val CONNECTING = 1 - val CONNECTED = 2 - val STOPPED = 3 -} - -object Action { - val CLOSE = "com.github.shadowsocks.ACTION_SHUTDOWN" - val UPDATE_STATE = "com.github.shadowsocks.ACTION_UPDATE_STATE" - 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) - } -} object Utils {