diff --git a/src/org/kde/kdeconnect/Device.kt b/src/org/kde/kdeconnect/Device.kt index bc07a56a..2ca38796 100644 --- a/src/org/kde/kdeconnect/Device.kt +++ b/src/org/kde/kdeconnect/Device.kt @@ -112,7 +112,7 @@ class Device : PacketReceiver { this.settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE) this.deviceInfo = loadFromSettings(context, deviceId, settings) this.pairingHandler = PairingHandler(this, createDefaultPairingCallback(), PairingHandler.PairState.Paired) - this.supportedPlugins = Vector(PluginFactory.getAvailablePlugins()) // Assume all are supported until we receive capabilities + this.supportedPlugins = Vector(PluginFactory.availablePlugins) // Assume all are supported until we receive capabilities Log.i("Device", "Loading trusted device: ${deviceInfo.name}") } @@ -126,7 +126,7 @@ class Device : PacketReceiver { this.deviceInfo = link.deviceInfo this.settings = context.getSharedPreferences(deviceInfo.id, Context.MODE_PRIVATE) this.pairingHandler = PairingHandler(this, createDefaultPairingCallback(), PairingHandler.PairState.NotPaired) - this.supportedPlugins = Vector(PluginFactory.getAvailablePlugins()) // Assume all are supported until we receive capabilities + this.supportedPlugins = Vector(PluginFactory.availablePlugins) // Assume all are supported until we receive capabilities Log.i("Device", "Creating untrusted device: " + deviceInfo.name) addLink(link) } @@ -622,7 +622,7 @@ class Device : PacketReceiver { supportedPlugins.forEach { pluginKey -> val pluginInfo = PluginFactory.getPluginInfo(pluginKey) - val listenToUnpaired = pluginInfo.listenToUnpaired() + val listenToUnpaired = pluginInfo.listenToUnpaired val pluginEnabled = (isPaired || listenToUnpaired) && this.isReachable && isPluginEnabled(pluginKey) diff --git a/src/org/kde/kdeconnect/Helpers/DeviceHelper.kt b/src/org/kde/kdeconnect/Helpers/DeviceHelper.kt index bdc5c0cc..45f8bd4c 100644 --- a/src/org/kde/kdeconnect/Helpers/DeviceHelper.kt +++ b/src/org/kde/kdeconnect/Helpers/DeviceHelper.kt @@ -160,8 +160,8 @@ object DeviceHelper { getDeviceName(context), deviceType, ProtocolVersion, - PluginFactory.getIncomingCapabilities(), - PluginFactory.getOutgoingCapabilities() + PluginFactory.incomingCapabilities, + PluginFactory.outgoingCapabilities ) } diff --git a/src/org/kde/kdeconnect/Plugins/PluginFactory.java b/src/org/kde/kdeconnect/Plugins/PluginFactory.java deleted file mode 100644 index e37c636d..00000000 --- a/src/org/kde/kdeconnect/Plugins/PluginFactory.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2014 Albert Vaca Cintora - * - * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL -*/ - -package org.kde.kdeconnect.Plugins; - -import static org.apache.commons.collections4.SetUtils.unmodifiableSet; - -import android.content.Context; -import android.util.Log; - -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.atteo.classindex.ClassIndex; -import org.atteo.classindex.IndexAnnotated; -import org.kde.kdeconnect.Device; - -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -public class PluginFactory { - - public static void sortPluginList(@NonNull List plugins) { - plugins.sort(Comparator.comparing(o -> pluginInfo.get(o).displayName)); - } - - @IndexAnnotated - public @interface LoadablePlugin { } //Annotate plugins with this so PluginFactory finds them - - public static class PluginInfo { - - PluginInfo(@NonNull String displayName, @NonNull String description, @DrawableRes int icon, - boolean enabledByDefault, boolean hasSettings, boolean supportsDeviceSpecificSettings, - boolean listenToUnpaired, @NonNull String[] supportedPacketTypes, @NonNull String[] outgoingPacketTypes, - @NonNull Class instantiableClass) { - this.displayName = displayName; - this.description = description; - this.icon = icon; - this.enabledByDefault = enabledByDefault; - this.hasSettings = hasSettings; - this.supportsDeviceSpecificSettings = supportsDeviceSpecificSettings; - this.listenToUnpaired = listenToUnpaired; - this.supportedPacketTypes = unmodifiableSet(supportedPacketTypes); - this.outgoingPacketTypes = unmodifiableSet(outgoingPacketTypes); - this.instantiableClass = instantiableClass; - } - - public @NonNull String getDisplayName() { - return displayName; - } - - public @NonNull String getDescription() { - return description; - } - - public @DrawableRes int getIcon() { - return icon; - } - - public boolean hasSettings() { - return hasSettings; - } - - public boolean supportsDeviceSpecificSettings() { return supportsDeviceSpecificSettings; } - - public boolean isEnabledByDefault() { - return enabledByDefault; - } - - public boolean listenToUnpaired() { - return listenToUnpaired; - } - - Set getOutgoingPacketTypes() { - return outgoingPacketTypes; - } - - public Set getSupportedPacketTypes() { - return supportedPacketTypes; - } - - Class getInstantiableClass() { - return instantiableClass; - } - - private final @NonNull String displayName; - private final @NonNull String description; - private final @DrawableRes int icon; - private final boolean enabledByDefault; - private final boolean hasSettings; - private final boolean supportsDeviceSpecificSettings; - private final boolean listenToUnpaired; - private final @NonNull Set supportedPacketTypes; - private final @NonNull Set outgoingPacketTypes; - private final Class instantiableClass; - - } - - private static final Map pluginInfo = new ConcurrentHashMap<>(); - - public static PluginInfo getPluginInfo(String pluginKey) { - return pluginInfo.get(pluginKey); - } - - public static void initPluginInfo(Context context) { - try { - for (Class pluginClass : ClassIndex.getAnnotated(LoadablePlugin.class)) { - Plugin p = ((Plugin) pluginClass.newInstance()); - p.setContext(context, null); - PluginInfo info = new PluginInfo(p.getDisplayName(), p.getDescription(), p.getIcon(), - p.isEnabledByDefault(), p.hasSettings(), p.supportsDeviceSpecificSettings(), - p.listensToUnpairedDevices(), p.getSupportedPacketTypes(), - p.getOutgoingPacketTypes(), p.getClass()); - pluginInfo.put(p.getPluginKey(), info); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - Log.i("PluginFactory","Loaded "+pluginInfo.size()+" plugins"); - } - - public static @NonNull Set getAvailablePlugins() { - return pluginInfo.keySet(); - } - - public static @Nullable Plugin instantiatePluginForDevice(Context context, String pluginKey, Device device) { - PluginInfo info = pluginInfo.get(pluginKey); - try { - Plugin plugin = info.getInstantiableClass().newInstance(); - plugin.setContext(context, device); - return plugin; - } catch (Exception e) { - Log.e("PluginFactory", "Could not instantiate plugin: " + pluginKey, e); - return null; - } - } - - public static @NonNull Set getIncomingCapabilities() { - HashSet capabilities = new HashSet<>(); - for (PluginInfo plugin : pluginInfo.values()) { - capabilities.addAll(plugin.getSupportedPacketTypes()); - } - return capabilities; - } - - public static @NonNull Set getOutgoingCapabilities() { - HashSet capabilities = new HashSet<>(); - for (PluginInfo plugin : pluginInfo.values()) { - capabilities.addAll(plugin.getOutgoingPacketTypes()); - } - return capabilities; - } - - public static @NonNull Set pluginsForCapabilities(Set incoming, Set outgoing) { - HashSet plugins = new HashSet<>(); - for (Map.Entry entry : pluginInfo.entrySet()) { - String pluginId = entry.getKey(); - PluginInfo info = entry.getValue(); - //Check incoming against outgoing - if (Collections.disjoint(outgoing, info.getSupportedPacketTypes()) - && Collections.disjoint(incoming, info.getOutgoingPacketTypes())) { - Log.d("PluginFactory", "Won't load " + pluginId + " because of unmatched capabilities"); - continue; //No capabilities in common, do not load this plugin - } - plugins.add(pluginId); - } - return plugins; - } - -} diff --git a/src/org/kde/kdeconnect/Plugins/PluginFactory.kt b/src/org/kde/kdeconnect/Plugins/PluginFactory.kt new file mode 100644 index 00000000..d12bc7ac --- /dev/null +++ b/src/org/kde/kdeconnect/Plugins/PluginFactory.kt @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: 2014 Albert Vaca Cintora + * + * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ +package org.kde.kdeconnect.Plugins + +import android.content.Context +import android.util.Log +import androidx.annotation.DrawableRes +import org.atteo.classindex.ClassIndex +import org.atteo.classindex.IndexAnnotated +import org.kde.kdeconnect.Device + +object PluginFactory { + private var pluginInfo: Map = mapOf() + + fun initPluginInfo(context: Context) { + try { + val plugins = ClassIndex.getAnnotated(LoadablePlugin::class.java) + .map { it.newInstance() as Plugin } + .map { plugin -> plugin.apply { setContext(context, null) } } + + pluginInfo = plugins.associate { plugin -> Pair(plugin.pluginKey, PluginInfo(plugin)) } + } catch (e: Exception) { + throw RuntimeException(e) + } + Log.i("PluginFactory", "Loaded " + pluginInfo.size + " plugins") + } + + val availablePlugins: Set + get() = pluginInfo.keys + val incomingCapabilities: Set + get() = pluginInfo.values.flatMap { plugin -> plugin.supportedPacketTypes }.toSet() + val outgoingCapabilities: Set + get() = pluginInfo.values.flatMap { plugin -> plugin.outgoingPacketTypes }.toSet() + + @JvmStatic + fun getPluginInfo(pluginKey: String): PluginInfo = pluginInfo[pluginKey]!! + + @JvmStatic + fun sortPluginList(plugins: List): List { + return plugins.sortedBy { pluginInfo[it]?.displayName } + } + + fun instantiatePluginForDevice(context: Context, pluginKey: String, device: Device): Plugin? { + try { + val plugin = pluginInfo[pluginKey]?.instantiableClass?.newInstance()?.apply { setContext(context, device) } + return plugin + } catch (e: Exception) { + Log.e("PluginFactory", "Could not instantiate plugin: $pluginKey", e) + return null + } + } + + fun pluginsForCapabilities(incoming: Set, outgoing: Set): Set { + fun hasCommonCapabilities(info: PluginInfo): Boolean = + outgoing.any { it in info.supportedPacketTypes } || + incoming.any { it in info.outgoingPacketTypes } + + val (used, unused) = pluginInfo.entries.partition { hasCommonCapabilities(it.value) } + + for (pluginId in unused.map { it.key }) { + Log.d("PluginFactory", "Won't load $pluginId because of unmatched capabilities") + } + + return used.map { it.key }.toSet() + } + + @IndexAnnotated + annotation class LoadablePlugin //Annotate plugins with this so PluginFactory finds them + + class PluginInfo private constructor( + val displayName: String, + val description: String, + // DrawableRes needs to be in the primary constructor, which is why we need 2 constructors + @field:DrawableRes @get:DrawableRes @param:DrawableRes val icon: Int, + val isEnabledByDefault: Boolean, + val hasSettings: Boolean, + val supportsDeviceSpecificSettings: Boolean, + val listenToUnpaired: Boolean, + supportedPacketTypes: Array, + outgoingPacketTypes: Array, + val instantiableClass: Class, + ) { + internal constructor(p: Plugin) : this(p.displayName, p.description, p.icon, + p.isEnabledByDefault, p.hasSettings(), p.supportsDeviceSpecificSettings(), + p.listensToUnpairedDevices(), p.supportedPacketTypes, + p.outgoingPacketTypes, p.javaClass) + + val supportedPacketTypes: Set = supportedPacketTypes.toSet() + val outgoingPacketTypes: Set = outgoingPacketTypes.toSet() + } +} diff --git a/src/org/kde/kdeconnect/UserInterface/PluginPreference.java b/src/org/kde/kdeconnect/UserInterface/PluginPreference.java index da550100..5da1b3ee 100644 --- a/src/org/kde/kdeconnect/UserInterface/PluginPreference.java +++ b/src/org/kde/kdeconnect/UserInterface/PluginPreference.java @@ -38,7 +38,7 @@ public class PluginPreference extends SwitchPreference { setSummary(info.getDescription()); setChecked(device.isPluginEnabled(pluginKey)); - if (info.hasSettings()) { + if (info.getHasSettings()) { this.listener = v -> { Plugin plugin = device.getPluginIncludingWithoutPermissions(pluginKey); if (plugin != null) { diff --git a/src/org/kde/kdeconnect/UserInterface/PluginSettingsListFragment.java b/src/org/kde/kdeconnect/UserInterface/PluginSettingsListFragment.java index d7e844f4..957e1aad 100644 --- a/src/org/kde/kdeconnect/UserInterface/PluginSettingsListFragment.java +++ b/src/org/kde/kdeconnect/UserInterface/PluginSettingsListFragment.java @@ -83,9 +83,8 @@ public class PluginSettingsListFragment extends PreferenceFragmentCompat { activity.runOnUiThread(activity::finish); return; } - List plugins = device.getSupportedPlugins(); - PluginFactory.sortPluginList(plugins); + List plugins = PluginFactory.sortPluginList(device.getSupportedPlugins()); for (final String pluginKey : plugins) { //TODO: Use PreferenceManagers context PluginPreference pref = new PluginPreference(requireContext(), pluginKey, device, callback);