diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml
index b79e5088..2a2b4c89 100755
--- a/src/main/res/values-zh/strings.xml
+++ b/src/main/res/values-zh/strings.xml
@@ -78,6 +78,11 @@
- 字节
+ 点击检查是否接入互联网。
+ 测试中……
+ 已接入互联网。(%d 毫秒)
+ 失败:%s
+ 状态码无效。(#%d)
为影梭添加此配置文件?
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index d3d7696f..177faac6 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -19,6 +19,11 @@
- Byte
- Bytes
+ Tap to check Internet connection availability.
+ Testing…
+ Internet connection available. (time=%dms)
+ Fail: %s
+ Invalid status code. (#%d)
Server Settings
diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
index 3e3d0a60..d1de1e87 100644
--- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
+++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
@@ -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) {
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala
index 464bb597..bfbc3d68 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala
@@ -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) => {
diff --git a/src/main/scala/com/github/shadowsocks/utils/CloseUtils.scala b/src/main/scala/com/github/shadowsocks/utils/CloseUtils.scala
new file mode 100644
index 00000000..edd85aeb
--- /dev/null
+++ b/src/main/scala/com/github/shadowsocks/utils/CloseUtils.scala
@@ -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 =>
+ }
+ }
+}