2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-29 13:17:43 +00:00

Use a prefences file per device/plugin combo

Cleanup global plugin preferences after migration to per device preferences
This commit is contained in:
Erik Duisters 2020-03-20 20:30:58 +00:00
parent e7651ec9bc
commit 4d8e7cadde
8 changed files with 212 additions and 70 deletions

View File

@ -15,8 +15,8 @@
* 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/>.
*/
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect;
@ -24,7 +24,6 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothClass;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@ -33,9 +32,12 @@ import android.net.ConnectivityManager;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
@ -53,14 +55,12 @@ import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import androidx.core.app.NotificationCompat;
//import org.kde.kdeconnect.Backends.BluetoothBackend.BluetoothLinkProvider;
public class BackgroundService extends Service {
@ -72,7 +72,7 @@ public class BackgroundService extends Service {
void onDeviceListChanged();
}
public interface PluginCallback<T extends Plugin> {
public interface PluginCallback<T extends Plugin> {
void run(T plugin);
}
@ -280,6 +280,7 @@ public class BackgroundService extends Service {
initializeSecurityParameters();
NotificationHelper.initializeChannels(this);
loadRememberedDevicesFromSettings();
migratePluginSettings();
registerLinkProviders();
//Link Providers need to be already registered
@ -290,6 +291,29 @@ public class BackgroundService extends Service {
}
}
private void migratePluginSettings() {
SharedPreferences globalPrefs = PreferenceManager.getDefaultSharedPreferences(this);
for (String pluginKey : PluginFactory.getAvailablePlugins()) {
if (PluginFactory.getPluginInfo(pluginKey).supportsDeviceSpecificSettings()) {
Iterator<Device> it = devices.values().iterator();
while (it.hasNext()) {
Device device = it.next();
Plugin plugin = PluginFactory.instantiatePluginForDevice(getBaseContext(), pluginKey, device);
if (plugin == null) {
continue;
}
plugin.copyGlobalToDeviceSpecificSettings(globalPrefs);
if (!it.hasNext()) {
plugin.removeSettings(globalPrefs);
}
}
}
}
}
public void changePersistentNotificationVisibility(boolean visible) {
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

View File

@ -33,6 +33,12 @@ import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log;
import androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Helpers.NotificationHelper;
@ -57,11 +63,6 @@ import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import androidx.annotation.AnyThread;
import androidx.annotation.WorkerThread;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
public class Device implements BaseLink.PacketReceiver {
private final Context context;
@ -712,11 +713,13 @@ public class Device implements BaseLink.PacketReceiver {
// Plugin-related functions
//
@Nullable
public <T extends Plugin> T getPlugin(Class<T> pluginClass) {
Plugin plugin = getPlugin(Plugin.getPluginKey(pluginClass));
return (T) plugin;
}
@Nullable
public Plugin getPlugin(String pluginKey) {
return plugins.get(pluginKey);
}

View File

@ -22,10 +22,17 @@ package org.kde.kdeconnect.Plugins;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.UserInterface.AlertDialogFragment;
@ -34,19 +41,37 @@ import org.kde.kdeconnect.UserInterface.PermissionsAlertDialogFragment;
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
import org.kde.kdeconnect_tp.R;
import androidx.annotation.StringRes;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
public abstract class Plugin {
protected Device device;
protected Context context;
protected int permissionExplanation = R.string.permission_explanation;
protected int optionalPermissionExplanation = R.string.optional_permission_explanation;
@Nullable
protected SharedPreferences preferences;
public final void setContext(Context context, Device device) {
public final void setContext(@NonNull Context context, @Nullable Device device) {
this.device = device;
this.context = context;
if (device != null) {
this.preferences = this.context.getSharedPreferences(this.getSharedPreferencesName(), Context.MODE_PRIVATE);
}
}
public String getSharedPreferencesName() {
if (device == null) {
throw new RuntimeException("You have to call setContext() before you can call getSharedPreferencesName()");
}
if (this.supportsDeviceSpecificSettings())
return this.device.getDeviceId() + "_" + this.getPluginKey() + "_preferences";
else
return this.getPluginKey() + "_preferences";
}
@Nullable
public SharedPreferences getPreferences() {
return this.preferences;
}
/**
@ -121,6 +146,30 @@ public abstract class Plugin {
return false;
}
/**
* Called to find out if a plugin supports device specific settings.
* If you return true your PluginSettingsFragment will use the device
* specific SharedPreferences to store the settings.
*
* @return true if this plugin supports device specific settings
*/
public boolean supportsDeviceSpecificSettings() { return false; }
/**
* Called when it's time to move the plugin settings from the global preferences
* to device specific preferences
*
* @param globalSharedPreferences The global Preferences to copy the settings from
*/
public void copyGlobalToDeviceSpecificSettings(SharedPreferences globalSharedPreferences) {}
/**
* Called when the plugin should remove it's settings from the provided ShardPreferences
*
* @param sharedPreferences The SharedPreferences to remove the settings from
*/
public void removeSettings(SharedPreferences sharedPreferences) {}
/**
* If hasSettings returns true, this will be called when the user
* wants to access this plugin's preferences. The default implementation
@ -244,5 +293,4 @@ public abstract class Plugin {
public int getMinSdk() {
return Build.VERSION_CODES.BASE;
}
}

View File

@ -42,14 +42,15 @@ public class PluginFactory {
public static class PluginInfo {
PluginInfo(String displayName, String description, Drawable icon,
boolean enabledByDefault, boolean hasSettings, boolean listenToUnpaired,
String[] supportedPacketTypes, String[] outgoingPacketTypes,
boolean enabledByDefault, boolean hasSettings, boolean supportsDeviceSpecificSettings,
boolean listenToUnpaired, String[] supportedPacketTypes, String[] outgoingPacketTypes,
Class<? extends Plugin> instantiableClass) {
this.displayName = displayName;
this.description = description;
this.icon = icon;
this.enabledByDefault = enabledByDefault;
this.hasSettings = hasSettings;
this.supportsDeviceSpecificSettings = supportsDeviceSpecificSettings;
this.listenToUnpaired = listenToUnpaired;
HashSet<String> incoming = new HashSet<>();
if (supportedPacketTypes != null) Collections.addAll(incoming, supportedPacketTypes);
@ -76,6 +77,8 @@ public class PluginFactory {
return hasSettings;
}
public boolean supportsDeviceSpecificSettings() { return supportsDeviceSpecificSettings; }
public boolean isEnabledByDefault() {
return enabledByDefault;
}
@ -101,6 +104,7 @@ public class PluginFactory {
private final Drawable icon;
private final boolean enabledByDefault;
private final boolean hasSettings;
private final boolean supportsDeviceSpecificSettings;
private final boolean listenToUnpaired;
private final Set<String> supportedPacketTypes;
private final Set<String> outgoingPacketTypes;
@ -120,8 +124,9 @@ public class PluginFactory {
Plugin p = ((Plugin) pluginClass.newInstance());
p.setContext(context, null);
PluginInfo info = new PluginInfo(p.getDisplayName(), p.getDescription(), p.getIcon(),
p.isEnabledByDefault(), p.hasSettings(), p.listensToUnpairedDevices(),
p.getSupportedPacketTypes(), p.getOutgoingPacketTypes(), p.getClass());
p.isEnabledByDefault(), p.hasSettings(), p.supportsDeviceSpecificSettings(),
p.listensToUnpairedDevices(), p.getSupportedPacketTypes(),
p.getOutgoingPacketTypes(), p.getClass());
pluginInfo.put(p.getPluginKey(), info);
}
} catch (Exception e) {

View File

@ -27,6 +27,8 @@ import android.net.Uri;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import org.json.JSONException;
import org.json.JSONObject;
import org.kde.kdeconnect.NetworkPacket;
@ -43,19 +45,16 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
@PluginFactory.LoadablePlugin
public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPreferenceChangeListener {
private final static String PACKET_TYPE_SFTP = "kdeconnect.sftp";
private final static String PACKET_TYPE_SFTP_REQUEST = "kdeconnect.sftp.request";
private static final SimpleSftpServer server = new SimpleSftpServer();
static int PREFERENCE_KEY_STORAGE_INFO_LIST = R.string.sftp_preference_key_storage_info_list;
private static int PREFERENCE_KEY_ADD_CAMERA_SHORTCUT = R.string.sftp_preference_key_add_camera_shortcut;
private String KeyStorageInfoList;
private String KeyAddCameraShortcut;
private static final SimpleSftpServer server = new SimpleSftpServer();
@Override
public String getDisplayName() {
@ -69,14 +68,11 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
@Override
public boolean onCreate() {
KeyStorageInfoList = context.getString(R.string.sftp_preference_key_storage_info_list);
KeyAddCameraShortcut = context.getString(R.string.sftp_preference_key_add_camera_shortcut);
try {
server.init(context, device);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
return SftpSettingsFragment.getStorageInfoList(context).size() != 0;
return SftpSettingsFragment.getStorageInfoList(context, this).size() != 0;
}
return true;
@ -89,7 +85,7 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
@Override
public boolean checkOptionalPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
return SftpSettingsFragment.getStorageInfoList(context).size() != 0;
return SftpSettingsFragment.getStorageInfoList(context, this).size() != 0;
}
return true;
@ -110,7 +106,9 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
@Override
public void onDestroy() {
server.stop();
PreferenceManager.getDefaultSharedPreferences(context).unregisterOnSharedPreferenceChangeListener(this);
if (preferences != null) {
preferences.unregisterOnSharedPreferenceChangeListener(this);
}
}
@Override
@ -119,7 +117,7 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
ArrayList<String> paths = new ArrayList<>();
ArrayList<String> pathNames = new ArrayList<>();
List<StorageInfo> storageInfoList = SftpSettingsFragment.getStorageInfoList(context);
List<StorageInfo> storageInfoList = SftpSettingsFragment.getStorageInfoList(context, this);
Collections.sort(storageInfoList, new StorageInfo.UriNameComparator());
if (storageInfoList.size() > 0) {
@ -141,7 +139,9 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
removeChildren(storageInfoList);
if (server.start(storageInfoList)) {
PreferenceManager.getDefaultSharedPreferences(context).registerOnSharedPreferenceChangeListener(this);
if (preferences != null) {
preferences.registerOnSharedPreferenceChangeListener(this);
}
NetworkPacket np2 = new NetworkPacket(PACKET_TYPE_SFTP);
@ -170,11 +170,13 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
private void getPathsAndNamesForStorageInfoList(List<String> paths, List<String> pathNames, List<StorageInfo> storageInfoList) {
StorageInfo prevInfo = null;
StringBuilder pathBuilder = new StringBuilder();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean addCameraShortcuts = false;
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
addCameraShortcuts = prefs.getBoolean(context.getString(R.string.sftp_preference_key_add_camera_shortcut), true);
if (preferences != null) {
addCameraShortcuts = preferences.getBoolean(context.getString(PREFERENCE_KEY_ADD_CAMERA_SHORTCUT), true);
}
}
for (StorageInfo curInfo : storageInfoList) {
@ -245,6 +247,33 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
return true;
}
@Override
public boolean supportsDeviceSpecificSettings() { return true; }
@Override
public void copyGlobalToDeviceSpecificSettings(SharedPreferences globalSharedPreferences) {
String KeyStorageInfoList = context.getString(PREFERENCE_KEY_STORAGE_INFO_LIST);
String KeyAddCameraShortcut = context.getString(PREFERENCE_KEY_ADD_CAMERA_SHORTCUT);
if (this.preferences != null &&
(!this.preferences.contains(KeyStorageInfoList) || !this.preferences.contains(KeyAddCameraShortcut))) {
this.preferences
.edit()
.putString(KeyStorageInfoList, globalSharedPreferences.getString(KeyStorageInfoList, "[]"))
.putBoolean(KeyAddCameraShortcut, globalSharedPreferences.getBoolean(KeyAddCameraShortcut, true))
.apply();
}
}
@Override
public void removeSettings(SharedPreferences sharedPreferences) {
sharedPreferences
.edit()
.remove(context.getString(PREFERENCE_KEY_STORAGE_INFO_LIST))
.remove(context.getString(PREFERENCE_KEY_ADD_CAMERA_SHORTCUT))
.apply();
}
@Override
public PluginSettingsFragment getSettingsFragment(Activity activity) {
return SftpSettingsFragment.newInstance(getPluginKey());
@ -252,7 +281,8 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(KeyStorageInfoList) || key.equals(KeyAddCameraShortcut)) {
if (key.equals(context.getString(PREFERENCE_KEY_STORAGE_INFO_LIST)) ||
key.equals(context.getString(PREFERENCE_KEY_ADD_CAMERA_SHORTCUT))) {
//TODO: There used to be a way to request an un-mount (see desktop SftpPlugin's Mounter::onPackageReceived) but that is not handled anymore by the SftpPlugin on KDE.
if (server.isStarted()) {
server.stop();

View File

@ -36,12 +36,21 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.appcompat.view.ActionMode;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.StorageHelper;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.PluginSettingsActivity;
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
import org.kde.kdeconnect_tp.R;
@ -52,15 +61,6 @@ import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import androidx.annotation.NonNull;
import androidx.appcompat.view.ActionMode;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
//TODO: Is it possible on API 19 to select a directory and then have write permission for everything beneath it
//TODO: Is it necessary to check if uri permissions are still in place? If it is make the user aware of the fact (red text or something)
public class SftpSettingsFragment
@ -116,10 +116,10 @@ public class SftpSettingsFragment
int sdkInt = Build.VERSION.SDK_INT;
storageInfoList = getStorageInfoList(requireContext());
storageInfoList = getStorageInfoList(requireContext(), plugin);
PreferenceScreen preferenceScreen = getPreferenceScreen();
preferenceCategory = (PreferenceCategory) preferenceScreen
preferenceCategory = preferenceScreen
.findPreference(getString(R.string.sftp_preference_key_preference_category));
if (sdkInt <= 19) {
@ -245,30 +245,30 @@ public class SftpSettingsFragment
}
private void saveStorageInfoList() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(requireContext());
SharedPreferences preferences = this.plugin.getPreferences();
JSONArray jsonArray = new JSONArray();
try {
for (SftpPlugin.StorageInfo storageInfo : storageInfoList) {
for (SftpPlugin.StorageInfo storageInfo : this.storageInfoList) {
jsonArray.put(storageInfo.toJSON());
}
} catch (JSONException ignored) {}
preferences
.edit()
.putString(requireContext().getString(R.string.sftp_preference_key_storage_info_list), jsonArray.toString())
.putString(requireContext().getString(SftpPlugin.PREFERENCE_KEY_STORAGE_INFO_LIST), jsonArray.toString())
.apply();
}
@NonNull
static List<SftpPlugin.StorageInfo> getStorageInfoList(@NonNull Context context) {
static List<SftpPlugin.StorageInfo> getStorageInfoList(@NonNull Context context, @NonNull Plugin plugin) {
ArrayList<SftpPlugin.StorageInfo> storageInfoList = new ArrayList<>();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences deviceSettings = plugin.getPreferences();
String jsonString = preferences
.getString(context.getString(R.string.sftp_preference_key_storage_info_list), "[]");
String jsonString = deviceSettings
.getString(context.getString(SftpPlugin.PREFERENCE_KEY_STORAGE_INFO_LIST), "[]");
try {
JSONArray jsonArray = new JSONArray(jsonString);
@ -414,11 +414,19 @@ public class SftpSettingsFragment
addStoragePreferences(preferenceCategory);
Device device = getDeviceOrThrow();
device.reloadPluginsFromSettings();
}
private Device getDeviceOrThrow() {
Device device = BackgroundService.getInstance().getDevice(getDeviceId());
if (device != null) {
device.reloadPluginsFromSettings();
if (device == null) {
throw new RuntimeException("SftpSettingsFragment.getDeviceOrThrow(): No device with id: " + getDeviceId());
}
return device;
}
@Override

View File

@ -20,20 +20,27 @@
package org.kde.kdeconnect.UserInterface;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect_tp.R;
import java.util.Locale;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceFragmentCompat;
public class PluginSettingsFragment extends PreferenceFragmentCompat {
private static final String ARG_PLUGIN_KEY = "plugin_key";
private String pluginKey;
protected Device device;
protected Plugin plugin;
public static PluginSettingsFragment newInstance(@NonNull String pluginKey) {
PluginSettingsFragment fragment = new PluginSettingsFragment();
@ -60,12 +67,20 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat {
}
pluginKey = getArguments().getString(ARG_PLUGIN_KEY);
this.device = getDeviceOrThrow(getDeviceId());
this.plugin = device.getPlugin(pluginKey);
super.onCreate(savedInstanceState);
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
if (this.plugin != null && this.plugin.supportsDeviceSpecificSettings()) {
PreferenceManager prefsManager = getPreferenceManager();
prefsManager.setSharedPreferencesName(this.plugin.getSharedPreferencesName());
prefsManager.setSharedPreferencesMode(Context.MODE_PRIVATE);
}
int resFile = getResources().getIdentifier(pluginKey.toLowerCase(Locale.ENGLISH) + "_preferences", "xml",
requireContext().getPackageName());
addPreferencesFromResource(resFile);
@ -82,4 +97,14 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat {
public String getDeviceId() {
return ((PluginSettingsActivity)requireActivity()).getDeviceId();
}
private Device getDeviceOrThrow(String deviceId) {
Device device = BackgroundService.getInstance().getDevice(deviceId);
if (device == null) {
throw new RuntimeException("PluginSettingsFragment.onCreatePreferences() - No device with id " + getDeviceId());
}
return device;
}
}

View File

@ -8,14 +8,6 @@ import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.view.View;
import com.google.android.material.snackbar.Snackbar;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Helpers.NotificationHelper;
import org.kde.kdeconnect_tp.R;
import androidx.preference.EditTextPreference;
import androidx.preference.Preference;
@ -24,6 +16,13 @@ import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreferenceCompat;
import androidx.preference.TwoStatePreference;
import com.google.android.material.snackbar.Snackbar;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Helpers.NotificationHelper;
import org.kde.kdeconnect_tp.R;
public class SettingsFragment extends PreferenceFragmentCompat {
private MainActivity mainActivity;