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 android.app.Activity; 0010 import android.content.Context; 0011 import android.content.SharedPreferences; 0012 import android.content.pm.PackageManager; 0013 import android.os.Build; 0014 0015 import androidx.annotation.CallSuper; 0016 import androidx.annotation.DrawableRes; 0017 import androidx.annotation.NonNull; 0018 import androidx.annotation.Nullable; 0019 import androidx.annotation.StringRes; 0020 import androidx.core.content.ContextCompat; 0021 import androidx.fragment.app.DialogFragment; 0022 0023 import org.apache.commons.lang3.ArrayUtils; 0024 import org.jetbrains.annotations.NotNull; 0025 import org.kde.kdeconnect.Device; 0026 import org.kde.kdeconnect.NetworkPacket; 0027 import org.kde.kdeconnect.UserInterface.AlertDialogFragment; 0028 import org.kde.kdeconnect.UserInterface.MainActivity; 0029 import org.kde.kdeconnect.UserInterface.PermissionsAlertDialogFragment; 0030 import org.kde.kdeconnect.UserInterface.PluginSettingsFragment; 0031 import org.kde.kdeconnect_tp.R; 0032 0033 public abstract class Plugin { 0034 protected Device device; 0035 protected Context context; 0036 @Nullable 0037 protected SharedPreferences preferences; 0038 0039 public final void setContext(@NonNull Context context, @Nullable Device device) { 0040 this.device = device; 0041 this.context = context; 0042 0043 if (device != null) { 0044 this.preferences = this.context.getSharedPreferences(this.getSharedPreferencesName(), Context.MODE_PRIVATE); 0045 } 0046 } 0047 0048 public @NotNull String getSharedPreferencesName() { 0049 if (device == null) { 0050 throw new RuntimeException("You have to call setContext() before you can call getSharedPreferencesName()"); 0051 } 0052 0053 if (this.supportsDeviceSpecificSettings()) 0054 return this.device.getDeviceId() + "_" + this.getPluginKey() + "_preferences"; 0055 else 0056 return this.getPluginKey() + "_preferences"; 0057 } 0058 0059 public @Nullable SharedPreferences getPreferences() { 0060 return this.preferences; 0061 } 0062 0063 /** 0064 * To receive the network packet from the unpaired device, override 0065 * listensToUnpairedDevices to return true and this method. 0066 */ 0067 public boolean onUnpairedDevicePacketReceived(@NonNull NetworkPacket np) { 0068 return false; 0069 } 0070 0071 /** 0072 * Returns whether this plugin should be loaded or not, to listen to NetworkPackets 0073 * from the unpaired devices. By default, returns false. 0074 */ 0075 public boolean listensToUnpairedDevices() { 0076 return false; 0077 } 0078 0079 /** 0080 * Return the internal plugin name, that will be used as a 0081 * unique key to distinguish it. Use the class name as key. 0082 */ 0083 public final @NonNull String getPluginKey() { 0084 return getPluginKey(this.getClass()); 0085 } 0086 0087 public static @NonNull String getPluginKey(Class<? extends Plugin> p) { 0088 return p.getSimpleName(); 0089 } 0090 0091 /** 0092 * Return the human-readable plugin name. This function can 0093 * access this.context to provide translated text. 0094 */ 0095 public abstract @NonNull String getDisplayName(); 0096 0097 /** 0098 * Return the human-readable description of this plugin. This 0099 * function can access this.context to provide translated text. 0100 */ 0101 public abstract @NonNull String getDescription(); 0102 0103 /** 0104 * Return the action name displayed in the main activity, that 0105 * will call startMainActivity when clicked 0106 */ 0107 public @NonNull String getActionName() { 0108 return getDisplayName(); 0109 } 0110 0111 /** 0112 * Return an icon associated to this plugin. Only needed if hasMainActivity() returns true and displayInContextMenu() returns false 0113 */ 0114 public @DrawableRes int getIcon() { 0115 return -1; 0116 } 0117 0118 /** 0119 * Return true if this plugin should be enabled on new devices. 0120 * This function can access this.context and perform compatibility 0121 * checks with the Android version, but can not access this.device. 0122 */ 0123 public boolean isEnabledByDefault() { 0124 return true; 0125 } 0126 0127 /** 0128 * Return true if this plugin needs an specific UI settings. 0129 */ 0130 public boolean hasSettings() { 0131 return false; 0132 } 0133 0134 /** 0135 * Called to find out if a plugin supports device specific settings. 0136 * If you return true your PluginSettingsFragment will use the device 0137 * specific SharedPreferences to store the settings. 0138 * 0139 * @return true if this plugin supports device specific settings 0140 */ 0141 public boolean supportsDeviceSpecificSettings() { return false; } 0142 0143 /** 0144 * If hasSettings returns true, this will be called when the user 0145 * wants to access this plugin's preferences. The default implementation 0146 * will return a PluginSettingsFragment with content from "yourplugin"_preferences.xml 0147 * 0148 * @return The PluginSettingsFragment used to display this plugins settings 0149 */ 0150 public @Nullable PluginSettingsFragment getSettingsFragment(Activity activity) { 0151 throw new RuntimeException("Plugin doesn't reimplement getSettingsFragment: " + getPluginKey()); 0152 0153 } 0154 0155 /** 0156 * Return true if the plugin should display something in the Device main view 0157 */ 0158 public boolean displayAsButton(Context context) { 0159 return false; 0160 } 0161 0162 /** 0163 * Return true if the entry for this app should appear in the context menu instead of the main view 0164 */ 0165 public boolean displayInContextMenu() { 0166 return false; 0167 } 0168 0169 /** 0170 * Implement here what your plugin should do when clicked 0171 */ 0172 public void startMainActivity(Activity parentActivity) { 0173 } 0174 0175 /** 0176 * Returns false when we should avoid loading this Plugin for {@link #device}. 0177 * <p> 0178 * Called after {@link #setContext(Context, Device)} but before {@link #onCreate()}. 0179 * </p> 0180 * <p> 0181 * By default, this just checks if {@link #getMinSdk()} is smaller or equal than the 0182 * {@link Build.VERSION#SDK_INT SDK version} of this Android device. 0183 * </p> 0184 * 0185 * @return true if it's safe to call {@link #onCreate()} 0186 */ 0187 @CallSuper 0188 public boolean isCompatible() { 0189 return Build.VERSION.SDK_INT >= getMinSdk(); 0190 } 0191 0192 /** 0193 * Initialize the listeners and structures in your plugin. 0194 * <p> 0195 * If {@link #isCompatible()} or {@link #checkRequiredPermissions()} returns false, this 0196 * will <em>not</em> be called. 0197 * </p> 0198 * 0199 * @return true if initialization was successful, false otherwise 0200 */ 0201 public boolean onCreate() { 0202 return true; 0203 } 0204 0205 /** 0206 * Finish any ongoing operations, remove listeners... so 0207 * this object could be garbage collected. Note that this gets 0208 * called as well if onCreate threw an exception, so your plugin 0209 * could be not fully initialized. 0210 */ 0211 public void onDestroy() { } 0212 0213 /** 0214 * Called when a plugin receives a packet. By convention we return true 0215 * when we have done something in response to the packet or false 0216 * otherwise, even though that value is unused as of now. 0217 */ 0218 public boolean onPacketReceived(@NonNull NetworkPacket np) { 0219 return false; 0220 } 0221 0222 /** 0223 * Should return the list of NetworkPacket types that this plugin can handle 0224 */ 0225 public abstract @NonNull String[] getSupportedPacketTypes(); 0226 0227 /** 0228 * Should return the list of NetworkPacket types that this plugin can send 0229 */ 0230 public abstract @NonNull String[] getOutgoingPacketTypes(); 0231 0232 /** 0233 * Should return the list of permissions from Manifest.permission.* that, if not present, 0234 * mean the plugin can't be loaded. 0235 */ 0236 protected @NonNull String[] getRequiredPermissions() { 0237 return ArrayUtils.EMPTY_STRING_ARRAY; 0238 } 0239 0240 /** 0241 * Should return the list of permissions from Manifest.permission.* that enable additional 0242 * functionality in the plugin (without preventing the plugin to load). 0243 */ 0244 protected @NonNull String[] getOptionalPermissions() { 0245 return ArrayUtils.EMPTY_STRING_ARRAY; 0246 } 0247 0248 /** 0249 * Returns the string to display before asking for the required permissions for the plugin. 0250 */ 0251 protected @StringRes int getPermissionExplanation() { 0252 return R.string.permission_explanation; 0253 } 0254 0255 /** 0256 * Returns the string to display before asking for the optional permissions for the plugin. 0257 */ 0258 protected @StringRes int getOptionalPermissionExplanation() { 0259 return R.string.optional_permission_explanation; 0260 } 0261 0262 //Permission from Manifest.permission.* 0263 protected boolean isPermissionGranted(@NonNull String permission) { 0264 int result = ContextCompat.checkSelfPermission(context, permission); 0265 return (result == PackageManager.PERMISSION_GRANTED); 0266 } 0267 0268 protected boolean arePermissionsGranted(@NonNull String[] permissions) { 0269 for (String permission : permissions) { 0270 if (!isPermissionGranted(permission)) { 0271 return false; 0272 } 0273 } 0274 return true; 0275 } 0276 0277 private @NonNull PermissionsAlertDialogFragment requestPermissionDialog(@NonNull final String[] permissions, @StringRes int reason) { 0278 return new PermissionsAlertDialogFragment.Builder() 0279 .setTitle(getDisplayName()) 0280 .setMessage(reason) 0281 .setPermissions(permissions) 0282 .setRequestCode(MainActivity.RESULT_NEEDS_RELOAD) 0283 .create(); 0284 } 0285 0286 /** 0287 * If onCreate returns false, should create a dialog explaining 0288 * the problem (and how to fix it, if possible) to the user. 0289 */ 0290 0291 public @NonNull DialogFragment getPermissionExplanationDialog() { 0292 return requestPermissionDialog(getRequiredPermissions(), getPermissionExplanation()); 0293 } 0294 0295 public @NonNull AlertDialogFragment getOptionalPermissionExplanationDialog() { 0296 return requestPermissionDialog(getOptionalPermissions(), getOptionalPermissionExplanation()); 0297 } 0298 0299 public boolean checkRequiredPermissions() { 0300 return arePermissionsGranted(getRequiredPermissions()); 0301 } 0302 0303 public boolean checkOptionalPermissions() { 0304 return arePermissionsGranted(getOptionalPermissions()); 0305 } 0306 0307 public int getMinSdk() { 0308 return Build.VERSION_CODES.BASE; 0309 } 0310 }