@@ -78,6 +78,11 @@
|
||||
<plurals name="bytes">
|
||||
<item quantity="other">字节</item>
|
||||
</plurals>
|
||||
<string name="connection_test_pending">点击检查是否接入互联网。</string>
|
||||
<string name="connection_test_testing">测试中……</string>
|
||||
<string name="connection_test_available">已接入互联网。(%d 毫秒)</string>
|
||||
<string name="connection_test_error">失败:%s</string>
|
||||
<string name="connection_test_error_status_code">状态码无效。(#%d)</string>
|
||||
|
||||
<!-- profile -->
|
||||
<string name="add_profile_dialog">为影梭添加此配置文件?</string>
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
<item quantity="one">Byte</item>
|
||||
<item quantity="other">Bytes</item>
|
||||
</plurals>
|
||||
<string name="connection_test_pending">Tap to check Internet connection availability.</string>
|
||||
<string name="connection_test_testing">Testing…</string>
|
||||
<string name="connection_test_available">Internet connection available. (time=%dms)</string>
|
||||
<string name="connection_test_error">Fail: %s</string>
|
||||
<string name="connection_test_error_status_code">Invalid status code. (#%d)</string>
|
||||
|
||||
<!-- proxy category -->
|
||||
<string name="proxy_cat">Server Settings</string>
|
||||
|
||||
@@ -152,6 +152,8 @@ class Shadowsocks
|
||||
var prepared = false
|
||||
var currentProfile = new Profile
|
||||
var vpnEnabled = -1
|
||||
var trafficCache: String = _
|
||||
var connectionTestResult: String = _
|
||||
|
||||
// Services
|
||||
var currentServiceName = classOf[ShadowsocksNatService].getName
|
||||
@@ -199,14 +201,17 @@ class Shadowsocks
|
||||
})
|
||||
}
|
||||
def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) {
|
||||
val trafficStat = getString(R.string.stat_summary)
|
||||
.formatLocal(Locale.ENGLISH, txRate, rxRate, txTotal, rxTotal)
|
||||
handler.post(() => {
|
||||
preferences.findPreference(Key.stat).setSummary(trafficStat)
|
||||
})
|
||||
trafficCache = getString(R.string.stat_summary).formatLocal(Locale.ENGLISH, txRate, rxRate, txTotal, rxTotal)
|
||||
handler.post(updateTraffic)
|
||||
}
|
||||
}
|
||||
|
||||
def updateTraffic(): Unit = if (trafficCache == null) callback.trafficUpdated(TrafficMonitor.getTxRate,
|
||||
TrafficMonitor.getRxRate, TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal) else {
|
||||
if (connectionTestResult == null) connectionTestResult = getString(R.string.connection_test_pending)
|
||||
preferences.stat.setSummary(trafficCache + '\n' + connectionTestResult)
|
||||
}
|
||||
|
||||
def attachService: Unit = attachService(callback)
|
||||
|
||||
override def onServiceConnected() {
|
||||
@@ -485,8 +490,7 @@ class Shadowsocks
|
||||
// Check if current profile changed
|
||||
if (ShadowsocksApplication.profileId != currentProfile.id) reloadProfile()
|
||||
|
||||
callback.trafficUpdated(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate,
|
||||
TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal)
|
||||
updateTraffic()
|
||||
}
|
||||
|
||||
private def setPreferenceEnabled(enabled: Boolean) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.github.shadowsocks
|
||||
|
||||
import java.lang.System.currentTimeMillis
|
||||
import java.lang.{Long => jLong}
|
||||
import java.net.{HttpURLConnection, URL}
|
||||
import java.util.Locale
|
||||
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
@@ -9,20 +12,60 @@ import android.os.{Build, Bundle}
|
||||
import android.preference.{Preference, PreferenceFragment, SwitchPreference}
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.webkit.{WebView, WebViewClient}
|
||||
import com.github.shadowsocks.utils.CloseUtils._
|
||||
import com.github.shadowsocks.utils.Key
|
||||
|
||||
// TODO: Move related logic here
|
||||
class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChangeListener {
|
||||
private def activity = getActivity.asInstanceOf[Shadowsocks]
|
||||
lazy val natSwitch = findPreference(Key.isNAT).asInstanceOf[SwitchPreference]
|
||||
var stat: Preference = _
|
||||
|
||||
private var isProxyApps: SwitchPreference = _
|
||||
private var testCount: Int = _
|
||||
|
||||
override def onCreate(savedInstanceState: Bundle) {
|
||||
super.onCreate(savedInstanceState)
|
||||
addPreferencesFromResource(R.xml.pref_all)
|
||||
getPreferenceManager.getSharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
|
||||
stat = findPreference(Key.stat)
|
||||
stat.setOnPreferenceClickListener(_ => {
|
||||
val id = synchronized {
|
||||
testCount += 1
|
||||
activity.connectionTestResult = getString(R.string.connection_test_testing)
|
||||
activity.updateTraffic()
|
||||
testCount
|
||||
}
|
||||
ThrowableFuture {
|
||||
// Based on: https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/connectivity/NetworkMonitor.java#640
|
||||
autoDisconnect(new URL("https", "www.google.com", "/generate_204").openConnection
|
||||
.asInstanceOf[HttpURLConnection]) { conn =>
|
||||
conn.setInstanceFollowRedirects(false)
|
||||
conn.setUseCaches(false)
|
||||
if (testCount == id) {
|
||||
var result: String = null
|
||||
try {
|
||||
val start = currentTimeMillis
|
||||
conn.getInputStream
|
||||
val elapsed = currentTimeMillis - start
|
||||
val code = conn.getResponseCode
|
||||
if (code == 204 || code == 200 && conn.getContentLength == 0)
|
||||
result = getString(R.string.connection_test_available, elapsed: java.lang.Long)
|
||||
else throw new Exception(getString(R.string.connection_test_error_status_code, code: Integer))
|
||||
} catch {
|
||||
case e: Exception => result = getString(R.string.connection_test_error, e.getMessage)
|
||||
}
|
||||
synchronized(if (testCount == id) {
|
||||
activity.connectionTestResult = result
|
||||
activity.handler.post(activity.updateTraffic)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
})
|
||||
|
||||
isProxyApps = findPreference(Key.isProxyApps).asInstanceOf[SwitchPreference]
|
||||
|
||||
findPreference("recovery").setOnPreferenceClickListener((preference: Preference) => {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.github.shadowsocks.utils
|
||||
|
||||
/**
|
||||
* @author Mygod
|
||||
*/
|
||||
object CloseUtils {
|
||||
type Disconnectable = {
|
||||
def disconnect()
|
||||
}
|
||||
|
||||
def autoClose[A <: AutoCloseable, B](x: => A)(block: A => B): B = {
|
||||
var a: Option[A] = None
|
||||
try {
|
||||
a = Some(x)
|
||||
block(a.get)
|
||||
} finally if (a.nonEmpty) try {
|
||||
val v = a.get
|
||||
if (v ne null) v.close
|
||||
} catch {
|
||||
case ignore: Exception =>
|
||||
}
|
||||
}
|
||||
def autoDisconnect[A <: Disconnectable, B](x: => A)(block: A => B): B = {
|
||||
var a: Option[A] = None
|
||||
try {
|
||||
a = Some(x)
|
||||
block(a.get)
|
||||
} finally if (a.nonEmpty) try {
|
||||
val v = a.get
|
||||
if (v ne null) v.disconnect
|
||||
} catch {
|
||||
case ignore: Exception =>
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user