diff --git a/build.sbt b/build.sbt index a7652e58..fe107f7f 100644 --- a/build.sbt +++ b/build.sbt @@ -29,13 +29,13 @@ resolvers += "JRAF" at "http://JRAF.org/static/maven/2" libraryDependencies ++= Seq( "dnsjava" % "dnsjava" % "2.1.7", "com.github.kevinsawicki" % "http-request" % "6.0", - "commons-net" % "commons-net" % "3.3", + "commons-net" % "commons-net" % "3.4", "eu.chainfire" % "libsuperuser" % "1.0.0.201510071325", "com.google.zxing" % "android-integration" % "3.2.1", "net.glxn.qrgen" % "android" % "2.0", - "com.google.android.gms" % "play-services-base" % "8.3.0", - "com.google.android.gms" % "play-services-ads" % "8.3.0", - "com.google.android.gms" % "play-services-analytics" % "8.3.0", + "com.google.android.gms" % "play-services-base" % "8.4.0", + "com.google.android.gms" % "play-services-ads" % "8.4.0", + "com.google.android.gms" % "play-services-analytics" % "8.4.0", "com.android.support" % "design" % "23.1.1", "com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01", "com.j256.ormlite" % "ormlite-core" % "4.48", diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml index 6a58ed3b..4c84e9df 100755 --- a/src/main/res/values-zh/strings.xml +++ b/src/main/res/values-zh/strings.xml @@ -85,6 +85,10 @@ 状态码无效。(#%d) + 配置文件 + • 点击选择配置文件;\n• 滑动删除;\n• + 长按拖动改变顺序。 + 好的 为影梭添加此配置文件? 扫描二维码 手动设置 diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml index 333f4545..311f4003 100644 --- a/src/main/res/values/colors.xml +++ b/src/main/res/values/colors.xml @@ -2,7 +2,7 @@ - #64DD17 + #388E3C #90A4AE #607D8B #455A64 diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 2affac0a..2bbab003 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -103,6 +103,10 @@ Failed to import. + Profiles + • Tap to select a profile;\n• Swipe to remove;\n• + Long press to reorder profiles. + Gotcha! Add this Shadowsocks Profile? Scan QR code Manual Settings diff --git a/src/main/scala/com/github/shadowsocks/BaseService.scala b/src/main/scala/com/github/shadowsocks/BaseService.scala index f580ed8a..0c81975a 100644 --- a/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -134,6 +134,7 @@ trait BaseService extends Service { def updateTrafficTotal(tx: Long, rx: Long) { val handler = new Handler(getContext.getMainLooper) handler.post(() => { + val config = this.config // avoid race conditions without locking if (config != null) { ShadowsocksApplication.profileManager.getProfile(config.profileId) match { case Some(profile) => diff --git a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala index e5c3df49..3eef2882 100644 --- a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala +++ b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala @@ -1,7 +1,7 @@ package com.github.shadowsocks import android.content.Intent -import android.os.{Handler, Bundle} +import android.os.{Bundle, Handler} import android.support.design.widget.Snackbar import android.support.v7.app.{AlertDialog, AppCompatActivity} import android.support.v7.widget.RecyclerView.ViewHolder @@ -25,7 +25,13 @@ import scala.collection.mutable.ArrayBuffer /** * @author Mygod */ +object ProfileManagerActivity { + private final val profileTip = "profileTip" +} + class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListener with ServiceBoundContext { + import ProfileManagerActivity._ + private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener { private var item: Profile = _ private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView] @@ -72,11 +78,12 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe handler.post(() => text.setText(builder)) } - def bind(item: Profile) { + def bind(item: Profile, i: Int) { this.item = item updateText() if (item.id == ShadowsocksApplication.profileId) { text.setChecked(true) + selectedIndex = i selectedItem = this } else { text.setChecked(false) @@ -92,12 +99,12 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] { private val recycleBin = new ArrayBuffer[(Int, Profile)] - private var profiles = new ArrayBuffer[Profile] + var profiles = new ArrayBuffer[Profile] profiles ++= ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List.empty[Profile]) def getItemCount = profiles.length - def onBindViewHolder(vh: ProfileViewHolder, i: Int) = vh.bind(profiles(i)) + def onBindViewHolder(vh: ProfileViewHolder, i: Int) = vh.bind(profiles(i), i) def onCreateViewHolder(vg: ViewGroup, i: Int) = new ProfileViewHolder(LayoutInflater.from(vg.getContext).inflate(R.layout.layout_profiles_item, vg, false)) @@ -110,6 +117,24 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe notifyItemInserted(pos) } + def move(from: Int, to: Int) { + val step = if (from < to) 1 else -1 + val first = profiles(from) + var previousOrder = profiles(from).userOrder + for (i <- from until to by step) { + val next = profiles(i + step) + val order = next.userOrder + next.userOrder = previousOrder + previousOrder = order + profiles(i) = next + ShadowsocksApplication.profileManager.updateProfile(next) + } + first.userOrder = previousOrder + profiles(to) = first + ShadowsocksApplication.profileManager.updateProfile(first) + notifyItemMoved(from, to) + } + def remove(pos: Int) { recycleBin.append((pos, profiles(pos))) profiles.remove(pos) @@ -131,6 +156,7 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe } } + private var selectedIndex = -1 private var selectedItem: ProfileViewHolder = _ private val handler = new Handler @@ -153,21 +179,30 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe ShadowsocksApplication.profileManager.setProfileAddedListener(profilesAdapter.add) val profilesList = findViewById(R.id.profilesList).asInstanceOf[RecyclerView] - profilesList.setLayoutManager(new LinearLayoutManager(this)) + val layoutManager = new LinearLayoutManager(this) + profilesList.setLayoutManager(layoutManager) profilesList.setItemAnimator(new DefaultItemAnimator) profilesList.setAdapter(profilesAdapter) + if (selectedIndex < 0) selectedIndex = profilesAdapter.profiles.zipWithIndex.collectFirst { + case (profile, i) if profile.id == ShadowsocksApplication.profileId => i + }.getOrElse(-1) + layoutManager.scrollToPosition(selectedIndex) removedSnackbar = Snackbar.make(profilesList, R.string.removed, Snackbar.LENGTH_LONG) .setAction(R.string.undo, ((v: View) => profilesAdapter.undoRemoves): OnClickListener) removedSnackbar.getView.addOnAttachStateChangeListener(new OnAttachStateChangeListener { def onViewDetachedFromWindow(v: View) = profilesAdapter.commitRemoves def onViewAttachedToWindow(v: View) = () }) - new ItemTouchHelper(new SimpleCallback(0, ItemTouchHelper.START | ItemTouchHelper.END) { + new ItemTouchHelper(new SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, + ItemTouchHelper.START | ItemTouchHelper.END) { def onSwiped(viewHolder: ViewHolder, direction: Int) = { profilesAdapter.remove(viewHolder.getAdapterPosition) removedSnackbar.show } - def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = false // TODO? + def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = { + profilesAdapter.move(viewHolder.getAdapterPosition, target.getAdapterPosition) + true + } }).attachToRecyclerView(profilesList) attachService(new IShadowsocksServiceCallback.Stub { @@ -175,6 +210,12 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) = if (selectedItem != null) selectedItem.updateText(true) }) + + if (ShadowsocksApplication.settings.getBoolean(profileTip, true)) { + ShadowsocksApplication.settings.edit.putBoolean(profileTip, false).commit + new AlertDialog.Builder(this).setTitle(R.string.profile_manager_dialog) + .setMessage(R.string.profile_manager_dialog_content).setPositiveButton(R.string.gotcha, null).create.show + } } override def onDestroy { diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 5f3391ac..d2c6646d 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -49,7 +49,6 @@ import android.content.res.AssetManager import android.graphics.Typeface import android.net.VpnService import android.os._ -import android.preference.{Preference, SwitchPreference} import android.support.design.widget.{FloatingActionButton, Snackbar} import android.support.v4.content.ContextCompat import android.support.v7.app.AppCompatActivity @@ -60,7 +59,6 @@ import android.widget._ import com.github.jorgecastilloprz.FABProgressCircle import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.database._ -import com.github.shadowsocks.preferences.{DropDownPreference, NumberPickerPreference, PasswordEditTextPreference, SummaryEditTextPreference} import com.github.shadowsocks.utils._ import com.google.android.gms.ads.{AdRequest, AdSize, AdView} @@ -91,52 +89,9 @@ object Shadowsocks { // Constants val TAG = "Shadowsocks" val REQUEST_CONNECT = 1 - - val PREFS_NAME = "Shadowsocks" - val PROXY_PREFS = Array(Key.profileName, Key.proxy, Key.remotePort, Key.localPort, Key.sitekey, Key.encMethod, - Key.isAuth) - val FEATURE_PREFS = Array(Key.route, Key.isProxyApps, Key.isUdpDns, Key.isIpv6) val EXECUTABLES = Array(Executable.PDNSD, Executable.REDSOCKS, Executable.SS_TUNNEL, Executable.SS_LOCAL, Executable.TUN2SOCKS) - // Helper functions - def updateDropDownPreference(pref: Preference, value: String) { - pref.asInstanceOf[DropDownPreference].setValue(value) - } - - def updatePasswordEditTextPreference(pref: Preference, value: String) { - pref.setSummary(value) - pref.asInstanceOf[PasswordEditTextPreference].setText(value) - } - - def updateNumberPickerPreference(pref: Preference, value: Int) { - pref.asInstanceOf[NumberPickerPreference].setValue(value) - } - - def updateSummaryEditTextPreference(pref: Preference, value: String) { - pref.setSummary(value) - pref.asInstanceOf[SummaryEditTextPreference].setText(value) - } - - def updateSwitchPreference(pref: Preference, value: Boolean) { - pref.asInstanceOf[SwitchPreference].setChecked(value) - } - - def updatePreference(pref: Preference, name: String, profile: Profile) { - name match { - case Key.profileName => updateSummaryEditTextPreference(pref, profile.name) - case Key.proxy => updateSummaryEditTextPreference(pref, profile.host) - case Key.remotePort => updateNumberPickerPreference(pref, profile.remotePort) - case Key.localPort => updateNumberPickerPreference(pref, profile.localPort) - case Key.sitekey => updatePasswordEditTextPreference(pref, profile.password) - case Key.encMethod => updateDropDownPreference(pref, profile.method) - case Key.route => updateDropDownPreference(pref, profile.route) - case Key.isProxyApps => updateSwitchPreference(pref, profile.proxyApps) - case Key.isUdpDns => updateSwitchPreference(pref, profile.udpdns) - case Key.isAuth => updateSwitchPreference(pref, profile.auth) - case Key.isIpv6 => updateSwitchPreference(pref, profile.ipv6) - } - } } class Shadowsocks @@ -166,7 +121,7 @@ class Shadowsocks fab.setImageResource(R.drawable.ic_cloud_queue) fab.setEnabled(false) fabProgressCircle.show() - setPreferenceEnabled(enabled = false) + preferences.setEnabled(false) case State.CONNECTED => fab.setBackgroundTintList(greenTint) if (state == State.CONNECTING) { @@ -176,7 +131,7 @@ class Shadowsocks } fab.setEnabled(true) changeSwitch(checked = true) - setPreferenceEnabled(enabled = false) + preferences.setEnabled(false) case State.STOPPED => fab.setBackgroundTintList(greyTint) handler.postDelayed(() => fabProgressCircle.hide(), 1000) @@ -189,13 +144,13 @@ class Shadowsocks (_ => preferences.natSwitch.setChecked(false)): View.OnClickListener) snackbar.show } - setPreferenceEnabled(enabled = true) + preferences.setEnabled(true) case State.STOPPING => fab.setBackgroundTintList(greyTint) fab.setImageResource(R.drawable.ic_cloud_queue) fab.setEnabled(false) if (state == State.CONNECTED) fabProgressCircle.show() // ignore for stopped - setPreferenceEnabled(enabled = false) + preferences.setEnabled(false) } state = s }) @@ -255,7 +210,7 @@ class Shadowsocks getFragmentManager.findFragmentById(android.R.id.content).asInstanceOf[ShadowsocksSettings] private var adView: AdView = _ private lazy val greyTint = ContextCompat.getColorStateList(this, R.color.material_blue_grey_700) - private lazy val greenTint = ContextCompat.getColorStateList(this, R.color.material_green_a700) + private lazy val greenTint = ContextCompat.getColorStateList(this, R.color.material_green_700) var handler = new Handler() @@ -367,12 +322,7 @@ class Shadowsocks changeSwitch(checked = false) } - def isReady: Boolean = { - if (!checkText(Key.proxy)) return false - if (!checkText(Key.sitekey)) return false - if (bgService == null) return false - true - } + def isReady: Boolean = checkText(Key.proxy) && checkText(Key.sitekey) && bgService != null def prepareStartService() { ThrowableFuture { @@ -438,9 +388,10 @@ class Shadowsocks currentProfile = ShadowsocksApplication.currentProfile match { case Some(profile) => profile // updated case None => // removed - val profiles = ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List[Profile]()) - if (profiles.isEmpty) ShadowsocksApplication.profileManager.createDefault() - else ShadowsocksApplication.switchProfile(profiles.head.id) + ShadowsocksApplication.profileManager.getFirstProfile match { + case Some(first) => ShadowsocksApplication.switchProfile(first.id) + case None => ShadowsocksApplication.profileManager.createDefault() + } } updatePreferenceScreen() @@ -461,25 +412,25 @@ class Shadowsocks fab.setBackgroundTintList(greyTint) serviceStarted = false fab.setImageResource(R.drawable.ic_cloud_queue) - setPreferenceEnabled(false) + preferences.setEnabled(false) fabProgressCircle.show() case State.CONNECTED => fab.setBackgroundTintList(greenTint) serviceStarted = true fab.setImageResource(R.drawable.ic_cloud) - setPreferenceEnabled(false) + preferences.setEnabled(false) fabProgressCircle.postDelayed(fabProgressCircle.hide, 100) case State.STOPPING => fab.setBackgroundTintList(greyTint) serviceStarted = false fab.setImageResource(R.drawable.ic_cloud_queue) - setPreferenceEnabled(false) + preferences.setEnabled(false) fabProgressCircle.show() case _ => fab.setBackgroundTintList(greyTint) serviceStarted = false fab.setImageResource(R.drawable.ic_cloud_off) - setPreferenceEnabled(true) + preferences.setEnabled(true) fabProgressCircle.postDelayed(fabProgressCircle.hide, 100) } state = bgService.getState @@ -497,26 +448,6 @@ class Shadowsocks updateTraffic() } - private def setPreferenceEnabled(enabled: Boolean) { - preferences.findPreference(Key.isNAT).setEnabled(enabled) - for (name <- Shadowsocks.PROXY_PREFS) { - val pref = preferences.findPreference(name) - if (pref != null) { - pref.setEnabled(enabled) - } - } - for (name <- Shadowsocks.FEATURE_PREFS) { - val pref = preferences.findPreference(name) - if (pref != null) { - if (name == Key.isProxyApps) { - pref.setEnabled(enabled && (Utils.isLollipopOrAbove || !ShadowsocksApplication.isVpnEnabled)) - } else { - pref.setEnabled(enabled) - } - } - } - } - private def updatePreferenceScreen() { val profile = currentProfile if (profile.host == "198.199.101.152" && adView == null) { @@ -527,14 +458,7 @@ class Shadowsocks adView.loadAd(new AdRequest.Builder().build()) } - for (name <- Shadowsocks.PROXY_PREFS) { - val pref = preferences.findPreference(name) - Shadowsocks.updatePreference(pref, name, profile) - } - for (name <- Shadowsocks.FEATURE_PREFS) { - val pref = preferences.findPreference(name) - Shadowsocks.updatePreference(pref, name, profile) - } + preferences.update(profile) } override def onStop() { @@ -542,8 +466,11 @@ class Shadowsocks clearDialog() } + private var _isDestroyed: Boolean = _ + override def isDestroyed = if (Build.VERSION.SDK_INT >= 17) super.isDestroyed else _isDestroyed override def onDestroy() { super.onDestroy() + _isDestroyed = true deattachService() new BackupManager(this).dataChanged() handler.removeCallbacksAndMessages(null) @@ -613,8 +540,8 @@ class Shadowsocks } def clearDialog() { - if (progressDialog != null) { - progressDialog.dismiss() + if (progressDialog != null && progressDialog.isShowing) { + if (!isDestroyed) progressDialog.dismiss() progressDialog = null progressTag = -1 } diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala index 46795624..21318675 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala @@ -11,11 +11,61 @@ 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.database.Profile +import com.github.shadowsocks.preferences.{DropDownPreference, NumberPickerPreference, PasswordEditTextPreference, SummaryEditTextPreference} import com.github.shadowsocks.utils.CloseUtils._ -import com.github.shadowsocks.utils.Key +import com.github.shadowsocks.utils.{Key, Utils} + +object ShadowsocksSettings { + // Constants + val PREFS_NAME = "Shadowsocks" + val PROXY_PREFS = Array(Key.profileName, Key.proxy, Key.remotePort, Key.localPort, Key.sitekey, Key.encMethod, + Key.isAuth) + val FEATURE_PREFS = Array(Key.route, Key.isProxyApps, Key.isUdpDns, Key.isIpv6) + + // Helper functions + def updateDropDownPreference(pref: Preference, value: String) { + pref.asInstanceOf[DropDownPreference].setValue(value) + } + + def updatePasswordEditTextPreference(pref: Preference, value: String) { + pref.setSummary(value) + pref.asInstanceOf[PasswordEditTextPreference].setText(value) + } + + def updateNumberPickerPreference(pref: Preference, value: Int) { + pref.asInstanceOf[NumberPickerPreference].setValue(value) + } + + def updateSummaryEditTextPreference(pref: Preference, value: String) { + pref.setSummary(value) + pref.asInstanceOf[SummaryEditTextPreference].setText(value) + } + + def updateSwitchPreference(pref: Preference, value: Boolean) { + pref.asInstanceOf[SwitchPreference].setChecked(value) + } + + def updatePreference(pref: Preference, name: String, profile: Profile) { + name match { + case Key.profileName => updateSummaryEditTextPreference(pref, profile.name) + case Key.proxy => updateSummaryEditTextPreference(pref, profile.host) + case Key.remotePort => updateNumberPickerPreference(pref, profile.remotePort) + case Key.localPort => updateNumberPickerPreference(pref, profile.localPort) + case Key.sitekey => updatePasswordEditTextPreference(pref, profile.password) + case Key.encMethod => updateDropDownPreference(pref, profile.method) + case Key.route => updateDropDownPreference(pref, profile.route) + case Key.isProxyApps => updateSwitchPreference(pref, profile.proxyApps) + case Key.isUdpDns => updateSwitchPreference(pref, profile.udpdns) + case Key.isAuth => updateSwitchPreference(pref, profile.auth) + case Key.isIpv6 => updateSwitchPreference(pref, profile.ipv6) + } + } +} -// TODO: Move related logic here class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChangeListener { + import ShadowsocksSettings._ + private def activity = getActivity.asInstanceOf[Shadowsocks] lazy val natSwitch = findPreference(Key.isNAT).asInstanceOf[SwitchPreference] var stat: Preference = _ @@ -122,10 +172,38 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan } def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) = key match { - case Key.isNAT => activity.handler.post(() => { - activity.deattachService - activity.attachService - }) + case Key.isNAT => + activity.handler.post(() => { + activity.deattachService + activity.attachService + }) + setEnabled(enabled) case _ => } + + private var enabled = true + def setEnabled(enabled: Boolean) { + this.enabled = enabled + findPreference(Key.isNAT).setEnabled(enabled) + for (name <- PROXY_PREFS) { + val pref = findPreference(name) + if (pref != null) pref.setEnabled(enabled) + } + for (name <- FEATURE_PREFS) { + val pref = findPreference(name) + if (pref != null) pref.setEnabled(enabled && + (name != Key.isProxyApps || Utils.isLollipopOrAbove || !ShadowsocksApplication.isVpnEnabled)) + } + } + + 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) + } + } } diff --git a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala index aee1fe9f..afd64535 100644 --- a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala +++ b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala @@ -64,7 +64,7 @@ object DBHelper { } class DBHelper(val context: Context) - extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 14) { + extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 15) { import DBHelper._ lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile]) @@ -116,6 +116,15 @@ class DBHelper(val context: Context) profileDao.update(profile) } } + if (oldVersion < 15) { + profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN userOrder LONG;") + var i = 0 + for (profile <- profileDao.queryForAll.asScala) { + profile.userOrder = i + profileDao.update(profile) + i += 1 + } + } } } } diff --git a/src/main/scala/com/github/shadowsocks/database/Profile.scala b/src/main/scala/com/github/shadowsocks/database/Profile.scala index 66744357..b7c6ae4e 100644 --- a/src/main/scala/com/github/shadowsocks/database/Profile.scala +++ b/src/main/scala/com/github/shadowsocks/database/Profile.scala @@ -55,13 +55,13 @@ class Profile { var localPort: Int = 1080 @DatabaseField - var remotePort: Int = 8338 + var remotePort: Int = 8388 @DatabaseField var password: String = "" @DatabaseField - var method: String = "rc4" + var method: String = "aes-256-cfb" @DatabaseField var route: String = "all" @@ -92,4 +92,7 @@ class Profile { @DatabaseField val date: java.util.Date = new java.util.Date() + + @DatabaseField + var userOrder: Long = _ } diff --git a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala index 0cf4d8e5..d11818b0 100644 --- a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala +++ b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala @@ -64,6 +64,9 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { profile.udpdns = oldProfile.udpdns case _ => } + val last = dbHelper.profileDao.queryRaw(dbHelper.profileDao.queryBuilder.selectRaw("MAX(userOrder)") + .prepareStatementString).getFirstResult + if (last != null && last.length == 1 && last(0) != null) profile.userOrder = last(0).toInt + 1 dbHelper.profileDao.createOrUpdate(profile) if (profileAddedListener != null) profileAddedListener(profile) profile @@ -109,10 +112,21 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { } } + def getFirstProfile = { + try { + val result = dbHelper.profileDao.query(dbHelper.profileDao.queryBuilder.limit(1L).prepare) + if (result.size == 1) Option(result.get(0)) else None + } catch { + case ex: Exception => + Log.e(Shadowsocks.TAG, "getAllProfiles", ex) + None + } + } + def getAllProfiles: Option[List[Profile]] = { try { import scala.collection.JavaConversions._ - Option(dbHelper.profileDao.queryForAll().toList) + Option(dbHelper.profileDao.query(dbHelper.profileDao.queryBuilder.orderBy("userOrder", true).prepare).toList) } catch { case ex: Exception => Log.e(Shadowsocks.TAG, "getAllProfiles", ex)