@@ -37,6 +37,7 @@ libraryDependencies ++= Seq(
|
||||
"com.google.android.gms" % "play-services-analytics" % "8.1.0",
|
||||
"com.android.support" % "support-v4" % "23.1.0",
|
||||
"com.android.support" % "design" % "23.1.0",
|
||||
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
|
||||
"com.nostra13.universalimageloader" % "universal-image-loader" % "1.9.4",
|
||||
"com.j256.ormlite" % "ormlite-core" % "4.48",
|
||||
"com.j256.ormlite" % "ormlite-android" % "4.48"
|
||||
|
||||
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 963 B After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 955 B After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 979 B After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
@@ -1,35 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical"
|
||||
android:duplicateParentState="false">
|
||||
<include layout="@layout/toolbar_light_dark" />
|
||||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="8dp">
|
||||
<include layout="@layout/toolbar_light_dark"/>
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingLeft="48dp"
|
||||
android:paddingStart="48dp"
|
||||
android:paddingTop="8dp"
|
||||
android:background="@android:color/white"
|
||||
android:elevation="1dp" tools:ignore="RtlSymmetry">
|
||||
<TextView
|
||||
android:id="@+id/bypassLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="3dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:textSize="18sp"
|
||||
android:text="@string/bypass_apps"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true"/>
|
||||
android:text="@string/bypass_apps"/>
|
||||
|
||||
<Switch
|
||||
android:id="@+id/bypassSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="16dp"/>
|
||||
</RelativeLayout>
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginRight="6dp"/>
|
||||
</LinearLayout>
|
||||
<ListView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
android:textSize="18sp"
|
||||
android:padding="3dp"/>
|
||||
|
||||
<CheckBox
|
||||
<Switch
|
||||
android:id="@+id/itemcheck"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="fill_parent"
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent" android:layout_height="match_parent">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent" android:layout_height="match_parent"
|
||||
tools:ignore="UnknownIdInLayout" android:clipChildren="false">
|
||||
<include layout="@layout/toolbar_light_dark" />
|
||||
<fragment android:name="com.github.shadowsocks.ShadowsocksSettings"
|
||||
android:id="@android:id/content" android:layout_below="@+id/toolbar"
|
||||
android:layout_width="match_parent" android:layout_height="match_parent" />
|
||||
<android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:backgroundTint="@color/material_blue_500"
|
||||
android:src="@drawable/ic_cloud_off"
|
||||
android:layout_gravity="end|bottom"
|
||||
android:layout_alignBottom="@+id/toolbar"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="-28dp" app:elevation="6dp"
|
||||
app:pressedTranslationZ="6dp" app:borderWidth="0dp" />
|
||||
android:layout_width="match_parent" android:layout_height="match_parent"/>
|
||||
<com.github.jorgecastilloprz.FABProgressCircle android:id="@+id/fabProgressCircle" app:reusable="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" android:elevation="6dp"
|
||||
android:layout_alignBottom="@+id/toolbar"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="@dimen/fab_margin_bottom">
|
||||
<android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:backgroundTint="@color/material_green_600"
|
||||
android:src="@drawable/ic_cloud_off"
|
||||
app:pressedTranslationZ="6dp" app:borderWidth="0dp" />
|
||||
</com.github.jorgecastilloprz.FABProgressCircle>
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="fab_margin_bottom">-28dp</dimen>
|
||||
</resources>
|
||||
@@ -26,7 +26,6 @@
|
||||
<string name="auto_set_gfwlist">国内路由</string>
|
||||
<string name="auto_set_gfwlist_summary">绕过所有位于中国的网站</string>
|
||||
<string name="route_list">路由</string>
|
||||
<string name="route_list_summary">绕过指定路由列表中的地址</string>
|
||||
<string name="auto_set_proxy">全局代理</string>
|
||||
<string name="auto_set_proxy_summary">设置系统代理</string>
|
||||
<string name="proxied_apps">分应用代理</string>
|
||||
@@ -54,7 +53,7 @@
|
||||
<string name="port_empty">端口号不能为空</string>
|
||||
<string name="proxy_empty">代理服务器地址不能为空</string>
|
||||
<string name="crash_alert">检测到意外退出,状态已被重置。</string>
|
||||
<string name="connecting">正在连接...</string>
|
||||
<string name="connect">连接</string>
|
||||
<string name="initializing">正在初始化...</string>
|
||||
<string name="recovering">重置中...</string>
|
||||
<string name="loading">正在加载...</string>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="SummaryPreference">
|
||||
<attr name="summary" format="string" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="DropDownPreference">
|
||||
<attr name="entries" format="reference" />
|
||||
<attr name="entryValues" format="reference" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
@@ -12,7 +12,7 @@
|
||||
<color name="md__transparent">#00000000</color>
|
||||
<color name="md__defaultBackground">#FF555555</color>
|
||||
|
||||
<color name="material_blue_500">#2196F3</color>
|
||||
<color name="material_green_600">#43A047</color>
|
||||
<color name="material_blue_grey_300">#90A4AE</color>
|
||||
<color name="material_blue_grey_500">#607D8B</color>
|
||||
<color name="material_blue_grey_700">#455A64</color>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- File created by the Android Action Bar Style Generator
|
||||
|
||||
Copyright (C) 2011 The Android Open Source Project
|
||||
Copyright (C) 2012 readyState Software Ltd
|
||||
|
||||
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.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<color name="pressed_shadow">#CC33B5E5</color>
|
||||
<color name="title">#EEE</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- TODO: Remove when this issue is fixed: https://github.com/JorgeCastilloPrz/FABProgressCircle/issues/19 -->
|
||||
<dimen name="fab_margin_bottom">-44dp</dimen>
|
||||
</resources>
|
||||
@@ -37,7 +37,6 @@
|
||||
<string name="auto_set_gfwlist_summary">Bypass all sites located in China
|
||||
</string>
|
||||
<string name="route_list">Route</string>
|
||||
<string name="route_list_summary">Bypass IP addresses in the route list</string>
|
||||
<string name="auto_set_proxy">Global Proxy</string>
|
||||
<string name="auto_set_proxy_summary">Set up system-wide proxy</string>
|
||||
<string name="proxied_apps">Per-App Proxy</string>
|
||||
@@ -75,7 +74,7 @@
|
||||
<string name="proxy_empty">Proxy should not be empty</string>
|
||||
<string name="crash_alert">An unexpected exit detected, the context has been reset.
|
||||
</string>
|
||||
<string name="connecting">Connecting…</string>
|
||||
<string name="connect">Connect</string>
|
||||
<string name="initializing">Initializing…</string>
|
||||
<string name="recovering">Resetting…</string>
|
||||
<string name="loading">Loading…</string>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?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"
|
||||
xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks">
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/proxy_cat">
|
||||
@@ -37,40 +38,41 @@
|
||||
android:summary="@string/sitekey_summary"
|
||||
android:title="@string/sitekey">
|
||||
</com.github.shadowsocks.preferences.PasswordEditTextPreference>
|
||||
<com.github.shadowsocks.preferences.SummaryListPreference
|
||||
<com.github.shadowsocks.preferences.DropDownPreference
|
||||
android:defaultValue="rc4"
|
||||
android:key="encMethod"
|
||||
android:entries="@array/enc_method_entry"
|
||||
android:entryValues="@array/enc_method_value"
|
||||
app:entries="@array/enc_method_entry"
|
||||
app:entryValues="@array/enc_method_value"
|
||||
app:summary="%s"
|
||||
android:title="@string/enc_method">
|
||||
</com.github.shadowsocks.preferences.SummaryListPreference>
|
||||
</com.github.shadowsocks.preferences.DropDownPreference>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/feature_cat">
|
||||
|
||||
<com.github.shadowsocks.preferences.SummaryListPreference
|
||||
<com.github.shadowsocks.preferences.DropDownPreference
|
||||
android:defaultValue="all"
|
||||
android:key="route"
|
||||
android:entries="@array/route_entry"
|
||||
android:entryValues="@array/route_value"
|
||||
android:summary="@string/route_list_summary"
|
||||
app:entries="@array/route_entry"
|
||||
app:entryValues="@array/route_value"
|
||||
app:summary="%s"
|
||||
android:title="@string/route_list">
|
||||
</com.github.shadowsocks.preferences.SummaryListPreference>
|
||||
<CheckBoxPreference
|
||||
</com.github.shadowsocks.preferences.DropDownPreference>
|
||||
<SwitchPreference
|
||||
android:key="isIpv6"
|
||||
android:defaultValue="false"
|
||||
android:summary="@string/ipv6_summary"
|
||||
android:title="@string/ipv6">
|
||||
</CheckBoxPreference>
|
||||
<CheckBoxPreference
|
||||
</SwitchPreference>
|
||||
<SwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="isGlobalProxy"
|
||||
android:disableDependentsState="true"
|
||||
android:summary="@string/auto_set_proxy_summary"
|
||||
android:title="@string/auto_set_proxy">
|
||||
</CheckBoxPreference>
|
||||
</SwitchPreference>
|
||||
<Preference
|
||||
android:key="proxyedApps"
|
||||
android:dependency="isGlobalProxy"
|
||||
@@ -78,23 +80,23 @@
|
||||
android:title="@string/proxied_apps">
|
||||
<intent android:action="com.github.shadowsocks.AppManager"/>
|
||||
</Preference>
|
||||
<CheckBoxPreference
|
||||
<SwitchPreference
|
||||
android:key="isUdpDns"
|
||||
android:defaultValue="false"
|
||||
android:summary="@string/udp_dns_summary"
|
||||
android:title="@string/udp_dns">
|
||||
</CheckBoxPreference>
|
||||
<CheckBoxPreference
|
||||
</SwitchPreference>
|
||||
<SwitchPreference
|
||||
android:key="isAuth"
|
||||
android:defaultValue="false"
|
||||
android:summary="@string/onetime_auth_summary"
|
||||
android:title="@string/onetime_auth">
|
||||
</CheckBoxPreference>
|
||||
<CheckBoxPreference
|
||||
</SwitchPreference>
|
||||
<SwitchPreference
|
||||
android:key="isAutoConnect"
|
||||
android:summary="@string/auto_connect_summary"
|
||||
android:title="@string/auto_connect">
|
||||
</CheckBoxPreference>
|
||||
</SwitchPreference>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ class ObjectArrayTools[T <: AnyRef](a: Array[T]) {
|
||||
}
|
||||
}
|
||||
|
||||
case class ListEntry(box: CheckBox, text: TextView, icon: ImageView)
|
||||
case class ListEntry(switch: Switch, text: TextView, icon: ImageView)
|
||||
|
||||
object AppManager {
|
||||
|
||||
@@ -162,13 +162,13 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe
|
||||
if (convertView == null) {
|
||||
convertView = getLayoutInflater.inflate(R.layout.layout_apps_item, parent, false)
|
||||
val icon = convertView.findViewById(R.id.itemicon).asInstanceOf[ImageView]
|
||||
val box = convertView.findViewById(R.id.itemcheck).asInstanceOf[CheckBox]
|
||||
val switch = convertView.findViewById(R.id.itemcheck).asInstanceOf[Switch]
|
||||
val text = convertView.findViewById(R.id.itemtext).asInstanceOf[TextView]
|
||||
entry = new ListEntry(box, text, icon)
|
||||
entry = new ListEntry(switch, text, icon)
|
||||
entry.text.setOnClickListener(AppManager.this)
|
||||
entry.text.setOnClickListener(AppManager.this)
|
||||
convertView.setTag(entry)
|
||||
entry.box.setOnCheckedChangeListener(AppManager.this)
|
||||
entry.switch.setOnCheckedChangeListener(AppManager.this)
|
||||
} else {
|
||||
entry = convertView.getTag.asInstanceOf[ListEntry]
|
||||
}
|
||||
@@ -187,10 +187,10 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe
|
||||
ImageLoader.getInstance().displayImage(Scheme.APP + app.packageName, entry.icon, options)
|
||||
|
||||
entry.text.setText(app.name)
|
||||
val box: CheckBox = entry.box
|
||||
box.setTag(app)
|
||||
box.setChecked(app.proxied)
|
||||
entry.text.setTag(box)
|
||||
val switch = entry.switch
|
||||
switch.setTag(app)
|
||||
switch.setChecked(app.proxied)
|
||||
entry.text.setTag(switch)
|
||||
convertView
|
||||
}
|
||||
}
|
||||
@@ -207,11 +207,11 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe
|
||||
}
|
||||
|
||||
def onClick(v: View) {
|
||||
val cbox: CheckBox = v.getTag.asInstanceOf[CheckBox]
|
||||
val app: ProxiedApp = cbox.getTag.asInstanceOf[ProxiedApp]
|
||||
val switch = v.getTag.asInstanceOf[Switch]
|
||||
val app: ProxiedApp = switch.getTag.asInstanceOf[ProxiedApp]
|
||||
if (app != null) {
|
||||
app.proxied = !app.proxied
|
||||
cbox.setChecked(app.proxied)
|
||||
switch.setChecked(app.proxied)
|
||||
}
|
||||
saveAppSettings(this)
|
||||
}
|
||||
@@ -247,8 +247,8 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe
|
||||
val array = proxiedAppString.split(" ")
|
||||
val bypass = array(0).toBoolean
|
||||
val apps = if (array.size > 1) array(1) else ""
|
||||
prefs.edit.putBoolean(Key.isBypassApps, bypass).commit()
|
||||
prefs.edit.putString(Key.proxied, apps).commit()
|
||||
prefs.edit.putBoolean(Key.isBypassApps, bypass).apply()
|
||||
prefs.edit.putString(Key.proxied, apps).apply()
|
||||
Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show()
|
||||
// Restart activity
|
||||
val intent = getIntent
|
||||
@@ -299,7 +299,7 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe
|
||||
val bypassSwitch = findViewById(R.id.bypassSwitch).asInstanceOf[Switch]
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext)
|
||||
bypassSwitch.setOnCheckedChangeListener((button: CompoundButton, checked: Boolean) =>
|
||||
prefs.edit().putBoolean(Key.isBypassApps, checked).commit())
|
||||
prefs.edit().putBoolean(Key.isBypassApps, checked).apply())
|
||||
bypassSwitch.setChecked(prefs.getBoolean(Key.isBypassApps, false))
|
||||
|
||||
appListView = findViewById(R.id.applistview).asInstanceOf[ListView]
|
||||
@@ -325,7 +325,7 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe
|
||||
})
|
||||
val edit: SharedPreferences.Editor = prefs.edit
|
||||
edit.putString(Key.proxied, proxiedApps.toString())
|
||||
edit.commit
|
||||
edit.apply
|
||||
}
|
||||
|
||||
val loadStartRunnable = new Runnable {
|
||||
|
||||
@@ -50,17 +50,18 @@ import android.content.res.AssetManager
|
||||
import android.graphics.{Bitmap, Color, Typeface}
|
||||
import android.net.{Uri, VpnService}
|
||||
import android.os._
|
||||
import android.preference._
|
||||
import android.preference.{Preference, PreferenceManager, SwitchPreference}
|
||||
import android.support.design.widget.{FloatingActionButton, Snackbar}
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.support.v7.widget.Toolbar
|
||||
import android.util.{DisplayMetrics, Log}
|
||||
import android.view._
|
||||
import android.view.{View, ViewGroup, ViewParent}
|
||||
import android.webkit.{WebView, WebViewClient}
|
||||
import android.widget._
|
||||
import com.github.jorgecastilloprz.FABProgressCircle
|
||||
import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
|
||||
import com.github.shadowsocks.database._
|
||||
import com.github.shadowsocks.preferences.{PasswordEditTextPreference, ProfileEditTextPreference, SummaryEditTextPreference}
|
||||
import com.github.shadowsocks.preferences.{DropDownPreference, PasswordEditTextPreference, ProfileEditTextPreference, SummaryEditTextPreference}
|
||||
import com.github.shadowsocks.utils._
|
||||
import com.google.android.gms.ads.{AdRequest, AdSize, AdView}
|
||||
import com.google.android.gms.analytics.HitBuilders
|
||||
@@ -132,8 +133,8 @@ object Shadowsocks {
|
||||
val EXECUTABLES = Array(Executable.PDNSD, Executable.REDSOCKS, Executable.SS_TUNNEL, Executable.SS_LOCAL, Executable.TUN2SOCKS)
|
||||
|
||||
// Helper functions
|
||||
def updateListPreference(pref: Preference, value: String) {
|
||||
pref.asInstanceOf[ListPreference].setValue(value)
|
||||
def updateDropDownPreference(pref: Preference, value: String) {
|
||||
pref.asInstanceOf[DropDownPreference].setValue(value)
|
||||
}
|
||||
|
||||
def updatePasswordEditTextPreference(pref: Preference, value: String) {
|
||||
@@ -151,8 +152,8 @@ object Shadowsocks {
|
||||
pref.asInstanceOf[ProfileEditTextPreference].setText(value)
|
||||
}
|
||||
|
||||
def updateCheckBoxPreference(pref: Preference, value: Boolean) {
|
||||
pref.asInstanceOf[CheckBoxPreference].setChecked(value)
|
||||
def updateSwitchPreference(pref: Preference, value: Boolean) {
|
||||
pref.asInstanceOf[SwitchPreference].setChecked(value)
|
||||
}
|
||||
|
||||
def updatePreference(pref: Preference, name: String, profile: Profile) {
|
||||
@@ -162,12 +163,12 @@ object Shadowsocks {
|
||||
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.route => updateListPreference(pref, profile.route)
|
||||
case Key.isGlobalProxy => updateCheckBoxPreference(pref, profile.global)
|
||||
case Key.isUdpDns => updateCheckBoxPreference(pref, profile.udpdns)
|
||||
case Key.isAuth => updateCheckBoxPreference(pref, profile.auth)
|
||||
case Key.isIpv6 => updateCheckBoxPreference(pref, profile.ipv6)
|
||||
case Key.encMethod => updateDropDownPreference(pref, profile.method)
|
||||
case Key.route => updateDropDownPreference(pref, profile.route)
|
||||
case Key.isGlobalProxy => updateSwitchPreference(pref, profile.global)
|
||||
case Key.isUdpDns => updateSwitchPreference(pref, profile.udpdns)
|
||||
case Key.isAuth => updateSwitchPreference(pref, profile.auth)
|
||||
case Key.isIpv6 => updateSwitchPreference(pref, profile.ipv6)
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
@@ -186,6 +187,7 @@ class Shadowsocks
|
||||
// Variables
|
||||
private var serviceStarted = false
|
||||
var fab: FloatingActionButton = _
|
||||
var fabProgressCircle: FABProgressCircle = _
|
||||
var progressDialog: ProgressDialog = _
|
||||
var progressTag = -1
|
||||
var state = State.INIT
|
||||
@@ -221,7 +223,7 @@ class Shadowsocks
|
||||
state = bgService.getState
|
||||
|
||||
if (!status.getBoolean(getVersionName, false)) {
|
||||
status.edit.putBoolean(getVersionName, true).commit()
|
||||
status.edit.putBoolean(getVersionName, true).apply()
|
||||
recovery()
|
||||
}
|
||||
}
|
||||
@@ -394,7 +396,6 @@ class Shadowsocks
|
||||
}
|
||||
|
||||
def prepareStartService() {
|
||||
showProgress(R.string.connecting)
|
||||
Future {
|
||||
if (isVpnEnabled) {
|
||||
val intent = VpnService.prepare(this)
|
||||
@@ -485,6 +486,7 @@ class Shadowsocks
|
||||
val tf: Typeface = Typefaces.get(this, "fonts/Iceland.ttf")
|
||||
if (tf != null) title.setTypeface(tf)
|
||||
fab = findViewById(R.id.fab).asInstanceOf[FloatingActionButton]
|
||||
fabProgressCircle = findViewById(R.id.fabProgressCircle).asInstanceOf[FABProgressCircle]
|
||||
fab.setOnClickListener((v: View) => {
|
||||
serviceStarted = !serviceStarted
|
||||
serviceStarted match {
|
||||
@@ -495,24 +497,31 @@ class Shadowsocks
|
||||
changeSwitch(checked = false)
|
||||
case false =>
|
||||
serviceStop()
|
||||
if (fab.isEnabled) {
|
||||
fab.setEnabled(false)
|
||||
handler.postDelayed(() => {
|
||||
fab.setEnabled(true)
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
if (fab.isEnabled) {
|
||||
fab.setEnabled(false)
|
||||
handler.postDelayed(() => fab.setEnabled(true), 1000)
|
||||
}
|
||||
})
|
||||
fab.setOnLongClickListener((v: View) => {
|
||||
Utils.positionToast(Toast.makeText(this, if (serviceStarted) R.string.stop else R.string.connect,
|
||||
Toast.LENGTH_SHORT), fab, getWindow, 0, Utils.dpToPx(this, 8).toInt).show
|
||||
true
|
||||
})
|
||||
toolbar.setNavigationIcon(R.drawable.ic_drawer)
|
||||
toolbar.setNavigationOnClickListener((v: View) => drawer.toggleMenu())
|
||||
title.setOnLongClickListener((v: View) => {
|
||||
if (Utils.isLollipopOrAbove && bgService != null
|
||||
&& (bgService.getState == State.INIT || bgService.getState == State.STOPPED)) {
|
||||
val natEnabled = status.getBoolean(Key.isNAT, false)
|
||||
status.edit().putBoolean(Key.isNAT, !natEnabled).commit()
|
||||
if (!natEnabled) {
|
||||
Toast.makeText(getBaseContext, R.string.enable_nat, Toast.LENGTH_LONG).show()
|
||||
} else {
|
||||
Toast.makeText(getBaseContext, R.string.disable_nat, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
val natEnabled = !status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove)
|
||||
status.edit().putBoolean(Key.isNAT, natEnabled).apply()
|
||||
deattachService()
|
||||
attachService()
|
||||
if (!Utils.isLollipopOrAbove) setPreferenceEnabled(State.isAvailable(bgService.getState))
|
||||
Toast.makeText(getBaseContext, if (natEnabled) R.string.enable_nat else R.string.disable_nat, Toast.LENGTH_LONG)
|
||||
.show()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@@ -523,13 +532,9 @@ class Shadowsocks
|
||||
registerReceiver(preferenceReceiver, new IntentFilter(Action.UPDATE_PREFS))
|
||||
|
||||
// Bind to the service
|
||||
Future {
|
||||
val isRoot = (!Utils.isLollipopOrAbove || status.getBoolean(Key.isNAT, false)) && Console.isRoot
|
||||
handler.post(() => {
|
||||
status.edit.putBoolean(Key.isRoot, isRoot).commit()
|
||||
attachService()
|
||||
})
|
||||
}
|
||||
handler.post(() => {
|
||||
attachService()
|
||||
})
|
||||
}
|
||||
|
||||
def attachService() {
|
||||
@@ -777,11 +782,17 @@ class Shadowsocks
|
||||
super.onResume()
|
||||
if (bgService != null) {
|
||||
bgService.getState match {
|
||||
case State.CONNECTED =>
|
||||
changeSwitch(checked = true)
|
||||
case State.CONNECTING =>
|
||||
fabProgressCircle.show()
|
||||
changeSwitch(checked = true)
|
||||
case State.CONNECTED =>
|
||||
fabProgressCircle.hide()
|
||||
changeSwitch(checked = true)
|
||||
case State.STOPPING =>
|
||||
fabProgressCircle.show()
|
||||
changeSwitch(checked = false)
|
||||
case _ =>
|
||||
fabProgressCircle.hide()
|
||||
changeSwitch(checked = false)
|
||||
}
|
||||
state = bgService.getState
|
||||
@@ -886,7 +897,6 @@ class Shadowsocks
|
||||
|
||||
private def showQrCode() {
|
||||
val image = new ImageView(this)
|
||||
image.setPadding(0, dp2px(20), 0, dp2px(20))
|
||||
image.setLayoutParams(new LinearLayout.LayoutParams(-1, -1))
|
||||
val qrcode = QRCode.from(Parser.generate(currentProfile))
|
||||
.withSize(dp2px(250), dp2px(250)).asInstanceOf[QRCode]
|
||||
@@ -928,15 +938,10 @@ class Shadowsocks
|
||||
}
|
||||
}
|
||||
|
||||
var isRoot: Option[Boolean] = None
|
||||
def isVpnEnabled: Boolean = {
|
||||
if (vpnEnabled < 0) {
|
||||
vpnEnabled = if (!status.getBoolean(Key.isRoot, false)) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
if (vpnEnabled == 1) true else false
|
||||
if (isRoot.isEmpty) isRoot = Some(Console.isRoot)
|
||||
!(isRoot.get && status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove))
|
||||
}
|
||||
|
||||
def serviceStop() {
|
||||
@@ -1015,36 +1020,31 @@ class Shadowsocks
|
||||
|
||||
def onStateChanged(s: Int, m: String) {
|
||||
handler.post(() => if (state != s) {
|
||||
state = s
|
||||
state match {
|
||||
s match {
|
||||
case State.CONNECTING =>
|
||||
if (progressDialog == null) {
|
||||
progressDialog = ProgressDialog
|
||||
.show(Shadowsocks.this, "", getString(R.string.connecting), true, true)
|
||||
progressTag = R.string.connecting
|
||||
}
|
||||
fab.setImageResource(R.drawable.ic_cloud_queue)
|
||||
fab.setEnabled(false)
|
||||
fabProgressCircle.show()
|
||||
setPreferenceEnabled(enabled = false)
|
||||
case State.CONNECTED =>
|
||||
if (progressTag == R.string.connecting) {
|
||||
clearDialog()
|
||||
}
|
||||
if (state == State.CONNECTING) fabProgressCircle.beginFinalAnimation() else fabProgressCircle.hide()
|
||||
fab.setEnabled(true)
|
||||
changeSwitch(checked = true)
|
||||
setPreferenceEnabled(enabled = false)
|
||||
case State.STOPPED =>
|
||||
if (progressTag == R.string.stopping || progressTag == R.string.connecting) {
|
||||
clearDialog()
|
||||
}
|
||||
handler.post(() => fabProgressCircle.hide())
|
||||
fab.setEnabled(true)
|
||||
changeSwitch(checked = false)
|
||||
if (m != null) Snackbar.make(getWindow.getDecorView.findViewById(android.R.id.content),
|
||||
getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m), Snackbar.LENGTH_LONG).show
|
||||
setPreferenceEnabled(enabled = true)
|
||||
case State.STOPPING =>
|
||||
if (progressDialog == null) {
|
||||
progressDialog = ProgressDialog
|
||||
.show(Shadowsocks.this, "", getString(R.string.stopping), true, true)
|
||||
progressTag = R.string.stopping
|
||||
}
|
||||
fab.setImageResource(R.drawable.ic_cloud_queue)
|
||||
fab.setEnabled(false)
|
||||
fabProgressCircle.show()
|
||||
setPreferenceEnabled(enabled = false)
|
||||
}
|
||||
state = s
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import android.content.pm.{PackageInfo, PackageManager}
|
||||
import android.net.{Network, ConnectivityManager}
|
||||
import android.os._
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.util.{SparseArray, Log}
|
||||
import android.widget.Toast
|
||||
import com.github.shadowsocks.aidl.Config
|
||||
@@ -319,6 +320,7 @@ class ShadowsocksNatService extends Service with BaseService {
|
||||
|
||||
builder
|
||||
.setWhen(0)
|
||||
.setColor(ContextCompat.getColor(this, R.color.material_accent_500))
|
||||
.setTicker(title)
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setContentText(info)
|
||||
|
||||
@@ -65,19 +65,13 @@ class ShadowsocksRunnerActivity extends Activity {
|
||||
}
|
||||
|
||||
// Variables
|
||||
var vpnEnabled = -1
|
||||
var bgService: IShadowsocksService = null
|
||||
var receiver:BroadcastReceiver = null
|
||||
var isRoot: Option[Boolean] = None
|
||||
var bgService: IShadowsocksService = _
|
||||
var receiver:BroadcastReceiver = _
|
||||
|
||||
def isVpnEnabled: Boolean = {
|
||||
if (vpnEnabled < 0) {
|
||||
vpnEnabled = if ((!Utils.isLollipopOrAbove || status.getBoolean(Key.isNAT, false)) && Console.isRoot) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
if (vpnEnabled == 1) true else false
|
||||
if (isRoot.isEmpty) isRoot = Some(Console.isRoot)
|
||||
!(isRoot.get && status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove))
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +121,7 @@ class ShadowsocksRunnerActivity extends Activity {
|
||||
} else {
|
||||
attachService()
|
||||
}
|
||||
finish
|
||||
}
|
||||
|
||||
override def onDestroy() {
|
||||
|
||||
@@ -64,18 +64,12 @@ class ShadowsocksRunnerService extends Service {
|
||||
}
|
||||
|
||||
// Variables
|
||||
var vpnEnabled = -1
|
||||
var bgService: IShadowsocksService = null
|
||||
var isRoot: Option[Boolean] = None
|
||||
var bgService: IShadowsocksService = _
|
||||
|
||||
def isVpnEnabled: Boolean = {
|
||||
if (vpnEnabled < 0) {
|
||||
vpnEnabled = if ((!Utils.isLollipopOrAbove || status.getBoolean(Key.isNAT, false)) && Console.isRoot) {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
if (vpnEnabled == 1) true else false
|
||||
if (isRoot.isEmpty) isRoot = Some(Console.isRoot)
|
||||
!(isRoot.get && status.getBoolean(Key.isNAT, !Utils.isLollipopOrAbove))
|
||||
}
|
||||
|
||||
override def onBind(intent: Intent): IBinder = {
|
||||
|
||||
@@ -48,6 +48,7 @@ import android.content.pm.{PackageInfo, PackageManager}
|
||||
import android.net.VpnService
|
||||
import android.os._
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import com.github.shadowsocks.aidl.Config
|
||||
@@ -113,6 +114,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
|
||||
builder
|
||||
.setWhen(0)
|
||||
.setColor(ContextCompat.getColor(this, R.color.material_accent_500))
|
||||
.setTicker(title)
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setContentText(info)
|
||||
|
||||
@@ -133,7 +133,7 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
|
||||
edit.putString(Key.proxied, profile.individual)
|
||||
edit.putInt(Key.profileId, profile.id)
|
||||
edit.putString(Key.route, profile.route)
|
||||
edit.commit()
|
||||
edit.apply()
|
||||
|
||||
profile
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.github.shadowsocks.preferences
|
||||
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.os.Build
|
||||
import android.preference.Preference
|
||||
import android.support.annotation.NonNull
|
||||
import android.support.v7.widget.AppCompatSpinner
|
||||
import android.util.AttributeSet
|
||||
import android.view.{View, ViewGroup}
|
||||
import android.widget.AdapterView.OnItemSelectedListener
|
||||
import android.widget.{AdapterView, ArrayAdapter}
|
||||
import com.github.shadowsocks.R
|
||||
|
||||
/**
|
||||
* Based on: https://github.com/Mygod/mygod-lib-android/blob/683c41cb86d10e99ab0f457d41a218c6915fccc8/lib/src/main/scala/tk/mygod/preference/DropDownPreference.scala
|
||||
* @author Mygod
|
||||
*/
|
||||
final class DropDownPreference(private val mContext: Context, attrs: AttributeSet = null)
|
||||
extends Preference(mContext, attrs) with SummaryPreference {
|
||||
|
||||
private val mAdapter = new ArrayAdapter[String](mContext, android.R.layout.simple_spinner_dropdown_item)
|
||||
private val mSpinner = new AppCompatSpinner(mContext)
|
||||
private var mEntries: Array[CharSequence] = _
|
||||
private var mEntryValues: Array[CharSequence] = _
|
||||
private var mSelectedIndex: Int = _
|
||||
private var mValueSet: Boolean = _
|
||||
|
||||
mSpinner.setVisibility(View.INVISIBLE)
|
||||
mSpinner.setAdapter(mAdapter)
|
||||
mSpinner.setOnItemSelectedListener(new OnItemSelectedListener {
|
||||
override def onNothingSelected(parent: AdapterView[_]) { }
|
||||
override def onItemSelected(parent: AdapterView[_], view: View, position: Int, id: Long) = setValueIndex(position)
|
||||
})
|
||||
setOnPreferenceClickListener((preference: Preference) => {
|
||||
// TODO: not working with scrolling
|
||||
// mSpinner.setDropDownVerticalOffset(Utils.dpToPx(getContext, -48 * mSelectedIndex).toInt)
|
||||
mSpinner.performClick
|
||||
true
|
||||
})
|
||||
initSummary(mContext, attrs)
|
||||
val a: TypedArray = mContext.obtainStyledAttributes(attrs, R.styleable.DropDownPreference)
|
||||
setEntries(a.getTextArray(R.styleable.DropDownPreference_entries))
|
||||
mEntryValues = a.getTextArray(R.styleable.DropDownPreference_entryValues)
|
||||
a.recycle
|
||||
|
||||
protected override def getSummaryValue = getEntry
|
||||
|
||||
/**
|
||||
* Sets the human-readable entries to be shown in the list. This will be shown in subsequent dialogs.
|
||||
*
|
||||
* Each entry must have a corresponding index in [[setEntryValues(CharSequence[])]].
|
||||
*
|
||||
* @param entries The entries.
|
||||
* @see [[setEntryValues(CharSequence[])]]
|
||||
*/
|
||||
def setEntries(entries: Array[CharSequence]) {
|
||||
mEntries = entries
|
||||
mAdapter.clear
|
||||
if (entries != null) for (entry <- entries) mAdapter.add(entry.toString)
|
||||
}
|
||||
/**
|
||||
* @see #setEntries(CharSequence[])
|
||||
* @param entriesResId The entries array as a resource.
|
||||
*/
|
||||
def setEntries(entriesResId: Int): Unit = setEntries(getContext.getResources.getTextArray(entriesResId))
|
||||
/**
|
||||
* The list of entries to be shown in the list in subsequent dialogs.
|
||||
*
|
||||
* @return The list as an array.
|
||||
*/
|
||||
def getEntries = mEntries
|
||||
|
||||
/**
|
||||
* The array to find the value to save for a preference when an entry from
|
||||
* entries is selected. If a user clicks on the second item in entries, the
|
||||
* second item in this array will be saved to the preference.
|
||||
*
|
||||
* @param entryValues The array to be used as values to save for the preference.
|
||||
*/
|
||||
def setEntryValues(entryValues: Array[CharSequence]) = mEntryValues = entryValues
|
||||
/**
|
||||
* @see #setEntryValues(CharSequence[])
|
||||
* @param entryValuesResId The entry values array as a resource.
|
||||
*/
|
||||
def setEntryValues(entryValuesResId: Int): Unit =
|
||||
setEntryValues(getContext.getResources.getTextArray(entryValuesResId))
|
||||
/**
|
||||
* Returns the array of values to be saved for the preference.
|
||||
*
|
||||
* @return The array of values.
|
||||
*/
|
||||
def getEntryValues: Array[CharSequence] = mEntryValues
|
||||
|
||||
/**
|
||||
* Sets the value of the key. This should be one of the entries in [[getEntryValues]].
|
||||
*
|
||||
* @param value The value to set for the key.
|
||||
*/
|
||||
def setValue(value: String) {
|
||||
val i = findIndexOfValue(value)
|
||||
if (i > -1) setValueIndex(i)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value to the given index from the entry values.
|
||||
*
|
||||
* @param index The index of the value to set.
|
||||
*/
|
||||
def setValueIndex(index: Int) {
|
||||
val changed = mSelectedIndex != index
|
||||
if (changed || !mValueSet) {
|
||||
mSelectedIndex = index
|
||||
mValueSet = true
|
||||
if (mEntryValues != null) {
|
||||
val value = mEntryValues(index).toString
|
||||
persistString(value)
|
||||
setValue(value)
|
||||
}
|
||||
if (changed) notifyChanged
|
||||
}
|
||||
mSpinner.setSelection(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the key. This should be one of the entries in [[getEntryValues]].
|
||||
*
|
||||
* @return The value of the key.
|
||||
*/
|
||||
def getValue = if (mEntryValues == null) null else mEntryValues(mSelectedIndex).toString
|
||||
|
||||
/**
|
||||
* Returns the entry corresponding to the current value.
|
||||
*
|
||||
* @return The entry corresponding to the current value, or null.
|
||||
*/
|
||||
def getEntry = {
|
||||
val index = getValueIndex
|
||||
if (index >= 0 && mEntries != null) mEntries(index) else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the given value (in the entry values array).
|
||||
*
|
||||
* @param value The value whose index should be returned.
|
||||
* @return The index of the value, or -1 if not found.
|
||||
*/
|
||||
def findIndexOfValue(value: String): Int = {
|
||||
if (value != null && mEntryValues != null) {
|
||||
var i = mEntryValues.length - 1
|
||||
while (i >= 0) {
|
||||
if (mEntryValues(i) == value) return i
|
||||
i -= 1
|
||||
}
|
||||
}
|
||||
-1
|
||||
}
|
||||
|
||||
def getValueIndex = mSelectedIndex
|
||||
|
||||
protected override def onGetDefaultValue(a: TypedArray, index: Int) = a.getString(index)
|
||||
protected override def onSetInitialValue (restoreValue: Boolean, defaultValue: AnyRef) =
|
||||
setValue(if (restoreValue) getPersistedString(getValue) else defaultValue.asInstanceOf[String])
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
||||
def setDropDownWidth(dimenResId: Int) =
|
||||
mSpinner.setDropDownWidth(mContext.getResources.getDimensionPixelSize(dimenResId))
|
||||
|
||||
protected override def onBindView(@NonNull view: View) {
|
||||
super.onBindView(view)
|
||||
val parent = mSpinner.getParent.asInstanceOf[ViewGroup]
|
||||
if (view eq parent) return
|
||||
if (parent != null) parent.removeView(mSpinner)
|
||||
view.asInstanceOf[ViewGroup].addView(mSpinner, 0)
|
||||
val lp = mSpinner.getLayoutParams
|
||||
lp.width = 0
|
||||
mSpinner.setLayoutParams(lp)
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Shadowsocks - A shadowsocks client for Android
|
||||
* Copyright (C) 2014 <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.preferences
|
||||
|
||||
import android.preference.ListPreference
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
|
||||
class SummaryListPreference(context: Context, attrs: AttributeSet)
|
||||
extends ListPreference(context, attrs) {
|
||||
|
||||
override def setValue(text: String) {
|
||||
super.setValue(text)
|
||||
val entry = getEntry
|
||||
if (entry != null)
|
||||
setSummary(entry)
|
||||
else
|
||||
setSummary(text)
|
||||
}
|
||||
|
||||
override def setSummary(summary: CharSequence) {
|
||||
if (summary == null || summary.toString.isEmpty) {
|
||||
super.setSummary("")
|
||||
} else {
|
||||
super.setSummary(summary)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.github.shadowsocks.preferences
|
||||
|
||||
import android.content.Context
|
||||
import android.preference.Preference
|
||||
import android.util.AttributeSet
|
||||
import com.github.shadowsocks.R
|
||||
|
||||
/**
|
||||
* Make your preference support %s in summary. Override getSummaryValue to customize what to put in.
|
||||
* Based on: https://github.com/Mygod/mygod-lib-android/blob/eb20c57a0da70bb5dbc27104002f20fb1eb31d76/lib/src/main/scala/tk/mygod/preference/SummaryPreference.scala
|
||||
* @author Mygod
|
||||
*/
|
||||
trait SummaryPreference extends Preference {
|
||||
private var mSummary: String = _
|
||||
|
||||
protected def initSummary(context: Context, attrs: AttributeSet) {
|
||||
val a = context.obtainStyledAttributes(attrs, R.styleable.SummaryPreference)
|
||||
mSummary = a.getString(R.styleable.SummaryPreference_summary)
|
||||
a.recycle
|
||||
}
|
||||
|
||||
protected def getSummaryValue: AnyRef
|
||||
|
||||
/**
|
||||
* Returns the summary of this SummaryPreference. If the summary has a String formatting marker in it
|
||||
* (i.e. "%s" or "%1$s"), then the current entry value will be substituted in its place.
|
||||
*
|
||||
* @return the summary with appropriate string substitution
|
||||
*/
|
||||
override def getSummary = {
|
||||
val entry = getSummaryValue
|
||||
if (mSummary == null || entry == null) super.getSummary else String.format(mSummary, entry)
|
||||
}
|
||||
/**
|
||||
* Sets the summary for this Preference with a CharSequence. If the summary has a String formatting marker in it
|
||||
* (i.e. "%s" or "%1$s"), then the current entry value will be substituted in its place when it's retrieved.
|
||||
*
|
||||
* @param summary The summary for the preference.
|
||||
*/
|
||||
override def setSummary(summary: CharSequence) {
|
||||
super.setSummary(summary)
|
||||
if (summary == null && mSummary != null) mSummary = null
|
||||
else if (summary != null && summary != mSummary) mSummary = summary.toString
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,6 @@ object Key {
|
||||
val proxied = "Proxyed"
|
||||
|
||||
val isNAT = "isNAT"
|
||||
val isRoot = "isRoot"
|
||||
val status = "status"
|
||||
val proxyedApps = "proxyedApps"
|
||||
val route = "route"
|
||||
|
||||
@@ -50,7 +50,10 @@ import android.graphics.drawable.{BitmapDrawable, Drawable}
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.util.{Base64, Log}
|
||||
import android.util.{DisplayMetrics, Base64, Log}
|
||||
import android.view.View.MeasureSpec
|
||||
import android.view.{Gravity, View, Window}
|
||||
import android.widget.Toast
|
||||
import com.github.shadowsocks.BuildConfig
|
||||
import org.xbill.DNS._
|
||||
|
||||
@@ -72,20 +75,13 @@ object Utils {
|
||||
val TIME_OUT: Int = -99
|
||||
var initialized: Boolean = false
|
||||
var hasRedirectSupport: Int = -1
|
||||
var isRoot: Int = -1
|
||||
var shell: String = null
|
||||
var root_shell: String = null
|
||||
var iptables: String = null
|
||||
var data_path: String = null
|
||||
var rootTries = 0
|
||||
|
||||
def isLollipopOrAbove: Boolean = {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
def isLollipopOrAbove: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
|
||||
|
||||
def getSignature(context: Context): String = {
|
||||
val info = context
|
||||
@@ -126,6 +122,23 @@ object Utils {
|
||||
bitmap
|
||||
}
|
||||
|
||||
// Based on: http://stackoverflow.com/a/21026866/2245107
|
||||
def positionToast(toast: Toast, view: View, window: Window, offsetX: Int = 0, offsetY: Int = 0) = {
|
||||
val rect = new Rect
|
||||
window.getDecorView.getWindowVisibleDisplayFrame(rect)
|
||||
val viewLocation = new Array[Int](2)
|
||||
view.getLocationInWindow(viewLocation)
|
||||
val metrics = new DisplayMetrics
|
||||
window.getWindowManager.getDefaultDisplay.getMetrics(metrics)
|
||||
val toastView = toast.getView
|
||||
toastView.measure(MeasureSpec.makeMeasureSpec(metrics.widthPixels, MeasureSpec.UNSPECIFIED),
|
||||
MeasureSpec.makeMeasureSpec(metrics.heightPixels, MeasureSpec.UNSPECIFIED))
|
||||
toast.setGravity(Gravity.LEFT | Gravity.TOP,
|
||||
viewLocation(0) - rect.left + (view.getWidth - toast.getView.getMeasuredWidth) / 2 + offsetX,
|
||||
viewLocation(1) - rect.top + view.getHeight + offsetY)
|
||||
toast
|
||||
}
|
||||
|
||||
// Blocked > 3 seconds
|
||||
def toggleAirplaneMode(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= 17) {
|
||||
|
||||