From 11dd7112c3b467bfa1a69c9888f458380b63158b Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 16 Dec 2017 16:07:01 -0800 Subject: [PATCH] Implement plugin entirely in Kotlin --- .../com/github/shadowsocks/MainActivity.kt | 3 +- .../shadowsocks/ProfileConfigFragment.kt | 2 +- .../com/github/shadowsocks/bg/BaseService.kt | 3 +- .../shadowsocks/plugin/PluginConfiguration.kt | 2 +- ...{PluginContract.java => PluginContract.kt} | 42 +++--- .../shadowsocks/plugin/PluginOptions.java | 127 ------------------ .../shadowsocks/plugin/PluginOptions.kt | 104 ++++++++++++++ 7 files changed, 128 insertions(+), 155 deletions(-) rename plugin/src/main/java/com/github/shadowsocks/plugin/{PluginContract.java => PluginContract.kt} (71%) delete mode 100644 plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java create mode 100644 plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.kt diff --git a/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt b/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt index 873dd598..1d1c946e 100644 --- a/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt +++ b/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt @@ -39,7 +39,6 @@ import android.support.v7.app.AppCompatActivity import android.support.v7.content.res.AppCompatResources import android.support.v7.preference.PreferenceDataStore import android.support.v7.widget.TooltipCompat -import android.text.TextUtils import android.util.Log import android.view.View import android.widget.TextView @@ -355,7 +354,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe } else -> null } - if (TextUtils.isEmpty(sharedStr)) return + if (sharedStr.isNullOrEmpty()) return val profiles = Profile.findAll(sharedStr).toList() if (profiles.isEmpty()) { Snackbar.make(findViewById(R.id.snackbar), R.string.profile_invalid_input, Snackbar.LENGTH_LONG).show() diff --git a/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt b/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt index bac205b8..fe6fe695 100644 --- a/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt @@ -143,7 +143,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean = try { val selected = pluginConfiguration.selected 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.dirty = true true diff --git a/mobile/src/main/java/com/github/shadowsocks/bg/BaseService.kt b/mobile/src/main/java/com/github/shadowsocks/bg/BaseService.kt index 11c6bec1..2ed24b2e 100644 --- a/mobile/src/main/java/com/github/shadowsocks/bg/BaseService.kt +++ b/mobile/src/main/java/com/github/shadowsocks/bg/BaseService.kt @@ -29,7 +29,6 @@ import android.os.Build import android.os.IBinder import android.os.RemoteCallbackList import android.support.v4.os.UserManagerCompat -import android.text.TextUtils import android.util.Base64 import android.util.Log import android.widget.Toast @@ -218,7 +217,7 @@ object BaseService { fun onBind(intent: Intent): IBinder? = if (intent.action == Action.SERVICE) data.binder else null 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)) false } else true diff --git a/mobile/src/main/java/com/github/shadowsocks/plugin/PluginConfiguration.kt b/mobile/src/main/java/com/github/shadowsocks/plugin/PluginConfiguration.kt index cfc41865..0f02b5bd 100644 --- a/mobile/src/main/java/com/github/shadowsocks/plugin/PluginConfiguration.kt +++ b/mobile/src/main/java/com/github/shadowsocks/plugin/PluginConfiguration.kt @@ -50,7 +50,7 @@ class PluginConfiguration(val pluginsOptions: Map, val se }) 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) override fun toString(): String { diff --git a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.kt similarity index 71% rename from plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java rename to plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.kt index 34ba1e6d..ecc8bc04 100644 --- a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.java +++ b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginContract.kt @@ -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. * * This class is written in Java to keep Java interoperability. */ -public final class PluginContract { - private PluginContract() { } - +object PluginContract { /** * ContentProvider Action: Used for NativePluginProvider. * * 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. * * 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. * * 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. @@ -55,7 +53,7 @@ public final class PluginContract { * * 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. * @@ -63,34 +61,34 @@ public final class PluginContract { * * 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. * - * Example: "obfs= Enable obfuscating: HTTP or TLS (Experimental). - * obfs-host= Hostname for obfuscating (Experimental)." + * Example: "obfs=|tls> Enable obfuscating: HTTP or TLS (Experimental). + * obfs-host= Hostname for obfuscating (Experimental)." * * Constant Value: "com.github.shadowsocks.plugin.EXTRA_HELP_MESSAGE" - */ - 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. * * 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. * * 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. */ - public static final int RESULT_FALLBACK = 1; + /** ConfigurationActivity result: fallback to manual edit mode. */ + const val RESULT_FALLBACK = 1 /** * Relative to the file to be copied. This column is required. @@ -99,7 +97,7 @@ public final class PluginContract { * * Type: String */ - public static final String COLUMN_PATH = "path"; + const val COLUMN_PATH = "path" /** * File mode bits. Default value is "644". * @@ -107,14 +105,14 @@ public final class PluginContract { * * Type: String */ - public static final String COLUMN_MODE = "mode"; + const val COLUMN_MODE = "mode" /** * The scheme for general plugin actions. */ - public static final String SCHEME = "plugin"; + const val SCHEME = "plugin" /** * The authority for general plugin actions. */ - public static final String AUTHORITY = "com.github.shadowsocks"; + const val AUTHORITY = "com.github.shadowsocks" } diff --git a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java deleted file mode 100644 index c5f4cc24..00000000 --- a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java +++ /dev/null @@ -1,127 +0,0 @@ -/******************************************************************************* - * * - * Copyright (C) 2017 by Max Lv * - * Copyright (C) 2017 by Mygod Studio * - * * - * 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 . * - * * - *******************************************************************************/ - -package com.github.shadowsocks.plugin; - -import android.text.TextUtils; - -import java.util.HashMap; -import java.util.Objects; -import java.util.StringTokenizer; - -/** - * Helper class for processing plugin options. - * - * Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java - */ -public final class PluginOptions extends HashMap { - public PluginOptions() { - 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 - public String getOrDefault(Object key, String defaultValue) { - String v; - return (((v = get(key)) != null) || containsKey(key)) - ? v - : defaultValue; - } - - private PluginOptions(String options, boolean parseId) { - this(); - if (TextUtils.isEmpty(options)) return; - final StringTokenizer tokenizer = new StringTokenizer(options + ';', "\\=;", true); - final StringBuilder current = new StringBuilder(); - String key = null; - while (tokenizer.hasMoreTokens()) { - String nextToken = tokenizer.nextToken(); - if ("\\".equals(nextToken)) current.append(tokenizer.nextToken()); - else if ("=".equals(nextToken) && key == null) { - key = current.toString(); - current.setLength(0); - } else if (";".equals(nextToken)) { - if (key != null) { - put(key, current.toString()); - key = null; - } else if (current.length() > 0) - if (parseId) id = current.toString(); else put(current.toString(), null); - current.setLength(0); - parseId = false; - } 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 = ""; - - private static void append(StringBuilder result, String str) { - for (int i = 0; i < str.length(); ++i) { - char ch = str.charAt(i); - switch (ch) { - case '\\': case '=': case ';': result.append('\\'); // intentionally no break - default: result.append(ch); - } - } - } - public String toString(boolean trimId) { - final StringBuilder result = new StringBuilder(); - if (!trimId) if (TextUtils.isEmpty(id)) return ""; else append(result, id); - for (Entry entry : entrySet()) { - if (result.length() > 0) result.append(';'); - append(result, entry.getKey()); - String value = entry.getValue(); - if (value != null) { - result.append('='); - append(result, value); - } - } - return result.toString(); - } - @Override - public String toString() { - return toString(true); - } - - @Override - 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 - public int hashCode() { - return Objects.hash(super.hashCode(), id); - } -} diff --git a/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.kt b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.kt new file mode 100644 index 00000000..e387df8a --- /dev/null +++ b/plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.kt @@ -0,0 +1,104 @@ +/******************************************************************************* + * * + * Copyright (C) 2017 by Max Lv * + * Copyright (C) 2017 by Mygod Studio * + * * + * 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 . * + * * + *******************************************************************************/ + +package com.github.shadowsocks.plugin + +import java.util.* + +/** + * Helper class for processing plugin options. + * + * Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java + */ +class PluginOptions : HashMap { + var id = "" + + constructor() : super() + constructor(initialCapacity: Int) : super(initialCapacity) + constructor(initialCapacity: Int, loadFactor: Float) : super(initialCapacity, loadFactor) + + private constructor(options: String?, parseId: Boolean) : this() { + @Suppress("NAME_SHADOWING") + var parseId = parseId + if (options.isNullOrEmpty()) return + val tokenizer = StringTokenizer(options + ';', "\\=;", true) + val current = StringBuilder() + var key: String? = null + while (tokenizer.hasMoreTokens()) { + val nextToken = tokenizer.nextToken() + when (nextToken) { + "\\" -> current.append(tokenizer.nextToken()) + "=" -> if (key == null) { + key = current.toString() + current.setLength(0) + } else current.append(nextToken) + ";" -> { + if (key != null) { + put(key, current.toString()) + key = null + } else if (current.isNotEmpty()) + if (parseId) id = current.toString() else put(current.toString(), null) + current.setLength(0) + parseId = false + } + else -> current.append(nextToken) + } + } + } + + constructor(options: String?) : this(options, true) + constructor(id: String, options: String?) : this(options, false) { + this.id = id + } + + private fun append(result: StringBuilder, str: String) = (0 until str.length) + .map { str[it] } + .forEach { + when (it) { + '\\', '=', ';' -> { + result.append('\\') // intentionally no break + result.append(it) + } + else -> result.append(it) + } + } + + fun toString(trimId: Boolean): String { + val result = StringBuilder() + if (!trimId) if (id.isEmpty()) return "" else append(result, id) + for ((key, value) in entries) { + if (result.isNotEmpty()) result.append(';') + append(result, key) + if (value != null) { + result.append('=') + append(result, value) + } + } + return result.toString() + } + + override fun toString(): String = toString(true) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + return javaClass == other?.javaClass && super.equals(other) && id == (other as PluginOptions).id + } + override fun hashCode(): Int = Objects.hash(super.hashCode(), id) +}