Commit 11dd7112 authored by Mygod's avatar Mygod

Implement plugin entirely in Kotlin

parent 612a34a6
...@@ -39,7 +39,6 @@ import android.support.v7.app.AppCompatActivity ...@@ -39,7 +39,6 @@ import android.support.v7.app.AppCompatActivity
import android.support.v7.content.res.AppCompatResources import android.support.v7.content.res.AppCompatResources
import android.support.v7.preference.PreferenceDataStore import android.support.v7.preference.PreferenceDataStore
import android.support.v7.widget.TooltipCompat import android.support.v7.widget.TooltipCompat
import android.text.TextUtils
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
...@@ -355,7 +354,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe ...@@ -355,7 +354,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
} }
else -> null else -> null
} }
if (TextUtils.isEmpty(sharedStr)) return if (sharedStr.isNullOrEmpty()) return
val profiles = Profile.findAll(sharedStr).toList() val profiles = Profile.findAll(sharedStr).toList()
if (profiles.isEmpty()) { if (profiles.isEmpty()) {
Snackbar.make(findViewById(R.id.snackbar), R.string.profile_invalid_input, Snackbar.LENGTH_LONG).show() Snackbar.make(findViewById(R.id.snackbar), R.string.profile_invalid_input, Snackbar.LENGTH_LONG).show()
......
...@@ -143,7 +143,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu ...@@ -143,7 +143,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean = try { override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean = try {
val selected = pluginConfiguration.selected val selected = pluginConfiguration.selected
pluginConfiguration = PluginConfiguration(pluginConfiguration.pluginsOptions + pluginConfiguration = PluginConfiguration(pluginConfiguration.pluginsOptions +
(pluginConfiguration.selected to PluginOptions(selected, newValue as String?)), selected) (pluginConfiguration.selected to PluginOptions(selected, newValue as? String?)), selected)
DataStore.plugin = pluginConfiguration.toString() DataStore.plugin = pluginConfiguration.toString()
DataStore.dirty = true DataStore.dirty = true
true true
......
...@@ -29,7 +29,6 @@ import android.os.Build ...@@ -29,7 +29,6 @@ import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.RemoteCallbackList import android.os.RemoteCallbackList
import android.support.v4.os.UserManagerCompat import android.support.v4.os.UserManagerCompat
import android.text.TextUtils
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
...@@ -218,7 +217,7 @@ object BaseService { ...@@ -218,7 +217,7 @@ object BaseService {
fun onBind(intent: Intent): IBinder? = if (intent.action == Action.SERVICE) data.binder else null fun onBind(intent: Intent): IBinder? = if (intent.action == Action.SERVICE) data.binder else null
fun checkProfile(profile: Profile): Boolean = fun checkProfile(profile: Profile): Boolean =
if (TextUtils.isEmpty(profile.host) || TextUtils.isEmpty(profile.password)) { if (profile.host.isEmpty() || profile.password.isEmpty()) {
stopRunner(true, (this as Context).getString(R.string.proxy_empty)) stopRunner(true, (this as Context).getString(R.string.proxy_empty))
false false
} else true } else true
......
...@@ -50,7 +50,7 @@ class PluginConfiguration(val pluginsOptions: Map<String, PluginOptions>, val se ...@@ -50,7 +50,7 @@ class PluginConfiguration(val pluginsOptions: Map<String, PluginOptions>, val se
}) })
fun getOptions(id: String): PluginOptions = if (id.isEmpty()) PluginOptions() else fun getOptions(id: String): PluginOptions = if (id.isEmpty()) PluginOptions() else
pluginsOptions.get(id) ?: PluginOptions(id, PluginManager.fetchPlugins()[id]?.defaultConfig) pluginsOptions[id] ?: PluginOptions(id, PluginManager.fetchPlugins()[id]?.defaultConfig)
val selectedOptions: PluginOptions get() = getOptions(selected) val selectedOptions: PluginOptions get() = getOptions(selected)
override fun toString(): String { override fun toString(): String {
......
...@@ -18,35 +18,33 @@ ...@@ -18,35 +18,33 @@
* * * *
*******************************************************************************/ *******************************************************************************/
package com.github.shadowsocks.plugin; package com.github.shadowsocks.plugin
/** /**
* The contract between the plugin provider and host. Contains definitions for the supported actions, extras, etc. * The contract between the plugin provider and host. Contains definitions for the supported actions, extras, etc.
* *
* This class is written in Java to keep Java interoperability. * This class is written in Java to keep Java interoperability.
*/ */
public final class PluginContract { object PluginContract {
private PluginContract() { }
/** /**
* ContentProvider Action: Used for NativePluginProvider. * ContentProvider Action: Used for NativePluginProvider.
* *
* Constant Value: "com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" * Constant Value: "com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"
*/ */
public static final String ACTION_NATIVE_PLUGIN = "com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"; const val ACTION_NATIVE_PLUGIN = "com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"
/** /**
* Activity Action: Used for ConfigurationActivity. * Activity Action: Used for ConfigurationActivity.
* *
* Constant Value: "com.github.shadowsocks.plugin.ACTION_CONFIGURE" * Constant Value: "com.github.shadowsocks.plugin.ACTION_CONFIGURE"
*/ */
public static final String ACTION_CONFIGURE = "com.github.shadowsocks.plugin.ACTION_CONFIGURE"; const val ACTION_CONFIGURE = "com.github.shadowsocks.plugin.ACTION_CONFIGURE"
/** /**
* Activity Action: Used for HelpActivity or HelpCallback. * Activity Action: Used for HelpActivity or HelpCallback.
* *
* Constant Value: "com.github.shadowsocks.plugin.ACTION_HELP" * Constant Value: "com.github.shadowsocks.plugin.ACTION_HELP"
*/ */
public static final String ACTION_HELP = "com.github.shadowsocks.plugin.ACTION_HELP"; const val ACTION_HELP = "com.github.shadowsocks.plugin.ACTION_HELP"
/** /**
* The lookup key for a string that provides the plugin entry binary. * The lookup key for a string that provides the plugin entry binary.
...@@ -55,7 +53,7 @@ public final class PluginContract { ...@@ -55,7 +53,7 @@ public final class PluginContract {
* *
* Constant Value: "com.github.shadowsocks.plugin.EXTRA_ENTRY" * Constant Value: "com.github.shadowsocks.plugin.EXTRA_ENTRY"
*/ */
public static final String EXTRA_ENTRY = "com.github.shadowsocks.plugin.EXTRA_ENTRY"; const val EXTRA_ENTRY = "com.github.shadowsocks.plugin.EXTRA_ENTRY"
/** /**
* The lookup key for a string that provides the options as a string. * The lookup key for a string that provides the options as a string.
* *
...@@ -63,34 +61,34 @@ public final class PluginContract { ...@@ -63,34 +61,34 @@ public final class PluginContract {
* *
* Constant Value: "com.github.shadowsocks.plugin.EXTRA_OPTIONS" * Constant Value: "com.github.shadowsocks.plugin.EXTRA_OPTIONS"
*/ */
public static final String EXTRA_OPTIONS = "com.github.shadowsocks.plugin.EXTRA_OPTIONS"; const val EXTRA_OPTIONS = "com.github.shadowsocks.plugin.EXTRA_OPTIONS"
/** /**
* The lookup key for a CharSequence that provides user relevant help message. * The lookup key for a CharSequence that provides user relevant help message.
* *
* Example: "obfs=<http|tls> Enable obfuscating: HTTP or TLS (Experimental). * Example: "obfs=<http></http>|tls> Enable obfuscating: HTTP or TLS (Experimental).
* obfs-host=<host_name> Hostname for obfuscating (Experimental)." * obfs-host=<host_name> Hostname for obfuscating (Experimental)."
* *
* Constant Value: "com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE" * Constant Value: "com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE"
*/ </host_name> */
public static final String EXTRA_HELP_MESSAGE = "com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE"; const val EXTRA_HELP_MESSAGE = "com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE"
/** /**
* The metadata key to retrieve plugin id. Required for plugins. * The metadata key to retrieve plugin id. Required for plugins.
* *
* Constant Value: "com.github.shadowsocks.plugin.id" * Constant Value: "com.github.shadowsocks.plugin.id"
*/ */
public static final String METADATA_KEY_ID = "com.github.shadowsocks.plugin.id"; const val METADATA_KEY_ID = "com.github.shadowsocks.plugin.id"
/** /**
* The metadata key to retrieve default configuration. Default value is empty. * The metadata key to retrieve default configuration. Default value is empty.
* *
* Constant Value: "com.github.shadowsocks.plugin.default_config" * Constant Value: "com.github.shadowsocks.plugin.default_config"
*/ */
public static final String METADATA_KEY_DEFAULT_CONFIG = "com.github.shadowsocks.plugin.default_config"; const val METADATA_KEY_DEFAULT_CONFIG = "com.github.shadowsocks.plugin.default_config"
public static final String METHOD_GET_EXECUTABLE = "shadowsocks:getExecutable"; const val METHOD_GET_EXECUTABLE = "shadowsocks:getExecutable"
/** ConfigurationActivity result: fallback to manual edit mode. */ /** ConfigurationActivity result: fallback to manual edit mode. */
public static final int RESULT_FALLBACK = 1; const val RESULT_FALLBACK = 1
/** /**
* Relative to the file to be copied. This column is required. * Relative to the file to be copied. This column is required.
...@@ -99,7 +97,7 @@ public final class PluginContract { ...@@ -99,7 +97,7 @@ public final class PluginContract {
* *
* Type: String * Type: String
*/ */
public static final String COLUMN_PATH = "path"; const val COLUMN_PATH = "path"
/** /**
* File mode bits. Default value is "644". * File mode bits. Default value is "644".
* *
...@@ -107,14 +105,14 @@ public final class PluginContract { ...@@ -107,14 +105,14 @@ public final class PluginContract {
* *
* Type: String * Type: String
*/ */
public static final String COLUMN_MODE = "mode"; const val COLUMN_MODE = "mode"
/** /**
* The scheme for general plugin actions. * The scheme for general plugin actions.
*/ */
public static final String SCHEME = "plugin"; const val SCHEME = "plugin"
/** /**
* The authority for general plugin actions. * The authority for general plugin actions.
*/ */
public static final String AUTHORITY = "com.github.shadowsocks"; const val AUTHORITY = "com.github.shadowsocks"
} }
...@@ -18,110 +18,87 @@ ...@@ -18,110 +18,87 @@
* * * *
*******************************************************************************/ *******************************************************************************/
package com.github.shadowsocks.plugin; package com.github.shadowsocks.plugin
import android.text.TextUtils; import java.util.*
import java.util.HashMap;
import java.util.Objects;
import java.util.StringTokenizer;
/** /**
* Helper class for processing plugin options. * Helper class for processing plugin options.
* *
* Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java * Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java
*/ */
public final class PluginOptions extends HashMap<String, String> { class PluginOptions : HashMap<String, String?> {
public PluginOptions() { var id = ""
super();
}
public PluginOptions(int initialCapacity) {
super(initialCapacity);
}
public PluginOptions(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
}
// TODO: this method is not needed since API 24 constructor() : super()
public String getOrDefault(Object key, String defaultValue) { constructor(initialCapacity: Int) : super(initialCapacity)
String v; constructor(initialCapacity: Int, loadFactor: Float) : super(initialCapacity, loadFactor)
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
private PluginOptions(String options, boolean parseId) { private constructor(options: String?, parseId: Boolean) : this() {
this(); @Suppress("NAME_SHADOWING")
if (TextUtils.isEmpty(options)) return; var parseId = parseId
final StringTokenizer tokenizer = new StringTokenizer(options + ';', "\\=;", true); if (options.isNullOrEmpty()) return
final StringBuilder current = new StringBuilder(); val tokenizer = StringTokenizer(options + ';', "\\=;", true)
String key = null; val current = StringBuilder()
var key: String? = null
while (tokenizer.hasMoreTokens()) { while (tokenizer.hasMoreTokens()) {
String nextToken = tokenizer.nextToken(); val nextToken = tokenizer.nextToken()
if ("\\".equals(nextToken)) current.append(tokenizer.nextToken()); when (nextToken) {
else if ("=".equals(nextToken) && key == null) { "\\" -> current.append(tokenizer.nextToken())
key = current.toString(); "=" -> if (key == null) {
current.setLength(0); key = current.toString()
} else if (";".equals(nextToken)) { current.setLength(0)
} else current.append(nextToken)
";" -> {
if (key != null) { if (key != null) {
put(key, current.toString()); put(key, current.toString())
key = null; key = null
} else if (current.length() > 0) } else if (current.isNotEmpty())
if (parseId) id = current.toString(); else put(current.toString(), null); if (parseId) id = current.toString() else put(current.toString(), null)
current.setLength(0); current.setLength(0)
parseId = false; parseId = false
} else current.append(nextToken);
} }
else -> current.append(nextToken)
} }
public PluginOptions(String options) {
this(options, true);
} }
public PluginOptions(String id, String options) {
this(options, false);
this.id = id;
} }
public String id = ""; constructor(options: String?) : this(options, true)
constructor(id: String, options: String?) : this(options, false) {
this.id = id
}
private static void append(StringBuilder result, String str) { private fun append(result: StringBuilder, str: String) = (0 until str.length)
for (int i = 0; i < str.length(); ++i) { .map { str[it] }
char ch = str.charAt(i); .forEach {
switch (ch) { when (it) {
case '\\': case '=': case ';': result.append('\\'); // intentionally no break '\\', '=', ';' -> {
default: result.append(ch); result.append('\\') // intentionally no break
result.append(it)
} }
else -> result.append(it)
} }
} }
public String toString(boolean trimId) {
final StringBuilder result = new StringBuilder(); fun toString(trimId: Boolean): String {
if (!trimId) if (TextUtils.isEmpty(id)) return ""; else append(result, id); val result = StringBuilder()
for (Entry<String, String> entry : entrySet()) { if (!trimId) if (id.isEmpty()) return "" else append(result, id)
if (result.length() > 0) result.append(';'); for ((key, value) in entries) {
append(result, entry.getKey()); if (result.isNotEmpty()) result.append(';')
String value = entry.getValue(); append(result, key)
if (value != null) { if (value != null) {
result.append('='); result.append('=')
append(result, value); append(result, value)
} }
} }
return result.toString(); return result.toString()
}
@Override
public String toString() {
return toString(true);
} }
@Override override fun toString(): String = toString(true)
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
PluginOptions that = (PluginOptions) o;
return Objects.equals(id, that.id) && super.equals(that);
}
@Override override fun equals(other: Any?): Boolean {
public int hashCode() { if (this === other) return true
return Objects.hash(super.hashCode(), id); return javaClass == other?.javaClass && super.equals(other) && id == (other as PluginOptions).id
} }
override fun hashCode(): Int = Objects.hash(super.hashCode(), id)
} }
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