@@ -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")
|
||||
|
||||
|
After Width: | Height: | Size: 963 B |
|
After Width: | Height: | Size: 173 B |
|
After Width: | Height: | Size: 191 B |
|
After Width: | Height: | Size: 154 B |
|
After Width: | Height: | Size: 159 B |
|
After Width: | Height: | Size: 189 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 955 B |
|
After Width: | Height: | Size: 165 B |
|
After Width: | Height: | Size: 171 B |
|
After Width: | Height: | Size: 151 B |
|
After Width: | Height: | Size: 158 B |
|
After Width: | Height: | Size: 172 B |
|
After Width: | Height: | Size: 869 B |
|
After Width: | Height: | Size: 979 B |
|
After Width: | Height: | Size: 179 B |
|
After Width: | Height: | Size: 203 B |
|
After Width: | Height: | Size: 158 B |
|
After Width: | Height: | Size: 163 B |
|
After Width: | Height: | Size: 190 B |
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,20 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?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>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?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" />
|
||||
@@ -0,0 +1,5 @@
|
||||
<?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" />
|
||||
@@ -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>
|
||||
@@ -0,0 +1,28 @@
|
||||
<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>
|
||||
@@ -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>
|
||||
@@ -1,6 +1,12 @@
|
||||
<?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,117 +62,40 @@ 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
|
||||
|
||||
object Shadowsocks {
|
||||
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
|
||||
|
||||
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 TAG = "Shadowsocks"
|
||||
val REQUEST_CONNECT = 1
|
||||
class ProfileIconDownloader(context: Context, connectTimeout: Int, readTimeout: Int)
|
||||
extends BaseImageDownloader(context, connectTimeout, readTimeout) {
|
||||
|
||||
private var vpnEnabled = -1
|
||||
|
||||
def isServiceStarted(context: Context): Boolean = {
|
||||
ShadowsocksService.isServiceStarted(context) || ShadowVpnService.isServiceStarted(context)
|
||||
def this(context: Context) {
|
||||
this(context, 0, 0)
|
||||
}
|
||||
|
||||
class ProxyFragment extends UnifiedPreferenceFragment {
|
||||
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)
|
||||
|
||||
var receiver: BroadcastReceiver = null
|
||||
|
||||
private def setPreferenceEnabled() {
|
||||
val state = getActivity.asInstanceOf[Shadowsocks].state
|
||||
val enabled = state != State.CONNECTED && state != State.CONNECTING
|
||||
for (name <- PROXY_PREFS) {
|
||||
val pref: Preference = findPreference(name)
|
||||
if (pref != null) {
|
||||
pref.setEnabled(enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def onCreate(bundle: Bundle) {
|
||||
super.onCreate(bundle)
|
||||
val filter = new IntentFilter()
|
||||
filter.addAction(Action.UPDATE_FRAGMENT)
|
||||
receiver = new BroadcastReceiver {
|
||||
def onReceive(p1: Context, p2: Intent) {
|
||||
setPreferenceEnabled()
|
||||
}
|
||||
}
|
||||
getActivity.getApplicationContext.registerReceiver(receiver, filter)
|
||||
}
|
||||
|
||||
override def onDestroy() {
|
||||
super.onDestroy()
|
||||
getActivity.getApplicationContext.unregisterReceiver(receiver)
|
||||
}
|
||||
|
||||
override def onResume() {
|
||||
super.onResume()
|
||||
setPreferenceEnabled()
|
||||
}
|
||||
|
||||
override def onPause() {
|
||||
super.onPause()
|
||||
}
|
||||
val os = new ByteArrayOutputStream()
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os)
|
||||
new ByteArrayInputStream(os.toByteArray)
|
||||
}
|
||||
|
||||
class FeatureFragment extends UnifiedPreferenceFragment with OnSharedPreferenceChangeListener {
|
||||
|
||||
var receiver: BroadcastReceiver = null
|
||||
|
||||
private def setPreferenceEnabled() {
|
||||
val state = getActivity.asInstanceOf[Shadowsocks].state
|
||||
val enabled: Boolean = state != State.CONNECTED && state != State.CONNECTING
|
||||
for (name <- Shadowsocks.FEATRUE_PREFS) {
|
||||
val pref: Preference = findPreference(name)
|
||||
if (pref != null) {
|
||||
val status = getActivity.getSharedPreferences(Key.status, Context.MODE_PRIVATE)
|
||||
val isRoot = status.getBoolean(Key.isRoot, false)
|
||||
if (Seq(Key.isAutoConnect, Key.isGlobalProxy, Key.isTrafficStat,
|
||||
Key.proxyedApps).contains(name)) {
|
||||
pref.setEnabled(enabled && isRoot)
|
||||
} else {
|
||||
pref.setEnabled(enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def onCreate(bundle: Bundle) {
|
||||
super.onCreate(bundle)
|
||||
val filter = new IntentFilter()
|
||||
filter.addAction(Action.UPDATE_FRAGMENT)
|
||||
receiver = new BroadcastReceiver {
|
||||
def onReceive(p1: Context, p2: Intent) {
|
||||
setPreferenceEnabled()
|
||||
}
|
||||
}
|
||||
getActivity.getApplicationContext.registerReceiver(receiver, filter)
|
||||
}
|
||||
|
||||
override def onDestroy() {
|
||||
super.onDestroy()
|
||||
getActivity.getApplicationContext.unregisterReceiver(receiver)
|
||||
}
|
||||
|
||||
override def onResume() {
|
||||
super.onResume()
|
||||
setPreferenceEnabled()
|
||||
}
|
||||
|
||||
override def onPause() {
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
def onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
|
||||
if (key == Key.isGlobalProxy) setPreferenceEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object Typefaces {
|
||||
@@ -202,21 +120,191 @@ object Typefaces {
|
||||
private final val cache: Hashtable[String, Typeface] = new Hashtable[String, Typeface]
|
||||
}
|
||||
|
||||
object Shadowsocks {
|
||||
|
||||
val PREFS_NAME = "Shadowsocks"
|
||||
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)
|
||||
}
|
||||
|
||||
class ProxyFragment extends UnifiedPreferenceFragment {
|
||||
|
||||
var receiver: BroadcastReceiver = null
|
||||
|
||||
private def setPreferenceEnabled() {
|
||||
val state = getActivity.asInstanceOf[Shadowsocks].state
|
||||
val enabled = state != State.CONNECTED && state != State.CONNECTING
|
||||
for (name <- PROXY_PREFS) {
|
||||
val pref: Preference = findPreference(name)
|
||||
if (pref != null) {
|
||||
pref.setEnabled(enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def updatePreferenceScreen() {
|
||||
for (name <- Shadowsocks.PROXY_PREFS) {
|
||||
val pref = findPreference(name)
|
||||
Shadowsocks.updatePreference(pref, name, Shadowsocks.currentProfile)
|
||||
}
|
||||
}
|
||||
|
||||
override def onCreate(bundle: Bundle) {
|
||||
super.onCreate(bundle)
|
||||
val filter = new IntentFilter()
|
||||
filter.addAction(Action.UPDATE_FRAGMENT)
|
||||
receiver = new BroadcastReceiver {
|
||||
def onReceive(p1: Context, p2: Intent) {
|
||||
setPreferenceEnabled()
|
||||
updatePreferenceScreen()
|
||||
}
|
||||
}
|
||||
getActivity.getApplicationContext.registerReceiver(receiver, filter)
|
||||
}
|
||||
|
||||
override def onDestroy() {
|
||||
super.onDestroy()
|
||||
getActivity.getApplicationContext.unregisterReceiver(receiver)
|
||||
}
|
||||
|
||||
override def onResume() {
|
||||
super.onResume()
|
||||
setPreferenceEnabled()
|
||||
}
|
||||
|
||||
override def onPause() {
|
||||
super.onPause()
|
||||
}
|
||||
}
|
||||
|
||||
class FeatureFragment extends UnifiedPreferenceFragment {
|
||||
|
||||
var receiver: BroadcastReceiver = null
|
||||
|
||||
private def setPreferenceEnabled() {
|
||||
val state = getActivity.asInstanceOf[Shadowsocks].state
|
||||
val enabled: Boolean = state != State.CONNECTED && state != State.CONNECTING
|
||||
for (name <- Shadowsocks.FEATRUE_PREFS) {
|
||||
val pref: Preference = findPreference(name)
|
||||
if (pref != null) {
|
||||
val status = getActivity.getSharedPreferences(Key.status, Context.MODE_PRIVATE)
|
||||
val isRoot = status.getBoolean(Key.isRoot, false)
|
||||
if (Seq(Key.isAutoConnect, Key.isGlobalProxy, Key.isTrafficStat, Key.proxyedApps)
|
||||
.contains(name)) {
|
||||
pref.setEnabled(enabled && isRoot)
|
||||
} else {
|
||||
pref.setEnabled(enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def updatePreferenceScreen() {
|
||||
for (name <- Shadowsocks.FEATRUE_PREFS) {
|
||||
val pref = findPreference(name)
|
||||
Shadowsocks.updatePreference(pref, name, Shadowsocks.currentProfile)
|
||||
}
|
||||
}
|
||||
|
||||
override def onCreate(bundle: Bundle) {
|
||||
super.onCreate(bundle)
|
||||
val filter = new IntentFilter()
|
||||
filter.addAction(Action.UPDATE_FRAGMENT)
|
||||
receiver = new BroadcastReceiver {
|
||||
def onReceive(p1: Context, p2: Intent) {
|
||||
setPreferenceEnabled()
|
||||
updatePreferenceScreen()
|
||||
}
|
||||
}
|
||||
getActivity.getApplicationContext.registerReceiver(receiver, filter)
|
||||
}
|
||||
|
||||
override def onDestroy() {
|
||||
super.onDestroy()
|
||||
getActivity.getApplicationContext.unregisterReceiver(receiver)
|
||||
}
|
||||
|
||||
override def onResume() {
|
||||
super.onResume()
|
||||
setPreferenceEnabled()
|
||||
}
|
||||
|
||||
override def onPause() {
|
||||
super.onPause()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Shadowsocks
|
||||
extends UnifiedSherlockPreferenceActivity
|
||||
with CompoundButton.OnCheckedChangeListener
|
||||
with OnSharedPreferenceChangeListener {
|
||||
with OnSharedPreferenceChangeListener
|
||||
with MenuAdapter.MenuListener{
|
||||
|
||||
private val MSG_CRASH_RECOVER: Int = 1
|
||||
private val MSG_INITIAL_FINISH: Int = 2
|
||||
|
||||
private val STATE_MENUDRAWER = "com.github.shadowsocks.menuDrawer"
|
||||
private val STATE_ACTIVE_VIEW_ID = "com.github.shadowsocks.activeViewId"
|
||||
|
||||
private var switchButton: Switch = null
|
||||
private var progressDialog: ProgressDialog = null
|
||||
private var settings: SharedPreferences = null
|
||||
private var status: SharedPreferences = null
|
||||
private var prepared = false
|
||||
|
||||
private var state = State.INIT
|
||||
private var receiver: StateBroadcastReceiver = null
|
||||
private var prepared = false
|
||||
|
||||
lazy val settings = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
lazy val status = getSharedPreferences(Key.status, Context.MODE_PRIVATE)
|
||||
lazy val receiver = new StateBroadcastReceiver
|
||||
lazy val drawer = MenuDrawer.attach(this)
|
||||
lazy val menuAdapter = new MenuAdapter(this, getMenuList)
|
||||
lazy val listView = new ListView(this)
|
||||
lazy val profileManager = new ProfileManager(settings, getApplication.asInstanceOf[ShadowsocksApplication].dbHelper)
|
||||
|
||||
private val handler: Handler = new Handler {
|
||||
override def handleMessage(msg: Message) {
|
||||
@@ -278,10 +366,10 @@ class Shadowsocks
|
||||
}
|
||||
}
|
||||
|
||||
def onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
|
||||
if (key == Key.isGlobalProxy) {
|
||||
val enabled = state != State.CONNECTED && state != State.CONNECTING
|
||||
setPreferenceEnabled(enabled)
|
||||
override def onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
|
||||
if (key == Key.profileName) {
|
||||
profileManager.save()
|
||||
menuAdapter.updateList(getMenuList, Shadowsocks.currentProfile.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,7 +454,7 @@ class Shadowsocks
|
||||
}
|
||||
}
|
||||
|
||||
def initAdView() {
|
||||
def initAdView(layoutResId: Int) {
|
||||
if (settings.getString(Key.proxy, "") == "198.199.101.152") {
|
||||
val adView = {
|
||||
if (isSinglePane) {
|
||||
@@ -375,7 +463,7 @@ class Shadowsocks
|
||||
new AdView(this, AdSize.BANNER, "a151becb8068b09")
|
||||
}
|
||||
}
|
||||
val layoutView = getLayoutView(getListView.asInstanceOf[ViewParent])
|
||||
val layoutView = findViewById(layoutResId).asInstanceOf[ViewGroup]
|
||||
if (layoutView != null) {
|
||||
layoutView.addView(adView, 0)
|
||||
adView.loadAd(new AdRequest)
|
||||
@@ -383,32 +471,46 @@ class Shadowsocks
|
||||
}
|
||||
}
|
||||
|
||||
override def setContentView(layoutResId: Int) {
|
||||
drawer.setContentView(layoutResId)
|
||||
initAdView(layoutResId)
|
||||
onContentChanged()
|
||||
}
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
override def onCreate(savedInstanceState: Bundle) {
|
||||
setHeaderRes(R.xml.shadowsocks_headers)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
Shadowsocks.currentProfile = {
|
||||
profileManager.getProfile(settings.getInt(Key.profileId, -1)) getOrElse new Profile()
|
||||
}
|
||||
|
||||
menuAdapter.setActiveId(settings.getInt(Key.profileId, -1))
|
||||
menuAdapter.setListener(this)
|
||||
listView.setAdapter(menuAdapter)
|
||||
drawer.setMenuView(listView)
|
||||
|
||||
// The drawable that replaces the up indicator in the action bar
|
||||
drawer.setSlideDrawable(R.drawable.ic_drawer)
|
||||
// Whether the previous drawable should be shown
|
||||
drawer.setDrawerIndicatorEnabled(true)
|
||||
|
||||
val switchLayout = getLayoutInflater
|
||||
.inflate(R.layout.layout_switch, null)
|
||||
.asInstanceOf[RelativeLayout]
|
||||
getSupportActionBar.setCustomView(switchLayout)
|
||||
getSupportActionBar.setDisplayShowTitleEnabled(false)
|
||||
getSupportActionBar.setDisplayShowCustomEnabled(true)
|
||||
getSupportActionBar.setDisplayShowHomeEnabled(false)
|
||||
|
||||
// getSupportActionBar.setDisplayShowHomeEnabled(false)
|
||||
val title: TextView = switchLayout.findViewById(R.id.title).asInstanceOf[TextView]
|
||||
val tf: Typeface = Typefaces.get(this, "fonts/Iceland.ttf")
|
||||
if (tf != null) title.setTypeface(tf)
|
||||
title.setText(R.string.app_name)
|
||||
|
||||
switchButton = switchLayout.findViewById(R.id.switchButton).asInstanceOf[Switch]
|
||||
settings = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
status = getSharedPreferences(Key.status, Context.MODE_PRIVATE)
|
||||
receiver = new StateBroadcastReceiver()
|
||||
registerReceiver(receiver, new IntentFilter(Action.UPDATE_STATE))
|
||||
|
||||
initAdView()
|
||||
|
||||
val init: Boolean = !Shadowsocks.isServiceStarted(this)
|
||||
if (init) {
|
||||
if (progressDialog == null) {
|
||||
@@ -425,51 +527,123 @@ class Shadowsocks
|
||||
}
|
||||
}
|
||||
|
||||
override def onCreateOptionsMenu(menu: Menu): Boolean = {
|
||||
val isRoot = status.getBoolean(Key.isRoot, false)
|
||||
menu
|
||||
.add(0, 0, 0, R.string.recovery)
|
||||
.setIcon(android.R.drawable.ic_menu_revert)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT)
|
||||
menu
|
||||
.add(0, 1, 1, R.string.about)
|
||||
.setIcon(android.R.drawable.ic_menu_info_details)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT)
|
||||
if (isRoot || Build.VERSION.SDK_INT < 17) {
|
||||
menu
|
||||
.add(0, 2, 2, R.string.flush_dnscache)
|
||||
.setIcon(android.R.drawable.ic_menu_revert)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT)
|
||||
override def onRestoreInstanceState(inState: Bundle) {
|
||||
super.onRestoreInstanceState(inState)
|
||||
drawer.restoreState(inState.getParcelable(STATE_MENUDRAWER))
|
||||
}
|
||||
|
||||
override def onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putParcelable(STATE_MENUDRAWER, drawer.saveState())
|
||||
outState.putInt(STATE_ACTIVE_VIEW_ID, Shadowsocks.currentProfile.id)
|
||||
}
|
||||
|
||||
override def onBackPressed() {
|
||||
val drawerState = drawer.getDrawerState
|
||||
if (drawerState == MenuDrawer.STATE_OPEN || drawerState == MenuDrawer.STATE_OPENING) {
|
||||
drawer.closeMenu()
|
||||
return
|
||||
}
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
override def onActiveViewChanged(v: View, pos: Int) {
|
||||
drawer.setActiveView(v, pos)
|
||||
}
|
||||
|
||||
def addProfile(id: Int) {
|
||||
drawer.closeMenu(true)
|
||||
|
||||
handler.postDelayed(new Runnable {
|
||||
def run() {
|
||||
Shadowsocks.currentProfile = profileManager.reload(id)
|
||||
updatePreferenceScreen()
|
||||
profileManager.save()
|
||||
menuAdapter.updateList(getMenuList, Shadowsocks.currentProfile.id)
|
||||
}
|
||||
}, 600)
|
||||
|
||||
if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
|
||||
}
|
||||
|
||||
def updateProfile(id: Int) {
|
||||
drawer.closeMenu(true)
|
||||
|
||||
handler.postDelayed(new Runnable {
|
||||
def run() {
|
||||
Shadowsocks.currentProfile = profileManager.reload(id)
|
||||
updatePreferenceScreen()
|
||||
menuAdapter.setActiveId(id)
|
||||
menuAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}, 600)
|
||||
|
||||
if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
|
||||
}
|
||||
|
||||
def delProfile(id: Int): Boolean = {
|
||||
drawer.closeMenu(true)
|
||||
|
||||
handler.postDelayed(new Runnable {
|
||||
def run() {
|
||||
profileManager.delProfile(id)
|
||||
val profileId = {
|
||||
val profiles = profileManager.getAllProfiles.getOrElse(List[Profile]())
|
||||
if (profiles.isEmpty) -1 else profiles(0).id
|
||||
}
|
||||
Shadowsocks.currentProfile = profileManager.load(profileId)
|
||||
updatePreferenceScreen()
|
||||
menuAdapter.updateList(getMenuList, Shadowsocks.currentProfile.id)
|
||||
}
|
||||
}, 600)
|
||||
|
||||
if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
override def onKeyDown(keyCode: Int, event: KeyEvent): Boolean = {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount == 0) {
|
||||
try {
|
||||
finish()
|
||||
} catch {
|
||||
case ignore: Exception => {
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
super.onKeyDown(keyCode, event)
|
||||
def getProfileList: List[Item] = {
|
||||
val list = profileManager.getAllProfiles getOrElse List[Profile]()
|
||||
list.map(p => new Item(p.id, p.name, -1, updateProfile, delProfile))
|
||||
}
|
||||
|
||||
def getMenuList: List[Any] = {
|
||||
|
||||
val buf = new ListBuffer[Any]()
|
||||
|
||||
buf += new Category("Profiles")
|
||||
|
||||
buf ++= getProfileList
|
||||
|
||||
buf += new Item(-1, getString(R.string.add_profile), android.R.drawable.ic_menu_add, addProfile)
|
||||
|
||||
buf += new Category("Settings")
|
||||
|
||||
buf += new Item(-100, getString(R.string.recovery), android.R.drawable.ic_menu_revert, _ => {
|
||||
EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "reset", getVersionName, 0L)
|
||||
recovery()
|
||||
})
|
||||
|
||||
buf +=
|
||||
new Item(-200, getString(R.string.flush_dnscache), android.R.drawable.ic_menu_delete, _ => {
|
||||
EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "flush_dnscache", getVersionName, 0L)
|
||||
flushDnsCache()
|
||||
})
|
||||
|
||||
buf += new Item(-300, getString(R.string.about), android.R.drawable.ic_menu_info_details, _ => {
|
||||
EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "about", getVersionName, 0L)
|
||||
showAbout()
|
||||
})
|
||||
|
||||
buf.toList
|
||||
}
|
||||
|
||||
override def onOptionsItemSelected(item: MenuItem): Boolean = {
|
||||
item.getItemId match {
|
||||
case 0 => {
|
||||
EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "reset", getVersionName, 0L)
|
||||
recovery()
|
||||
}
|
||||
case 1 => {
|
||||
EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "about", getVersionName, 0L)
|
||||
showAbout()
|
||||
}
|
||||
case 2 => {
|
||||
EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "flush_dnscache", getVersionName, 0L)
|
||||
flushDnsCache()
|
||||
case android.R.id.home => {
|
||||
EasyTracker.getTracker.sendEvent(Shadowsocks.TAG, "home", getVersionName, 0L)
|
||||
drawer.toggleMenu()
|
||||
return true
|
||||
}
|
||||
}
|
||||
super.onOptionsItemSelected(item)
|
||||
@@ -478,6 +652,7 @@ class Shadowsocks
|
||||
protected override def onPause() {
|
||||
super.onPause()
|
||||
prepared = false
|
||||
settings.unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
protected override def onResume() {
|
||||
@@ -511,6 +686,8 @@ class Shadowsocks
|
||||
}
|
||||
|
||||
switchButton.setOnCheckedChangeListener(this)
|
||||
|
||||
settings.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
private def setPreferenceEnabled(enabled: Boolean) {
|
||||
@@ -524,8 +701,8 @@ class Shadowsocks
|
||||
for (name <- Shadowsocks.FEATRUE_PREFS) {
|
||||
val pref = findPreference(name)
|
||||
if (pref != null) {
|
||||
if (Seq(Key.isAutoConnect, Key.isGlobalProxy, Key.isTrafficStat,
|
||||
Key.proxyedApps).contains(name)) {
|
||||
if (Seq(Key.isAutoConnect, Key.isGlobalProxy, Key.isTrafficStat, Key.proxyedApps)
|
||||
.contains(name)) {
|
||||
pref.setEnabled(enabled && isRoot)
|
||||
} else {
|
||||
pref.setEnabled(enabled)
|
||||
@@ -534,6 +711,18 @@ class Shadowsocks
|
||||
}
|
||||
}
|
||||
|
||||
private def updatePreferenceScreen() {
|
||||
val profile = Shadowsocks.currentProfile
|
||||
for (name <- Shadowsocks.PROXY_PREFS) {
|
||||
val pref = findPreference(name)
|
||||
Shadowsocks.updatePreference(pref, name, profile)
|
||||
}
|
||||
for (name <- Shadowsocks.FEATRUE_PREFS) {
|
||||
val pref = findPreference(name)
|
||||
Shadowsocks.updatePreference(pref, name, profile)
|
||||
}
|
||||
}
|
||||
|
||||
override def onStart() {
|
||||
super.onStart()
|
||||
EasyTracker.getInstance.activityStart(this)
|
||||
@@ -752,7 +941,7 @@ class Shadowsocks
|
||||
setPreferenceEnabled(enabled = true)
|
||||
}
|
||||
}
|
||||
sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
|
||||
if (!isSinglePane) sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 = ""
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||