Commit c4c74c01 authored by Max Lv's avatar Max Lv

Merge pull request #38 from shadowsocks/multi-profile

Multi profile
parents ae2319ca 1155c731
......@@ -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"
)
......@@ -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 =
......
......@@ -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")
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/md__list_pressed_holo_dark" />
<item android:drawable="@drawable/md__list_longpressed_holo" />
</transition>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="false" android:drawable="@color/md__transparent" />
<!-- Even though these two point to the same resource, have two states so the drawable will invalidate itself when coming out of pressed state. -->
<item android:state_focused="true" android:state_enabled="false" android:state_pressed="true" android:drawable="@drawable/md__list_selector_disabled_holo_dark" />
<item android:state_focused="true" android:state_enabled="false" android:drawable="@drawable/md__list_selector_disabled_holo_dark" />
<item android:state_focused="true" android:state_pressed="true" android:drawable="@drawable/md__list_selector_background_transition_holo_dark" />
<item android:state_focused="false" android:state_pressed="true" android:drawable="@drawable/md__list_selector_background_transition_holo_dark" />
<item android:state_focused="true" android:drawable="@drawable/md__list_focused_holo" />
</selector>
......@@ -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"/>
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/MenuDrawer.Widget.Category" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/MenuDrawer.Widget.Title" />
\ No newline at end of file
......@@ -22,5 +22,7 @@
<color name="grey1">#646464</color>
<color name="grey2">#c8c8c8</color>
<color name="grey3">#323232</color>
<color name="md__transparent">#00000000</color>
<color name="md__defaultBackground">#FF555555</color>
</resources>
\ No newline at end of file
<resources>
<!-- ID used when defining the content layout in XML. -->
<item name="mdContent" type="id"/>
<!-- ID used when defining the menu layout in XML. -->
<item name="mdMenu" type="id"/>
<!-- The ID of the content container. -->
<item name="md__content" type="id"/>
<!-- The ID of the menu container. -->
<item name="md__menu" type="id"/>
<!-- The ID of the drawer. -->
<item name="md__drawer" type="id"/>
<!-- Used with View#setTag(int) to specify a position for the active view. -->
<item name="mdActiveViewPosition" type="id"/>
<item name="mdItem" type="id"/>
<item name="md__translationX" type="id"/>
<item name="md__translationY" type="id"/>
</resources>
\ No newline at end of file
......@@ -10,6 +10,8 @@
<!-- proxy category -->
<string name="proxy_cat">Server Settings</string>
<string name="profile">Profile</string>
<string name="profile_summary">Profile name</string>
<string name="proxy">Server</string>
<string name="proxy_summary">Hostname or IP Address of your Shadowsocks server</string>
<string name="remote_port">Remote Port</string>
......@@ -73,5 +75,6 @@
<string name="about_title">Shadowsocks %s</string>
<string name="flush_dnscache">Flush DNS Cache</string>
<string name="flushing">Flushing</string>
<string name="add_profile">Add Profile</string>
</resources>
......@@ -23,11 +23,47 @@
<!-- Variation on the Light theme that turns off the title -->
<style name="Theme.Shadowsocks" parent="@style/Theme.Shadow">
<item name="switchStyle">@style/Widget.Shadow.Switch</item>
<item name="menuDrawerStyle">@style/MenuDrawerStyle</item>
</style>
<style name="Theme.AppManager" parent="@style/Theme.Shadow">
<item name="switchStyle">@style/Widget.Holo.Light.CompoundButton.Switch</item>
</style>
<style name="MenuDrawer"/>
<style name="MenuDrawer.Widget"/>
<style name="MenuDrawer.Widget.Category">
<item name="android:textStyle">bold</item>
<item name="android:textColor">?android:attr/textColorSecondaryInverse</item>
<item name="android:textSize">14sp</item>
<item name="android:textAllCaps">true</item>
<item name="android:gravity">center_vertical</item>
<item name="android:paddingLeft">16dp</item>
<item name="android:background">@drawable/md__category_background</item>
<item name="android:singleLine">true</item>
<item name="android:ellipsize">end</item>
</style>
<style name="MenuDrawer.Widget.Title">
<item name="android:background">@drawable/md__list_selector_holo_dark</item>
<item name="android:textAppearance">?android:attr/textAppearance</item>
<item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
<item name="android:textSize">18sp</item>
<item name="android:paddingLeft">16dp</item>
<item name="android:paddingRight">32dp</item>
<item name="android:paddingTop">8dp</item>
<item name="android:paddingBottom">8dp</item>
<item name="android:drawablePadding">16dp</item>
<item name="android:gravity">center_vertical</item>
</style>
<style name="MenuDrawerStyle" parent="Widget.MenuDrawer">
<item name="mdActiveIndicator">@drawable/menu_arrow</item>
<item name="mdMenuSize">250dp</item>
</style>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<com.github.shadowsocks.preferences.SummaryEditTextPreference
android:defaultValue="Default"
android:key="profileName"
android:summary="@string/profile_summary"
android:title="@string/profile">
</com.github.shadowsocks.preferences.SummaryEditTextPreference>
<com.github.shadowsocks.preferences.SummaryEditTextPreference
android:defaultValue="198.199.101.152"
android:key="proxy"
......
......@@ -109,7 +109,6 @@ class AppManager extends SherlockActivity with OnCheckedChangeListener with OnCl
val MSG_LOAD_START = 1
val MSG_LOAD_FINISH = 2
val SCHEME = "app://"
val STUB = android.R.drawable.sym_def_app_icon
implicit def anyrefarray_tools[T <: AnyRef](a: Array[T]) = new ObjectArrayTools(a)
......@@ -184,7 +183,7 @@ class AppManager extends SherlockActivity with OnCheckedChangeListener with OnCl
.cacheOnDisc()
.displayer(new FadeInBitmapDisplayer(300))
.build()
ImageLoader.getInstance().displayImage("app://" + app.uid, entry.icon, options)
ImageLoader.getInstance().displayImage(Scheme.APP + app.uid, entry.icon, options)
entry.text.setText(app.name)
val box: CheckBox = entry.box
......@@ -327,7 +326,7 @@ class AppManager extends SherlockActivity with OnCheckedChangeListener with OnCl
}
override def getStreamFromOtherSource(imageUri: String, extra: AnyRef): InputStream = {
val uid = imageUri.substring(SCHEME.length).toInt
val uid = imageUri.substring(Scheme.APP.length).toInt
val drawable = Utils.getAppIcon(getBaseContext, uid)
val bitmap = Utils.drawableToBitmap(drawable)
......
......@@ -41,21 +41,16 @@ package com.github.shadowsocks
import android.app.{Activity, AlertDialog, ProgressDialog}
import android.content._
import android.content.res.AssetManager
import android.graphics.Typeface
import android.graphics.{Color, Bitmap, Typeface}
import android.os._
import android.preference.{Preference, PreferenceManager}
import android.preference._
import android.util.Log
import android.view.{ViewParent, KeyEvent}
import android.widget.{LinearLayout, CompoundButton, RelativeLayout, TextView}
import com.actionbarsherlock.view.Menu
import com.actionbarsherlock.view.MenuItem
import android.view.{View, ViewGroup, ViewParent, KeyEvent}
import android.widget._
import com.google.analytics.tracking.android.EasyTracker
import de.keyboardsurfer.android.widget.crouton.Crouton
import de.keyboardsurfer.android.widget.crouton.Style
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.io._
import java.util.Hashtable
import net.saik0.android.unifiedpreference.UnifiedPreferenceFragment
import net.saik0.android.unifiedpreference.UnifiedSherlockPreferenceActivity
......@@ -67,18 +62,111 @@ import android.app.backup.BackupManager
import scala.concurrent.ops._
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import com.google.ads.{AdRequest, AdSize, AdView}
import net.simonvt.menudrawer.MenuDrawer
import com.github.shadowsocks.database._
import scala.collection.mutable.ListBuffer
import com.github.shadowsocks.database.Profile
import com.nostra13.universalimageloader.core.{ImageLoader, ImageLoaderConfiguration}
import com.nostra13.universalimageloader.core.download.BaseImageDownloader
import com.github.shadowsocks.database.Item
import com.github.shadowsocks.database.Category
import com.actionbarsherlock.view.MenuItem
import com.github.shadowsocks.preferences.{PasswordEditTextPreference, SummaryEditTextPreference}
import com.github.shadowsocks.database.Item
import com.github.shadowsocks.database.Category
import scala.Option
class ProfileIconDownloader(context: Context, connectTimeout: Int, readTimeout: Int)
extends BaseImageDownloader(context, connectTimeout, readTimeout) {
def this(context: Context) {
this(context, 0, 0)
}
override def getStreamFromOtherSource(imageUri: String, extra: AnyRef): InputStream = {
val text = imageUri.substring(Scheme.PROFILE.length)
val size = Utils.dpToPx(context, 16).toInt
val idx = text.getBytes.last % 6
val color = Seq(Color.MAGENTA, Color.GREEN, Color.YELLOW, Color.BLUE, Color.DKGRAY, Color.CYAN)(
idx)
val bitmap = Utils.getBitmap(text, size, size, color)
val os = new ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os)
new ByteArrayInputStream(os.toByteArray)
}
}
object Typefaces {
def get(c: Context, assetPath: String): Typeface = {
cache synchronized {
if (!cache.containsKey(assetPath)) {
try {
val t: Typeface = Typeface.createFromAsset(c.getAssets, assetPath)
cache.put(assetPath, t)
} catch {
case e: Exception => {
Log.e(TAG, "Could not get typeface '" + assetPath + "' because " + e.getMessage)
return null
}
}
}
return cache.get(assetPath)
}
}
private final val TAG: String = "Typefaces"
private final val cache: Hashtable[String, Typeface] = new Hashtable[String, Typeface]
}
object Shadowsocks {
val PREFS_NAME = "Shadowsocks"
val PROXY_PREFS = Array(Key.proxy, Key.remotePort, Key.localPort, Key.sitekey, Key.encMethod)
val FEATRUE_PREFS = Array(Key.isGFWList, Key.isGlobalProxy, Key.proxyedApps,
Key.isTrafficStat, Key.isAutoConnect)
val PROXY_PREFS = Array(Key.profileName, Key.proxy, Key.remotePort, Key.localPort, Key.sitekey, Key.encMethod)
val FEATRUE_PREFS = Array(Key.isGFWList, Key.isGlobalProxy, Key.proxyedApps, Key.isTrafficStat,
Key.isAutoConnect)
val TAG = "Shadowsocks"
val REQUEST_CONNECT = 1
private var vpnEnabled = -1
var currentProfile: Profile = null
def updateListPreference(pref: Preference, value: String) {
pref.setSummary(value)
pref.asInstanceOf[ListPreference].setValue(value)
}
def updatePasswordEditTextPreference(pref: Preference, value: String) {
pref.setSummary(value)
pref.asInstanceOf[PasswordEditTextPreference].setText(value)
}
def updateSummaryEditTextPreference(pref: Preference, value: String) {
pref.setSummary(value)
pref.asInstanceOf[SummaryEditTextPreference].setText(value)
}
def updateCheckBoxPreference(pref: Preference, value: Boolean) {
pref.asInstanceOf[CheckBoxPreference].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 => 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)
}
......@@ -98,6 +186,13 @@ object Shadowsocks {
}
}
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()
......@@ -105,6 +200,7 @@ object Shadowsocks {
receiver = new BroadcastReceiver {
def onReceive(p1: Context, p2: Intent) {
setPreferenceEnabled()
updatePreferenceScreen()
}
}
getActivity.getApplicationContext.registerReceiver(receiver, filter)
......@@ -125,7 +221,7 @@ object Shadowsocks {
}
}
class FeatureFragment extends UnifiedPreferenceFragment with OnSharedPreferenceChangeListener {
class FeatureFragment extends UnifiedPreferenceFragment {
var receiver: BroadcastReceiver = null
......@@ -137,8 +233,8 @@ object Shadowsocks {
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)) {
if (Seq(Key.isAutoConnect, Key.isGlobalProxy, Key.isTrafficStat, Key.proxyedApps)
.contains(name)) {
pref.setEnabled(enabled && isRoot)
} else {
pref.setEnabled(enabled)
......@@ -147,6 +243,13 @@ object Shadowsocks {
}
}
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()
......@@ -154,6 +257,7 @@ object Shadowsocks {
receiver = new BroadcastReceiver {
def onReceive(p1: Context, p2: Intent) {
setPreferenceEnabled()
updatePreferenceScreen()
}
}
getActivity.getApplicationContext.registerReceiver(receiver, filter)
......@@ -172,51 +276,35 @@ object Shadowsocks {
override def onPause() {
super.onPause()
}
def onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
if (key == Key.isGlobalProxy) setPreferenceEnabled()
}
}
}
object Typefaces {
def get(c: Context, assetPath: String): Typeface = {
cache synchronized {
if (!cache.containsKey(assetPath)) {
try {
val t: Typeface = Typeface.createFromAsset(c.getAssets, assetPath)
cache.put(assetPath, t)
} catch {
case e: Exception => {
Log.e(TAG, "Could not get typeface '" + assetPath + "' because " + e.getMessage)
return null
}
}
}
return cache.get(assetPath)
}
}
private final val TAG: String = "Typefaces"
private final val cache: Hashtable[String, Typeface] = new Hashtable[String, Typeface]
}
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
}
true
super.onBackPressed()
}
override def onKeyDown(keyCode: Int, event: KeyEvent): Boolean = {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount == 0) {
try {
finish()
} catch {
case ignore: Exception => {
}
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)
}
return true
}
super.onKeyDown(keyCode, event)
}, 600)
if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
}
override def onOptionsItemSelected(item: MenuItem): Boolean = {
item.getItemId match {
case 0 => {
EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "reset", getVersionName, 0L)
recovery()
def updateProfile(id: Int) {
drawer.closeMenu(true)
handler.postDelayed(new Runnable {
def run() {
Shadowsocks.currentProfile = profileManager.reload(id)
updatePreferenceScreen()
menuAdapter.setActiveId(id)
menuAdapter.notifyDataSetChanged()
}
case 1 => {
EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "about", getVersionName, 0L)
showAbout()
}, 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)
}
case 2 => {
}, 600)
if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
true
}
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 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))
}
}
......
......@@ -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)
}
}
......@@ -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) {
......
......@@ -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) {
......
/*
* Shadowsocks - A shadowsocks client for Android
* Copyright (C) 2013 <max.c.lv@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* ___====-_ _-====___
* _--^^^#####// \\#####^^^--_
* _-^##########// ( ) \\##########^-_
* -############// |\^^/| \\############-
* _/############// (@::@) \\############\_
* /#############(( \\// ))#############\
* -###############\\ (oo) //###############-
* -#################\\ / VV \ //#################-
* -###################\\/ \//###################-
* _#/|##########/\######( /\ )######/\##########|\#_
* |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
* ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
* ` ` ` ` / | | | | \ ' ' ' '
* ( | | | | )
* __\ | | | | /__
* (vvv(VVV)(VVV)vvv)
*
* HERE BE DRAGONS
*
*/
package com.github.shadowsocks.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)
}
}
/*
* Shadowsocks - A shadowsocks client for Android
* Copyright (C) 2013 <max.c.lv@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* ___====-_ _-====___
* _--^^^#####// \\#####^^^--_
* _-^##########// ( ) \\##########^-_
* -############// |\^^/| \\############-
* _/############// (@::@) \\############\_
* /#############(( \\// ))#############\
* -###############\\ (oo) //###############-
* -#################\\ / VV \ //#################-
* -###################\\/ \//###################-
* _#/|##########/\######( /\ )######/\##########|\#_
* |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
* ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
* ` ` ` ` / | | | | \ ' ' ' '
* ( | | | | )
* __\ | | | | /__
* (vvv(VVV)(VVV)vvv)
*
* HERE BE DRAGONS
*
*/
package com.github.shadowsocks.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
}
/*
* Shadowsocks - A shadowsocks client for Android
* Copyright (C) 2013 <max.c.lv@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* ___====-_ _-====___
* _--^^^#####// \\#####^^^--_
* _-^##########// ( ) \\##########^-_
* -############// |\^^/| \\############-
* _/############// (@::@) \\############\_
* /#############(( \\// ))#############\
* -###############\\ (oo) //###############-
* -#################\\ / VV \ //#################-
* -###################\\/ \//###################-
* _#/|##########/\######( /\ )######/\##########|\#_
* |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
* ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
* ` ` ` ` / | | | | \ ' ' ' '
* ( | | | | )
* __\ | | | | /__
* (vvv(VVV)(VVV)vvv)
*
* HERE BE DRAGONS
*
*/
package com.github.shadowsocks.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 = ""
}
/*
* Shadowsocks - A shadowsocks client for Android
* Copyright (C) 2013 <max.c.lv@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
* ___====-_ _-====___
* _--^^^#####// \\#####^^^--_
* _-^##########// ( ) \\##########^-_
* -############// |\^^/| \\############-
* _/############// (@::@) \\############\_
* /#############(( \\// ))#############\
* -###############\\ (oo) //###############-
* -#################\\ / VV \ //#################-
* -###################\\/ \//###################-
* _#/|##########/\######( /\ )######/\##########|\#_
* |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
* ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
* ` ` ` ` / | | | | \ ' ' ' '
* ( | | | | )
* __\ | | | | /__
* (vvv(VVV)(VVV)vvv)
*
* HERE BE DRAGONS
*
*/
package com.github.shadowsocks.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)
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment