Initial draft for porting this project to Gradle + Kotlin

This is one of my stupidest idea ever.
This commit is contained in:
Mygod
2017-12-03 10:20:57 -08:00
parent 2f682ef3ec
commit 3e8b254118
206 changed files with 7885 additions and 7475 deletions
@@ -1,155 +0,0 @@
package com.github.shadowsocks.utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringTokenizer;
/**
* Commandline objects help handling command lines specifying processes to
* execute.
*
* The class can be used to define a command line as nested elements or as a
* helper to define a command line by an application.
* <p>
* <code>
* &lt;someelement&gt;<br>
* &nbsp;&nbsp;&lt;acommandline executable="/executable/to/run"&gt;<br>
* &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 1" /&gt;<br>
* &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument line="argument_1 argument_2 argument_3" /&gt;<br>
* &nbsp;&nbsp;&nbsp;&nbsp;&lt;argument value="argument 4" /&gt;<br>
* &nbsp;&nbsp;&lt;/acommandline&gt;<br>
* &lt;/someelement&gt;<br>
* </code>
*
* Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java
*
* Adds support for escape character '\'.
*
* @author Mygod
*/
public final class Commandline {
private Commandline() { }
/**
* Quote the parts of the given array in way that makes them
* usable as command line arguments.
* @param args the list of arguments to quote.
* @return empty string for null or no command, else every argument split
* by spaces and quoted by quoting rules.
*/
public static String toString(Iterable<String> args) {
// empty path return empty string
if (args == null) {
return "";
}
// path containing one or more elements
final StringBuilder result = new StringBuilder();
for (String arg : args) {
if (result.length() > 0) result.append(' ');
for (int j = 0; j < arg.length(); ++j) {
char ch = arg.charAt(j);
switch (ch) {
case ' ': case '\\': case '"': case '\'': result.append('\\'); // intentionally no break
default: result.append(ch);
}
}
}
return result.toString();
}
/**
* Quote the parts of the given array in way that makes them
* usable as command line arguments.
* @param args the list of arguments to quote.
* @return empty string for null or no command, else every argument split
* by spaces and quoted by quoting rules.
*/
public static String toString(String[] args) {
return toString(Arrays.asList(args)); // thanks to Java, arrays aren't iterable
}
/**
* Crack a command line.
* @param toProcess the command line to process.
* @return the command line broken into strings.
* An empty or null toProcess parameter results in a zero sized array.
*/
public static String[] translateCommandline(String toProcess) {
if (toProcess == null || toProcess.length() == 0) {
//no command? no string
return new String[0];
}
// parse with a simple finite state machine
final int normal = 0;
final int inQuote = 1;
final int inDoubleQuote = 2;
int state = normal;
final StringTokenizer tok = new StringTokenizer(toProcess, "\\\"\' ", true);
final ArrayList<String> result = new ArrayList<>();
final StringBuilder current = new StringBuilder();
boolean lastTokenHasBeenQuoted = false;
boolean lastTokenIsSlash = false;
while (tok.hasMoreTokens()) {
String nextTok = tok.nextToken();
switch (state) {
case inQuote:
if ("\'".equals(nextTok)) {
lastTokenHasBeenQuoted = true;
state = normal;
} else {
current.append(nextTok);
}
break;
case inDoubleQuote:
if ("\"".equals(nextTok)) {
if (lastTokenIsSlash) {
current.append(nextTok);
lastTokenIsSlash = false;
} else {
lastTokenHasBeenQuoted = true;
state = normal;
}
} else if ("\\".equals(nextTok)) {
if (lastTokenIsSlash) {
current.append(nextTok);
lastTokenIsSlash = false;
} else lastTokenIsSlash = true;
} else {
if (lastTokenIsSlash) {
current.append("\\"); // unescaped
lastTokenIsSlash = false;
}
current.append(nextTok);
}
break;
default:
if (lastTokenIsSlash) {
current.append(nextTok);
lastTokenIsSlash = false;
} else if ("\\".equals(nextTok)) lastTokenIsSlash = true; else if ("\'".equals(nextTok)) {
state = inQuote;
} else if ("\"".equals(nextTok)) {
state = inDoubleQuote;
} else if (" ".equals(nextTok)) {
if (lastTokenHasBeenQuoted || current.length() != 0) {
result.add(current.toString());
current.setLength(0);
}
} else {
current.append(nextTok);
}
lastTokenHasBeenQuoted = false;
break;
}
}
if (lastTokenHasBeenQuoted || current.length() != 0) {
result.add(current.toString());
}
if (state == inQuote || state == inDoubleQuote) {
throw new IllegalArgumentException("unbalanced quotes in " + toProcess);
}
if (lastTokenIsSlash) throw new IllegalArgumentException("escape character following nothing in " + toProcess);
return result.toArray(new String[result.size()]);
}
}
@@ -0,0 +1,177 @@
/*******************************************************************************
* *
* 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.utils
import java.util.ArrayList
import java.util.Arrays
import java.util.StringTokenizer
/**
* Commandline objects help handling command lines specifying processes to
* execute.
*
* The class can be used to define a command line as nested elements or as a
* helper to define a command line by an application.
*
*
* `
* <someelement><br></br>
* &nbsp;&nbsp;<acommandline executable="/executable/to/run"><br></br>
* &nbsp;&nbsp;&nbsp;&nbsp;<argument value="argument 1" /><br></br>
* &nbsp;&nbsp;&nbsp;&nbsp;<argument line="argument_1 argument_2 argument_3" /><br></br>
* &nbsp;&nbsp;&nbsp;&nbsp;<argument value="argument 4" /><br></br>
* &nbsp;&nbsp;</acommandline><br></br>
* </someelement><br></br>
` *
*
* Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java
*
* Adds support for escape character '\'.
*/
object Commandline {
/**
* Quote the parts of the given array in way that makes them
* usable as command line arguments.
* @param args the list of arguments to quote.
* @return empty string for null or no command, else every argument split
* by spaces and quoted by quoting rules.
*/
fun toString(args: Iterable<String>?): String {
// empty path return empty string
if (args == null) {
return ""
}
// path containing one or more elements
val result = StringBuilder()
for (arg in args) {
if (result.isNotEmpty()) result.append(' ')
(0 until arg.length)
.map { arg[it] }
.forEach {
when (it) {
' ', '\\', '"', '\'' -> {
result.append('\\') // intentionally no break
result.append(it)
}
else -> result.append(it)
}
}
}
return result.toString()
}
/**
* Quote the parts of the given array in way that makes them
* usable as command line arguments.
* @param args the list of arguments to quote.
* @return empty string for null or no command, else every argument split
* by spaces and quoted by quoting rules.
*/
fun toString(args: Array<String>): String {
return toString(Arrays.asList(*args)) // thanks to Java, arrays aren't iterable
}
/**
* Crack a command line.
* @param toProcess the command line to process.
* @return the command line broken into strings.
* An empty or null toProcess parameter results in a zero sized array.
*/
fun translateCommandline(toProcess: String?): Array<String> {
if (toProcess == null || toProcess.isEmpty()) {
//no command? no string
return arrayOf()
}
// parse with a simple finite state machine
val normal = 0
val inQuote = 1
val inDoubleQuote = 2
var state = normal
val tok = StringTokenizer(toProcess, "\\\"\' ", true)
val result = ArrayList<String>()
val current = StringBuilder()
var lastTokenHasBeenQuoted = false
var lastTokenIsSlash = false
while (tok.hasMoreTokens()) {
val nextTok = tok.nextToken()
when (state) {
inQuote -> if ("\'" == nextTok) {
lastTokenHasBeenQuoted = true
state = normal
} else {
current.append(nextTok)
}
inDoubleQuote -> if ("\"" == nextTok) {
if (lastTokenIsSlash) {
current.append(nextTok)
lastTokenIsSlash = false
} else {
lastTokenHasBeenQuoted = true
state = normal
}
} else if ("\\" == nextTok) {
lastTokenIsSlash = if (lastTokenIsSlash) {
current.append(nextTok)
false
} else
true
} else {
if (lastTokenIsSlash) {
current.append("\\") // unescaped
lastTokenIsSlash = false
}
current.append(nextTok)
}
else -> {
if (lastTokenIsSlash) {
current.append(nextTok)
lastTokenIsSlash = false
} else if ("\\" == nextTok)
lastTokenIsSlash = true
else if ("\'" == nextTok) {
state = inQuote
} else if ("\"" == nextTok) {
state = inDoubleQuote
} else if (" " == nextTok) {
if (lastTokenHasBeenQuoted || current.isNotEmpty()) {
result.add(current.toString())
current.setLength(0)
}
} else {
current.append(nextTok)
}
lastTokenHasBeenQuoted = false
}
}
}
if (lastTokenHasBeenQuoted || current.isNotEmpty()) {
result.add(current.toString())
}
if (state == inQuote || state == inDoubleQuote) {
throw IllegalArgumentException("unbalanced quotes in " + toProcess)
}
if (lastTokenIsSlash) throw IllegalArgumentException("escape character following nothing in " + toProcess)
return result.toTypedArray()
}
}
@@ -0,0 +1,69 @@
/*******************************************************************************
* *
* 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.utils
object Key {
const val PROFILE = "profile.db"
const val id = "profileId"
const val name = "profileName"
const val individual = "Proxyed"
const val serviceMode = "serviceMode"
const val modeProxy = "proxy"
const val modeVpn = "vpn"
const val modeTransproxy = "transproxy"
const val portProxy = "portProxy"
const val portLocalDns = "portLocalDns"
const val portTransproxy = "portTransproxy"
const val route = "route"
const val isAutoConnect = "isAutoConnect"
const val proxyApps = "isProxyApps"
const val bypass = "isBypassApps"
const val udpdns = "isUdpDns"
const val ipv6 = "isIpv6"
const val host = "proxy"
const val password = "sitekey"
const val method = "encMethod"
const val remotePort = "remotePortNum"
const val remoteDns = "remoteDns"
const val plugin = "plugin"
const val pluginConfigure = "plugin.configure"
const val dirty = "profileDirty"
const val tfo = "tcp_fastopen"
const val assetUpdateTime = "assetUpdateTime"
}
object Action {
const val SERVICE = "com.github.shadowsocks.SERVICE"
const val CLOSE = "com.github.shadowsocks.CLOSE"
const val RELOAD = "com.github.shadowsocks.RELOAD"
const val EXTRA_PROFILE_ID = "com.github.shadowsocks.EXTRA_PROFILE_ID"
}
@@ -1,64 +0,0 @@
/*
* Copyright 2010 Brave New Software Project, Inc.
*
* 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.
*/
package com.github.shadowsocks.utils;
import android.annotation.TargetApi;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.os.Build;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collection;
public class Dns {
public static String getDnsResolver(Context context) throws Exception {
Collection<InetAddress> dnsResolvers = getDnsResolvers(context);
if (dnsResolvers.isEmpty()) {
throw new Exception("Couldn't find an active DNS resolver");
}
String dnsResolver = dnsResolvers.iterator().next().toString();
if (dnsResolver.startsWith("/")) {
dnsResolver = dnsResolver.substring(1);
}
return dnsResolver;
}
private static Collection<InetAddress> getDnsResolvers(Context context) throws Exception {
ArrayList<InetAddress> addresses = new ArrayList<InetAddress>();
ConnectivityManager connectivityManager =
(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
Class<?> LinkPropertiesClass = Class.forName("android.net.LinkProperties");
Method getActiveLinkPropertiesMethod = ConnectivityManager.class.getMethod("getActiveLinkProperties");
Object linkProperties = getActiveLinkPropertiesMethod.invoke(connectivityManager);
if (linkProperties != null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
Method getDnsesMethod = LinkPropertiesClass.getMethod("getDnses");
Collection<?> dnses = (Collection<?>)getDnsesMethod.invoke(linkProperties);
for (Object dns : dnses) {
addresses.add((InetAddress)dns);
}
} else {
addresses.addAll(((LinkProperties) linkProperties).getDnsServers());
}
}
return addresses;
}
}
@@ -0,0 +1,67 @@
/*******************************************************************************
* *
* 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.utils
import java.net.InetAddress
import java.util.*
class Subnet(val address: InetAddress, val prefixSize: Int) : Comparable<Subnet> {
companion object {
@Throws(IllegalArgumentException::class)
fun fromString(value: String): Subnet {
val parts = value.split('/')
val addr = parts[0].parseNumericAddress()
return when (parts.size) {
1 -> Subnet(addr, addr.address.size shl 3)
2 -> Subnet(addr, parts[1].toInt())
else -> throw IllegalArgumentException()
}
}
}
private val addressLength get() = address.address.size shl 3
init {
if (prefixSize < 0 || prefixSize > addressLength) throw IllegalArgumentException()
}
override fun toString(): String =
if (prefixSize == addressLength) address.hostAddress else address.hostAddress + '/' + prefixSize
private fun Byte.unsigned() = toInt() and 0xFF
override fun compareTo(other: Subnet): Int {
val addrThis = address.address
val addrThat = other.address.address
var result = addrThis.size.compareTo(addrThat.size) // IPv4 address goes first
if (result != 0) return result
for ((x, y) in addrThis zip addrThat) {
result = x.unsigned().compareTo(y.unsigned()) // undo sign extension of signed byte
if (result != 0) return result
}
return prefixSize.compareTo(other.prefixSize)
}
override fun equals(other: Any?): Boolean {
val that = other as? Subnet
return address == that?.address && prefixSize == that.prefixSize
}
override fun hashCode(): Int = Objects.hash(address, prefixSize)
}
@@ -0,0 +1,54 @@
/*******************************************************************************
* *
* 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.utils
import eu.chainfire.libsuperuser.Shell
import java.io.File
object TcpFastOpen {
/**
* Is kernel version >= 3.7.1.
*/
val supported by lazy {
val match = """^(\d+)\.(\d+)\.(\d+)""".toRegex().find(System.getProperty("os.version"))
if (match == null) false else when (match.groupValues[1].toInt()) {
in Int.MIN_VALUE .. 2 -> false
3 -> when (match.groupValues[2].toInt()) {
in Int.MIN_VALUE .. 6 -> false
7 -> match.groupValues[3].toInt() >= 1
else -> true
}
else -> true
}
}
val sendEnabled: Boolean get() {
val file = File("/proc/sys/net/ipv4/tcp_fastopen")
return file.canRead() && file.bufferedReader().use { it.readText() }.trim().toInt() and 1 > 0
}
fun enabled(value: Boolean): String? = if (sendEnabled == value) null else Shell.run("su", arrayOf(
"if echo " + (if (value) 3 else 0) + " > /proc/sys/net/ipv4/tcp_fastopen; then",
" echo Success.",
"else",
" echo Failed.",
"fi"), null, true)?.joinToString("\n")
}
@@ -0,0 +1,95 @@
package com.github.shadowsocks.utils
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.graphics.Rect
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.DisplayMetrics
import android.view.Gravity
import android.view.View
import android.view.Window
import android.widget.Toast
import com.github.shadowsocks.App.Companion.app
import java.lang.reflect.InvocationTargetException
import java.net.InetAddress
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)
}
private val fieldChildFragmentManager by lazy {
val field = Fragment::class.java.getDeclaredField("mChildFragmentManager")
field.isAccessible = true
field
}
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 parsePort(str: String?, default: Int, min: Int = 1025): Int {
val x = str?.toIntOrNull() ?: default
return if (x < min || x > 65535) default else x
}
fun broadcastReceiver(callback: (Context, Intent) -> Unit): BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) = callback(context, intent)
}
/**
* Wrapper for kotlin.concurrent.thread that tracks uncaught exceptions.
*/
fun thread(start: Boolean = true, isDaemon: Boolean = false, contextClassLoader: ClassLoader? = null,
name: String? = null, priority: Int = -1, block: () -> Unit): Thread {
val thread = kotlin.concurrent.thread(false, isDaemon, contextClassLoader, name, priority, block)
thread.setUncaughtExceptionHandler(app::track)
if (start) thread.start()
return thread
}
val URLConnection.responseLength: Long
get() = if (Build.VERSION.SDK_INT >= 24) contentLengthLong else contentLength.toLong()
/**
* Based on: http://stackoverflow.com/a/21026866/2245107
*/
fun Toast.position(view: View, window: Window, offsetX: Int = 0, offsetY: Int = 0): Toast {
val rect = Rect()
window.decorView.getWindowVisibleDisplayFrame(rect)
val viewLocation = IntArray(2)
view.getLocationInWindow(viewLocation)
val metrics = DisplayMetrics()
window.windowManager.defaultDisplay.getMetrics(metrics)
val toastView = this.view
toastView.measure(View.MeasureSpec.makeMeasureSpec(metrics.widthPixels, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(metrics.heightPixels, View.MeasureSpec.UNSPECIFIED))
setGravity(Gravity.START or Gravity.TOP,
viewLocation[0] - rect.left + (view.width - toastView.measuredWidth) / 2 + offsetX,
viewLocation[1] - rect.top + view.height + offsetY)
return this
}
/**
* Based on: https://stackoverflow.com/a/15656428/2245107
*/
var Fragment.childFragManager: FragmentManager?
get() = childFragmentManager
set(value) = fieldChildFragmentManager.set(this, value)
private class SortedListIterable<out T>(private val list: SortedList<T>) : Iterable<T> {
override fun iterator(): Iterator<T> = SortedListIterator(list)
}
private class SortedListIterator<out T>(private val list: SortedList<T>) : Iterator<T> {
private var count = 0
override fun hasNext() = count < list.size()
override fun next(): T = list[count++]
}
fun <T> SortedList<T>.asIterable(): Iterable<T> = SortedListIterable(this)