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 }