Compare commits

...
18 Commits
Author SHA1 Message Date
Max Lv 8c28fffbdb Bump version 2017-12-15 01:41:49 -08:00
Max Lv 96c86b2441 Make QR code dialog public #1500 2017-12-14 20:04:37 -08:00
Mygod 15373f8d44 Add missing lint ignores 2017-12-14 15:34:45 -08:00
Mygod 30b56c97ea Add more tests, fix lint errors and add check to travis 2017-12-14 14:55:00 -08:00
Mygod d081d06903 Replace png with lossless webp 2017-12-14 14:53:11 -08:00
Mygod 090851ed3b Refine Travis cache 2017-12-14 13:07:01 -08:00
Mygod 9352f2dfe7 Fix SubnetTest 2017-12-14 12:41:09 -08:00
Mygod 9a2643ec2c Add missing error message 2017-12-14 12:00:13 -08:00
Mygod 5c2d548dee Reduce the number of default methods 2017-12-14 12:00:13 -08:00
Mygod 2fa8bc7f41 Refine #1500 2017-12-14 12:00:13 -08:00
Mygod 94527ea460 Refine #1497 2017-12-14 12:00:13 -08:00
Mygod 9259789fb9 Refine #1481 2017-12-14 12:00:13 -08:00
Mygod 8db642e721 Refine build.gradle format 2017-12-14 12:00:13 -08:00
Max Lv fca0e87b6c Update GFW List 2017-12-14 00:48:54 -08:00
Max Lv b88f836616 Refine #1489 2017-12-13 23:35:10 +08:00
Max Lv c3bbd09ef3 Fix #1500 2017-12-13 22:32:37 +08:00
Max Lv ea7ede033d Fix #1497 2017-12-13 02:06:20 -08:00
Max Lv 956443ef28 Fix the shortcut #1481 2017-12-12 23:45:22 -08:00
48 changed files with 254 additions and 142 deletions
+7 -2
View File
@@ -7,9 +7,14 @@ env:
global:
- GOROOT_BOOTSTRAP=$GOROOT
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
install:
- pushd $ANDROID_HOME
@@ -25,4 +30,4 @@ install:
>/dev/null
script:
- ./gradlew assembleDebug
- ./gradlew assembleDebug check
+2 -2
View File
@@ -1,3 +1,5 @@
import org.apache.tools.ant.taskdefs.condition.Os
apply plugin: 'com.android.library'
android {
@@ -43,8 +45,6 @@ task goClean(type: Exec) {
args "-c", "src/overture/clean.bash"
}
import org.apache.tools.ant.taskdefs.condition.Os
tasks.whenTaskAdded { task ->
if ((task.name == 'generateJsonModelDebug' ||
task.name == 'generateJsonModelRelease') &&
+3 -6
View File
@@ -9,7 +9,7 @@ def getCurrentFlavor() {
.matcher(getGradle().getStartParameter().getTaskRequests().toString())
if (matcher.find()) return matcher.group(1).toLowerCase() else {
println "NO MATCH FOUND"
return ""
return "debug"
}
}
@@ -20,8 +20,8 @@ android {
applicationId "com.github.shadowsocks"
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.sdkVersion
versionCode 200
versionName "4.4.0"
versionCode 201
versionName "4.4.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary true
resConfigs "fa", "ja", "ko", "ru", "zh-rCN", "zh-rTW"
@@ -36,9 +36,6 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
sourceSets.main.jniLibs.srcDirs +=
new File(project(':core').buildDir, "intermediates/bundles/${getCurrentFlavor()}/jni")
sourceSets.main.jniLibs.srcDirs += new File(project(':core').projectDir, "src/overture/bin")
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="InvalidPackage">
<ignore regexp="ormlite-core" />
</issue>
<issue id="ImpliedQuantity" severity="warning" />
<issue id="ExtraTranslation" severity="warning" />
<issue id="MissingTranslation" severity="informational" />
</lint>
@@ -0,0 +1,41 @@
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* 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/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.acl
import org.junit.Assert
import org.junit.Test
import java.io.StringReader
class AclTest {
companion object {
private const val INPUT1 = """[proxy_all]
[bypass_list]
1.0.1.0/24
[proxy_list]
(^|\.)4tern\.com${'$'}
"""
}
@Test
fun parse() {
Assert.assertEquals(INPUT1, Acl().fromReader(StringReader(INPUT1)).toString());
}
}
@@ -0,0 +1,36 @@
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* 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/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.database
import android.net.Uri
import org.junit.Assert
import org.junit.Test
class ProfileTest {
@Test
fun parsing() {
val results = Profile.findAll("garble ss://YmYtY2ZiOnRlc3RAMTkyLjE2OC4xMDAuMTo4ODg4#example-server garble")
.toList()
Assert.assertEquals(1, results.size)
Assert.assertEquals(Uri.parse("ss://YmYtY2ZiOnRlc3Q@192.168.100.1:8888#example-server"),
results.single().toUri())
}
}
+3 -3
View File
@@ -4742,7 +4742,7 @@ mangaapi.manhuaren.com
^(.*\.)?android\.com$
^(.*\.)?androidify\.com$
^(.*\.)?appspot\.com$
^(.*\.)?blogspot(\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,2}$
^(.*\.)?blogspot\.[a-zA-Z0-9-]{1,63}[a-zA-Z0-9-]{1,2}$
^(.*\.)?certificate-transparency\.org$
^(.*\.)?chrome\.com$
^(.*\.)?chromecast\.com$
@@ -4765,8 +4765,8 @@ mangaapi.manhuaren.com
^(.*\.)?gmail\.com$
^(.*\.)?gmodules\.com$
^(.*\.)?goo\.gl$
^(.*\.)?google(\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,2}$
^(.*\.)?googleapis(\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,2}$
^(.*\.)?google\.[a-zA-Z0-9-]{1,63}[a-zA-Z0-9-]{1,2}$
^(.*\.)?googleapis\.[a-zA-Z0-9-]{1,63}[a-zA-Z0-9-]{1,2}$
^(.*\.)?googleapps\.com$
^(.*\.)?googleartproject\.com$
^(.*\.)?googleblog\.com$
@@ -4756,7 +4756,7 @@ mangaapi.manhuaren.com
^(.*\.)?android\.com$
^(.*\.)?androidify\.com$
^(.*\.)?appspot\.com$
^(.*\.)?blogspot(\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,2}$
^(.*\.)?blogspot\.[a-zA-Z0-9-]{1,63}[a-zA-Z0-9-]{1,2}$
^(.*\.)?certificate-transparency\.org$
^(.*\.)?chrome\.com$
^(.*\.)?chromecast\.com$
@@ -4779,8 +4779,8 @@ mangaapi.manhuaren.com
^(.*\.)?gmail\.com$
^(.*\.)?gmodules\.com$
^(.*\.)?goo\.gl$
^(.*\.)?google(\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,2}$
^(.*\.)?googleapis(\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,2}$
^(.*\.)?google\.[a-zA-Z0-9-]{1,63}[a-zA-Z0-9-]{1,2}$
^(.*\.)?googleapis\.[a-zA-Z0-9-]{1,63}[a-zA-Z0-9-]{1,2}$
^(.*\.)?googleapps\.com$
^(.*\.)?googleartproject\.com$
^(.*\.)?googleblog\.com$
+3 -3
View File
@@ -320,7 +320,7 @@ mangaapi.manhuaren.com
^(.*\.)?android\.com$
^(.*\.)?androidify\.com$
^(.*\.)?appspot\.com$
^(.*\.)?blogspot(\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,2}$
^(.*\.)?blogspot\.[a-zA-Z0-9-]{1,63}[a-zA-Z0-9-]{1,2}$
^(.*\.)?certificate-transparency\.org$
^(.*\.)?chrome\.com$
^(.*\.)?chromecast\.com$
@@ -343,8 +343,8 @@ mangaapi.manhuaren.com
^(.*\.)?gmail\.com$
^(.*\.)?gmodules\.com$
^(.*\.)?goo\.gl$
^(.*\.)?google(\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,2}$
^(.*\.)?googleapis(\.(?!-)[a-zA-Z0-9-]{1,63}(?<!-)){1,2}$
^(.*\.)?google\.[a-zA-Z0-9-]{1,63}[a-zA-Z0-9-]{1,2}$
^(.*\.)?googleapis\.[a-zA-Z0-9-]{1,63}[a-zA-Z0-9-]{1,2}$
^(.*\.)?googleapps\.com$
^(.*\.)?googleartproject\.com$
^(.*\.)?googleblog\.com$
@@ -180,7 +180,9 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
setContentView(R.layout.layout_apps)
toolbar = findViewById(R.id.toolbar)
toolbar.setTitle(R.string.proxied_apps)
toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_material)
val arr = obtainStyledAttributes(intArrayOf(R.attr.homeAsUpIndicator))
toolbar.navigationIcon = arr.getDrawable(0)
arr.recycle()
toolbar.setNavigationOnClickListener {
val intent = parentActivityIntent
if (shouldUpRecreateTask(intent) || isTaskRoot)
@@ -76,7 +76,8 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
}
@SuppressLint("ValidFragment")
private class QRCodeDialog() : DialogFragment() {
class QRCodeDialog() : DialogFragment() {
constructor(url: String) : this() {
val bundle = Bundle()
bundle.putString(KEY_URL, url)
@@ -104,9 +105,11 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
}
override fun onDetach() {
adapter?.setNdefPushMessage(null, activity)
adapter = null
super.onDetach()
val activity = activity
if (activity != null && !activity.isFinishing && !activity.isDestroyed)
adapter?.setNdefPushMessage(null, activity)
adapter = null
}
}
@@ -25,7 +25,6 @@ import android.content.Intent
import android.content.pm.ShortcutManager
import android.os.Build
import android.os.Bundle
import android.os.PersistableBundle
import android.support.v4.content.pm.ShortcutInfoCompat
import android.support.v4.content.pm.ShortcutManagerCompat
import android.support.v4.graphics.drawable.IconCompat
@@ -34,8 +33,8 @@ import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.bg.BaseService
class QuickToggleShortcut : Activity(), ShadowsocksConnection.Interface {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (intent.action == Intent.ACTION_CREATE_SHORTCUT) {
setResult(Activity.RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this,
ShortcutInfoCompat.Builder(this, "toggle")
@@ -54,7 +54,9 @@ class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
setContentView(R.layout.layout_scanner)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.title = title
toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_material)
val arr = obtainStyledAttributes(intArrayOf(R.attr.homeAsUpIndicator))
toolbar.navigationIcon = arr.getDrawable(0)
arr.recycle()
toolbar.setNavigationOnClickListener { navigateUp() }
scannerView = findViewById(R.id.scanner)
if (Build.VERSION.SDK_INT >= 25) getSystemService(ShortcutManager::class.java).reportShortcutUsed("scan")
@@ -20,6 +20,7 @@
package com.github.shadowsocks
import android.app.Activity
import android.app.KeyguardManager
import android.content.BroadcastReceiver
import android.content.Context
@@ -27,15 +28,13 @@ import android.content.Intent
import android.content.IntentFilter
import android.net.VpnService
import android.os.Bundle
import android.os.PersistableBundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.utils.broadcastReceiver
class VpnRequestActivity : AppCompatActivity(), ShadowsocksConnection.Interface {
class VpnRequestActivity : Activity(), ShadowsocksConnection.Interface {
companion object {
private const val TAG = "VpnRequestActivity"
private const val REQUEST_CONNECT = 1
@@ -43,8 +42,8 @@ class VpnRequestActivity : AppCompatActivity(), ShadowsocksConnection.Interface
private var receiver: BroadcastReceiver? = null
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!BaseService.usingVpnMode) {
finish()
return
@@ -91,10 +91,8 @@ object BaseService {
}
var closeReceiverRegistered = false
private fun state() = state
val binder = object : IShadowsocksService.Stub() {
override fun getState(): Int = state()
override fun getState(): Int = this@Data.state
override fun getProfileName(): String = profile?.formattedName ?: "Idle"
override fun registerCallback(cb: IShadowsocksServiceCallback) {
@@ -182,6 +180,24 @@ object BaseService {
}
File(app.filesDir, "shadowsocks.json").bufferedWriter().use { it.write(config.toString()) }
}
val aclFile: File? get() {
val route = profile!!.route
return if (route == Acl.ALL) null else
Acl.getFile(if (route == Acl.CUSTOM_RULES) Acl.CUSTOM_RULES_FLATTENED else route)
}
fun changeState(s: Int, msg: String? = null) {
if (state == s && msg == null) return
if (callbacks.registeredCallbackCount > 0) app.handler.post {
val n = callbacks.beginBroadcast()
for (i in 0 until n) try {
callbacks.getBroadcastItem(i).stateChanged(s, binder.profileName, msg)
} catch (_: Exception) { } // ignore
callbacks.finishBroadcast()
}
state = s
}
}
interface Interface {
val tag: String
@@ -221,7 +237,7 @@ object BaseService {
"-t", "600",
"-c", "shadowsocks.json"))
val acl = getAclFile()
val acl = data.aclFile
if (acl != null) {
cmd += "--acl"
cmd += acl.absolutePath
@@ -232,28 +248,6 @@ object BaseService {
data.sslocalProcess = GuardedProcess(cmd).start()
}
fun getAclFile(): File? {
val route = data.profile!!.route
return if (route == Acl.ALL) null else
Acl.getFile(if (route == Acl.CUSTOM_RULES) Acl.CUSTOM_RULES_FLATTENED else route)
}
fun getListFile(): File? {
val aclFile = getAclFile() ?: return null
val listFile = File(aclFile.absolutePath + ".lst")
val list = StringBuilder()
aclFile.bufferedReader().useLines {
for (line in it) {
if (line.startsWith("^(.*\\.)?")) {
list.append(line.substring(8).replace("\\","").replace("$",""))
list.append("\n")
}
}
}
listFile.bufferedWriter().use { it.write(list.toString()) }
return listFile
}
fun createNotification(): ServiceNotification
fun startRunner() {
@@ -270,14 +264,14 @@ object BaseService {
fun stopRunner(stopService: Boolean, msg: String? = null) {
// channge the state
changeState(STOPPING)
val data = data
data.changeState(STOPPING)
app.track(tag, "stop")
killProcesses()
// clean up recevier
val data = data
this as Service
if (data.closeReceiverRegistered) {
unregisterReceiver(data.closeReceiver)
@@ -295,7 +289,7 @@ object BaseService {
data.trafficMonitorThread = null
// change the state
changeState(STOPPED, msg)
data.changeState(STOPPED, msg)
// stop the service if nothing has bound to it
if (stopService) stopSelf()
@@ -309,8 +303,9 @@ object BaseService {
val data = data
if (data.state != STOPPED) return Service.START_NOT_STICKY
val profile = app.currentProfile
this as Context
if (profile == null) {
stopRunner(true)
stopRunner(true, getString(R.string.profile_empty))
return Service.START_NOT_STICKY
}
data.profile = profile
@@ -327,17 +322,16 @@ object BaseService {
filter.addAction(Action.RELOAD)
filter.addAction(Intent.ACTION_SHUTDOWN)
filter.addAction(Action.CLOSE)
(this as Context).registerReceiver(data.closeReceiver, filter)
registerReceiver(data.closeReceiver, filter)
data.closeReceiverRegistered = true
}
data.notification = createNotification()
app.track(tag, "start")
changeState(CONNECTING)
data.changeState(CONNECTING)
thread {
this as Context
try {
if (profile.host == "198.199.101.152") {
val client = OkHttpClient.Builder()
@@ -385,7 +379,7 @@ object BaseService {
if (profile.route !in arrayOf(Acl.ALL, Acl.CUSTOM_RULES)) AclSyncJob.schedule(profile.route)
changeState(CONNECTED)
data.changeState(CONNECTED)
} catch (_: UnknownHostException) {
stopRunner(true, getString(R.string.invalid_server))
} catch (_: VpnService.NullConnectionException) {
@@ -397,19 +391,6 @@ object BaseService {
}
return Service.START_NOT_STICKY
}
fun changeState(s: Int, msg: String? = null) {
val data = instances[this]!!
if (data.state == s && msg == null) return
if (data.callbacks.registeredCallbackCount > 0) app.handler.post {
val n = data.callbacks.beginBroadcast()
for (i in 0 until n) try {
data.callbacks.getBroadcastItem(i).stateChanged(s, data.binder.profileName, msg)
} catch (_: Exception) { } // ignore
data.callbacks.finishBroadcast()
}
data.state = s
}
}
private val instances = WeakHashMap<Interface, Data>()
@@ -44,6 +44,7 @@ object LocalDnsService {
override fun startNativeProcesses() {
super.startNativeProcesses()
val data = data
val profile = data.profile!!
fun makeDns(name: String, address: String, edns: Boolean = true): JSONObject {
@@ -89,7 +90,7 @@ object LocalDnsService {
.put("PrimaryDNS", localDns)
.put("AlternativeDNS", remoteDns)
.put("IPNetworkFile", "china_ip_list.txt")
.put("DomainFile", getListFile()!!.absolutePath)
.put("DomainFile", data.aclFile!!.absolutePath)
Acl.CHINALIST -> config
.put("PrimaryDNS", localDns)
.put("AlternativeDNS", remoteDns)
@@ -53,7 +53,7 @@ class KeyValuePair() {
get() = if (valueType == TYPE_LONG) ByteBuffer.wrap(value).long else null
val string: String?
get() = if (valueType == TYPE_STRING) String(value) else null
val stringSet: MutableSet<String>?
val stringSet: Set<String>?
get() = if (valueType == TYPE_STRING_SET) {
val buffer = ByteBuffer.wrap(value)
val result = HashSet<String>()
@@ -95,7 +95,7 @@ class KeyValuePair() {
this.value = value.toByteArray()
return this
}
fun put(value: MutableSet<String>): KeyValuePair {
fun put(value: Set<String>): KeyValuePair {
valueType = TYPE_STRING_SET
val stream = ByteArrayOutputStream()
for (v in value) {
@@ -27,7 +27,7 @@ import com.github.shadowsocks.R
import com.github.shadowsocks.database.ProfileManager
import com.twofortyfouram.locale.api.Intent as ApiIntent
class Settings(bundle: Bundle) {
class Settings(bundle: Bundle?) {
companion object {
private val KEY_SWITCH_ON = "switch_on"
private val KEY_PROFILE_ID = "profile_id"
@@ -35,8 +35,8 @@ class Settings(bundle: Bundle) {
fun fromIntent(intent: Intent) = Settings(intent.getBundleExtra(ApiIntent.EXTRA_BUNDLE))
}
var switchOn: Boolean = bundle.getBoolean(KEY_SWITCH_ON, true)
var profileId: Int = bundle.getInt(KEY_PROFILE_ID, -1)
var switchOn: Boolean = bundle?.getBoolean(KEY_SWITCH_ON, true) ?: true
var profileId: Int = bundle?.getInt(KEY_PROFILE_ID, -1) ?: -1
fun toIntent(context: Context): Intent {
val bundle = Bundle()
@@ -7,7 +7,6 @@ import android.os.Build
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v7.util.SortedList
import android.util.TypedValue
import com.github.shadowsocks.App.Companion.app
import java.lang.reflect.InvocationTargetException
import java.net.InetAddress
@@ -15,7 +14,11 @@ import java.net.URLConnection
private val isNumericMethod by lazy { InetAddress::class.java.getMethod("isNumeric", String::class.java) }
private val parseNumericAddressMethod by lazy {
InetAddress::class.java.getMethod("parseNumericAddress", String::class.java)
try {
InetAddress::class.java.getMethod("parseNumericAddress", String::class.java)
} catch (_: NoSuchMethodException) {
null
}
}
private val fieldChildFragmentManager by lazy {
val field = Fragment::class.java.getDeclaredField("mChildFragmentManager")
@@ -24,11 +27,12 @@ private val fieldChildFragmentManager by lazy {
}
fun String.isNumericAddress(): Boolean = isNumericMethod.invoke(null, this) as Boolean
fun String.parseNumericAddress(): InetAddress = try {
parseNumericAddressMethod.invoke(null, this) as InetAddress
} catch (exc: InvocationTargetException) {
throw exc.cause ?: exc
}
fun String.parseNumericAddress(): InetAddress =
if (parseNumericAddressMethod == null) InetAddress.getByName(this) else try {
parseNumericAddressMethod!!.invoke(null, this) as InetAddress
} catch (exc: InvocationTargetException) {
throw exc.cause ?: exc
}
fun parsePort(str: String?, default: Int, min: Int = 1025): Int {
val x = str?.toIntOrNull() ?: default
Binary file not shown.

Before

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

@@ -6,6 +6,7 @@
<Spinner android:id="@+id/template_selector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:labelFor="@+id/content"
android:entries="@array/acl_rule_templates"/>
<EditText android:id="@+id/content"
android:layout_width="match_parent"
+1 -2
View File
@@ -10,8 +10,7 @@
android:scaleType="centerCrop"
android:src="@drawable/background_header"/>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawer_title"
<TextView android:id="@+id/drawer_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
+2 -1
View File
@@ -26,7 +26,8 @@
android:ellipsize="marquee"
android:paddingEnd="72dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
android:layout_height="wrap_content"
tools:ignore="RtlSymmetry"/>
<com.github.shadowsocks.widget.BoundedGridLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
app:bounded_width="288dp"
+32 -27
View File
@@ -1,34 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/selection"
android:title="@string/action_selection"
android:icon="@drawable/abc_ic_menu_selectall_mtrl_alpha"
android:alphabeticShortcut="s"
android:numericShortcut="0"
android:visible="false"
app:showAsAction="always">
<item
android:id="@+id/selection"
android:alphabeticShortcut="s"
android:icon="?attr/actionModeSelectAllDrawable"
android:numericShortcut="0"
android:title="@string/action_selection"
android:visible="false"
app:showAsAction="always">
<menu>
<item android:id="@+id/action_select_all"
android:title="@android:string/selectAll"
android:alphabeticShortcut="a"
android:numericShortcut="1"
app:showAsAction="always"/>
<item android:id="@+id/action_cut"
android:title="@android:string/cut"
android:alphabeticShortcut="t"
android:numericShortcut="2"
app:showAsAction="always"/>
<item android:id="@+id/action_copy"
android:title="@android:string/copy"
android:alphabeticShortcut="c"
android:numericShortcut="3"
app:showAsAction="always"/>
<item android:id="@+id/action_delete"
android:title="@string/delete"
android:alphabeticShortcut="d"
android:numericShortcut="4"
app:showAsAction="always"/>
<item
android:id="@+id/action_select_all"
android:alphabeticShortcut="a"
android:numericShortcut="1"
android:title="@android:string/selectAll"
app:showAsAction="always"/>
<item
android:id="@+id/action_cut"
android:alphabeticShortcut="t"
android:numericShortcut="2"
android:title="@android:string/cut"
app:showAsAction="always"/>
<item
android:id="@+id/action_copy"
android:alphabeticShortcut="c"
android:numericShortcut="3"
android:title="@android:string/copy"
app:showAsAction="always"/>
<item
android:id="@+id/action_delete"
android:alphabeticShortcut="d"
android:numericShortcut="4"
android:title="@string/delete"
app:showAsAction="always"/>
</menu>
</item>
<item android:title="@string/action_add_rule"
@@ -1,17 +0,0 @@
package com.github.shadowsocks
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see [Testing documentation](http://d.android.com/tools/testing)
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, (2 + 2).toLong())
}
}
@@ -0,0 +1,39 @@
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* 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/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.database
import org.junit.Assert
import org.junit.Test
class KeyValuePairTest {
@Test
fun putAndGet() {
val kvp = KeyValuePair()
Assert.assertEquals(true, kvp.put(true).boolean)
Assert.assertEquals(3f, kvp.put(3f).float)
Assert.assertEquals(3, kvp.put(3).int)
Assert.assertEquals(3L, kvp.put(3L).long)
Assert.assertEquals("3", kvp.put("3").string)
val set = (0 until 3).map(Int::toString).toSet()
Assert.assertEquals(set, kvp.put(set).stringSet)
Assert.assertEquals(null, kvp.boolean)
}
}
@@ -1,11 +1,10 @@
package com.github.shadowsocks.utils
import com.github.shadowsocks.utils.Subnet
import org.junit.Assert
import org.junit.Test
import java.net.InetAddress
object SubnetTest {
class SubnetTest {
@Test
fun parsingAndEquals() {
Assert.assertEquals(Subnet(InetAddress.getByName("1.10.11.12"), 25), Subnet.fromString("1.10.11.12/25"))
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="ImpliedQuantity" severity="warning" />
<issue id="ExtraTranslation" severity="warning" />
<issue id="MissingTranslation" severity="informational" />
</lint>