From 7e5b44d36f51566c2a0d149e16f1a1328356e4b7 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 25 Dec 2015 14:20:13 +0800 Subject: [PATCH 01/12] Scroll to selected profile --- .../shadowsocks/ProfileManagerActivity.scala | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala index e5c3df49..4d2fdb17 100644 --- a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala +++ b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala @@ -72,11 +72,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 +93,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)) @@ -131,6 +132,7 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe } } + private var selectedIndex = -1 private var selectedItem: ProfileViewHolder = _ private val handler = new Handler @@ -153,9 +155,14 @@ 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 { From eed7ec6eba468a3369f5745f8af515c1e89eef6d Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 25 Dec 2015 14:24:37 +0800 Subject: [PATCH 02/12] Update library dependencies --- build.sbt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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", From 164becfed08bdb673df247c5d79a492153dbe118 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 25 Dec 2015 15:51:18 +0800 Subject: [PATCH 03/12] Implement dragging to move profiles --- .../shadowsocks/ProfileManagerActivity.scala | 26 +++++++++++++++++-- .../com/github/shadowsocks/Shadowsocks.scala | 7 ++--- .../shadowsocks/database/DBHelper.scala | 11 +++++++- .../github/shadowsocks/database/Profile.scala | 3 +++ .../shadowsocks/database/ProfileManager.scala | 15 ++++++++++- 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala index 4d2fdb17..2bf39cc5 100644 --- a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala +++ b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala @@ -111,6 +111,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) @@ -169,12 +187,16 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe 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 { diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 5f3391ac..5a61774f 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -438,9 +438,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() 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..4f3c7917 100644 --- a/src/main/scala/com/github/shadowsocks/database/Profile.scala +++ b/src/main/scala/com/github/shadowsocks/database/Profile.scala @@ -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..50cae234 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) profile.userOrder = last(0).toInt + 1 dbHelper.profileDao.createOrUpdate(profile) if (profileAddedListener != null) profileAddedListener(profile) profile @@ -109,10 +112,20 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { } } + def getFirstProfile = { + try { + Option(dbHelper.profileDao.query(dbHelper.profileDao.queryBuilder.limit(1L).prepare).get(0)) + } 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) From 9fb0639f864846c8ac3ba81072c8e4fe99fa4604 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 25 Dec 2015 16:07:07 +0800 Subject: [PATCH 04/12] Move preference related logic to ShadowsocksSettings --- .../com/github/shadowsocks/Shadowsocks.scala | 97 ++----------------- .../shadowsocks/ShadowsocksSettings.scala | 78 ++++++++++++++- 2 files changed, 86 insertions(+), 89 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 5a61774f..f6aa9677 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 }) @@ -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 { @@ -462,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 @@ -498,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) { @@ -528,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() { diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala index 46795624..df774c1e 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 = _ @@ -128,4 +178,28 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan }) case _ => } + + def setEnabled(enabled: Boolean) { + 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) + } + } } From aed63088411f46d4f3b0b115ef851e0ada8d3e76 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 25 Dec 2015 16:25:39 +0800 Subject: [PATCH 05/12] Fix a race condition --- src/main/scala/com/github/shadowsocks/BaseService.scala | 1 + 1 file changed, 1 insertion(+) 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) => From 0cf509833a768882ea74f4d91201f32528772e31 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 25 Dec 2015 16:33:44 +0800 Subject: [PATCH 06/12] Fix dismissing ProgressDialog after activity destroyed --- src/main/scala/com/github/shadowsocks/Shadowsocks.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index f6aa9677..64dc7616 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -537,8 +537,8 @@ class Shadowsocks } def clearDialog() { - if (progressDialog != null) { - progressDialog.dismiss() + if (progressDialog != null && progressDialog.isShowing) { + if (!isDestroyed) progressDialog.dismiss() progressDialog = null progressTag = -1 } From c93a0348ad29045318f81b74faaa60fd8df5e4e9 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 25 Dec 2015 16:37:30 +0800 Subject: [PATCH 07/12] Fix isDestroyed is missing on API 16 --- src/main/scala/com/github/shadowsocks/Shadowsocks.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 64dc7616..95a78ec9 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -466,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) From 0f9b686e5530a5d8e5f9f06d1fdad36721338c34 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 25 Dec 2015 17:29:00 +0800 Subject: [PATCH 08/12] Fix per-app proxy not enabled when NAT is on --- .../com/github/shadowsocks/ShadowsocksSettings.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala index df774c1e..21318675 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala @@ -172,14 +172,18 @@ 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) From eadb95ca2d40404eea8786969171999f84e5b905 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 25 Dec 2015 17:43:18 +0800 Subject: [PATCH 09/12] Fix creating profiles --- .../com/github/shadowsocks/database/ProfileManager.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala index 50cae234..d11818b0 100644 --- a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala +++ b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala @@ -66,7 +66,7 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { } val last = dbHelper.profileDao.queryRaw(dbHelper.profileDao.queryBuilder.selectRaw("MAX(userOrder)") .prepareStatementString).getFirstResult - if (last != null && last.length == 1) profile.userOrder = last(0).toInt + 1 + 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 @@ -114,7 +114,8 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) { def getFirstProfile = { try { - Option(dbHelper.profileDao.query(dbHelper.profileDao.queryBuilder.limit(1L).prepare).get(0)) + 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) From 8cf37954d7654729212cb488cd36fbbc807a60d9 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 25 Dec 2015 19:08:38 +0800 Subject: [PATCH 10/12] Revert "Use brighter accent color for FAB" This reverts commit edd5fb4a4c8aa94a599e8f6ed647e03d88e63289. --- src/main/res/values/colors.xml | 2 +- src/main/scala/com/github/shadowsocks/Shadowsocks.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 95a78ec9..d2c6646d 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -210,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() From 0d582f2392d78951dd24d1e99873a45798c86b88 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 25 Dec 2015 22:03:15 +0800 Subject: [PATCH 11/12] Update profile default values --- src/main/scala/com/github/shadowsocks/database/Profile.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/database/Profile.scala b/src/main/scala/com/github/shadowsocks/database/Profile.scala index 4f3c7917..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" From c4583bea227965f033986724bdbd09d85fac5f49 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 25 Dec 2015 22:33:31 +0800 Subject: [PATCH 12/12] Add a friendly dialog --- src/main/res/values-zh/strings.xml | 4 ++++ src/main/res/values/strings.xml | 4 ++++ .../shadowsocks/ProfileManagerActivity.scala | 14 +++++++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) 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/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/ProfileManagerActivity.scala b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala index 2bf39cc5..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] @@ -204,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 {