Commit 62a572ee authored by Mygod's avatar Mygod

Implement HTTP client test

Fix #479
parent 3108306b
...@@ -78,6 +78,11 @@ ...@@ -78,6 +78,11 @@
<plurals name="bytes"> <plurals name="bytes">
<item quantity="other">字节</item> <item quantity="other">字节</item>
</plurals> </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 --> <!-- profile -->
<string name="add_profile_dialog">为影梭添加此配置文件?</string> <string name="add_profile_dialog">为影梭添加此配置文件?</string>
......
...@@ -19,6 +19,11 @@ ...@@ -19,6 +19,11 @@
<item quantity="one">Byte</item> <item quantity="one">Byte</item>
<item quantity="other">Bytes</item> <item quantity="other">Bytes</item>
</plurals> </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 --> <!-- proxy category -->
<string name="proxy_cat">Server Settings</string> <string name="proxy_cat">Server Settings</string>
......
...@@ -152,6 +152,8 @@ class Shadowsocks ...@@ -152,6 +152,8 @@ class Shadowsocks
var prepared = false var prepared = false
var currentProfile = new Profile var currentProfile = new Profile
var vpnEnabled = -1 var vpnEnabled = -1
var trafficCache: String = _
var connectionTestResult: String = _
// Services // Services
var currentServiceName = classOf[ShadowsocksNatService].getName var currentServiceName = classOf[ShadowsocksNatService].getName
...@@ -199,14 +201,17 @@ class Shadowsocks ...@@ -199,14 +201,17 @@ class Shadowsocks
}) })
} }
def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) { def trafficUpdated(txRate: String, rxRate: String, txTotal: String, rxTotal: String) {
val trafficStat = getString(R.string.stat_summary) trafficCache = getString(R.string.stat_summary).formatLocal(Locale.ENGLISH, txRate, rxRate, txTotal, rxTotal)
.formatLocal(Locale.ENGLISH, txRate, rxRate, txTotal, rxTotal) handler.post(updateTraffic)
handler.post(() => {
preferences.findPreference(Key.stat).setSummary(trafficStat)
})
} }
} }
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) def attachService: Unit = attachService(callback)
override def onServiceConnected() { override def onServiceConnected() {
...@@ -485,8 +490,7 @@ class Shadowsocks ...@@ -485,8 +490,7 @@ class Shadowsocks
// Check if current profile changed // Check if current profile changed
if (ShadowsocksApplication.profileId != currentProfile.id) reloadProfile() if (ShadowsocksApplication.profileId != currentProfile.id) reloadProfile()
callback.trafficUpdated(TrafficMonitor.getTxRate, TrafficMonitor.getRxRate, updateTraffic()
TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal)
} }
private def setPreferenceEnabled(enabled: Boolean) { private def setPreferenceEnabled(enabled: Boolean) {
......
package com.github.shadowsocks package com.github.shadowsocks
import java.lang.System.currentTimeMillis
import java.lang.{Long => jLong}
import java.net.{HttpURLConnection, URL}
import java.util.Locale import java.util.Locale
import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.SharedPreferences.OnSharedPreferenceChangeListener
...@@ -9,20 +12,60 @@ import android.os.{Build, Bundle} ...@@ -9,20 +12,60 @@ import android.os.{Build, Bundle}
import android.preference.{Preference, PreferenceFragment, SwitchPreference} import android.preference.{Preference, PreferenceFragment, SwitchPreference}
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.webkit.{WebView, WebViewClient} import android.webkit.{WebView, WebViewClient}
import com.github.shadowsocks.utils.CloseUtils._
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
// TODO: Move related logic here // TODO: Move related logic here
class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChangeListener { class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChangeListener {
private def activity = getActivity.asInstanceOf[Shadowsocks] private def activity = getActivity.asInstanceOf[Shadowsocks]
lazy val natSwitch = findPreference(Key.isNAT).asInstanceOf[SwitchPreference] lazy val natSwitch = findPreference(Key.isNAT).asInstanceOf[SwitchPreference]
var stat: Preference = _
private var isProxyApps: SwitchPreference = _ private var isProxyApps: SwitchPreference = _
private var testCount: Int = _
override def onCreate(savedInstanceState: Bundle) { override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
addPreferencesFromResource(R.xml.pref_all) addPreferencesFromResource(R.xml.pref_all)
getPreferenceManager.getSharedPreferences.registerOnSharedPreferenceChangeListener(this) 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] isProxyApps = findPreference(Key.isProxyApps).asInstanceOf[SwitchPreference]
findPreference("recovery").setOnPreferenceClickListener((preference: Preference) => { findPreference("recovery").setOnPreferenceClickListener((preference: Preference) => {
......
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 =>
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment