Commit a38c9920 authored by Mygod's avatar Mygod

Add initial support for configuring plugins

parent 1887c6ff
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<dimen name="profile_item_max_width">480dp</dimen> <dimen name="profile_item_max_width">480dp</dimen>
<dimen name="icon_list_item_padding">8dp</dimen>
<!-- TODO: Remove when this issue is fixed: https://github.com/JorgeCastilloPrz/FABProgressCircle/issues/19 --> <!-- TODO: Remove when this issue is fixed: https://github.com/JorgeCastilloPrz/FABProgressCircle/issues/19 -->
<dimen name="fab_margin_top">-44dp</dimen> <dimen name="fab_margin_top">-44dp</dimen>
</resources> </resources>
...@@ -142,4 +142,10 @@ ...@@ -142,4 +142,10 @@
<string name="acl_rule_templates_generic">Subnet/Hostname PCRE pattern</string> <string name="acl_rule_templates_generic">Subnet/Hostname PCRE pattern</string>
<string name="acl_rule_templates_domain">Domain name and all its subdomain names</string> <string name="acl_rule_templates_domain">Domain name and all its subdomain names</string>
<string name="edit_rule">Edit rule</string> <string name="edit_rule">Edit rule</string>
<!-- plugin -->
<string name="plugin">Plugin</string>
<string name="plugin_configure">Configure…</string>
<string name="plugin_disabled">Disabled</string>
<string name="plugin_unknown">Unknown plugin %s</string>
</resources> </resources>
...@@ -71,4 +71,20 @@ ...@@ -71,4 +71,20 @@
</be.mygod.preference.PreferenceCategory> </be.mygod.preference.PreferenceCategory>
<be.mygod.preference.PreferenceCategory
android:title="@string/plugin">
<com.github.shadowsocks.preference.IconListPreference
android:key="plugin"
android:persistent="false"
android:summary="%s"
android:title="@string/plugin"/>
<be.mygod.preference.EditTextPreference
android:key="plugin.configure"
android:persistent="false"
android:summary="%s"
android:title="@string/plugin_configure"/>
</be.mygod.preference.PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>
...@@ -48,21 +48,13 @@ object AppManager { ...@@ -48,21 +48,13 @@ object AppManager {
private var instance: AppManager = _ private var instance: AppManager = _
private var receiverRegistered: Boolean = _ private var receiver: BroadcastReceiver = _
private var cachedApps: Array[ProxiedApp] = _ private var cachedApps: Array[ProxiedApp] = _
private def getApps(pm: PackageManager) = { private def getApps(pm: PackageManager) = {
if (!receiverRegistered) { if (receiver == null) receiver = app.listenForPackageChanges {
val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED) synchronized(cachedApps = null)
filter.addAction(Intent.ACTION_PACKAGE_REMOVED) val instance = AppManager.instance
filter.addDataScheme("package") if (instance != null) instance.reloadApps()
app.registerReceiver((_: Context, intent: Intent) =>
if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED ||
!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
synchronized(cachedApps = null)
val instance = AppManager.instance
if (instance != null) instance.reloadApps()
}, filter)
receiverRegistered = true
} }
synchronized { synchronized {
if (cachedApps == null) cachedApps = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS) if (cachedApps == null) cachedApps = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS)
......
...@@ -23,19 +23,33 @@ package com.github.shadowsocks ...@@ -23,19 +23,33 @@ package com.github.shadowsocks
import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content.{DialogInterface, Intent, SharedPreferences} import android.content.{DialogInterface, Intent, SharedPreferences}
import android.os.{Build, Bundle, UserManager} import android.os.{Build, Bundle, UserManager}
import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference import android.support.v14.preference.SwitchPreference
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.support.v7.preference.Preference
import android.support.v7.widget.Toolbar.OnMenuItemClickListener import android.support.v7.widget.Toolbar.OnMenuItemClickListener
import android.text.TextUtils
import android.view.MenuItem import android.view.MenuItem
import be.mygod.preference.PreferenceFragment import be.mygod.preference.{EditTextPreference, PreferenceFragment}
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.plugin.{PluginConfiguration, PluginInterface, PluginManager, PluginOptions}
import com.github.shadowsocks.preference.{IconListPreference, PluginConfigurationDialogFragment}
import com.github.shadowsocks.utils.{Action, Key, Utils} import com.github.shadowsocks.utils.{Action, Key, Utils}
object ProfileConfigFragment {
private final val REQUEST_CODE_CONFIGURE = 1
}
class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListener class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListener
with OnSharedPreferenceChangeListener { with OnSharedPreferenceChangeListener {
import ProfileConfigFragment._
private var profile: Profile = _ private var profile: Profile = _
private var isProxyApps: SwitchPreference = _ private var isProxyApps: SwitchPreference = _
private var plugin: IconListPreference = _
private var pluginConfigure: EditTextPreference = _
private var pluginConfiguration: PluginConfiguration = _
override def onCreatePreferences(bundle: Bundle, key: String) { override def onCreatePreferences(bundle: Bundle, key: String) {
app.profileManager.getProfile(getActivity.getIntent.getIntExtra(Action.EXTRA_PROFILE_ID, -1)) match { app.profileManager.getProfile(getActivity.getIntent.getIntExtra(Action.EXTRA_PROFILE_ID, -1)) match {
...@@ -57,9 +71,43 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe ...@@ -57,9 +71,43 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
isProxyApps.setChecked(true) isProxyApps.setChecked(true)
false false
}) })
plugin = findPreference(Key.plugin).asInstanceOf[IconListPreference]
pluginConfigure = findPreference("plugin.configure").asInstanceOf[EditTextPreference]
plugin.unknownValueSummary = getString(R.string.plugin_unknown)
plugin.setOnPreferenceChangeListener((_, value) => {
val selected = value.asInstanceOf[String]
pluginConfiguration = new PluginConfiguration(pluginConfiguration.pluginsOptions, selected)
app.editor.putString(Key.plugin, pluginConfiguration.toString).putBoolean(Key.dirty, true).apply()
pluginConfigure.setEnabled(!TextUtils.isEmpty(selected))
true
})
pluginConfigure.setOnPreferenceChangeListener((_, value) => try {
val selected = pluginConfiguration.selected
pluginConfiguration = new PluginConfiguration(pluginConfiguration.pluginsOptions +
(pluginConfiguration.selected -> new PluginOptions(selected, value.asInstanceOf[String])), selected)
app.editor.putString(Key.plugin, pluginConfiguration.toString).putBoolean(Key.dirty, true).apply()
true
} catch {
case exc: IllegalArgumentException =>
Snackbar.make(getActivity.findViewById(R.id.snackbar), exc.getLocalizedMessage, Snackbar.LENGTH_LONG).show()
false
})
initPlugins()
app.listenForPackageChanges(getView.post(initPlugins))
app.settings.registerOnSharedPreferenceChangeListener(this) app.settings.registerOnSharedPreferenceChangeListener(this)
} }
def initPlugins() {
val plugins = PluginManager.fetchPlugins()
plugin.setEntries(plugins.map(_.label))
plugin.setEntryValues(plugins.map(_.id.asInstanceOf[CharSequence]))
plugin.setEntryIcons(plugins.map(_.icon))
pluginConfiguration = new PluginConfiguration(app.settings.getString(Key.plugin, null))
plugin.setValue(pluginConfiguration.selected)
plugin.checkSummary()
pluginConfigure.setText(pluginConfiguration.selectedOptions.toString)
}
override def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String): Unit = override def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String): Unit =
if (key != Key.proxyApps && findPreference(key) != null) app.editor.putBoolean(Key.dirty, true).apply() if (key != Key.proxyApps && findPreference(key) != null) app.editor.putBoolean(Key.dirty, true).apply()
...@@ -73,6 +121,18 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe ...@@ -73,6 +121,18 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
isProxyApps.setChecked(app.settings.getBoolean(Key.proxyApps, false)) // fetch proxyApps updated by AppManager isProxyApps.setChecked(app.settings.getBoolean(Key.proxyApps, false)) // fetch proxyApps updated by AppManager
} }
override def onDisplayPreferenceDialog(preference: Preference): Unit = if (preference eq pluginConfigure) {
val selected = pluginConfiguration.selected
val intent = new Intent(PluginInterface.ACTION_CONFIGURE(selected))
if (intent.resolveActivity(getActivity.getPackageManager) != null)
startActivityForResult(intent.putExtra(PluginInterface.EXTRA_OPTIONS,
pluginConfiguration.selectedOptions.toString), REQUEST_CODE_CONFIGURE) else {
val bundle = new Bundle()
bundle.putString(PluginConfigurationDialogFragment.PLUGIN_ID_FRAGMENT_TAG, selected)
displayPreferenceDialog(preference.getKey, new PluginConfigurationDialogFragment, bundle)
}
} else super.onDisplayPreferenceDialog(preference)
override def onMenuItemClick(item: MenuItem): Boolean = item.getItemId match { override def onMenuItemClick(item: MenuItem): Boolean = item.getItemId match {
case R.id.action_delete => case R.id.action_delete =>
new AlertDialog.Builder(getActivity) new AlertDialog.Builder(getActivity)
......
...@@ -27,7 +27,7 @@ import java.util.concurrent.TimeUnit ...@@ -27,7 +27,7 @@ import java.util.concurrent.TimeUnit
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content._
import android.content.res.Configuration import android.content.res.Configuration
import android.os.{Build, LocaleList} import android.os.{Build, LocaleList}
import android.preference.PreferenceManager import android.preference.PreferenceManager
...@@ -210,4 +210,15 @@ class ShadowsocksApplication extends Application { ...@@ -210,4 +210,15 @@ class ShadowsocksApplication extends Application {
} }
def updateAssets(): Unit = if (settings.getInt(Key.currentVersionCode, -1) != BuildConfig.VERSION_CODE) copyAssets() def updateAssets(): Unit = if (settings.getInt(Key.currentVersionCode, -1) != BuildConfig.VERSION_CODE) copyAssets()
def listenForPackageChanges(callback: => Unit): BroadcastReceiver = {
val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED)
filter.addAction(Intent.ACTION_PACKAGE_REMOVED)
filter.addDataScheme("package")
val result: BroadcastReceiver = (_: Context, intent: Intent) =>
if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED || !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false))
callback
app.registerReceiver(result, filter)
result
}
} }
package com.github.shadowsocks.plugin
import android.content.pm.{PackageManager, ResolveInfo}
/**
* @author Mygod
*/
class NativePlugin(resolveInfo: ResolveInfo, packageManager: PackageManager)
extends ResolvedPlugin(resolveInfo, packageManager) {
assert(resolveInfo.providerInfo != null)
assert(resolveInfo.providerInfo.authority.startsWith(PluginInterface.AUTHORITY_BASE))
override final lazy val id: String =
resolveInfo.providerInfo.authority.substring(PluginInterface.AUTHORITY_BASE.length)
override final lazy val defaultConfig: String =
resolveInfo.providerInfo.metaData.getString(PluginInterface.METADATA_KEY_DEFAULT_CONFIG)
}
package com.github.shadowsocks.plugin
import com.github.shadowsocks.ShadowsocksApplication.app
/**
* @author Mygod
*/
object NoPlugin extends Plugin {
override def id: String = ""
override def label: CharSequence = app.getText(com.github.shadowsocks.R.string.plugin_disabled)
}
package com.github.shadowsocks.plugin
import android.graphics.drawable.Drawable
/**
* @author Mygod
*/
abstract class Plugin {
def id: String
def label: CharSequence
def icon: Drawable = null
def defaultConfig: String = null
}
package com.github.shadowsocks.plugin
import android.util.Log
import com.github.shadowsocks.utils.Commandline
import scala.collection.mutable
/**
* @author Mygod
*/
class PluginConfiguration(val pluginsOptions: Map[String, PluginOptions], val selected: String) {
private def this(plugins: Array[PluginOptions]) =
this(plugins.map(opt => opt.id -> opt).toMap, if (plugins.isEmpty) "" else plugins(0).id)
def this(plugin: String) = this(if (plugin == null) Array[PluginOptions]() else plugin.split("\n").map {
case line if line.startsWith("kcptun ") =>
val opt = new PluginOptions()
opt.id = "kcptun"
try {
val args = mutable.Queue(Commandline.translateCommandline(line): _*)
args.dequeue()
while (args.nonEmpty) args.dequeue() match {
case "--nocomp" => opt.put("nocomp", "1")
case option if option.startsWith("--") => opt.put(option.substring(2), args.dequeue())
case option => throw new IllegalArgumentException("Unknown kcptun parameter: " + option)
}
} catch {
case exc: Exception => Log.w("PluginConfiguration", exc.getMessage)
}
opt
case line => new PluginOptions(line)
})
def selectedOptions: PluginOptions = pluginsOptions(selected)
override def toString: String = {
val result = new mutable.ListBuffer[PluginOptions]()
for ((id, opt) <- pluginsOptions) id match {
case this.selected => result.prepend(opt)
case _ => result.append(opt)
}
result.map(_.toString(false)).mkString("\n")
}
}
package com.github.shadowsocks.plugin
import android.content.pm.PackageManager
import android.content.{BroadcastReceiver, Intent}
import com.github.shadowsocks.ShadowsocksApplication.app
import scala.collection.JavaConverters._
/**
* @author Mygod
*/
object PluginManager {
private var receiver: BroadcastReceiver = _
private var cachedPlugins: Array[Plugin] = _
def fetchPlugins(): Array[Plugin] = {
if (receiver == null) receiver = app.listenForPackageChanges(synchronized(cachedPlugins = null))
synchronized(if (cachedPlugins == null) {
val pm = app.getPackageManager
cachedPlugins = (NoPlugin +:
pm.queryIntentContentProviders(new Intent().addCategory(PluginInterface.CATEGORY_NATIVE_PLUGIN),
PackageManager.GET_META_DATA).asScala.map(new NativePlugin(_, pm))).toArray
})
cachedPlugins
}
}
package com.github.shadowsocks.plugin
import android.content.pm.{PackageManager, ResolveInfo}
import android.graphics.drawable.Drawable
import com.github.shadowsocks.ShadowsocksApplication.app
/**
* Base class for any kind of plugin that can be used.
*
* @author Mygod
*/
abstract class ResolvedPlugin(resolveInfo: ResolveInfo, packageManager: PackageManager = app.getPackageManager)
extends Plugin {
override final lazy val label: CharSequence = resolveInfo.loadLabel(packageManager)
override final lazy val icon: Drawable = resolveInfo.loadIcon(packageManager)
}
package com.github.shadowsocks.preference
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.support.design.widget.BottomSheetDialog
import android.support.v14.preference.PreferenceDialogFragment
import android.support.v7.widget.RecyclerView.ViewHolder
import android.support.v7.widget.{LinearLayoutManager, RecyclerView}
import android.view.ViewGroup.LayoutParams
import android.view.{LayoutInflater, View, ViewGroup}
import android.widget.TextView
import com.github.shadowsocks.R
/**
* @author Mygod
*/
final class BottomSheetPreferenceDialogFragment extends PreferenceDialogFragment {
private lazy val preference = getPreference.asInstanceOf[IconListPreference]
private lazy val index: Int = preference.selectedEntry
private lazy val entries: Array[CharSequence] = preference.getEntries
private lazy val entryIcons: Array[Drawable] = preference.getEntryIcons
private var clickedIndex = -1
override def onCreateDialog(savedInstanceState: Bundle): BottomSheetDialog = {
val activity = getActivity
val dialog = new BottomSheetDialog(activity, getTheme)
val recycler = new RecyclerView(activity)
recycler.setHasFixedSize(true)
recycler.setLayoutManager(new LinearLayoutManager(activity))
recycler.setAdapter(new IconListAdapter(dialog))
recycler.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
dialog.setContentView(recycler)
dialog
}
def onDialogClosed(positiveResult: Boolean): Unit = if (clickedIndex >= 0) {
val value = preference.getEntryValues()(clickedIndex).toString
if (preference.callChangeListener(value)) preference.setValue(value)
}
private final class IconListViewHolder(val dialog: BottomSheetDialog, val view: TextView) extends ViewHolder(view)
with View.OnClickListener {
private var index: Int = _
{
val padding = getResources.getDimension(R.dimen.icon_list_item_padding).toInt
view.setPadding(padding, padding, padding, padding)
view.setCompoundDrawablePadding(padding)
view.setOnClickListener(this)
val typedArray = dialog.getContext.obtainStyledAttributes(Array(android.R.attr.selectableItemBackground))
view.setBackgroundResource(typedArray.getResourceId(0, 0))
typedArray.recycle()
}
def bind(i: Int, selected: Boolean = false) {
view.setText(entries(i))
view.setCompoundDrawablesWithIntrinsicBounds(entryIcons(i), null, null, null)
view.setTypeface(null, if (selected) Typeface.BOLD else Typeface.NORMAL)
index = i
}
def onClick(v: View) {
clickedIndex = index
dialog.dismiss()
}
}
private final class IconListAdapter(dialog: BottomSheetDialog) extends RecyclerView.Adapter[IconListViewHolder] {
def getItemCount: Int = entries.length
def onBindViewHolder(vh: IconListViewHolder, i: Int): Unit = if (index < 0) vh.bind(i) else i match {
case 0 => vh.bind(index, selected = true)
case _ if i > index => vh.bind(i)
case _ => vh.bind(i - 1)
}
def onCreateViewHolder(vg: ViewGroup, i: Int): IconListViewHolder = new IconListViewHolder(dialog,
LayoutInflater.from(vg.getContext).inflate(android.R.layout.simple_list_item_1, vg, false).asInstanceOf[TextView])
}
}
package com.github.shadowsocks.preference
import android.content.Context
import android.graphics.drawable.Drawable
import android.support.annotation.ArrayRes
import android.support.v7.preference.ListPreference
import android.support.v7.preference.Preference.OnPreferenceChangeListener
import android.util.AttributeSet
import be.mygod.preference.DialogPreferencePlus
/**
* @author Mygod
*/
class IconListPreference(context: Context, attrs: AttributeSet = null) extends ListPreference(context, attrs)
with DialogPreferencePlus {
override def createDialog() = new BottomSheetPreferenceDialogFragment()
private var mEntryIcons: Array[Drawable] = _
def selectedEntry: Int = getEntryValues.indexOf(getValue)
var unknownValueSummary: String = _
private var listener: OnPreferenceChangeListener = _
override def getOnPreferenceChangeListener: OnPreferenceChangeListener = listener
override def setOnPreferenceChangeListener(listener: OnPreferenceChangeListener): Unit = this.listener = listener
super.setOnPreferenceChangeListener((preference, newValue) =>
if (listener == null || listener.onPreferenceChange(preference, newValue)) {
setValue(newValue.toString)
checkSummary()
if (mEntryIcons != null) setIcon(getEntryIcon)
true
} else false)
def checkSummary(): Unit =
if (unknownValueSummary != null) setSummary(if (selectedEntry < 0) unknownValueSummary.format(getValue) else "%s")
// {
// val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.IconListPreference)
// val entryIconsResId: Int = a.getResourceId(R.styleable.IconListPreference_entryIcons, -1)
// if (entryIconsResId != -1) setEntryIcons(entryIconsResId)
// a.recycle()
// }
def getEntryIcon: Drawable = mEntryIcons(selectedEntry)
def getEntryIcons: Array[Drawable] = mEntryIcons
def setEntryIcons(entryIcons: Array[Drawable]): Unit = mEntryIcons = entryIcons
def setEntryIcons(@ArrayRes entryIconsResId: Int) {
val icons_array = getContext.getResources.obtainTypedArray(entryIconsResId)
val icon_ids_array = new Array[Drawable](icons_array.length)
for (i <- 0 until icons_array.length) icon_ids_array(i) = icons_array.getDrawable(i)
setEntryIcons(icon_ids_array)
icons_array.recycle()
}
def init() {
val entryValues = getEntryValues
if (entryValues == null) return
if (mEntryIcons != null) setIcon(getEntryIcon)
}
protected override def onSetInitialValue(restoreValue: Boolean, defaultValue: AnyRef) {
super.onSetInitialValue(restoreValue, defaultValue)
init()
}
}
/*******************************************************************************/
/* */
/* 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.preference
import android.app.{Activity, AlertDialog}
import android.content.{DialogInterface, Intent}
import be.mygod.preference.EditTextPreferenceDialogFragment
import com.github.shadowsocks.plugin.PluginInterface
/**
* @author Mygod
*/
object PluginConfigurationDialogFragment {
final val PLUGIN_ID_FRAGMENT_TAG = "com.github.shadowsocks.preference.PluginConfigurationDialogFragment.PLUGIN_ID"
private final val REQUEST_CODE_HELP = 1
}
class PluginConfigurationDialogFragment extends EditTextPreferenceDialogFragment {
import PluginConfigurationDialogFragment._
override def onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
val intent = new Intent(PluginInterface.ACTION_HELP(getArguments.getString(PLUGIN_ID_FRAGMENT_TAG)))
if (intent.resolveActivity(getContext.getPackageManager) != null) builder.setNeutralButton("?", ((_, _) =>
startActivityForResult(intent.putExtra(PluginInterface.EXTRA_OPTIONS, editText.getText.toString),
REQUEST_CODE_HELP)): DialogInterface.OnClickListener)
}
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = requestCode match {
case REQUEST_CODE_HELP => requestCode match {
case Activity.RESULT_OK => new AlertDialog.Builder(getContext)
.setTitle("?")
.setMessage(data.getCharSequenceExtra(PluginInterface.EXTRA_HELP_MESSAGE))
.show()
case _ =>
}
case _ => super.onActivityResult(requestCode, resultCode, data)
}
}
...@@ -3,3 +3,5 @@ android.useSupportVectors ...@@ -3,3 +3,5 @@ android.useSupportVectors
name := "plugin" name := "plugin"
version := "0.0.1-SNAPSHOT" version := "0.0.1-SNAPSHOT"
publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath + "/.m2/repository")))
...@@ -51,6 +51,8 @@ public final class PluginInterface { ...@@ -51,6 +51,8 @@ public final class PluginInterface {
*/ */
public static final String COLUMN_PATH = "path"; public static final String COLUMN_PATH = "path";
static final String AUTHORITY_BASE = "com.github.shadowsocks.plugin.";
/** /**
* Authority to use for native plugin ContentProvider. * Authority to use for native plugin ContentProvider.
* *
...@@ -58,7 +60,7 @@ public final class PluginInterface { ...@@ -58,7 +60,7 @@ public final class PluginInterface {
* @return com.github.shadowsocks.plugin.$PLUGIN_ID * @return com.github.shadowsocks.plugin.$PLUGIN_ID
*/ */
public static String getAuthority(String pluginId) { public static String getAuthority(String pluginId) {
return "com.github.shadowsocks.plugin." + pluginId; return AUTHORITY_BASE + pluginId;
} }
/** /**
......
...@@ -37,8 +37,10 @@ public final class PluginOptions extends HashMap<String, String> { ...@@ -37,8 +37,10 @@ public final class PluginOptions extends HashMap<String, String> {
key = current.toString(); key = current.toString();
current.setLength(0); current.setLength(0);
} else if (";".equals(nextToken)) { } else if (";".equals(nextToken)) {
if (current.length() > 0) put(key, current.toString()); if (key != null) {
else if (firstEntry) id = key; put(key, current.toString());
key = null;
} else if (firstEntry) id = current.toString();
else throw new IllegalArgumentException("Value missing in " + options); else throw new IllegalArgumentException("Value missing in " + options);
firstEntry = false; firstEntry = false;
} }
...@@ -49,7 +51,7 @@ public final class PluginOptions extends HashMap<String, String> { ...@@ -49,7 +51,7 @@ public final class PluginOptions extends HashMap<String, String> {
this.id = id; this.id = id;
} }
public String id; public String id = "";
private static void append(StringBuilder result, String str) { private static void append(StringBuilder result, String str) {
for (int i = 0; i < str.length(); ++i) { for (int i = 0; i < str.length(); ++i) {
...@@ -62,7 +64,7 @@ public final class PluginOptions extends HashMap<String, String> { ...@@ -62,7 +64,7 @@ public final class PluginOptions extends HashMap<String, String> {
} }
public String toString(boolean trimId) { public String toString(boolean trimId) {
final StringBuilder result = new StringBuilder(); final StringBuilder result = new StringBuilder();
if (!trimId && !TextUtils.isEmpty(id)) append(result, id); if (!trimId) if (TextUtils.isEmpty(id)) return ""; else append(result, id);
for (Entry<String, String> entry : entrySet()) if (entry.getValue() != null) { for (Entry<String, String> entry : entrySet()) if (entry.getValue() != null) {
if (result.length() > 0) result.append(';'); if (result.length() > 0) result.append(';');
append(result, entry.getKey()); append(result, entry.getKey());
...@@ -73,6 +75,6 @@ public final class PluginOptions extends HashMap<String, String> { ...@@ -73,6 +75,6 @@ public final class PluginOptions extends HashMap<String, String> {
} }
@Override @Override
public String toString() { public String toString() {
return toString(false); return toString(true);
} }
} }
...@@ -27,7 +27,7 @@ import android.view.{View, ViewGroup} ...@@ -27,7 +27,7 @@ import android.view.{View, ViewGroup}
class EditTextPreferenceDialogFragment extends PreferenceDialogFragment { class EditTextPreferenceDialogFragment extends PreferenceDialogFragment {
private lazy val preference = getPreference.asInstanceOf[EditTextPreference] private lazy val preference = getPreference.asInstanceOf[EditTextPreference]
private lazy val editText = preference.editText protected lazy val editText: AppCompatEditText = preference.editText
override protected def onCreateDialogView(context: Context): AppCompatEditText = { override protected def onCreateDialogView(context: Context): AppCompatEditText = {
val parent = editText.getParent.asInstanceOf[ViewGroup] val parent = editText.getParent.asInstanceOf[ViewGroup]
......
...@@ -30,9 +30,10 @@ abstract class PreferenceFragment extends Base { ...@@ -30,9 +30,10 @@ abstract class PreferenceFragment extends Base {
override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View = override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View =
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
protected final def displayPreferenceDialog(key: String, fragment: DialogFragment) { protected final def displayPreferenceDialog(key: String, fragment: DialogFragment, other: Bundle = null) {
val bundle = new Bundle(1) val bundle = new Bundle(1)
bundle.putString("key", key) bundle.putString("key", key)
if (other != null) bundle.putAll(other)
fragment.setArguments(bundle) fragment.setArguments(bundle)
fragment.setTargetFragment(this, 0) fragment.setTargetFragment(this, 0)
getFragmentManager.beginTransaction() getFragmentManager.beginTransaction()
......
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