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( ...@@ -2,7 +2,6 @@ libraryDependencies ++= Seq(
"com.google.android" % "support-v4" % "r12", "com.google.android" % "support-v4" % "r12",
"com.google.android.analytics" % "analytics" % "2.0beta4", "com.google.android.analytics" % "analytics" % "2.0beta4",
"dnsjava" % "dnsjava" % "2.1.5", "dnsjava" % "dnsjava" % "2.1.5",
"com.nostra13.universalimageloader" % "universal-image-loader" % "1.8.4",
"com.google.android.admob" % "admob" % "6.3.1" "com.google.android.admob" % "admob" % "6.3.1"
) )
...@@ -10,6 +9,13 @@ libraryDependencies ++= Seq( ...@@ -10,6 +9,13 @@ libraryDependencies ++= Seq(
"com.actionbarsherlock" % "actionbarsherlock" % "4.4.0" artifacts(Artifact("actionbarsherlock", "apklib", "apklib")), "com.actionbarsherlock" % "actionbarsherlock" % "4.4.0" artifacts(Artifact("actionbarsherlock", "apklib", "apklib")),
"net.saik0.android.unifiedpreference" % "unifiedpreference" % "0.0.2" artifacts(Artifact("unifiedpreference", "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")), "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._ ...@@ -5,8 +5,8 @@ import sbtandroid._
import sbtandroid.AndroidPlugin._ import sbtandroid.AndroidPlugin._
object App { object App {
val version = "1.8.3" val version = "1.9.0"
val versionCode = 48 val versionCode = 50
} }
object General { object General {
...@@ -28,6 +28,7 @@ object General { ...@@ -28,6 +28,7 @@ object General {
"-keep interface com.actionbarsherlock.** { *; }", "-keep interface com.actionbarsherlock.** { *; }",
"-keep class org.jraf.android.backport.** { *; }", "-keep class org.jraf.android.backport.** { *; }",
"-keep class com.github.shadowsocks.** { *; }", "-keep class com.github.shadowsocks.** { *; }",
"-keep class * extends com.j256.ormlite.** { *; }",
"-keepattributes *Annotation*") "-keepattributes *Annotation*")
val proguardSettings = Seq ( val proguardSettings = Seq (
...@@ -36,8 +37,7 @@ object General { ...@@ -36,8 +37,7 @@ object General {
) )
val miscSettings = Seq ( val miscSettings = Seq (
cachePasswords := true, cachePasswords := true
publicServer := "127.0.0.1"
) )
lazy val fullAndroidSettings = lazy val fullAndroidSettings =
......
...@@ -6,4 +6,4 @@ resolvers += Resolver.url("madeye private releases", new URL("http://madeye-mave ...@@ -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("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 @@ ...@@ -9,8 +9,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:layout_marginLeft="16dp" android:textSize="24sp"
android:textSize="28sp"
android:textColor="@color/title" android:textColor="@color/title"
android:text="@string/app_name" android:text="@string/app_name"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
...@@ -21,6 +20,7 @@ ...@@ -21,6 +20,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_marginRight = "10dp"
android:layout_centerVertical="true"/> android:layout_centerVertical="true"/>
</RelativeLayout> </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 @@ ...@@ -22,5 +22,7 @@
<color name="grey1">#646464</color> <color name="grey1">#646464</color>
<color name="grey2">#c8c8c8</color> <color name="grey2">#c8c8c8</color>
<color name="grey3">#323232</color> <color name="grey3">#323232</color>
<color name="md__transparent">#00000000</color>
<color name="md__defaultBackground">#FF555555</color>
</resources> </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 @@ ...@@ -10,6 +10,8 @@
<!-- proxy category --> <!-- proxy category -->
<string name="proxy_cat">Server Settings</string> <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">Server</string>
<string name="proxy_summary">Hostname or IP Address of your Shadowsocks server</string> <string name="proxy_summary">Hostname or IP Address of your Shadowsocks server</string>
<string name="remote_port">Remote Port</string> <string name="remote_port">Remote Port</string>
...@@ -73,5 +75,6 @@ ...@@ -73,5 +75,6 @@
<string name="about_title">Shadowsocks %s</string> <string name="about_title">Shadowsocks %s</string>
<string name="flush_dnscache">Flush DNS Cache</string> <string name="flush_dnscache">Flush DNS Cache</string>
<string name="flushing">Flushing</string> <string name="flushing">Flushing</string>
<string name="add_profile">Add Profile</string>
</resources> </resources>
...@@ -23,11 +23,47 @@ ...@@ -23,11 +23,47 @@
<!-- Variation on the Light theme that turns off the title --> <!-- Variation on the Light theme that turns off the title -->
<style name="Theme.Shadowsocks" parent="@style/Theme.Shadow"> <style name="Theme.Shadowsocks" parent="@style/Theme.Shadow">
<item name="switchStyle">@style/Widget.Shadow.Switch</item> <item name="switchStyle">@style/Widget.Shadow.Switch</item>
<item name="menuDrawerStyle">@style/MenuDrawerStyle</item>
</style> </style>
<style name="Theme.AppManager" parent="@style/Theme.Shadow"> <style name="Theme.AppManager" parent="@style/Theme.Shadow">
<item name="switchStyle">@style/Widget.Holo.Light.CompoundButton.Switch</item> <item name="switchStyle">@style/Widget.Holo.Light.CompoundButton.Switch</item>
</style> </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> </resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <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 <com.github.shadowsocks.preferences.SummaryEditTextPreference
android:defaultValue="198.199.101.152" android:defaultValue="198.199.101.152"
android:key="proxy" android:key="proxy"
......
...@@ -109,7 +109,6 @@ class AppManager extends SherlockActivity with OnCheckedChangeListener with OnCl ...@@ -109,7 +109,6 @@ class AppManager extends SherlockActivity with OnCheckedChangeListener with OnCl
val MSG_LOAD_START = 1 val MSG_LOAD_START = 1
val MSG_LOAD_FINISH = 2 val MSG_LOAD_FINISH = 2
val SCHEME = "app://"
val STUB = android.R.drawable.sym_def_app_icon val STUB = android.R.drawable.sym_def_app_icon
implicit def anyrefarray_tools[T <: AnyRef](a: Array[T]) = new ObjectArrayTools(a) implicit def anyrefarray_tools[T <: AnyRef](a: Array[T]) = new ObjectArrayTools(a)
...@@ -184,7 +183,7 @@ class AppManager extends SherlockActivity with OnCheckedChangeListener with OnCl ...@@ -184,7 +183,7 @@ class AppManager extends SherlockActivity with OnCheckedChangeListener with OnCl
.cacheOnDisc() .cacheOnDisc()
.displayer(new FadeInBitmapDisplayer(300)) .displayer(new FadeInBitmapDisplayer(300))
.build() .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) entry.text.setText(app.name)
val box: CheckBox = entry.box val box: CheckBox = entry.box
...@@ -327,7 +326,7 @@ class AppManager extends SherlockActivity with OnCheckedChangeListener with OnCl ...@@ -327,7 +326,7 @@ class AppManager extends SherlockActivity with OnCheckedChangeListener with OnCl
} }
override def getStreamFromOtherSource(imageUri: String, extra: AnyRef): InputStream = { 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 drawable = Utils.getAppIcon(getBaseContext, uid)
val bitmap = Utils.drawableToBitmap(drawable) val bitmap = Utils.drawableToBitmap(drawable)
......
...@@ -41,21 +41,16 @@ package com.github.shadowsocks ...@@ -41,21 +41,16 @@ package com.github.shadowsocks
import android.app.{Activity, AlertDialog, ProgressDialog} import android.app.{Activity, AlertDialog, ProgressDialog}
import android.content._ import android.content._
import android.content.res.AssetManager import android.content.res.AssetManager
import android.graphics.Typeface import android.graphics.{Color, Bitmap, Typeface}
import android.os._ import android.os._
import android.preference.{Preference, PreferenceManager} import android.preference._
import android.util.Log import android.util.Log
import android.view.{ViewParent, KeyEvent} import android.view.{View, ViewGroup, ViewParent, KeyEvent}
import android.widget.{LinearLayout, CompoundButton, RelativeLayout, TextView} import android.widget._
import com.actionbarsherlock.view.Menu
import com.actionbarsherlock.view.MenuItem
import com.google.analytics.tracking.android.EasyTracker import com.google.analytics.tracking.android.EasyTracker
import de.keyboardsurfer.android.widget.crouton.Crouton import de.keyboardsurfer.android.widget.crouton.Crouton
import de.keyboardsurfer.android.widget.crouton.Style import de.keyboardsurfer.android.widget.crouton.Style
import java.io.FileOutputStream import java.io._
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.Hashtable import java.util.Hashtable
import net.saik0.android.unifiedpreference.UnifiedPreferenceFragment import net.saik0.android.unifiedpreference.UnifiedPreferenceFragment
import net.saik0.android.unifiedpreference.UnifiedSherlockPreferenceActivity import net.saik0.android.unifiedpreference.UnifiedSherlockPreferenceActivity
...@@ -67,18 +62,111 @@ import android.app.backup.BackupManager ...@@ -67,18 +62,111 @@ import android.app.backup.BackupManager
import scala.concurrent.ops._ import scala.concurrent.ops._
import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import com.google.ads.{AdRequest, AdSize, AdView} 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 { object Shadowsocks {
val PREFS_NAME = "Shadowsocks" val PREFS_NAME = "Shadowsocks"
val PROXY_PREFS = Array(Key.proxy, Key.remotePort, Key.localPort, Key.sitekey, Key.encMethod) 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, val FEATRUE_PREFS = Array(Key.isGFWList, Key.isGlobalProxy, Key.proxyedApps, Key.isTrafficStat,
Key.isTrafficStat, Key.isAutoConnect) Key.isAutoConnect)
val TAG = "Shadowsocks" val TAG = "Shadowsocks"
val REQUEST_CONNECT = 1 val REQUEST_CONNECT = 1
private var vpnEnabled = -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 = { def isServiceStarted(context: Context): Boolean = {
ShadowsocksService.isServiceStarted(context) || ShadowVpnService.isServiceStarted(context) ShadowsocksService.isServiceStarted(context) || ShadowVpnService.isServiceStarted(context)
} }
...@@ -98,6 +186,13 @@ object Shadowsocks { ...@@ -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) { override def onCreate(bundle: Bundle) {
super.onCreate(bundle) super.onCreate(bundle)
val filter = new IntentFilter() val filter = new IntentFilter()
...@@ -105,6 +200,7 @@ object Shadowsocks { ...@@ -105,6 +200,7 @@ object Shadowsocks {
receiver = new BroadcastReceiver { receiver = new BroadcastReceiver {
def onReceive(p1: Context, p2: Intent) { def onReceive(p1: Context, p2: Intent) {
setPreferenceEnabled() setPreferenceEnabled()
updatePreferenceScreen()
} }
} }
getActivity.getApplicationContext.registerReceiver(receiver, filter) getActivity.getApplicationContext.registerReceiver(receiver, filter)
...@@ -125,7 +221,7 @@ object Shadowsocks { ...@@ -125,7 +221,7 @@ object Shadowsocks {
} }
} }
class FeatureFragment extends UnifiedPreferenceFragment with OnSharedPreferenceChangeListener { class FeatureFragment extends UnifiedPreferenceFragment {
var receiver: BroadcastReceiver = null var receiver: BroadcastReceiver = null
...@@ -137,8 +233,8 @@ object Shadowsocks { ...@@ -137,8 +233,8 @@ object Shadowsocks {
if (pref != null) { if (pref != null) {
val status = getActivity.getSharedPreferences(Key.status, Context.MODE_PRIVATE) val status = getActivity.getSharedPreferences(Key.status, Context.MODE_PRIVATE)
val isRoot = status.getBoolean(Key.isRoot, false) val isRoot = status.getBoolean(Key.isRoot, false)
if (Seq(Key.isAutoConnect, Key.isGlobalProxy, Key.isTrafficStat, if (Seq(Key.isAutoConnect, Key.isGlobalProxy, Key.isTrafficStat, Key.proxyedApps)
Key.proxyedApps).contains(name)) { .contains(name)) {
pref.setEnabled(enabled && isRoot) pref.setEnabled(enabled && isRoot)
} else { } else {
pref.setEnabled(enabled) pref.setEnabled(enabled)
...@@ -147,6 +243,13 @@ object Shadowsocks { ...@@ -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) { override def onCreate(bundle: Bundle) {
super.onCreate(bundle) super.onCreate(bundle)
val filter = new IntentFilter() val filter = new IntentFilter()
...@@ -154,6 +257,7 @@ object Shadowsocks { ...@@ -154,6 +257,7 @@ object Shadowsocks {
receiver = new BroadcastReceiver { receiver = new BroadcastReceiver {
def onReceive(p1: Context, p2: Intent) { def onReceive(p1: Context, p2: Intent) {
setPreferenceEnabled() setPreferenceEnabled()
updatePreferenceScreen()
} }
} }
getActivity.getApplicationContext.registerReceiver(receiver, filter) getActivity.getApplicationContext.registerReceiver(receiver, filter)
...@@ -172,51 +276,35 @@ object Shadowsocks { ...@@ -172,51 +276,35 @@ object Shadowsocks {
override def onPause() { override def onPause() {
super.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 class Shadowsocks
extends UnifiedSherlockPreferenceActivity extends UnifiedSherlockPreferenceActivity
with CompoundButton.OnCheckedChangeListener with CompoundButton.OnCheckedChangeListener
with OnSharedPreferenceChangeListener { with OnSharedPreferenceChangeListener
with MenuAdapter.MenuListener{
private val MSG_CRASH_RECOVER: Int = 1 private val MSG_CRASH_RECOVER: Int = 1
private val MSG_INITIAL_FINISH: Int = 2 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 switchButton: Switch = null
private var progressDialog: ProgressDialog = 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 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 { private val handler: Handler = new Handler {
override def handleMessage(msg: Message) { override def handleMessage(msg: Message) {
...@@ -278,10 +366,10 @@ class Shadowsocks ...@@ -278,10 +366,10 @@ class Shadowsocks
} }
} }
def onSharedPreferenceChanged(prefs: SharedPreferences, key: String) { override def onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
if (key == Key.isGlobalProxy) { if (key == Key.profileName) {
val enabled = state != State.CONNECTED && state != State.CONNECTING profileManager.save()
setPreferenceEnabled(enabled) menuAdapter.updateList(getMenuList, Shadowsocks.currentProfile.id)
} }
} }
...@@ -366,7 +454,7 @@ class Shadowsocks ...@@ -366,7 +454,7 @@ class Shadowsocks
} }
} }
def initAdView() { def initAdView(layoutResId: Int) {
if (settings.getString(Key.proxy, "") == "198.199.101.152") { if (settings.getString(Key.proxy, "") == "198.199.101.152") {
val adView = { val adView = {
if (isSinglePane) { if (isSinglePane) {
...@@ -375,7 +463,7 @@ class Shadowsocks ...@@ -375,7 +463,7 @@ class Shadowsocks
new AdView(this, AdSize.BANNER, "a151becb8068b09") new AdView(this, AdSize.BANNER, "a151becb8068b09")
} }
} }
val layoutView = getLayoutView(getListView.asInstanceOf[ViewParent]) val layoutView = findViewById(layoutResId).asInstanceOf[ViewGroup]
if (layoutView != null) { if (layoutView != null) {
layoutView.addView(adView, 0) layoutView.addView(adView, 0)
adView.loadAd(new AdRequest) adView.loadAd(new AdRequest)
...@@ -383,32 +471,46 @@ class Shadowsocks ...@@ -383,32 +471,46 @@ class Shadowsocks
} }
} }
override def setContentView(layoutResId: Int) {
drawer.setContentView(layoutResId)
initAdView(layoutResId)
onContentChanged()
}
/** Called when the activity is first created. */ /** Called when the activity is first created. */
override def onCreate(savedInstanceState: Bundle) { override def onCreate(savedInstanceState: Bundle) {
setHeaderRes(R.xml.shadowsocks_headers) setHeaderRes(R.xml.shadowsocks_headers)
super.onCreate(savedInstanceState) 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 val switchLayout = getLayoutInflater
.inflate(R.layout.layout_switch, null) .inflate(R.layout.layout_switch, null)
.asInstanceOf[RelativeLayout] .asInstanceOf[RelativeLayout]
getSupportActionBar.setCustomView(switchLayout) getSupportActionBar.setCustomView(switchLayout)
getSupportActionBar.setDisplayShowTitleEnabled(false) getSupportActionBar.setDisplayShowTitleEnabled(false)
getSupportActionBar.setDisplayShowCustomEnabled(true) getSupportActionBar.setDisplayShowCustomEnabled(true)
getSupportActionBar.setDisplayShowHomeEnabled(false) // getSupportActionBar.setDisplayShowHomeEnabled(false)
val title: TextView = switchLayout.findViewById(R.id.title).asInstanceOf[TextView] val title: TextView = switchLayout.findViewById(R.id.title).asInstanceOf[TextView]
val tf: Typeface = Typefaces.get(this, "fonts/Iceland.ttf") val tf: Typeface = Typefaces.get(this, "fonts/Iceland.ttf")
if (tf != null) title.setTypeface(tf) if (tf != null) title.setTypeface(tf)
title.setText(R.string.app_name) title.setText(R.string.app_name)
switchButton = switchLayout.findViewById(R.id.switchButton).asInstanceOf[Switch] 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)) registerReceiver(receiver, new IntentFilter(Action.UPDATE_STATE))
initAdView()
val init: Boolean = !Shadowsocks.isServiceStarted(this) val init: Boolean = !Shadowsocks.isServiceStarted(this)
if (init) { if (init) {
if (progressDialog == null) { if (progressDialog == null) {
...@@ -425,51 +527,123 @@ class Shadowsocks ...@@ -425,51 +527,123 @@ class Shadowsocks
} }
} }
override def onCreateOptionsMenu(menu: Menu): Boolean = { override def onRestoreInstanceState(inState: Bundle) {
val isRoot = status.getBoolean(Key.isRoot, false) super.onRestoreInstanceState(inState)
menu drawer.restoreState(inState.getParcelable(STATE_MENUDRAWER))
.add(0, 0, 0, R.string.recovery) }
.setIcon(android.R.drawable.ic_menu_revert)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT) override def onSaveInstanceState(outState: Bundle) {
menu super.onSaveInstanceState(outState)
.add(0, 1, 1, R.string.about) outState.putParcelable(STATE_MENUDRAWER, drawer.saveState())
.setIcon(android.R.drawable.ic_menu_info_details) outState.putInt(STATE_ACTIVE_VIEW_ID, Shadowsocks.currentProfile.id)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT) }
if (isRoot || Build.VERSION.SDK_INT < 17) {
menu override def onBackPressed() {
.add(0, 2, 2, R.string.flush_dnscache) val drawerState = drawer.getDrawerState
.setIcon(android.R.drawable.ic_menu_revert) if (drawerState == MenuDrawer.STATE_OPEN || drawerState == MenuDrawer.STATE_OPENING) {
.setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT) drawer.closeMenu()
return
} }
true super.onBackPressed()
} }
override def onKeyDown(keyCode: Int, event: KeyEvent): Boolean = { override def onActiveViewChanged(v: View, pos: Int) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount == 0) { drawer.setActiveView(v, pos)
try { }
finish()
} catch { def addProfile(id: Int) {
case ignore: Exception => { 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 }, 600)
}
super.onKeyDown(keyCode, event) if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
} }
override def onOptionsItemSelected(item: MenuItem): Boolean = { def updateProfile(id: Int) {
item.getItemId match { drawer.closeMenu(true)
case 0 => {
EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "reset", getVersionName, 0L) handler.postDelayed(new Runnable {
recovery() def run() {
Shadowsocks.currentProfile = profileManager.reload(id)
updatePreferenceScreen()
menuAdapter.setActiveId(id)
menuAdapter.notifyDataSetChanged()
} }
case 1 => { }, 600)
EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "about", getVersionName, 0L)
showAbout() 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) EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "flush_dnscache", getVersionName, 0L)
flushDnsCache() 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) super.onOptionsItemSelected(item)
...@@ -478,6 +652,7 @@ class Shadowsocks ...@@ -478,6 +652,7 @@ class Shadowsocks
protected override def onPause() { protected override def onPause() {
super.onPause() super.onPause()
prepared = false prepared = false
settings.unregisterOnSharedPreferenceChangeListener(this)
} }
protected override def onResume() { protected override def onResume() {
...@@ -511,6 +686,8 @@ class Shadowsocks ...@@ -511,6 +686,8 @@ class Shadowsocks
} }
switchButton.setOnCheckedChangeListener(this) switchButton.setOnCheckedChangeListener(this)
settings.registerOnSharedPreferenceChangeListener(this)
} }
private def setPreferenceEnabled(enabled: Boolean) { private def setPreferenceEnabled(enabled: Boolean) {
...@@ -524,8 +701,8 @@ class Shadowsocks ...@@ -524,8 +701,8 @@ class Shadowsocks
for (name <- Shadowsocks.FEATRUE_PREFS) { for (name <- Shadowsocks.FEATRUE_PREFS) {
val pref = findPreference(name) val pref = findPreference(name)
if (pref != null) { if (pref != null) {
if (Seq(Key.isAutoConnect, Key.isGlobalProxy, Key.isTrafficStat, if (Seq(Key.isAutoConnect, Key.isGlobalProxy, Key.isTrafficStat, Key.proxyedApps)
Key.proxyedApps).contains(name)) { .contains(name)) {
pref.setEnabled(enabled && isRoot) pref.setEnabled(enabled && isRoot)
} else { } else {
pref.setEnabled(enabled) pref.setEnabled(enabled)
...@@ -534,6 +711,18 @@ class Shadowsocks ...@@ -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() { override def onStart() {
super.onStart() super.onStart()
EasyTracker.getInstance.activityStart(this) EasyTracker.getInstance.activityStart(this)
...@@ -752,7 +941,7 @@ class Shadowsocks ...@@ -752,7 +941,7 @@ class Shadowsocks
setPreferenceEnabled(enabled = true) 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 ...@@ -40,9 +40,13 @@ package com.github.shadowsocks
import android.app.Application import android.app.Application
import com.google.analytics.tracking.android.EasyTracker import com.google.analytics.tracking.android.EasyTracker
import com.github.shadowsocks.database.{ProfileManager, DBHelper}
class ShadowsocksApplication extends Application { class ShadowsocksApplication extends Application {
lazy val dbHelper = new DBHelper(this)
override def onCreate() { override def onCreate() {
EasyTracker.getInstance.setContext(this) EasyTracker.getInstance.setContext(this)
} }
} }
...@@ -324,18 +324,8 @@ class ShadowsocksService extends Service { ...@@ -324,18 +324,8 @@ class ShadowsocksService extends Service {
val icon = getResources.getDrawable(R.drawable.ic_stat_shadowsocks) val icon = getResources.getDrawable(R.drawable.ic_stat_shadowsocks)
if (rate >= 0) { if (rate >= 0) {
val bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth * 4, val bitmap =Utils.getBitmap(rate.toString, icon.getIntrinsicWidth * 4,
icon.getIntrinsicHeight * 4, Bitmap.Config.ARGB_8888) icon.getIntrinsicHeight * 4, Color.TRANSPARENT)
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)
builder.setLargeIcon(bitmap) builder.setLargeIcon(bitmap)
if (rate < 1000) { if (rate < 1000) {
......
...@@ -49,10 +49,11 @@ import org.apache.http.conn.util.InetAddressUtils ...@@ -49,10 +49,11 @@ import org.apache.http.conn.util.InetAddressUtils
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
import org.xbill.DNS._ import org.xbill.DNS._
import scala.Some import scala.Some
import android.graphics.{Canvas, Bitmap} import android.graphics._
import android.app.ActivityManager import android.app.ActivityManager
import android.os.Build import android.os.Build
import android.provider.Settings import android.provider.Settings
import scala.Some
object Config { object Config {
val SHADOWSOCKS = "{\"server\": [%s], \"server_port\": %d, \"local_port\": %d, \"password\": %s, \"timeout\": %d}" val SHADOWSOCKS = "{\"server\": [%s], \"server_port\": %d, \"local_port\": %d, \"password\": %s, \"timeout\": %d}"
...@@ -112,10 +113,13 @@ object Config { ...@@ -112,10 +113,13 @@ object Config {
} }
case class Config(isGlobalProxy: Boolean, isGFWList: Boolean, isBypassApps: Boolean, isTrafficStat: Boolean, 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) localPort: Int, proxiedAppString: String)
object Key { object Key {
val profileId = "profileId"
val profileName = "profileName"
val proxied = "Proxyed" val proxied = "Proxyed"
val isRoot = "isRoot" val isRoot = "isRoot"
...@@ -137,6 +141,11 @@ object Key { ...@@ -137,6 +141,11 @@ object Key {
val localPort = "port" val localPort = "port"
} }
object Scheme {
val APP = "app://"
val PROFILE = "profile://"
}
object State { object State {
val INIT = 0 val INIT = 0
val CONNECTING = 1 val CONNECTING = 1
...@@ -162,6 +171,7 @@ object Extra { ...@@ -162,6 +171,7 @@ object Extra {
edit.putBoolean(Key.isBypassApps, config.isBypassApps) edit.putBoolean(Key.isBypassApps, config.isBypassApps)
edit.putBoolean(Key.isTrafficStat, config.isTrafficStat) edit.putBoolean(Key.isTrafficStat, config.isTrafficStat)
edit.putString(Key.profileName, config.profileName)
edit.putString(Key.proxy, config.proxy) edit.putString(Key.proxy, config.proxy)
edit.putString(Key.sitekey, config.sitekey) edit.putString(Key.sitekey, config.sitekey)
edit.putString(Key.encMethod, config.encMethod) edit.putString(Key.encMethod, config.encMethod)
...@@ -177,6 +187,7 @@ object Extra { ...@@ -177,6 +187,7 @@ object Extra {
val isBypassApps = intent.getBooleanExtra(Key.isBypassApps, false) val isBypassApps = intent.getBooleanExtra(Key.isBypassApps, false)
val isTrafficStat = intent.getBooleanExtra(Key.isTrafficStat, false) val isTrafficStat = intent.getBooleanExtra(Key.isTrafficStat, false)
val profileName = intent.getStringExtra(Key.profileName)
val proxy = intent.getStringExtra(Key.proxy) val proxy = intent.getStringExtra(Key.proxy)
val sitekey = intent.getStringExtra(Key.sitekey) val sitekey = intent.getStringExtra(Key.sitekey)
val encMethod = intent.getStringExtra(Key.encMethod) val encMethod = intent.getStringExtra(Key.encMethod)
...@@ -184,7 +195,7 @@ object Extra { ...@@ -184,7 +195,7 @@ object Extra {
val localPort = intent.getIntExtra(Key.localPort, 1984) val localPort = intent.getIntExtra(Key.localPort, 1984)
val proxiedString = intent.getStringExtra(Key.proxied) 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) localPort, proxiedString)
} }
...@@ -194,6 +205,7 @@ object Extra { ...@@ -194,6 +205,7 @@ object Extra {
val isBypassApps = settings.getBoolean(Key.isBypassApps, false) val isBypassApps = settings.getBoolean(Key.isBypassApps, false)
val isTrafficStat = settings.getBoolean(Key.isTrafficStat, 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 { val proxy = settings.getString(Key.proxy, "127.0.0.1") match {
case "198.199.101.152" => BuildConfig.SERVER case "198.199.101.152" => BuildConfig.SERVER
case s: String => s case s: String => s
...@@ -232,6 +244,7 @@ object Extra { ...@@ -232,6 +244,7 @@ object Extra {
intent.putExtra(Key.isBypassApps, isBypassApps) intent.putExtra(Key.isBypassApps, isBypassApps)
intent.putExtra(Key.isTrafficStat, isTrafficStat) intent.putExtra(Key.isTrafficStat, isTrafficStat)
intent.putExtra(Key.profileName, profileName)
intent.putExtra(Key.proxy, proxy) intent.putExtra(Key.proxy, proxy)
intent.putExtra(Key.sitekey, sitekey) intent.putExtra(Key.sitekey, sitekey)
intent.putExtra(Key.encMethod, encMethod) intent.putExtra(Key.encMethod, encMethod)
...@@ -265,6 +278,36 @@ object Utils { ...@@ -265,6 +278,36 @@ object Utils {
var data_path: String = null var data_path: String = null
var rootTries = 0 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 // Blocked > 3 seconds
def toggleAirplaneMode(context: Context) { def toggleAirplaneMode(context: Context) {
if (Build.VERSION.SDK_INT >= 17) { 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