File indexing completed on 2024-12-22 04:41:42

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Albert Vaca Cintora <albertvaka@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 package org.kde.kdeconnect.Plugins;
0008 
0009 import static org.apache.commons.collections4.SetUtils.unmodifiableSet;
0010 
0011 import android.content.Context;
0012 import android.util.Log;
0013 
0014 import androidx.annotation.DrawableRes;
0015 import androidx.annotation.NonNull;
0016 import androidx.annotation.Nullable;
0017 
0018 import org.atteo.classindex.ClassIndex;
0019 import org.atteo.classindex.IndexAnnotated;
0020 import org.kde.kdeconnect.Device;
0021 
0022 import java.util.Collections;
0023 import java.util.Comparator;
0024 import java.util.HashSet;
0025 import java.util.List;
0026 import java.util.Map;
0027 import java.util.Set;
0028 import java.util.concurrent.ConcurrentHashMap;
0029 
0030 public class PluginFactory {
0031 
0032     public static void sortPluginList(@NonNull List<String> plugins) {
0033         plugins.sort(Comparator.comparing(o -> pluginInfo.get(o).displayName));
0034     }
0035 
0036     @IndexAnnotated
0037     public @interface LoadablePlugin { } //Annotate plugins with this so PluginFactory finds them
0038 
0039     public static class PluginInfo {
0040 
0041         PluginInfo(@NonNull String displayName, @NonNull String description, @DrawableRes int icon,
0042                    boolean enabledByDefault, boolean hasSettings, boolean supportsDeviceSpecificSettings,
0043                    boolean listenToUnpaired, @NonNull String[] supportedPacketTypes, @NonNull String[] outgoingPacketTypes,
0044                    @NonNull Class<? extends Plugin> instantiableClass) {
0045             this.displayName = displayName;
0046             this.description = description;
0047             this.icon = icon;
0048             this.enabledByDefault = enabledByDefault;
0049             this.hasSettings = hasSettings;
0050             this.supportsDeviceSpecificSettings = supportsDeviceSpecificSettings;
0051             this.listenToUnpaired = listenToUnpaired;
0052             this.supportedPacketTypes = unmodifiableSet(supportedPacketTypes);
0053             this.outgoingPacketTypes = unmodifiableSet(outgoingPacketTypes);
0054             this.instantiableClass = instantiableClass;
0055         }
0056 
0057         public @NonNull String getDisplayName() {
0058             return displayName;
0059         }
0060 
0061         public @NonNull String getDescription() {
0062             return description;
0063         }
0064 
0065         public @DrawableRes int getIcon() {
0066             return icon;
0067         }
0068 
0069         public boolean hasSettings() {
0070             return hasSettings;
0071         }
0072 
0073         public boolean supportsDeviceSpecificSettings() { return supportsDeviceSpecificSettings; }
0074 
0075         public boolean isEnabledByDefault() {
0076             return enabledByDefault;
0077         }
0078 
0079         public boolean listenToUnpaired() {
0080             return listenToUnpaired;
0081         }
0082 
0083         Set<String> getOutgoingPacketTypes() {
0084             return outgoingPacketTypes;
0085         }
0086 
0087         public Set<String> getSupportedPacketTypes() {
0088             return supportedPacketTypes;
0089         }
0090 
0091         Class<? extends Plugin> getInstantiableClass() {
0092             return instantiableClass;
0093         }
0094 
0095         private final @NonNull String displayName;
0096         private final @NonNull String description;
0097         private final @DrawableRes int icon;
0098         private final boolean enabledByDefault;
0099         private final boolean hasSettings;
0100         private final boolean supportsDeviceSpecificSettings;
0101         private final boolean listenToUnpaired;
0102         private final @NonNull Set<String> supportedPacketTypes;
0103         private final @NonNull Set<String> outgoingPacketTypes;
0104         private final Class<? extends Plugin> instantiableClass;
0105 
0106     }
0107 
0108     private static final Map<String, PluginInfo> pluginInfo = new ConcurrentHashMap<>();
0109 
0110     public static PluginInfo getPluginInfo(String pluginKey) {
0111         return pluginInfo.get(pluginKey);
0112     }
0113 
0114     public static void initPluginInfo(Context context) {
0115         try {
0116             for (Class<?> pluginClass : ClassIndex.getAnnotated(LoadablePlugin.class)) {
0117                 Plugin p = ((Plugin) pluginClass.newInstance());
0118                 p.setContext(context, null);
0119                 PluginInfo info = new PluginInfo(p.getDisplayName(), p.getDescription(), p.getIcon(),
0120                         p.isEnabledByDefault(), p.hasSettings(), p.supportsDeviceSpecificSettings(),
0121                         p.listensToUnpairedDevices(), p.getSupportedPacketTypes(),
0122                         p.getOutgoingPacketTypes(), p.getClass());
0123                 pluginInfo.put(p.getPluginKey(), info);
0124             }
0125         } catch (Exception e) {
0126             throw new RuntimeException(e);
0127         }
0128         Log.i("PluginFactory","Loaded "+pluginInfo.size()+" plugins");
0129     }
0130 
0131     public static @NonNull Set<String> getAvailablePlugins() {
0132         return pluginInfo.keySet();
0133     }
0134 
0135     public static @Nullable Plugin instantiatePluginForDevice(Context context, String pluginKey, Device device) {
0136         PluginInfo info = pluginInfo.get(pluginKey);
0137         try {
0138             Plugin plugin = info.getInstantiableClass().newInstance();
0139             plugin.setContext(context, device);
0140             return plugin;
0141         } catch (Exception e) {
0142             Log.e("PluginFactory", "Could not instantiate plugin: " + pluginKey, e);
0143             return null;
0144         }
0145     }
0146 
0147     public static @NonNull Set<String> getIncomingCapabilities() {
0148         HashSet<String> capabilities = new HashSet<>();
0149         for (PluginInfo plugin : pluginInfo.values()) {
0150             capabilities.addAll(plugin.getSupportedPacketTypes());
0151         }
0152         return capabilities;
0153     }
0154 
0155     public static @NonNull Set<String> getOutgoingCapabilities() {
0156         HashSet<String> capabilities = new HashSet<>();
0157         for (PluginInfo plugin : pluginInfo.values()) {
0158             capabilities.addAll(plugin.getOutgoingPacketTypes());
0159         }
0160         return capabilities;
0161     }
0162 
0163     public static @NonNull Set<String> pluginsForCapabilities(Set<String> incoming, Set<String> outgoing) {
0164         HashSet<String> plugins = new HashSet<>();
0165         for (Map.Entry<String, PluginInfo> entry : pluginInfo.entrySet()) {
0166             String pluginId = entry.getKey();
0167             PluginInfo info = entry.getValue();
0168             //Check incoming against outgoing
0169             if (Collections.disjoint(outgoing, info.getSupportedPacketTypes())
0170                     && Collections.disjoint(incoming, info.getOutgoingPacketTypes())) {
0171                 Log.d("PluginFactory", "Won't load " + pluginId + " because of unmatched capabilities");
0172                 continue; //No capabilities in common, do not load this plugin
0173             }
0174             plugins.add(pluginId);
0175         }
0176         return plugins;
0177     }
0178 
0179 }