diff --git a/build.sbt b/build.sbt
index db33d8df..0a01f5e0 100644
--- a/build.sbt
+++ b/build.sbt
@@ -2,7 +2,6 @@ libraryDependencies ++= Seq(
"com.google.android" % "support-v4" % "r12",
"com.google.android.analytics" % "analytics" % "2.0beta4",
"dnsjava" % "dnsjava" % "2.1.5",
- "com.nostra13.universalimageloader" % "universal-image-loader" % "1.8.4",
"com.google.android.admob" % "admob" % "6.3.1"
)
@@ -10,6 +9,13 @@ libraryDependencies ++= Seq(
"com.actionbarsherlock" % "actionbarsherlock" % "4.4.0" artifacts(Artifact("actionbarsherlock", "apklib", "apklib")),
"net.saik0.android.unifiedpreference" % "unifiedpreference" % "0.0.2" artifacts(Artifact("unifiedpreference", "apklib", "apklib")),
"org.jraf" % "android-switch-backport" % "1.0" artifacts(Artifact("android-switch-backport", "apklib", "apklib")),
- "de.keyboardsurfer.android.widget" % "crouton" % "1.7"
+ "net.simonvt.menudrawer" % "menudrawer" % "3.0.4" artifacts(Artifact("menudrawer", "apklib", "apklib"))
+)
+
+libraryDependencies ++= Seq(
+ "de.keyboardsurfer.android.widget" % "crouton" % "1.7",
+ "com.nostra13.universalimageloader" % "universal-image-loader" % "1.8.4",
+ "com.j256.ormlite" % "ormlite-core" % "4.47",
+ "com.j256.ormlite" % "ormlite-android" % "4.47"
)
diff --git a/project/Build.scala b/project/Build.scala
index 822738a0..55aa12fb 100644
--- a/project/Build.scala
+++ b/project/Build.scala
@@ -5,8 +5,8 @@ import sbtandroid._
import sbtandroid.AndroidPlugin._
object App {
- val version = "1.8.3"
- val versionCode = 48
+ val version = "1.9.0"
+ val versionCode = 50
}
object General {
@@ -28,6 +28,7 @@ object General {
"-keep interface com.actionbarsherlock.** { *; }",
"-keep class org.jraf.android.backport.** { *; }",
"-keep class com.github.shadowsocks.** { *; }",
+ "-keep class * extends com.j256.ormlite.** { *; }",
"-keepattributes *Annotation*")
val proguardSettings = Seq (
@@ -36,8 +37,7 @@ object General {
)
val miscSettings = Seq (
- cachePasswords := true,
- publicServer := "127.0.0.1"
+ cachePasswords := true
)
lazy val fullAndroidSettings =
diff --git a/project/plugins.sbt b/project/plugins.sbt
index b17b415f..3bbacfde 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -6,4 +6,4 @@ resolvers += Resolver.url("madeye private releases", new URL("http://madeye-mave
addSbtPlugin("org.scala-sbt" % "sbt-android" % "0.7.1-SNAPSHOT")
-addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.4.0")
+addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.5.1")
diff --git a/src/main/res/drawable-hdpi/ic_drawer.png b/src/main/res/drawable-hdpi/ic_drawer.png
new file mode 100644
index 00000000..ec8c51f8
Binary files /dev/null and b/src/main/res/drawable-hdpi/ic_drawer.png differ
diff --git a/src/main/res/drawable-hdpi/md__category_background.9.png b/src/main/res/drawable-hdpi/md__category_background.9.png
new file mode 100644
index 00000000..43a20adc
Binary files /dev/null and b/src/main/res/drawable-hdpi/md__category_background.9.png differ
diff --git a/src/main/res/drawable-hdpi/md__list_focused_holo.9.png b/src/main/res/drawable-hdpi/md__list_focused_holo.9.png
new file mode 100644
index 00000000..55527084
Binary files /dev/null and b/src/main/res/drawable-hdpi/md__list_focused_holo.9.png differ
diff --git a/src/main/res/drawable-hdpi/md__list_longpressed_holo.9.png b/src/main/res/drawable-hdpi/md__list_longpressed_holo.9.png
new file mode 100644
index 00000000..4ea7afa0
Binary files /dev/null and b/src/main/res/drawable-hdpi/md__list_longpressed_holo.9.png differ
diff --git a/src/main/res/drawable-hdpi/md__list_pressed_holo_dark.9.png b/src/main/res/drawable-hdpi/md__list_pressed_holo_dark.9.png
new file mode 100644
index 00000000..5654cd69
Binary files /dev/null and b/src/main/res/drawable-hdpi/md__list_pressed_holo_dark.9.png differ
diff --git a/src/main/res/drawable-hdpi/md__list_selector_disabled_holo_dark.9.png b/src/main/res/drawable-hdpi/md__list_selector_disabled_holo_dark.9.png
new file mode 100644
index 00000000..f6fd30dc
Binary files /dev/null and b/src/main/res/drawable-hdpi/md__list_selector_disabled_holo_dark.9.png differ
diff --git a/src/main/res/drawable-hdpi/menu_arrow.png b/src/main/res/drawable-hdpi/menu_arrow.png
new file mode 100644
index 00000000..c28d98f3
Binary files /dev/null and b/src/main/res/drawable-hdpi/menu_arrow.png differ
diff --git a/src/main/res/drawable-mdpi/ic_drawer.png b/src/main/res/drawable-mdpi/ic_drawer.png
new file mode 100644
index 00000000..7420a68d
Binary files /dev/null and b/src/main/res/drawable-mdpi/ic_drawer.png differ
diff --git a/src/main/res/drawable-mdpi/md__category_background.9.png b/src/main/res/drawable-mdpi/md__category_background.9.png
new file mode 100644
index 00000000..af0bc168
Binary files /dev/null and b/src/main/res/drawable-mdpi/md__category_background.9.png differ
diff --git a/src/main/res/drawable-mdpi/md__list_focused_holo.9.png b/src/main/res/drawable-mdpi/md__list_focused_holo.9.png
new file mode 100644
index 00000000..00f05d8c
Binary files /dev/null and b/src/main/res/drawable-mdpi/md__list_focused_holo.9.png differ
diff --git a/src/main/res/drawable-mdpi/md__list_longpressed_holo.9.png b/src/main/res/drawable-mdpi/md__list_longpressed_holo.9.png
new file mode 100644
index 00000000..3bf8e036
Binary files /dev/null and b/src/main/res/drawable-mdpi/md__list_longpressed_holo.9.png differ
diff --git a/src/main/res/drawable-mdpi/md__list_pressed_holo_dark.9.png b/src/main/res/drawable-mdpi/md__list_pressed_holo_dark.9.png
new file mode 100644
index 00000000..6e77525d
Binary files /dev/null and b/src/main/res/drawable-mdpi/md__list_pressed_holo_dark.9.png differ
diff --git a/src/main/res/drawable-mdpi/md__list_selector_disabled_holo_dark.9.png b/src/main/res/drawable-mdpi/md__list_selector_disabled_holo_dark.9.png
new file mode 100644
index 00000000..92da2f0d
Binary files /dev/null and b/src/main/res/drawable-mdpi/md__list_selector_disabled_holo_dark.9.png differ
diff --git a/src/main/res/drawable-mdpi/menu_arrow.png b/src/main/res/drawable-mdpi/menu_arrow.png
new file mode 100644
index 00000000..ecacda71
Binary files /dev/null and b/src/main/res/drawable-mdpi/menu_arrow.png differ
diff --git a/src/main/res/drawable-xhdpi/ic_drawer.png b/src/main/res/drawable-xhdpi/ic_drawer.png
new file mode 100644
index 00000000..7ffbe5c5
Binary files /dev/null and b/src/main/res/drawable-xhdpi/ic_drawer.png differ
diff --git a/src/main/res/drawable-xhdpi/md__category_background.9.png b/src/main/res/drawable-xhdpi/md__category_background.9.png
new file mode 100644
index 00000000..942d72e6
Binary files /dev/null and b/src/main/res/drawable-xhdpi/md__category_background.9.png differ
diff --git a/src/main/res/drawable-xhdpi/md__list_focused_holo.9.png b/src/main/res/drawable-xhdpi/md__list_focused_holo.9.png
new file mode 100644
index 00000000..b545f8e5
Binary files /dev/null and b/src/main/res/drawable-xhdpi/md__list_focused_holo.9.png differ
diff --git a/src/main/res/drawable-xhdpi/md__list_longpressed_holo.9.png b/src/main/res/drawable-xhdpi/md__list_longpressed_holo.9.png
new file mode 100644
index 00000000..eda10e61
Binary files /dev/null and b/src/main/res/drawable-xhdpi/md__list_longpressed_holo.9.png differ
diff --git a/src/main/res/drawable-xhdpi/md__list_pressed_holo_dark.9.png b/src/main/res/drawable-xhdpi/md__list_pressed_holo_dark.9.png
new file mode 100644
index 00000000..e4b33935
Binary files /dev/null and b/src/main/res/drawable-xhdpi/md__list_pressed_holo_dark.9.png differ
diff --git a/src/main/res/drawable-xhdpi/md__list_selector_disabled_holo_dark.9.png b/src/main/res/drawable-xhdpi/md__list_selector_disabled_holo_dark.9.png
new file mode 100644
index 00000000..88726b69
Binary files /dev/null and b/src/main/res/drawable-xhdpi/md__list_selector_disabled_holo_dark.9.png differ
diff --git a/src/main/res/drawable-xhdpi/menu_arrow.png b/src/main/res/drawable-xhdpi/menu_arrow.png
new file mode 100644
index 00000000..b1599372
Binary files /dev/null and b/src/main/res/drawable-xhdpi/menu_arrow.png differ
diff --git a/src/main/res/drawable/md__list_selector_background_transition_holo_dark.xml b/src/main/res/drawable/md__list_selector_background_transition_holo_dark.xml
new file mode 100644
index 00000000..1d43396d
--- /dev/null
+++ b/src/main/res/drawable/md__list_selector_background_transition_holo_dark.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/src/main/res/drawable/md__list_selector_holo_dark.xml b/src/main/res/drawable/md__list_selector_holo_dark.xml
new file mode 100644
index 00000000..bd71aa8b
--- /dev/null
+++ b/src/main/res/drawable/md__list_selector_holo_dark.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/res/layout/layout_switch.xml b/src/main/res/layout/layout_switch.xml
index b9e1c86f..3d0aabc6 100644
--- a/src/main/res/layout/layout_switch.xml
+++ b/src/main/res/layout/layout_switch.xml
@@ -9,8 +9,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
- android:layout_marginLeft="16dp"
- android:textSize="28sp"
+ android:textSize="24sp"
android:textColor="@color/title"
android:text="@string/app_name"
android:layout_alignParentLeft="true"
@@ -21,6 +20,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
+ android:layout_marginRight = "10dp"
android:layout_centerVertical="true"/>
diff --git a/src/main/res/layout/menu_row_category.xml b/src/main/res/layout/menu_row_category.xml
new file mode 100644
index 00000000..c89e5d96
--- /dev/null
+++ b/src/main/res/layout/menu_row_category.xml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/src/main/res/layout/menu_row_item.xml b/src/main/res/layout/menu_row_item.xml
new file mode 100644
index 00000000..af2c4b38
--- /dev/null
+++ b/src/main/res/layout/menu_row_item.xml
@@ -0,0 +1,5 @@
+
+
\ No newline at end of file
diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml
index bc48ae9a..c9f62e57 100644
--- a/src/main/res/values/colors.xml
+++ b/src/main/res/values/colors.xml
@@ -22,5 +22,7 @@
#646464
#c8c8c8
#323232
+ #00000000
+ #FF555555
\ No newline at end of file
diff --git a/src/main/res/values/ids.xml b/src/main/res/values/ids.xml
new file mode 100644
index 00000000..925ac058
--- /dev/null
+++ b/src/main/res/values/ids.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 2838123b..dd1e3d8d 100755
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -10,6 +10,8 @@
Server Settings
+ Profile
+ Profile name
Server
Hostname or IP Address of your Shadowsocks server
Remote Port
@@ -73,5 +75,6 @@
Shadowsocks %s
Flush DNS Cache
Flushing
+ Add Profile
diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml
index 93e381d0..3382923c 100644
--- a/src/main/res/values/styles.xml
+++ b/src/main/res/values/styles.xml
@@ -23,11 +23,47 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/res/xml/pref_proxy.xml b/src/main/res/xml/pref_proxy.xml
index 08f15dbc..5c9b61cf 100755
--- a/src/main/res/xml/pref_proxy.xml
+++ b/src/main/res/xml/pref_proxy.xml
@@ -1,6 +1,12 @@
+
+
updateSummaryEditTextPreference(pref, profile.name)
+ case Key.proxy => updateSummaryEditTextPreference(pref, profile.host)
+ case Key.remotePort => updateSummaryEditTextPreference(pref, profile.remotePort.toString)
+ case Key.localPort => updateSummaryEditTextPreference(pref, profile.localPort.toString)
+ case Key.sitekey => updatePasswordEditTextPreference(pref, profile.password)
+ case Key.encMethod => updateListPreference(pref, profile.method)
+ case Key.isGFWList => updateCheckBoxPreference(pref, profile.chnroute)
+ case Key.isGlobalProxy => updateCheckBoxPreference(pref, profile.global)
+ case Key.isTrafficStat => updateCheckBoxPreference(pref, profile.traffic)
+ case _ =>
+ }
+ }
+
+ def isServiceStarted(context: Context): Boolean = {
+ ShadowsocksService.isServiceStarted(context) || ShadowVpnService.isServiceStarted(context)
+ }
+
+ class ProxyFragment extends UnifiedPreferenceFragment {
+
+ var receiver: BroadcastReceiver = null
+
+ private def setPreferenceEnabled() {
+ val state = getActivity.asInstanceOf[Shadowsocks].state
+ val enabled = state != State.CONNECTED && state != State.CONNECTING
+ for (name <- PROXY_PREFS) {
+ val pref: Preference = findPreference(name)
+ if (pref != null) {
+ pref.setEnabled(enabled)
+ }
+ }
+ }
+
+ private def updatePreferenceScreen() {
+ for (name <- Shadowsocks.PROXY_PREFS) {
+ val pref = findPreference(name)
+ Shadowsocks.updatePreference(pref, name, Shadowsocks.currentProfile)
+ }
+ }
+
+ override def onCreate(bundle: Bundle) {
+ super.onCreate(bundle)
+ val filter = new IntentFilter()
+ filter.addAction(Action.UPDATE_FRAGMENT)
+ receiver = new BroadcastReceiver {
+ def onReceive(p1: Context, p2: Intent) {
+ setPreferenceEnabled()
+ updatePreferenceScreen()
+ }
+ }
+ getActivity.getApplicationContext.registerReceiver(receiver, filter)
+ }
+
+ override def onDestroy() {
+ super.onDestroy()
+ getActivity.getApplicationContext.unregisterReceiver(receiver)
+ }
+
+ override def onResume() {
+ super.onResume()
+ setPreferenceEnabled()
+ }
+
+ override def onPause() {
+ super.onPause()
+ }
+ }
+
+ class FeatureFragment extends UnifiedPreferenceFragment {
+
+ var receiver: BroadcastReceiver = null
+
+ private def setPreferenceEnabled() {
+ val state = getActivity.asInstanceOf[Shadowsocks].state
+ val enabled: Boolean = state != State.CONNECTED && state != State.CONNECTING
+ for (name <- Shadowsocks.FEATRUE_PREFS) {
+ val pref: Preference = findPreference(name)
+ if (pref != null) {
+ val status = getActivity.getSharedPreferences(Key.status, Context.MODE_PRIVATE)
+ val isRoot = status.getBoolean(Key.isRoot, false)
+ if (Seq(Key.isAutoConnect, Key.isGlobalProxy, Key.isTrafficStat, Key.proxyedApps)
+ .contains(name)) {
+ pref.setEnabled(enabled && isRoot)
+ } else {
+ pref.setEnabled(enabled)
+ }
+ }
+ }
+ }
+
+ private def updatePreferenceScreen() {
+ for (name <- Shadowsocks.FEATRUE_PREFS) {
+ val pref = findPreference(name)
+ Shadowsocks.updatePreference(pref, name, Shadowsocks.currentProfile)
+ }
+ }
+
+ override def onCreate(bundle: Bundle) {
+ super.onCreate(bundle)
+ val filter = new IntentFilter()
+ filter.addAction(Action.UPDATE_FRAGMENT)
+ receiver = new BroadcastReceiver {
+ def onReceive(p1: Context, p2: Intent) {
+ setPreferenceEnabled()
+ updatePreferenceScreen()
+ }
+ }
+ getActivity.getApplicationContext.registerReceiver(receiver, filter)
+ }
+
+ override def onDestroy() {
+ super.onDestroy()
+ getActivity.getApplicationContext.unregisterReceiver(receiver)
+ }
+
+ override def onResume() {
+ super.onResume()
+ setPreferenceEnabled()
+ }
+
+ override def onPause() {
+ super.onPause()
+ }
+ }
+
+}
+
class Shadowsocks
extends UnifiedSherlockPreferenceActivity
with CompoundButton.OnCheckedChangeListener
- with OnSharedPreferenceChangeListener {
+ with OnSharedPreferenceChangeListener
+ with MenuAdapter.MenuListener{
private val MSG_CRASH_RECOVER: Int = 1
private val MSG_INITIAL_FINISH: Int = 2
+ private val STATE_MENUDRAWER = "com.github.shadowsocks.menuDrawer"
+ private val STATE_ACTIVE_VIEW_ID = "com.github.shadowsocks.activeViewId"
+
private var switchButton: Switch = null
private var progressDialog: ProgressDialog = null
- private var settings: SharedPreferences = null
- private var status: SharedPreferences = null
- private var prepared = false
+
private var state = State.INIT
- private var receiver: StateBroadcastReceiver = null
+ private var prepared = false
+
+ lazy val settings = PreferenceManager.getDefaultSharedPreferences(this)
+ lazy val status = getSharedPreferences(Key.status, Context.MODE_PRIVATE)
+ lazy val receiver = new StateBroadcastReceiver
+ lazy val drawer = MenuDrawer.attach(this)
+ lazy val menuAdapter = new MenuAdapter(this, getMenuList)
+ lazy val listView = new ListView(this)
+ lazy val profileManager = new ProfileManager(settings, getApplication.asInstanceOf[ShadowsocksApplication].dbHelper)
private val handler: Handler = new Handler {
override def handleMessage(msg: Message) {
@@ -278,10 +366,10 @@ class Shadowsocks
}
}
- def onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
- if (key == Key.isGlobalProxy) {
- val enabled = state != State.CONNECTED && state != State.CONNECTING
- setPreferenceEnabled(enabled)
+ override def onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
+ if (key == Key.profileName) {
+ profileManager.save()
+ menuAdapter.updateList(getMenuList, Shadowsocks.currentProfile.id)
}
}
@@ -366,7 +454,7 @@ class Shadowsocks
}
}
- def initAdView() {
+ def initAdView(layoutResId: Int) {
if (settings.getString(Key.proxy, "") == "198.199.101.152") {
val adView = {
if (isSinglePane) {
@@ -375,7 +463,7 @@ class Shadowsocks
new AdView(this, AdSize.BANNER, "a151becb8068b09")
}
}
- val layoutView = getLayoutView(getListView.asInstanceOf[ViewParent])
+ val layoutView = findViewById(layoutResId).asInstanceOf[ViewGroup]
if (layoutView != null) {
layoutView.addView(adView, 0)
adView.loadAd(new AdRequest)
@@ -383,32 +471,46 @@ class Shadowsocks
}
}
+ override def setContentView(layoutResId: Int) {
+ drawer.setContentView(layoutResId)
+ initAdView(layoutResId)
+ onContentChanged()
+ }
+
/** Called when the activity is first created. */
override def onCreate(savedInstanceState: Bundle) {
setHeaderRes(R.xml.shadowsocks_headers)
super.onCreate(savedInstanceState)
+ Shadowsocks.currentProfile = {
+ profileManager.getProfile(settings.getInt(Key.profileId, -1)) getOrElse new Profile()
+ }
+
+ menuAdapter.setActiveId(settings.getInt(Key.profileId, -1))
+ menuAdapter.setListener(this)
+ listView.setAdapter(menuAdapter)
+ drawer.setMenuView(listView)
+
+ // The drawable that replaces the up indicator in the action bar
+ drawer.setSlideDrawable(R.drawable.ic_drawer)
+ // Whether the previous drawable should be shown
+ drawer.setDrawerIndicatorEnabled(true)
+
val switchLayout = getLayoutInflater
.inflate(R.layout.layout_switch, null)
.asInstanceOf[RelativeLayout]
getSupportActionBar.setCustomView(switchLayout)
getSupportActionBar.setDisplayShowTitleEnabled(false)
getSupportActionBar.setDisplayShowCustomEnabled(true)
- getSupportActionBar.setDisplayShowHomeEnabled(false)
-
+ // getSupportActionBar.setDisplayShowHomeEnabled(false)
val title: TextView = switchLayout.findViewById(R.id.title).asInstanceOf[TextView]
val tf: Typeface = Typefaces.get(this, "fonts/Iceland.ttf")
if (tf != null) title.setTypeface(tf)
title.setText(R.string.app_name)
switchButton = switchLayout.findViewById(R.id.switchButton).asInstanceOf[Switch]
- settings = PreferenceManager.getDefaultSharedPreferences(this)
- status = getSharedPreferences(Key.status, Context.MODE_PRIVATE)
- receiver = new StateBroadcastReceiver()
registerReceiver(receiver, new IntentFilter(Action.UPDATE_STATE))
- initAdView()
-
val init: Boolean = !Shadowsocks.isServiceStarted(this)
if (init) {
if (progressDialog == null) {
@@ -425,51 +527,123 @@ class Shadowsocks
}
}
- override def onCreateOptionsMenu(menu: Menu): Boolean = {
- val isRoot = status.getBoolean(Key.isRoot, false)
- menu
- .add(0, 0, 0, R.string.recovery)
- .setIcon(android.R.drawable.ic_menu_revert)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT)
- menu
- .add(0, 1, 1, R.string.about)
- .setIcon(android.R.drawable.ic_menu_info_details)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT)
- if (isRoot || Build.VERSION.SDK_INT < 17) {
- menu
- .add(0, 2, 2, R.string.flush_dnscache)
- .setIcon(android.R.drawable.ic_menu_revert)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT)
+ override def onRestoreInstanceState(inState: Bundle) {
+ super.onRestoreInstanceState(inState)
+ drawer.restoreState(inState.getParcelable(STATE_MENUDRAWER))
+ }
+
+ override def onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ outState.putParcelable(STATE_MENUDRAWER, drawer.saveState())
+ outState.putInt(STATE_ACTIVE_VIEW_ID, Shadowsocks.currentProfile.id)
+ }
+
+ override def onBackPressed() {
+ val drawerState = drawer.getDrawerState
+ if (drawerState == MenuDrawer.STATE_OPEN || drawerState == MenuDrawer.STATE_OPENING) {
+ drawer.closeMenu()
+ return
}
+ super.onBackPressed()
+ }
+
+ override def onActiveViewChanged(v: View, pos: Int) {
+ drawer.setActiveView(v, pos)
+ }
+
+ def addProfile(id: Int) {
+ drawer.closeMenu(true)
+
+ handler.postDelayed(new Runnable {
+ def run() {
+ Shadowsocks.currentProfile = profileManager.reload(id)
+ updatePreferenceScreen()
+ profileManager.save()
+ menuAdapter.updateList(getMenuList, Shadowsocks.currentProfile.id)
+ }
+ }, 600)
+
+ if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
+ }
+
+ def updateProfile(id: Int) {
+ drawer.closeMenu(true)
+
+ handler.postDelayed(new Runnable {
+ def run() {
+ Shadowsocks.currentProfile = profileManager.reload(id)
+ updatePreferenceScreen()
+ menuAdapter.setActiveId(id)
+ menuAdapter.notifyDataSetChanged()
+ }
+ }, 600)
+
+ if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
+ }
+
+ def delProfile(id: Int): Boolean = {
+ drawer.closeMenu(true)
+
+ handler.postDelayed(new Runnable {
+ def run() {
+ profileManager.delProfile(id)
+ val profileId = {
+ val profiles = profileManager.getAllProfiles.getOrElse(List[Profile]())
+ if (profiles.isEmpty) -1 else profiles(0).id
+ }
+ Shadowsocks.currentProfile = profileManager.load(profileId)
+ updatePreferenceScreen()
+ menuAdapter.updateList(getMenuList, Shadowsocks.currentProfile.id)
+ }
+ }, 600)
+
+ if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
+
true
}
- override def onKeyDown(keyCode: Int, event: KeyEvent): Boolean = {
- if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount == 0) {
- try {
- finish()
- } catch {
- case ignore: Exception => {
- }
- }
- return true
- }
- super.onKeyDown(keyCode, event)
+ def getProfileList: List[Item] = {
+ val list = profileManager.getAllProfiles getOrElse List[Profile]()
+ list.map(p => new Item(p.id, p.name, -1, updateProfile, delProfile))
+ }
+
+ def getMenuList: List[Any] = {
+
+ val buf = new ListBuffer[Any]()
+
+ buf += new Category("Profiles")
+
+ buf ++= getProfileList
+
+ buf += new Item(-1, getString(R.string.add_profile), android.R.drawable.ic_menu_add, addProfile)
+
+ buf += new Category("Settings")
+
+ buf += new Item(-100, getString(R.string.recovery), android.R.drawable.ic_menu_revert, _ => {
+ EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "reset", getVersionName, 0L)
+ recovery()
+ })
+
+ buf +=
+ new Item(-200, getString(R.string.flush_dnscache), android.R.drawable.ic_menu_delete, _ => {
+ EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "flush_dnscache", getVersionName, 0L)
+ flushDnsCache()
+ })
+
+ buf += new Item(-300, getString(R.string.about), android.R.drawable.ic_menu_info_details, _ => {
+ EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "about", getVersionName, 0L)
+ showAbout()
+ })
+
+ buf.toList
}
override def onOptionsItemSelected(item: MenuItem): Boolean = {
item.getItemId match {
- case 0 => {
- EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "reset", getVersionName, 0L)
- recovery()
- }
- case 1 => {
- EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "about", getVersionName, 0L)
- showAbout()
- }
- case 2 => {
- EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "flush_dnscache", getVersionName, 0L)
- flushDnsCache()
+ case android.R.id.home => {
+ EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "home", getVersionName, 0L)
+ drawer.toggleMenu()
+ return true
}
}
super.onOptionsItemSelected(item)
@@ -478,6 +652,7 @@ class Shadowsocks
protected override def onPause() {
super.onPause()
prepared = false
+ settings.unregisterOnSharedPreferenceChangeListener(this)
}
protected override def onResume() {
@@ -511,6 +686,8 @@ class Shadowsocks
}
switchButton.setOnCheckedChangeListener(this)
+
+ settings.registerOnSharedPreferenceChangeListener(this)
}
private def setPreferenceEnabled(enabled: Boolean) {
@@ -524,8 +701,8 @@ class Shadowsocks
for (name <- Shadowsocks.FEATRUE_PREFS) {
val pref = findPreference(name)
if (pref != null) {
- if (Seq(Key.isAutoConnect, Key.isGlobalProxy, Key.isTrafficStat,
- Key.proxyedApps).contains(name)) {
+ if (Seq(Key.isAutoConnect, Key.isGlobalProxy, Key.isTrafficStat, Key.proxyedApps)
+ .contains(name)) {
pref.setEnabled(enabled && isRoot)
} else {
pref.setEnabled(enabled)
@@ -534,6 +711,18 @@ class Shadowsocks
}
}
+ private def updatePreferenceScreen() {
+ val profile = Shadowsocks.currentProfile
+ for (name <- Shadowsocks.PROXY_PREFS) {
+ val pref = findPreference(name)
+ Shadowsocks.updatePreference(pref, name, profile)
+ }
+ for (name <- Shadowsocks.FEATRUE_PREFS) {
+ val pref = findPreference(name)
+ Shadowsocks.updatePreference(pref, name, profile)
+ }
+ }
+
override def onStart() {
super.onStart()
EasyTracker.getInstance.activityStart(this)
@@ -752,7 +941,7 @@ class Shadowsocks
setPreferenceEnabled(enabled = true)
}
}
- sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
+ if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
}
}
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala
index f704c868..9a3703ab 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala
@@ -40,9 +40,13 @@ package com.github.shadowsocks
import android.app.Application
import com.google.analytics.tracking.android.EasyTracker
+import com.github.shadowsocks.database.{ProfileManager, DBHelper}
class ShadowsocksApplication extends Application {
+ lazy val dbHelper = new DBHelper(this)
+
override def onCreate() {
EasyTracker.getInstance.setContext(this)
}
+
}
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala
index 242e5ff0..2dd7b27a 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksService.scala
@@ -324,18 +324,8 @@ class ShadowsocksService extends Service {
val icon = getResources.getDrawable(R.drawable.ic_stat_shadowsocks)
if (rate >= 0) {
- val bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth * 4,
- icon.getIntrinsicHeight * 4, Bitmap.Config.ARGB_8888)
- val r = rate.toString
- val size = bitmap.getHeight / 4
- val canvas = new Canvas(bitmap)
- val paint = new Paint()
- paint.setColor(Color.WHITE)
- paint.setTextSize(size)
- val bounds = new Rect()
- paint.getTextBounds(r, 0, r.length, bounds)
- canvas.drawText(r, (bitmap.getWidth - bounds.width()) / 2,
- bitmap.getHeight - (bitmap.getHeight - bounds.height()) / 2, paint)
+ val bitmap =Utils.getBitmap(rate.toString, icon.getIntrinsicWidth * 4,
+ icon.getIntrinsicHeight * 4, Color.TRANSPARENT)
builder.setLargeIcon(bitmap)
if (rate < 1000) {
diff --git a/src/main/scala/com/github/shadowsocks/Utils.scala b/src/main/scala/com/github/shadowsocks/Utils.scala
index dc9dc6a3..e3e63e9c 100644
--- a/src/main/scala/com/github/shadowsocks/Utils.scala
+++ b/src/main/scala/com/github/shadowsocks/Utils.scala
@@ -49,10 +49,11 @@ import org.apache.http.conn.util.InetAddressUtils
import scala.collection.mutable.ArrayBuffer
import org.xbill.DNS._
import scala.Some
-import android.graphics.{Canvas, Bitmap}
+import android.graphics._
import android.app.ActivityManager
import android.os.Build
import android.provider.Settings
+import scala.Some
object Config {
val SHADOWSOCKS = "{\"server\": [%s], \"server_port\": %d, \"local_port\": %d, \"password\": %s, \"timeout\": %d}"
@@ -112,10 +113,13 @@ object Config {
}
case class Config(isGlobalProxy: Boolean, isGFWList: Boolean, isBypassApps: Boolean, isTrafficStat: Boolean,
- var proxy: String, sitekey: String, encMethod: String, remotePort: Int,
+ profileName: String, var proxy: String, sitekey: String, encMethod: String, remotePort: Int,
localPort: Int, proxiedAppString: String)
object Key {
+ val profileId = "profileId"
+ val profileName = "profileName"
+
val proxied = "Proxyed"
val isRoot = "isRoot"
@@ -137,6 +141,11 @@ object Key {
val localPort = "port"
}
+object Scheme {
+ val APP = "app://"
+ val PROFILE = "profile://"
+}
+
object State {
val INIT = 0
val CONNECTING = 1
@@ -162,6 +171,7 @@ object Extra {
edit.putBoolean(Key.isBypassApps, config.isBypassApps)
edit.putBoolean(Key.isTrafficStat, config.isTrafficStat)
+ edit.putString(Key.profileName, config.profileName)
edit.putString(Key.proxy, config.proxy)
edit.putString(Key.sitekey, config.sitekey)
edit.putString(Key.encMethod, config.encMethod)
@@ -177,6 +187,7 @@ object Extra {
val isBypassApps = intent.getBooleanExtra(Key.isBypassApps, false)
val isTrafficStat = intent.getBooleanExtra(Key.isTrafficStat, false)
+ val profileName = intent.getStringExtra(Key.profileName)
val proxy = intent.getStringExtra(Key.proxy)
val sitekey = intent.getStringExtra(Key.sitekey)
val encMethod = intent.getStringExtra(Key.encMethod)
@@ -184,7 +195,7 @@ object Extra {
val localPort = intent.getIntExtra(Key.localPort, 1984)
val proxiedString = intent.getStringExtra(Key.proxied)
- new Config(isGlobalProxy, isGFWList, isBypassApps, isTrafficStat, proxy, sitekey, encMethod, remotePort,
+ new Config(isGlobalProxy, isGFWList, isBypassApps, isTrafficStat, profileName, proxy, sitekey, encMethod, remotePort,
localPort, proxiedString)
}
@@ -194,6 +205,7 @@ object Extra {
val isBypassApps = settings.getBoolean(Key.isBypassApps, false)
val isTrafficStat = settings.getBoolean(Key.isTrafficStat, false)
+ val profileName = settings.getString(Key.profileName, "default")
val proxy = settings.getString(Key.proxy, "127.0.0.1") match {
case "198.199.101.152" => BuildConfig.SERVER
case s: String => s
@@ -232,6 +244,7 @@ object Extra {
intent.putExtra(Key.isBypassApps, isBypassApps)
intent.putExtra(Key.isTrafficStat, isTrafficStat)
+ intent.putExtra(Key.profileName, profileName)
intent.putExtra(Key.proxy, proxy)
intent.putExtra(Key.sitekey, sitekey)
intent.putExtra(Key.encMethod, encMethod)
@@ -265,6 +278,36 @@ object Utils {
var data_path: String = null
var rootTries = 0
+ /*
+ * round or floor depending on whether you are using offsets(floor) or
+ * widths(round)
+ */
+ def dpToPx (context: Context, dp: Float): Float = {
+ val density = context.getResources.getDisplayMetrics.density
+ dp * density
+ }
+
+ def pxToDp (context: Context, px: Float): Float = {
+ val density = context.getResources.getDisplayMetrics.density
+ px / density
+ }
+
+ def getBitmap(text: String, width: Int, height: Int, background: Int): Bitmap = {
+ val bitmap = Bitmap.createBitmap(width,
+ height, Bitmap.Config.ARGB_8888)
+ val size = bitmap.getHeight / 4
+ val canvas = new Canvas(bitmap)
+ canvas.drawColor(background)
+ val paint = new Paint()
+ paint.setColor(Color.WHITE)
+ paint.setTextSize(size)
+ val bounds = new Rect()
+ paint.getTextBounds(text, 0, text.length, bounds)
+ canvas.drawText(text, (bitmap.getWidth - bounds.width()) / 2,
+ bitmap.getHeight - (bitmap.getHeight - bounds.height()) / 2, paint)
+ bitmap
+ }
+
// Blocked > 3 seconds
def toggleAirplaneMode(context: Context) {
if (Build.VERSION.SDK_INT >= 17) {
diff --git a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala
new file mode 100644
index 00000000..9ed987e4
--- /dev/null
+++ b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala
@@ -0,0 +1,61 @@
+/*
+ * Shadowsocks - A shadowsocks client for Android
+ * Copyright (C) 2013
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * ___====-_ _-====___
+ * _--^^^#####// \\#####^^^--_
+ * _-^##########// ( ) \\##########^-_
+ * -############// |\^^/| \\############-
+ * _/############// (@::@) \\############\_
+ * /#############(( \\// ))#############\
+ * -###############\\ (oo) //###############-
+ * -#################\\ / VV \ //#################-
+ * -###################\\/ \//###################-
+ * _#/|##########/\######( /\ )######/\##########|\#_
+ * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
+ * ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
+ * ` ` ` ` / | | | | \ ' ' ' '
+ * ( | | | | )
+ * __\ | | | | /__
+ * (vvv(VVV)(VVV)vvv)
+ *
+ * HERE BE DRAGONS
+ *
+ */
+
+package com.github.shadowsocks.database
+
+import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper
+import android.content.Context
+import android.database.sqlite.SQLiteDatabase
+import com.j256.ormlite.support.ConnectionSource
+import com.j256.ormlite.table.TableUtils
+import com.j256.ormlite.dao.Dao
+
+class DBHelper(val context: Context) extends OrmLiteSqliteOpenHelper(context, "profile.db", null, 7) {
+
+ lazy val profileDao:Dao[Profile,Int] = getDao(classOf[Profile])
+
+ def onCreate(database: SQLiteDatabase, connectionSource: ConnectionSource) {
+ TableUtils.createTable(connectionSource, classOf[Profile])
+ }
+
+ def onUpgrade(database: SQLiteDatabase, connectionSource: ConnectionSource, oldVersion: Int, newVersion: Int) {
+ TableUtils.dropTable(connectionSource, classOf[Profile], true)
+ onCreate(database, connectionSource)
+ }
+}
diff --git a/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala b/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala
new file mode 100644
index 00000000..66448a6e
--- /dev/null
+++ b/src/main/scala/com/github/shadowsocks/database/MenuAdapter.scala
@@ -0,0 +1,164 @@
+/*
+ * Shadowsocks - A shadowsocks client for Android
+ * Copyright (C) 2013
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * ___====-_ _-====___
+ * _--^^^#####// \\#####^^^--_
+ * _-^##########// ( ) \\##########^-_
+ * -############// |\^^/| \\############-
+ * _/############// (@::@) \\############\_
+ * /#############(( \\// ))#############\
+ * -###############\\ (oo) //###############-
+ * -#################\\ / VV \ //#################-
+ * -###################\\/ \//###################-
+ * _#/|##########/\######( /\ )######/\##########|\#_
+ * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
+ * ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
+ * ` ` ` ` / | | | | \ ' ' ' '
+ * ( | | | | )
+ * __\ | | | | /__
+ * (vvv(VVV)(VVV)vvv)
+ *
+ * HERE BE DRAGONS
+ *
+ */
+
+package com.github.shadowsocks.database
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.BaseAdapter
+import android.widget.TextView
+import com.github.shadowsocks.{Shadowsocks, R}
+import android.view.View.{OnLongClickListener, OnClickListener}
+
+case class Item(id: Int, title: String, iconRes: Int,
+ click: Int => Unit, longClick: Int => Boolean = _ => false)
+
+case class Category(title: String)
+
+object MenuAdapter {
+
+ trait MenuListener {
+ def onActiveViewChanged(v: View, pos: Int)
+ }
+
+ val UNDEFINED = -1
+ val CATEGORY = 0
+ val ITEM = 1
+}
+
+class MenuAdapter(context: Context, var items: List[Any]) extends BaseAdapter {
+
+ def setListener(listener: MenuAdapter.MenuListener) {
+ this.listener = listener
+ }
+
+ def setActiveId(activeId: Int) {
+ this.activeId = activeId
+ }
+
+ @Override def getCount: Int = {
+ items.size
+ }
+
+ @Override def getItem(position: Int): Object = {
+ items(position).asInstanceOf[Object]
+ }
+
+ @Override def getItemId(position: Int): Long = {
+ position
+ }
+
+ @Override override def getItemViewType(position: Int): Int = {
+ getItem(position) match {
+ case Category => MenuAdapter.CATEGORY
+ case Item => MenuAdapter.ITEM
+ case _ => MenuAdapter.UNDEFINED
+ }
+ }
+
+ @Override override def getViewTypeCount: Int = {
+ 3
+ }
+
+ @Override override def isEnabled(position: Int): Boolean = {
+ getItemViewType(position) != MenuAdapter.UNDEFINED
+ }
+
+ @Override override def areAllItemsEnabled: Boolean = {
+ false
+ }
+
+ @Override def getView(position: Int, convertView: View, parent: ViewGroup): View = {
+ var v: View = convertView
+ val item = getItem(position)
+ item match {
+ case value: Category =>
+ if (v == null) {
+ v = LayoutInflater.from(context).inflate(R.layout.menu_row_category, parent, false)
+ }
+ v.asInstanceOf[TextView].setText(value.title)
+ case value: Item =>
+ if (v == null) {
+ v = LayoutInflater.from(context).inflate(R.layout.menu_row_item, parent, false)
+ }
+ val tv: TextView = v.asInstanceOf[TextView]
+ tv.setText(value.title)
+ if (value.iconRes != -1) {
+ tv.setCompoundDrawablesWithIntrinsicBounds(value.iconRes, 0, 0, 0)
+ }
+ tv.setOnClickListener(new OnClickListener {
+ def onClick(view: View) {
+ val item = view.getTag(R.id.mdItem).asInstanceOf[Item]
+ item.click(item.id)
+ }
+ })
+ tv.setOnLongClickListener(new OnLongClickListener {
+ def onLongClick(view: View): Boolean = {
+ val item = view.getTag(R.id.mdItem).asInstanceOf[Item]
+ item.longClick(item.id)
+ }
+ })
+
+ v.setTag(R.id.mdItem, value)
+
+ if (value.id == activeId) {
+ if (listener != null) listener.onActiveViewChanged(v, position)
+ }
+
+ case _ =>
+ }
+ v.setTag(R.id.mdActiveViewPosition, position)
+
+
+ v
+ }
+
+ def updateList(list: List[Any], activeId: Int) {
+ items = list
+ this.activeId = activeId
+ notifyDataSetChanged()
+ }
+
+ private var listener: MenuAdapter.MenuListener = null
+ private var activeId: Int = -1
+}
+
+
diff --git a/src/main/scala/com/github/shadowsocks/database/Profile.scala b/src/main/scala/com/github/shadowsocks/database/Profile.scala
new file mode 100644
index 00000000..38f7d711
--- /dev/null
+++ b/src/main/scala/com/github/shadowsocks/database/Profile.scala
@@ -0,0 +1,92 @@
+/*
+ * Shadowsocks - A shadowsocks client for Android
+ * Copyright (C) 2013
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * ___====-_ _-====___
+ * _--^^^#####// \\#####^^^--_
+ * _-^##########// ( ) \\##########^-_
+ * -############// |\^^/| \\############-
+ * _/############// (@::@) \\############\_
+ * /#############(( \\// ))#############\
+ * -###############\\ (oo) //###############-
+ * -#################\\ / VV \ //#################-
+ * -###################\\/ \//###################-
+ * _#/|##########/\######( /\ )######/\##########|\#_
+ * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
+ * ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
+ * ` ` ` ` / | | | | \ ' ' ' '
+ * ( | | | | )
+ * __\ | | | | /__
+ * (vvv(VVV)(VVV)vvv)
+ *
+ * HERE BE DRAGONS
+ *
+ */
+
+package com.github.shadowsocks.database
+
+import com.j256.ormlite.field.{DataType, DatabaseField}
+
+class Profile {
+ @DatabaseField(generatedId = true)
+ var id: Int = 0
+
+ @DatabaseField
+ var name: String = "Untitled"
+
+ @DatabaseField
+ var host: String = ""
+
+ @DatabaseField
+ var localPort: Int = 0
+
+ @DatabaseField
+ var remotePort: Int = 0
+
+ @DatabaseField
+ var password: String = ""
+
+ @DatabaseField
+ var method: String = ""
+
+ @DatabaseField
+ var date: String = ""
+
+ @DatabaseField
+ var upload: Int = 0
+
+ @DatabaseField
+ var download: Int = 0
+
+ @DatabaseField
+ var chnroute: Boolean = false
+
+ @DatabaseField
+ var global: Boolean = false
+
+ @DatabaseField
+ var bypass: Boolean = false
+
+ @DatabaseField
+ var traffic: Boolean = false
+
+ @DatabaseField(dataType = DataType.LONG_STRING)
+ var individual: String = ""
+
+ @DatabaseField(dataType = DataType.LONG_STRING)
+ var description: String = ""
+}
diff --git a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala
new file mode 100644
index 00000000..d4772cdb
--- /dev/null
+++ b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala
@@ -0,0 +1,158 @@
+/*
+ * Shadowsocks - A shadowsocks client for Android
+ * Copyright (C) 2013
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ *
+ * ___====-_ _-====___
+ * _--^^^#####// \\#####^^^--_
+ * _-^##########// ( ) \\##########^-_
+ * -############// |\^^/| \\############-
+ * _/############// (@::@) \\############\_
+ * /#############(( \\// ))#############\
+ * -###############\\ (oo) //###############-
+ * -#################\\ / VV \ //#################-
+ * -###################\\/ \//###################-
+ * _#/|##########/\######( /\ )######/\##########|\#_
+ * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
+ * ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
+ * ` ` ` ` / | | | | \ ' ' ' '
+ * ( | | | | )
+ * __\ | | | | /__
+ * (vvv(VVV)(VVV)vvv)
+ *
+ * HERE BE DRAGONS
+ *
+ */
+
+package com.github.shadowsocks.database
+
+import android.util.Log
+import com.github.shadowsocks._
+import android.content.{SharedPreferences, Context}
+
+class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
+
+ def createOrUpdateProfile(profile: Profile): Boolean = {
+ try {
+ dbHelper.profileDao.createOrUpdate(profile)
+ true
+ } catch {
+ case ex: Exception =>
+ Log.e(Shadowsocks.TAG, "addProfile", ex)
+ false
+ }
+ }
+
+ def getProfile(id: Int): Option[Profile] = {
+ try {
+ dbHelper.profileDao.queryForId(id) match {
+ case profile: Profile => Option(profile)
+ case _ => None
+ }
+ } catch {
+ case ex: Exception =>
+ Log.e(Shadowsocks.TAG, "getProfile", ex)
+ return None
+ }
+ }
+
+ def delProfile(id: Int): Boolean = {
+ try {
+ dbHelper.profileDao.deleteById(id)
+ true
+ } catch {
+ case ex: Exception =>
+ Log.e(Shadowsocks.TAG, "delProfile", ex)
+ false
+ }
+ }
+
+ def getAllProfiles: Option[List[Profile]] = {
+ try {
+ import scala.collection.JavaConversions._
+ Option(dbHelper.profileDao.queryForAll().toList)
+ } catch {
+ case ex: Exception =>
+ Log.e(Shadowsocks.TAG, "getAllProfiles", ex)
+ None
+ }
+ }
+
+ def reload(id: Int): Profile = {
+ save()
+ load(id)
+ }
+
+ def load(id: Int): Profile = {
+
+ val profile = getProfile(id) getOrElse {
+ val p = new Profile()
+ createOrUpdateProfile(p)
+ p
+ }
+
+ val edit = settings.edit()
+ edit.putBoolean(Key.isGlobalProxy, profile.global)
+ edit.putBoolean(Key.isGFWList, profile.chnroute)
+ edit.putBoolean(Key.isBypassApps, profile.bypass)
+ edit.putBoolean(Key.isTrafficStat, profile.traffic)
+ 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.putString(Key.remotePort, profile.remotePort.toString)
+ edit.putString(Key.localPort, profile.localPort.toString)
+ edit.putString(Key.proxied, profile.individual)
+ edit.putInt(Key.profileId, profile.id)
+ edit.commit()
+
+ profile
+ }
+
+ def save() {
+ val profile = new Profile()
+
+ profile.global = settings.getBoolean(Key.isGlobalProxy, false)
+ profile.chnroute = settings.getBoolean(Key.isGFWList, false)
+ profile.bypass = settings.getBoolean(Key.isBypassApps, false)
+ profile.traffic = settings.getBoolean(Key.isTrafficStat, 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.remotePort = try {
+ Integer.valueOf(settings.getString(Key.remotePort, "1984"))
+ } catch {
+ case ex: NumberFormatException => {
+ 1984
+ }
+ }
+ profile.localPort = try {
+ Integer.valueOf(settings.getString(Key.localPort, "1984"))
+ } catch {
+ case ex: NumberFormatException => {
+ 1984
+ }
+ }
+ profile.individual = settings.getString(Key.proxied, "")
+
+ profile.id = settings.getInt(Key.profileId, -1)
+
+ Log.d(Shadowsocks.TAG, "save " + profile.id + " " + profile.name)
+
+ createOrUpdateProfile(profile)
+ }
+}