Commit c480137d authored by Mygod's avatar Mygod Committed by Max Lv

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;
* `<data dir>/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.
parent 8515edff
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 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<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 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);
}
}
package com.github.shadowsocks.aidl; package com.github.shadowsocks.aidl;
import com.github.shadowsocks.aidl.Config;
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback; import com.github.shadowsocks.aidl.IShadowsocksServiceCallback;
interface IShadowsocksService { interface IShadowsocksService {
...@@ -9,5 +8,5 @@ interface IShadowsocksService { ...@@ -9,5 +8,5 @@ interface IShadowsocksService {
oneway void registerCallback(IShadowsocksServiceCallback cb); oneway void registerCallback(IShadowsocksServiceCallback cb);
oneway void unregisterCallback(IShadowsocksServiceCallback cb); oneway void unregisterCallback(IShadowsocksServiceCallback cb);
oneway void use(in Config config); oneway void use(in int profileId);
} }
...@@ -7,36 +7,43 @@ ...@@ -7,36 +7,43 @@
<com.github.shadowsocks.preferences.SummaryEditTextPreference <com.github.shadowsocks.preferences.SummaryEditTextPreference
android:key="profileName" android:key="profileName"
android:persistent="false"
android:title="@string/profile_name"/> android:title="@string/profile_name"/>
<com.github.shadowsocks.preferences.SummaryEditTextPreference <com.github.shadowsocks.preferences.SummaryEditTextPreference
android:key="proxy" android:key="proxy"
android:persistent="false"
android:summary="@string/proxy_summary" android:summary="@string/proxy_summary"
android:title="@string/proxy"/> android:title="@string/proxy"/>
<com.github.shadowsocks.preferences.NumberPickerPreference <com.github.shadowsocks.preferences.NumberPickerPreference
app:min="1" app:min="1"
app:max="65535" app:max="65535"
android:key="remotePortNum" android:key="remotePortNum"
android:persistent="false"
android:summary="@string/remote_port_summary" android:summary="@string/remote_port_summary"
android:title="@string/remote_port"/> android:title="@string/remote_port"/>
<com.github.shadowsocks.preferences.NumberPickerPreference <com.github.shadowsocks.preferences.NumberPickerPreference
app:min="1025" app:min="1025"
app:max="65535" app:max="65535"
android:key="localPortNum" android:key="localPortNum"
android:persistent="false"
android:summary="@string/port_summary" android:summary="@string/port_summary"
android:title="@string/port"/> android:title="@string/port"/>
<com.github.shadowsocks.preferences.PasswordEditTextPreference <com.github.shadowsocks.preferences.PasswordEditTextPreference
android:inputType="textPassword" android:inputType="textPassword"
android:key="sitekey" android:key="sitekey"
android:persistent="false"
android:summary="@string/sitekey_summary" android:summary="@string/sitekey_summary"
android:title="@string/sitekey"/> android:title="@string/sitekey"/>
<com.github.shadowsocks.preferences.DropDownPreference <com.github.shadowsocks.preferences.DropDownPreference
android:key="encMethod" android:key="encMethod"
android:persistent="false"
app:entries="@array/enc_method_entry" app:entries="@array/enc_method_entry"
app:entryValues="@array/enc_method_value" app:entryValues="@array/enc_method_value"
android:summary="%s" android:summary="%s"
android:title="@string/enc_method"/> android:title="@string/enc_method"/>
<SwitchPreference <SwitchPreference
android:key="isAuth" android:key="isAuth"
android:persistent="false"
android:summary="@string/onetime_auth_summary" android:summary="@string/onetime_auth_summary"
android:title="@string/onetime_auth"/> android:title="@string/onetime_auth"/>
...@@ -47,20 +54,24 @@ ...@@ -47,20 +54,24 @@
<com.github.shadowsocks.preferences.DropDownPreference <com.github.shadowsocks.preferences.DropDownPreference
android:key="route" android:key="route"
android:persistent="false"
app:entries="@array/route_entry" app:entries="@array/route_entry"
app:entryValues="@array/route_value" app:entryValues="@array/route_value"
android:summary="%s" android:summary="%s"
android:title="@string/route_list"/> android:title="@string/route_list"/>
<SwitchPreference <SwitchPreference
android:key="isIpv6" android:key="isIpv6"
android:persistent="false"
android:summary="@string/ipv6_summary" android:summary="@string/ipv6_summary"
android:title="@string/ipv6"/> android:title="@string/ipv6"/>
<SwitchPreference <SwitchPreference
android:key="isProxyApps" android:key="isProxyApps"
android:persistent="false"
android:summary="@string/proxied_apps_summary" android:summary="@string/proxied_apps_summary"
android:title="@string/proxied_apps"/> android:title="@string/proxied_apps"/>
<SwitchPreference <SwitchPreference
android:key="isUdpDns" android:key="isUdpDns"
android:persistent="false"
android:summary="@string/udp_dns_summary" android:summary="@string/udp_dns_summary"
android:title="@string/udp_dns"/> android:title="@string/udp_dns"/>
......
...@@ -117,8 +117,10 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { ...@@ -117,8 +117,10 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
proxiedApps.add(item.packageName) proxiedApps.add(item.packageName)
check.setChecked(true) check.setChecked(true)
} }
if (!appsLoading.get) if (!appsLoading.get) {
app.editor.putString(Key.proxied, proxiedApps.mkString("\n")).apply profile.individual = proxiedApps.mkString("\n")
app.profileManager.updateProfile(profile)
}
} }
} }
...@@ -141,9 +143,9 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { ...@@ -141,9 +143,9 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
private var loadingView: View = _ private var loadingView: View = _
private val appsLoading = new AtomicBoolean private val appsLoading = new AtomicBoolean
private var handler: Handler = _ private var handler: Handler = _
private val profile = app.currentProfile.get
private def initProxiedApps(str: String = app.settings.getString(Key.proxied, "")) = private def initProxiedApps(str: String = profile.individual) = proxiedApps = str.split('\n').to[mutable.HashSet]
proxiedApps = str.split('\n').to[mutable.HashSet]
override def onDestroy() { override def onDestroy() {
instance = null instance = null
...@@ -225,17 +227,23 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { ...@@ -225,17 +227,23 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
toolbar.inflateMenu(R.menu.app_manager_menu) toolbar.inflateMenu(R.menu.app_manager_menu)
toolbar.setOnMenuItemClickListener(this) 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] findViewById(R.id.onSwitch).asInstanceOf[Switch]
.setOnCheckedChangeListener((_, checked) => { .setOnCheckedChangeListener((_, checked) => {
app.editor.putBoolean(Key.isProxyApps, checked).apply profile.proxyApps = checked
app.profileManager.updateProfile(profile)
finish() finish()
}) })
bypassSwitch = findViewById(R.id.bypassSwitch).asInstanceOf[Switch] bypassSwitch = findViewById(R.id.bypassSwitch).asInstanceOf[Switch]
bypassSwitch.setChecked(app.settings.getBoolean(Key.isBypassApps, false)) bypassSwitch.setChecked(profile.bypass)
bypassSwitch.setOnCheckedChangeListener((_, checked) => bypassSwitch.setOnCheckedChangeListener((_, checked) => {
app.editor.putBoolean(Key.isBypassApps, checked).apply()) profile.bypass = checked
app.profileManager.updateProfile(profile)
})
initProxiedApps() initProxiedApps()
loadingView = findViewById(R.id.loading) loadingView = findViewById(R.id.loading)
......
...@@ -47,17 +47,19 @@ import android.os.{Handler, RemoteCallbackList} ...@@ -47,17 +47,19 @@ import android.os.{Handler, RemoteCallbackList}
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import android.widget.Toast 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.utils._
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.database.Profile
trait BaseService extends Service { trait BaseService extends Service {
@volatile private var state = State.STOPPED @volatile private var state = State.STOPPED
@volatile protected var config: Config = null @volatile protected var profile: Profile = _
var timer: Timer = null var timer: Timer = _
var trafficMonitorThread: TrafficMonitorThread = null var trafficMonitorThread: TrafficMonitorThread = _
final val callbacks = new RemoteCallbackList[IShadowsocksServiceCallback] final val callbacks = new RemoteCallbackList[IShadowsocksServiceCallback]
var callbacksCount: Int = _ var callbacksCount: Int = _
...@@ -101,26 +103,50 @@ trait BaseService extends Service { ...@@ -101,26 +103,50 @@ trait BaseService extends Service {
} }
} }
override def use(config: Config) = synchronized(state match { override def use(profileId: Int) = synchronized(if (profileId < 0) stopRunner(true) else {
case State.STOPPED => if (config != null && checkConfig(config)) startRunner(config) val profile = app.profileManager.getProfile(profileId).orNull
case State.CONNECTED => if (profile == null) stopRunner(true) else state match {
if (config == null) stopRunner(true) case State.STOPPED => if (checkProfile(profile)) startRunner(profile)
else if (config.profileId != BaseService.this.config.profileId && checkConfig(config)) { case State.CONNECTED => if (profileId != BaseService.this.profile.id && checkProfile(profile)) {
stopRunner(false) 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) changeState(State.STOPPED)
stopRunner(true) stopRunner(true)
false false
} else true } else true
def startRunner(config: Config) { def connect() = if (profile.host == "198.199.101.152") try {
this.config = config 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)) startService(new Intent(this, getClass))
TrafficMonitor.reset() TrafficMonitor.reset()
...@@ -135,6 +161,12 @@ trait BaseService extends Service { ...@@ -135,6 +161,12 @@ trait BaseService extends Service {
registerReceiver(closeReceiver, filter) registerReceiver(closeReceiver, filter)
closeReceiverRegistered = true closeReceiverRegistered = true
} }
app.track(getClass.getSimpleName, "start")
changeState(State.CONNECTING)
Utils.ThrowableFuture(connect)
} }
def stopRunner(stopService: Boolean) { def stopRunner(stopService: Boolean) {
...@@ -158,17 +190,19 @@ trait BaseService extends Service { ...@@ -158,17 +190,19 @@ trait BaseService extends Service {
// stop the service if nothing has bound to it // stop the service if nothing has bound to it
if (stopService) stopSelf() if (stopService) stopSelf()
profile = null
} }
def updateTrafficTotal(tx: Long, rx: Long) { def updateTrafficTotal(tx: Long, rx: Long) {
val config = this.config // avoid race conditions without locking val profile = this.profile // avoid race conditions without locking
if (config != null) { if (profile != null) {
app.profileManager.getProfile(config.profileId) match { app.profileManager.getProfile(profile.id) match {
case Some(profile) => case Some(p) => // default profile may have host, etc. modified
profile.tx += tx p.tx += tx
profile.rx += rx p.rx += rx
app.profileManager.updateProfile(profile) app.profileManager.updateProfile(p)
case None => // Ignore case None =>
} }
} }
} }
...@@ -197,6 +231,12 @@ trait BaseService extends Service { ...@@ -197,6 +231,12 @@ trait BaseService extends Service {
}) })
} }
override def onCreate() {
super.onCreate()
app.refreshContainerHolder
}
// Service of shadowsocks should always be started explicitly // Service of shadowsocks should always be started explicitly
override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = Service.START_NOT_STICKY override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = Service.START_NOT_STICKY
...@@ -217,4 +257,15 @@ trait BaseService extends Service { ...@@ -217,4 +257,15 @@ trait BaseService extends Service {
state = s 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
}
}
} }
...@@ -11,20 +11,19 @@ import android.provider.Settings ...@@ -11,20 +11,19 @@ import android.provider.Settings
import android.support.v7.app.{AlertDialog, AppCompatActivity} import android.support.v7.app.{AlertDialog, AppCompatActivity}
import android.support.v7.widget.RecyclerView.ViewHolder import android.support.v7.widget.RecyclerView.ViewHolder
import android.support.v7.widget.Toolbar.OnMenuItemClickListener 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
import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback
import android.support.v7.widget._
import android.text.style.TextAppearanceSpan import android.text.style.TextAppearanceSpan
import android.text.{SpannableStringBuilder, Spanned, TextUtils} import android.text.{SpannableStringBuilder, Spanned, TextUtils}
import android.util.Log
import android.view._ import android.view._
import android.widget.{CheckedTextView, ImageView, LinearLayout, Toast} import android.widget.{CheckedTextView, ImageView, LinearLayout, Toast}
import com.github.clans.fab.{FloatingActionButton, FloatingActionMenu} import com.github.clans.fab.{FloatingActionButton, FloatingActionMenu}
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils.{Key, Parser, TrafficMonitor, Utils} import com.github.shadowsocks.utils.{Key, Parser, TrafficMonitor, Utils}
import com.github.shadowsocks.widget.UndoSnackbarManager import com.github.shadowsocks.widget.UndoSnackbarManager
import com.github.shadowsocks.ShadowsocksApplication.app
import net.glxn.qrgen.android.QRCode import net.glxn.qrgen.android.QRCode
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
...@@ -281,8 +280,9 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic ...@@ -281,8 +280,9 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
v.getId match { v.getId match {
case R.id.fab_manual_add => case R.id.fab_manual_add =>
menu.toggle(true) menu.toggle(true)
app.profileManager.reload(-1) val profile = app.profileManager.createProfile()
app.switchProfile(app.profileManager.save.id) app.profileManager.updateProfile(profile)
app.switchProfile(profile.id)
finish finish
case R.id.fab_qrcode_add => case R.id.fab_qrcode_add =>
menu.toggle(false) menu.toggle(false)
......
...@@ -100,7 +100,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { ...@@ -100,7 +100,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
import Shadowsocks._ import Shadowsocks._
// Variables // Variables
var serviceStarted = false var serviceStarted: Boolean = _
var fab: FloatingActionButton = _ var fab: FloatingActionButton = _
var fabProgressCircle: FABProgressCircle = _ var fabProgressCircle: FABProgressCircle = _
var progressDialog: ProgressDialog = _ var progressDialog: ProgressDialog = _
...@@ -186,23 +186,6 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { ...@@ -186,23 +186,6 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
if (!app.settings.getBoolean(app.getVersionName, false)) { if (!app.settings.getBoolean(app.getVersionName, false)) {
app.editor.putBoolean(app.getVersionName, true).apply() 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() recovery()
...@@ -364,7 +347,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { ...@@ -364,7 +347,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
val title = field.get(toolbar).asInstanceOf[TextView] val title = field.get(toolbar).asInstanceOf[TextView]
title.setFocusable(true) title.setFocusable(true)
title.setGravity(0x10) 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]))) title.setOnClickListener(_ => startActivity(new Intent(this, classOf[ProfileManagerActivity])))
val typedArray = obtainStyledAttributes(Array(R.attr.selectableItemBackgroundBorderless)) val typedArray = obtainStyledAttributes(Array(R.attr.selectableItemBackgroundBorderless))
title.setBackgroundResource(typedArray.getResourceId(0, 0)) title.setBackgroundResource(typedArray.getResourceId(0, 0))
...@@ -434,11 +417,6 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { ...@@ -434,11 +417,6 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
handler.post(() => attachService) handler.post(() => attachService)
} }
protected override def onPause() {
super.onPause()
app.profileManager.save
}
private def hideCircle() { private def hideCircle() {
try { try {
fabProgressCircle.hide() fabProgressCircle.hide()
...@@ -489,34 +467,34 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { ...@@ -489,34 +467,34 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
private def updateCurrentProfile() = { private def updateCurrentProfile() = {
// Check if current profile changed // Check if current profile changed
if (app.profileId != currentProfile.id) { if (preferences.profile == null || app.profileId != preferences.profile.id) {
currentProfile = app.currentProfile match { updatePreferenceScreen(app.currentProfile match {
case Some(profile) => profile // updated case Some(profile) => profile // updated
case None => // removed case None => // removed
app.switchProfile((app.profileManager.getFirstProfile match { app.switchProfile((app.profileManager.getFirstProfile match {
case Some(first) => first case Some(first) => first
case None => app.profileManager.createDefault() case None => app.profileManager.createDefault()
}).id) }).id)
} })
updatePreferenceScreen()
if (serviceStarted) serviceLoad() if (serviceStarted) serviceLoad()
true true
} else false } else {
preferences.refreshProfile()
false
}
} }
protected override def onResume() { protected override def onResume() {
super.onResume() super.onResume()
ConfigUtils.refresh(this) app.refreshContainerHolder
updateState(updateCurrentProfile()) updateState(updateCurrentProfile())
} }
private def updatePreferenceScreen() { private def updatePreferenceScreen(profile: Profile) {
val profile = currentProfile
if (profile.host == "198.199.101.152") if (adView == null) { if (profile.host == "198.199.101.152") if (adView == null) {
adView = new AdView(this) adView = new AdView(this)
adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322") adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322")
...@@ -525,7 +503,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { ...@@ -525,7 +503,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
adView.loadAd(new AdRequest.Builder().build()) adView.loadAd(new AdRequest.Builder().build())
} else adView.setVisibility(View.VISIBLE) else if (adView != null) adView.setVisibility(View.GONE) } else adView.setVisibility(View.VISIBLE) else if (adView != null) adView.setVisibility(View.GONE)
preferences.update(profile) preferences.setProfile(profile)
} }
override def onStart() { override def onStart() {
...@@ -587,7 +565,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { ...@@ -587,7 +565,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
} }
def serviceStop() { def serviceStop() {
if (bgService != null) bgService.use(null) if (bgService != null) bgService.use(-1)
} }
def checkText(key: String): Boolean = { def checkText(key: String): Boolean = {
...@@ -599,7 +577,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext { ...@@ -599,7 +577,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
/** Called when connect button is clicked. */ /** Called when connect button is clicked. */
def serviceLoad() { def serviceLoad() {
bgService.use(ConfigUtils.loadFromSharedPreferences) bgService.use(app.profileId)
if (app.isVpnEnabled) { if (app.isVpnEnabled) {
changeSwitch(checked = false) changeSwitch(checked = false)
......
...@@ -60,13 +60,12 @@ object ShadowsocksApplication { ...@@ -60,13 +60,12 @@ object ShadowsocksApplication {
class ShadowsocksApplication extends Application { class ShadowsocksApplication extends Application {
import ShadowsocksApplication._ import ShadowsocksApplication._
lazy val dbHelper = new DBHelper(this)
final val SIG_FUNC = "getSignature" final val SIG_FUNC = "getSignature"
var containerHolder: ContainerHolder = _ var containerHolder: ContainerHolder = _
lazy val tracker = GoogleAnalytics.getInstance(this).newTracker(R.xml.tracker) lazy val tracker = GoogleAnalytics.getInstance(this).newTracker(R.xml.tracker)
lazy val settings = PreferenceManager.getDefaultSharedPreferences(this) lazy val settings = PreferenceManager.getDefaultSharedPreferences(this)
lazy val editor = settings.edit 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 isNatEnabled = settings.getBoolean(Key.isNAT, false)
def isVpnEnabled = !isNatEnabled def isVpnEnabled = !isNatEnabled
...@@ -85,12 +84,12 @@ class ShadowsocksApplication extends Application { ...@@ -85,12 +84,12 @@ class ShadowsocksApplication extends Application {
.build()) .build())
def profileId = settings.getInt(Key.profileId, -1) 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 currentProfile = profileManager.getProfile(profileId)
def switchProfile(id: Int) = { def switchProfile(id: Int) = {
profileId(id) profileId(id)
profileManager.load(id) profileManager.getProfile(id) getOrElse profileManager.createProfile()
} }
override def onCreate() { override def onCreate() {
...@@ -117,4 +116,9 @@ class ShadowsocksApplication extends Application { ...@@ -117,4 +116,9 @@ class ShadowsocksApplication extends Application {
} }
pending.setResultCallback(callback, 2, TimeUnit.SECONDS) pending.setResultCallback(callback, 2, TimeUnit.SECONDS)
} }
def refreshContainerHolder {
val holder = app.containerHolder
if (holder != null) holder.refresh()
}
} }
...@@ -66,7 +66,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext { ...@@ -66,7 +66,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
def startBackgroundService() { def startBackgroundService() {
if (app.isNatEnabled) { if (app.isNatEnabled) {
bgService.use(ConfigUtils.loadFromSharedPreferences) bgService.use(app.profileId)
finish() finish()
} else { } else {
val intent = VpnService.prepare(ShadowsocksRunnerActivity.this) val intent = VpnService.prepare(ShadowsocksRunnerActivity.this)
...@@ -109,7 +109,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext { ...@@ -109,7 +109,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
resultCode match { resultCode match {
case Activity.RESULT_OK => case Activity.RESULT_OK =>
if (bgService != null) { if (bgService != null) {
bgService.use(ConfigUtils.loadFromSharedPreferences) bgService.use(app.profileId)
} }
case _ => case _ =>
Log.e(TAG, "Failed to start VpnService") Log.e(TAG, "Failed to start VpnService")
......
...@@ -58,11 +58,11 @@ class ShadowsocksRunnerService extends Service with ServiceBoundContext { ...@@ -58,11 +58,11 @@ class ShadowsocksRunnerService extends Service with ServiceBoundContext {
} }
def startBackgroundService() { def startBackgroundService() {
if (app.isNatEnabled) bgService.use(ConfigUtils.loadFromSharedPreferences) else { if (app.isNatEnabled) bgService.use(app.profileId) else {
val intent = VpnService.prepare(ShadowsocksRunnerService.this) val intent = VpnService.prepare(ShadowsocksRunnerService.this)
if (intent == null) { if (intent == null) {
if (bgService != null) { if (bgService != null) {
bgService.use(ConfigUtils.loadFromSharedPreferences) bgService.use(app.profileId)
} }
} }
} }
......
...@@ -9,10 +9,10 @@ import android.os.{Build, Bundle} ...@@ -9,10 +9,10 @@ import android.os.{Build, Bundle}
import android.preference.{Preference, PreferenceFragment, SwitchPreference} import android.preference.{Preference, PreferenceFragment, SwitchPreference}
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.webkit.{WebView, WebViewClient} import android.webkit.{WebView, WebViewClient}
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.preferences._ import com.github.shadowsocks.preferences._
import com.github.shadowsocks.utils.{Key, Utils} import com.github.shadowsocks.utils.{Key, Utils}
import com.github.shadowsocks.ShadowsocksApplication.app
object ShadowsocksSettings { object ShadowsocksSettings {
// Constants // Constants
...@@ -74,12 +74,58 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan ...@@ -74,12 +74,58 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
addPreferencesFromResource(R.xml.pref_all) addPreferencesFromResource(R.xml.pref_all)
getPreferenceManager.getSharedPreferences.registerOnSharedPreferenceChangeListener(this) 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 = findPreference(Key.isProxyApps).asInstanceOf[SwitchPreference]
isProxyApps.setOnPreferenceClickListener((preference: Preference) => { isProxyApps.setOnPreferenceClickListener(_ => {
startActivity(new Intent(activity, classOf[AppManager])) startActivity(new Intent(activity, classOf[AppManager]))
isProxyApps.setChecked(true) isProxyApps.setChecked(true)
false 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] val switch = findPreference(Key.isAutoConnect).asInstanceOf[SwitchPreference]
switch.setOnPreferenceChangeListener((_, value) => { switch.setOnPreferenceChangeListener((_, value) => {
...@@ -131,9 +177,9 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan ...@@ -131,9 +177,9 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
}) })
} }
override def onResume { def refreshProfile() {
super.onResume() profile = app.currentProfile.get
isProxyApps.setChecked(app.settings.getBoolean(Key.isProxyApps, false)) // update isProxyApps.setChecked(profile.proxyApps)
} }
override def onDestroy { override def onDestroy {
...@@ -160,14 +206,9 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan ...@@ -160,14 +206,9 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
} }
} }
def update(profile: Profile) { var profile: Profile = _
for (name <- PROXY_PREFS) { def setProfile(profile: Profile) {
val pref = findPreference(name) this.profile = profile
updatePreference(pref, name, profile) for (name <- Array(PROXY_PREFS, FEATURE_PREFS).flatten) updatePreference(findPreference(name), name, profile)
}
for (name <- FEATURE_PREFS) {
val pref = findPreference(name)
updatePreference(pref, name, profile)
}
} }
} }
...@@ -48,9 +48,9 @@ import android.content.pm.PackageManager.NameNotFoundException ...@@ -48,9 +48,9 @@ import android.content.pm.PackageManager.NameNotFoundException
import android.net.VpnService import android.net.VpnService
import android.os._ import android.os._
import android.util.Log import android.util.Log
import com.github.shadowsocks.aidl.Config
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.database.Profile
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
...@@ -78,11 +78,6 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -78,11 +78,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
null null
} }
override def onCreate() {
super.onCreate()
ConfigUtils.refresh(this)
}
override def onRevoke() { override def onRevoke() {
stopRunner(true) stopRunner(true)
} }
...@@ -132,66 +127,53 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -132,66 +127,53 @@ class ShadowsocksVpnService extends VpnService with BaseService {
} }
} }
override def startRunner(config: Config) { override def startRunner(profile: Profile) {
super.startRunner(config)
vpnThread = new ShadowsocksVpnThread(this)
vpnThread.start()
// ensure the VPNService is prepared // ensure the VPNService is prepared
if (VpnService.prepare(this) != null) { if (VpnService.prepare(this) != null) {
val i = new Intent(this, classOf[ShadowsocksRunnerActivity]) val i = new Intent(this, classOf[ShadowsocksRunnerActivity])
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(i) startActivity(i)
stopRunner(true)
return return
} }
app.track(TAG, "start") super.startRunner(profile)
}
changeState(State.CONNECTING) override def connect() = {
super.connect()
Utils.ThrowableFuture { if (profile != null) {
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 (config != null) { vpnThread = new ShadowsocksVpnThread(this)
vpnThread.start()
// reset the context // reset the context
killProcesses() killProcesses()
// Resolve the server address // Resolve the server address
var resolved: Boolean = false var resolved: Boolean = false
if (!Utils.isNumeric(config.proxy)) { if (!Utils.isNumeric(profile.host)) {
Utils.resolve(config.proxy, enableIPv6 = true) match { Utils.resolve(profile.host, enableIPv6 = true) match {
case Some(addr) => case Some(addr) =>
config.proxy = addr profile.host = addr
resolved = true resolved = true
case None => resolved = false case None => resolved = false
}
} else {
resolved = true
} }
} else {
resolved = true
}
if (!resolved) { if (!resolved) {
changeState(State.STOPPED, getString(R.string.invalid_server)) changeState(State.STOPPED, getString(R.string.invalid_server))
stopRunner(true) stopRunner(true)
} else if (handleConnection) { } else if (handleConnection) {
changeState(State.CONNECTED) changeState(State.CONNECTED)
notification = new ShadowsocksNotification(this, config.profileName) notification = new ShadowsocksNotification(this, profile.name)
} else { } else {
changeState(State.STOPPED, getString(R.string.service_failed)) changeState(State.STOPPED, getString(R.string.service_failed))
stopRunner(true) stopRunner(true)
}
} }
} }
} }
...@@ -199,7 +181,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -199,7 +181,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
/** Called when the activity is first created. */ /** Called when the activity is first created. */
def handleConnection: Boolean = { def handleConnection: Boolean = {
startShadowsocksDaemon() startShadowsocksDaemon()
if (!config.isUdpDns) { if (!profile.udpdns) {
startDnsDaemon() startDnsDaemon()
startDnsTunnel() startDnsTunnel()
} }
...@@ -210,22 +192,22 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -210,22 +192,22 @@ class ShadowsocksVpnService extends VpnService with BaseService {
def startShadowsocksDaemon() { def startShadowsocksDaemon() {
if (config.route != Route.ALL) { if (profile.route != Route.ALL) {
val acl: Array[Array[String]] = config.route match { val acl: Array[Array[String]] = profile.route match {
case Route.BYPASS_LAN => Array(getResources.getStringArray(R.array.private_route)) 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_CHN => Array(getResources.getStringArray(R.array.chn_route))
case Route.BYPASS_LAN_CHN => case Route.BYPASS_LAN_CHN =>
Array(getResources.getStringArray(R.array.private_route), getResources.getStringArray(R.array.chn_route)) 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) acl.flatten.foreach(p.println)
}) })
} }
val conf = ConfigUtils val conf = ConfigUtils
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, config.localPort, .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, profile.localPort,
config.sitekey, config.encMethod, 600) profile.password, profile.method, 600)
ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/ss-local-vpn.conf"))(p => { Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-local-vpn.conf"))(p => {
p.println(conf) p.println(conf)
}) })
...@@ -236,9 +218,9 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -236,9 +218,9 @@ class ShadowsocksVpnService extends VpnService with BaseService {
, "-P", getApplicationInfo.dataDir , "-P", getApplicationInfo.dataDir
, "-c", getApplicationInfo.dataDir + "/ss-local-vpn.conf") , "-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 += "--acl"
cmd += (getApplicationInfo.dataDir + "/acl.list") cmd += (getApplicationInfo.dataDir + "/acl.list")
} }
...@@ -250,9 +232,9 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -250,9 +232,9 @@ class ShadowsocksVpnService extends VpnService with BaseService {
def startDnsTunnel() = { def startDnsTunnel() = {
val conf = ConfigUtils val conf = ConfigUtils
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, 8163, .SHADOWSOCKS.formatLocal(Locale.ENGLISH, profile.host, profile.remotePort, 8163,
config.sitekey, config.encMethod, 10) profile.password, profile.method, 10)
ConfigUtils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf"))(p => { Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf"))(p => {
p.println(conf) p.println(conf)
}) })
val cmd = new ArrayBuffer[String] val cmd = new ArrayBuffer[String]
...@@ -266,7 +248,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -266,7 +248,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
, "-P", getApplicationInfo.dataDir , "-P", getApplicationInfo.dataDir
, "-c", getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf") , "-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(" ")) if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
...@@ -274,18 +256,17 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -274,18 +256,17 @@ class ShadowsocksVpnService extends VpnService with BaseService {
} }
def startDnsDaemon() { def startDnsDaemon() {
val ipv6 = if (config.isIpv6) "" else "reject = ::/0;" val ipv6 = if (profile.ipv6) "" else "reject = ::/0;"
val conf = { val conf = {
if (config.route == Route.BYPASS_CHN || config.route == Route.BYPASS_LAN_CHN) { if (profile.route == Route.BYPASS_CHN || profile.route == Route.BYPASS_LAN_CHN) {
val blackList = ConfigUtils.getBlackList(this)
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, 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 { } else {
ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
"0.0.0.0", 8153, 8163, ipv6) "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) p.println(conf)
}) })
val cmd = getApplicationInfo.dataDir + "/pdnsd -c " + getApplicationInfo.dataDir + "/pdnsd-vpn.conf" val cmd = getApplicationInfo.dataDir + "/pdnsd -c " + getApplicationInfo.dataDir + "/pdnsd-vpn.conf"
...@@ -299,22 +280,22 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -299,22 +280,22 @@ class ShadowsocksVpnService extends VpnService with BaseService {
val builder = new Builder() val builder = new Builder()
builder builder
.setSession(config.profileName) .setSession(profile.name)
.setMtu(VPN_MTU) .setMtu(VPN_MTU)
.addAddress(PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), 24) .addAddress(PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), 24)
.addDnsServer("8.8.8.8") .addDnsServer("8.8.8.8")
if (config.isIpv6) { if (profile.ipv6) {
builder.addAddress(PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "1"), 126) builder.addAddress(PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "1"), 126)
builder.addRoute("::", 0) builder.addRoute("::", 0)
} }
if (Utils.isLollipopOrAbove) { if (Utils.isLollipopOrAbove) {
if (config.isProxyApps) { if (profile.proxyApps) {
for (pkg <- config.proxiedAppString.split('\n')) { for (pkg <- profile.individual.split('\n')) {
try { try {
if (!config.isBypassApps) { if (!profile.bypass) {
builder.addAllowedApplication(pkg) builder.addAllowedApplication(pkg)
} else { } else {
builder.addDisallowedApplication(pkg) builder.addDisallowedApplication(pkg)
...@@ -327,7 +308,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -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) builder.addRoute("0.0.0.0", 0)
} else { } else {
val privateList = getResources.getStringArray(R.array.bypass_private_route) val privateList = getResources.getStringArray(R.array.bypass_private_route)
...@@ -368,12 +349,12 @@ class ShadowsocksVpnService extends VpnService with BaseService { ...@@ -368,12 +349,12 @@ class ShadowsocksVpnService extends VpnService with BaseService {
+ "--loglevel 3") + "--loglevel 3")
.formatLocal(Locale.ENGLISH, .formatLocal(Locale.ENGLISH,
PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "2"), 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") cmd += " --netif-ip6addr " + PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "2")
if (config.isUdpDns) if (profile.udpdns)
cmd += " --enable-udprelay" cmd += " --enable-udprelay"
else else
cmd += " --dnsgw %s:8153".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1")) cmd += " --dnsgw %s:8153".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"))
......
...@@ -39,16 +39,14 @@ ...@@ -39,16 +39,14 @@
package com.github.shadowsocks.database package com.github.shadowsocks.database
import android.content.SharedPreferences
import android.util.Log import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.Key
object ProfileManager { object ProfileManager {
private final val TAG = "ProfileManager" private final val TAG = "ProfileManager"
} }
class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { class ProfileManager(dbHelper: DBHelper) {
import ProfileManager._ import ProfileManager._
var profileAddedListener: Profile => Any = _ var profileAddedListener: Profile => Any = _
...@@ -139,67 +137,6 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { ...@@ -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 = { def createDefault(): Profile = {
val profile = new Profile { val profile = new Profile {
name = "Default" name = "Default"
......
/*
* Shadowsocks - A shadowsocks client for Android
* Copyright (C) 2014 <max.c.lv@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
*
* ___====-_ _-====___
* _--^^^#####// \\#####^^^--_
* _-^##########// ( ) \\##########^-_
* -############// |\^^/| \\############-
* _/############// (@::@) \\############\_
* /#############(( \\// ))#############\
* -###############\\ (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)
}
}
...@@ -47,6 +47,101 @@ object Executable { ...@@ -47,6 +47,101 @@ object Executable {
val TUN2SOCKS = "tun2socks" 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 { object Key {
val profileId = "profileId" val profileId = "profileId"
val profileName = "profileName" val profileName = "profileName"
......
...@@ -120,6 +120,15 @@ object Utils { ...@@ -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 // Blocked > 3 seconds
def toggleAirplaneMode(context: Context) = { def toggleAirplaneMode(context: Context) = {
if (Console.isRoot) { if (Console.isRoot) {
......
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