diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0f1fad2c..a053c49b 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -52,7 +52,7 @@ android:networkSecurityConfig="@xml/network_security_config" android:localeConfig="@xml/locales_config" android:theme="@style/KdeConnectTheme.NoActionBar" - android:name="org.kde.kdeconnect.MyApplication" + android:name="org.kde.kdeconnect.KdeConnect" android:enableOnBackInvokedCallback="true"> { - Device device = service.getDevice(deviceId); - if (device == null) return; - device.unpair(); - //Retry as unpaired - identityPacketReceived(identityPacket, socket, connectionStarted); - }); + Device device = KdeConnect.getInstance().getDevice(deviceId); + if (device == null) { + return; + } + device.unpair(); + //Retry as unpaired + identityPacketReceived(identityPacket, socket, connectionStarted); } Log.i("KDE/LanLinkProvider", "Starting SSL handshake with " + identityPacket.getString("deviceName") + " trusted:" + isDeviceTrusted); @@ -217,11 +217,11 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis addLink(identityPacket, sslsocket, connectionStarted); } catch (Exception e) { Log.e("KDE/LanLinkProvider", "Handshake as " + mode + " failed with " + identityPacket.getString("deviceName"), e); - BackgroundService.RunCommand(context, service -> { - Device device = service.getDevice(deviceId); - if (device == null) return; - device.unpair(); - }); + Device device = KdeConnect.getInstance().getDevice(deviceId); + if (device == null) { + return; + } + device.unpair(); } }); //Handshake is blocking, so do it on another thread and free this thread to keep receiving new connection diff --git a/src/org/kde/kdeconnect/BackgroundService.java b/src/org/kde/kdeconnect/BackgroundService.java index 01ecdd17..1b178565 100644 --- a/src/org/kde/kdeconnect/BackgroundService.java +++ b/src/org/kde/kdeconnect/BackgroundService.java @@ -14,34 +14,26 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; -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 androidx.core.content.ContextCompat; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; -import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLinkProvider; import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider; -import org.kde.kdeconnect.Helpers.DeviceHelper; import org.kde.kdeconnect.Helpers.NotificationHelper; -import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper; -import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; -import org.kde.kdeconnect.Helpers.ThreadHelper; import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardFloatingActivity; -import org.kde.kdeconnect.Plugins.Plugin; -import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandActivity; import org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandPlugin; import org.kde.kdeconnect.Plugins.SharePlugin.SendFileActivity; @@ -49,92 +41,34 @@ import org.kde.kdeconnect.UserInterface.MainActivity; 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 org.kde.kdeconnect.Backends.BluetoothBackend.BluetoothLinkProvider; +/* + * This class (still) does 3 things: + * - Keeps the app running by creating a foreground notification. + * - Holds references to the active LinkProviders, but doesn't handle the DeviceLink those create (the KdeConnect class does that). + * - Listens for network connectivity changes and tells the LinkProviders to re-check for devices. + * It can be started by the KdeConnectBroadcastReceiver on some events or when the MainActivity is launched. + */ public class BackgroundService extends Service { private static final int FOREGROUND_NOTIFICATION_ID = 1; private static BackgroundService instance; - public interface DeviceListChangedCallback { - void onDeviceListChanged(boolean isConnectedToNonCellularNetwork); - } - - public interface PluginCallback { - void run(T plugin); - } - - private final ConcurrentHashMap deviceListChangedCallbacks = new ConcurrentHashMap<>(); + private KdeConnect applicationInstance; private final ArrayList linkProviders = new ArrayList<>(); - private final ConcurrentHashMap devices = new ConcurrentHashMap<>(); - - private final HashSet discoveryModeAcquisitions = new HashSet<>(); - public static BackgroundService getInstance() { return instance; } - boolean isConnectedToNonCellularNetwork; // True when connected over wifi/usb/bluetooth/(anything other than cellular) - - private boolean acquireDiscoveryMode(Object key) { - boolean wasEmpty = discoveryModeAcquisitions.isEmpty(); - discoveryModeAcquisitions.add(key); - if (wasEmpty) { - onNetworkChange(); - } - //Log.e("acquireDiscoveryMode",key.getClass().getName() +" ["+discoveryModeAcquisitions.size()+"]"); - return wasEmpty; + // This indicates when connected over wifi/usb/bluetooth/(anything other than cellular) + private final MutableLiveData connectedToNonCellularNetwork = new MutableLiveData<>(); + public LiveData isConnectedToNonCellularNetwork() { + return connectedToNonCellularNetwork; } - private void releaseDiscoveryMode(Object key) { - boolean removed = discoveryModeAcquisitions.remove(key); - //Log.e("releaseDiscoveryMode",key.getClass().getName() +" ["+discoveryModeAcquisitions.size()+"]"); - if (removed && discoveryModeAcquisitions.isEmpty()) { - cleanDevices(); - } - } - - private boolean isInDiscoveryMode() { - //return !discoveryModeAcquisitions.isEmpty(); - return true; // Keep it always on for now - } - - private final Device.PairingCallback devicePairingCallback = new Device.PairingCallback() { - @Override - public void incomingRequest() { - onDeviceListChanged(); - } - - @Override - public void pairingSuccessful() { - onDeviceListChanged(); - } - - @Override - public void pairingFailed(String error) { - onDeviceListChanged(); - } - - @Override - public void unpaired() { - onDeviceListChanged(); - } - }; - - public void onDeviceListChanged() { - for (DeviceListChangedCallback callback : deviceListChangedCallbacks.values()) { - callback.onDeviceListChanged(isConnectedToNonCellularNetwork); - } - + public void updateForegroundNotification() { if (NotificationHelper.isPersistentNotificationEnabled(this)) { //Update the foreground notification with the currently connected device list NotificationManager nm = ContextCompat.getSystemService(this, NotificationManager.class); @@ -142,97 +76,14 @@ public class BackgroundService extends Service { } } - private void loadRememberedDevicesFromSettings() { - //Log.e("BackgroundService", "Loading remembered trusted devices"); - SharedPreferences preferences = getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); - Set trustedDevices = preferences.getAll().keySet(); - for (String deviceId : trustedDevices) { - //Log.e("BackgroundService", "Loading device "+deviceId); - if (preferences.getBoolean(deviceId, false)) { - Device device = new Device(this, deviceId); - devices.put(deviceId, device); - device.addPairingCallback(devicePairingCallback); - } - } - } - private void registerLinkProviders() { linkProviders.add(new LanLinkProvider(this)); // linkProviders.add(new LoopbackLinkProvider(this)); // linkProviders.add(new BluetoothLinkProvider(this)); } - public ArrayList getLinkProviders() { - return linkProviders; - } - - public Device getDevice(String id) { - if (id == null) { - return null; - } - return devices.get(id); - } - - private void cleanDevices() { - ThreadHelper.execute(() -> { - for (Device d : devices.values()) { - if (!d.isPaired() && !d.isPairRequested() && !d.isPairRequestedByPeer() && !d.deviceShouldBeKeptAlive()) { - d.disconnect(); - } - } - }); - } - - private final BaseLinkProvider.ConnectionReceiver deviceListener = new BaseLinkProvider.ConnectionReceiver() { - @Override - public void onConnectionReceived(final NetworkPacket identityPacket, final BaseLink link) { - - String deviceId = identityPacket.getString("deviceId"); - - Device device = devices.get(deviceId); - - if (device != null) { - Log.i("KDE/BackgroundService", "addLink, known device: " + deviceId); - device.addLink(identityPacket, link); - } else { - Log.i("KDE/BackgroundService", "addLink,unknown device: " + deviceId); - device = new Device(BackgroundService.this, identityPacket, link); - if (device.isPaired() || device.isPairRequested() || device.isPairRequestedByPeer() - || link.linkShouldBeKeptAlive() - || isInDiscoveryMode()) { - devices.put(deviceId, device); - device.addPairingCallback(devicePairingCallback); - } else { - device.disconnect(); - } - } - - onDeviceListChanged(); - } - - @Override - public void onConnectionLost(BaseLink link) { - Device d = devices.get(link.getDeviceId()); - Log.i("KDE/onConnectionLost", "removeLink, deviceId: " + link.getDeviceId()); - if (d != null) { - d.removeLink(link); - if (!d.isReachable() && !d.isPaired()) { - //Log.e("onConnectionLost","Removing connection device because it was not paired"); - devices.remove(link.getDeviceId()); - d.removePairingCallback(devicePairingCallback); - } - } else { - //Log.d("KDE/onConnectionLost","Removing connection to unknown device"); - } - onDeviceListChanged(); - } - }; - - public ConcurrentHashMap getDevices() { - return devices; - } - public void onNetworkChange() { + Log.d("KDE/BackgroundService", "onNetworkChange"); for (BaseLinkProvider a : linkProviders) { a.onNetworkChange(); } @@ -250,22 +101,14 @@ public class BackgroundService extends Service { } } - public void addDeviceListChangedCallback(String key, DeviceListChangedCallback callback) { - deviceListChangedCallbacks.put(key, callback); - } - - public void removeDeviceListChangedCallback(String key) { - deviceListChangedCallbacks.remove(key); - } - //This will called only once, even if we launch the service intent several times @Override public void onCreate() { super.onCreate(); - + Log.d("KdeConnect/BgService", "onCreate"); instance = this; - DeviceHelper.initializeDeviceId(this); + KdeConnect.getInstance().addDeviceListChangedCallback("BackgroundService", this::updateForegroundNotification); // Register screen on listener IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); @@ -281,29 +124,19 @@ public class BackgroundService extends Service { cm.registerNetworkCallback(networkRequestBuilder.build(), new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { - isConnectedToNonCellularNetwork = true; - onDeviceListChanged(); + connectedToNonCellularNetwork.postValue(true); onNetworkChange(); } @Override public void onLost(Network network) { - isConnectedToNonCellularNetwork = false; - onDeviceListChanged(); + connectedToNonCellularNetwork.postValue(false); } }); - Log.i("KDE/BackgroundService", "Service not started yet, initializing..."); + applicationInstance = KdeConnect.getInstance(); - PluginFactory.initPluginInfo(getBaseContext()); - initializeSecurityParameters(); - NotificationHelper.initializeChannels(this); - loadRememberedDevicesFromSettings(); - migratePluginSettings(); registerLinkProviders(); - - //Link Providers need to be already registered - addConnectionListener(deviceListener); - + addConnectionListener(applicationInstance.getConnectionListener()); // Link Providers need to be already registered for (BaseLinkProvider a : linkProviders) { a.onStart(); } @@ -325,36 +158,11 @@ public class BackgroundService extends Service { return networkRequestBuilder; } - private void migratePluginSettings() { - SharedPreferences globalPrefs = PreferenceManager.getDefaultSharedPreferences(this); - - for (String pluginKey : PluginFactory.getAvailablePlugins()) { - if (PluginFactory.getPluginInfo(pluginKey).supportsDeviceSpecificSettings()) { - Iterator 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 = ContextCompat.getSystemService(this, NotificationManager.class); if (visible) { - nm.notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification()); + updateForegroundNotification(); } else { - stopForeground(true); + Stop(); Start(this); } } @@ -365,7 +173,7 @@ public class BackgroundService extends Service { ArrayList connectedDevices = new ArrayList<>(); ArrayList connectedDeviceIds = new ArrayList<>(); - for (Device device : getDevices().values()) { + for (Device device : applicationInstance.getDevices().values()) { if (device.isReachable() && device.isPaired()) { connectedDeviceIds.add(device.getDeviceId()); connectedDevices.add(device.getName()); @@ -409,7 +217,7 @@ public class BackgroundService extends Service { if (connectedDeviceIds.size() == 1) { String deviceId = connectedDeviceIds.get(0); - Device device = getDevice(deviceId); + Device device = KdeConnect.getInstance().getDevice(deviceId); if (device != null) { // Adding two action buttons only when there is a single device connected. // Setting up Send File Intent. @@ -432,49 +240,24 @@ public class BackgroundService extends Service { return notification.build(); } - private void initializeSecurityParameters() { - RsaHelper.initialiseRsaKeys(this); - SslHelper.initialiseCertificate(this); - } - @Override public void onDestroy() { - stopForeground(true); + Log.d("KdeConnect/BgService", "onDestroy"); for (BaseLinkProvider a : linkProviders) { a.onStop(); } + KdeConnect.getInstance().removeDeviceListChangedCallback("BackgroundService"); super.onDestroy(); } @Override public IBinder onBind(Intent intent) { - return new Binder(); + return null; } - - //To use the service from the gui - - public interface InstanceCallback { - void onServiceStart(BackgroundService service); - } - - private final static ArrayList callbacks = new ArrayList<>(); - - private final static Lock mutex = new ReentrantLock(true); - @Override public int onStartCommand(Intent intent, int flags, int startId) { - //This will be called for each intent launch, even if the service is already started and it is reused - mutex.lock(); - try { - for (InstanceCallback c : callbacks) { - c.onServiceStart(this); - } - callbacks.clear(); - } finally { - mutex.unlock(); - } - + Log.d("KDE/BackgroundService", "onStartCommand"); if (NotificationHelper.isPersistentNotificationEnabled(this)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { startForeground(FOREGROUND_NOTIFICATION_ID, createForegroundNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE); @@ -482,43 +265,26 @@ public class BackgroundService extends Service { startForeground(FOREGROUND_NOTIFICATION_ID, createForegroundNotification()); } } + if (intent.hasExtra("refresh")) { + onNetworkChange(); + } return Service.START_STICKY; } - private static void Start(Context c) { - RunCommand(c, null); + public static void Start(Context context) { + Log.d("KDE/BackgroundService", "Start"); + ContextCompat.startForegroundService(context, new Intent(context, BackgroundService.class)); } - public static void RunCommand(final Context c, final InstanceCallback callback) { - ThreadHelper.execute(() -> { - if (callback != null) { - mutex.lock(); - try { - callbacks.add(callback); - } finally { - mutex.unlock(); - } - } - ContextCompat.startForegroundService(c, new Intent(c, BackgroundService.class)); - }); + public static void ForceRefreshConnections(Context context) { + Log.d("KDE/BackgroundService", "ForceRefreshConnections"); + Intent i = new Intent(context, BackgroundService.class); + i.putExtra("refresh", true); + ContextCompat.startForegroundService(context, i); } - public static void RunWithPlugin(final Context c, final String deviceId, final Class pluginClass, final PluginCallback cb) { - RunCommand(c, service -> { - Device device = service.getDevice(deviceId); - - if (device == null) { - Log.e("BackgroundService", "Device " + deviceId + " not found"); - return; - } - - final T plugin = device.getPlugin(pluginClass); - - if (plugin == null) { - Log.e("BackgroundService", "Device " + device.getName() + " does not have plugin " + pluginClass.getName()); - return; - } - cb.run(plugin); - }); + public void Stop() { + stopForeground(true); } + } diff --git a/src/org/kde/kdeconnect/Device.java b/src/org/kde/kdeconnect/Device.java index 9be63e88..5a198f25 100644 --- a/src/org/kde/kdeconnect/Device.java +++ b/src/org/kde/kdeconnect/Device.java @@ -883,22 +883,6 @@ public class Device implements BaseLink.PacketReceiver { } } - public boolean deviceShouldBeKeptAlive() { - - SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); - if (preferences.contains(getDeviceId())) { - //Log.e("DeviceShouldBeKeptAlive", "because it's a paired device"); - return true; //Already paired - } - - for (BaseLink l : links) { - if (l.linkShouldBeKeptAlive()) { - return true; - } - } - return false; - } - public List getSupportedPlugins() { return supportedPlugins; } diff --git a/src/org/kde/kdeconnect/Helpers/IntentHelper.java b/src/org/kde/kdeconnect/Helpers/IntentHelper.java index 312c885e..573b17c4 100644 --- a/src/org/kde/kdeconnect/Helpers/IntentHelper.java +++ b/src/org/kde/kdeconnect/Helpers/IntentHelper.java @@ -15,7 +15,6 @@ import android.os.Build; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; -import org.kde.kdeconnect.MyApplication; import org.kde.kdeconnect_tp.R; public class IntentHelper { @@ -27,8 +26,8 @@ public class IntentHelper { * @param intent the Intent to be started * @param title a title which is shown in the notification on Android 10+ */ - public static void startActivityFromBackground(Context context, Intent intent, String title) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !MyApplication.isInForeground()) { + public static void startActivityFromBackgroundOrCreateNotification(Context context, Intent intent, String title) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !LifecycleHelper.isInForeground()) { PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE); Notification notification = new NotificationCompat .Builder(context, NotificationHelper.Channels.HIGHPRIORITY) diff --git a/src/org/kde/kdeconnect/MyApplication.java b/src/org/kde/kdeconnect/Helpers/LifecycleHelper.java similarity index 71% rename from src/org/kde/kdeconnect/MyApplication.java rename to src/org/kde/kdeconnect/Helpers/LifecycleHelper.java index c128c43b..70982823 100644 --- a/src/org/kde/kdeconnect/MyApplication.java +++ b/src/org/kde/kdeconnect/Helpers/LifecycleHelper.java @@ -1,15 +1,12 @@ -package org.kde.kdeconnect; - -import android.app.Application; +package org.kde.kdeconnect.Helpers; import androidx.annotation.NonNull; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.ProcessLifecycleOwner; -import org.kde.kdeconnect.UserInterface.ThemeUtil; +public class LifecycleHelper { -public class MyApplication extends Application { private static class LifecycleObserver implements DefaultLifecycleObserver { private boolean inForeground = false; @@ -28,16 +25,13 @@ public class MyApplication extends Application { } } - private static final LifecycleObserver foregroundTracker = new LifecycleObserver(); - - @Override - public void onCreate() { - super.onCreate(); - ThemeUtil.setUserPreferredTheme(this); - ProcessLifecycleOwner.get().getLifecycle().addObserver(foregroundTracker); - } + private final static LifecycleObserver foregroundTracker = new LifecycleObserver(); public static boolean isInForeground() { return foregroundTracker.isInForeground(); } + + public static void initializeObserver() { + ProcessLifecycleOwner.get().getLifecycle().addObserver(foregroundTracker); + } } diff --git a/src/org/kde/kdeconnect/KdeConnect.java b/src/org/kde/kdeconnect/KdeConnect.java new file mode 100644 index 00000000..7ad1b0ae --- /dev/null +++ b/src/org/kde/kdeconnect/KdeConnect.java @@ -0,0 +1,174 @@ +package org.kde.kdeconnect; + +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import org.kde.kdeconnect.Backends.BaseLink; +import org.kde.kdeconnect.Backends.BaseLinkProvider; +import org.kde.kdeconnect.Helpers.DeviceHelper; +import org.kde.kdeconnect.Helpers.LifecycleHelper; +import org.kde.kdeconnect.Helpers.NotificationHelper; +import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper; +import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; +import org.kde.kdeconnect.Plugins.Plugin; +import org.kde.kdeconnect.Plugins.PluginFactory; +import org.kde.kdeconnect.UserInterface.ThemeUtil; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/* + * This class holds all the active devices and makes them accessible from every other class. + * It also takes care of initializing all classes that need so when the app boots. + * It provides a ConnectionReceiver that the BackgroundService uses to ping this class every time a new DeviceLink is created. + */ +public class KdeConnect extends Application { + + public interface DeviceListChangedCallback { + void onDeviceListChanged(); + } + + private static KdeConnect instance = null; + + private final ConcurrentHashMap devices = new ConcurrentHashMap<>(); + + private final ConcurrentHashMap deviceListChangedCallbacks = new ConcurrentHashMap<>(); + + @Override + public void onCreate() { + super.onCreate(); + instance = this; + Log.d("KdeConnect/Application", "onCreate"); + ThemeUtil.setUserPreferredTheme(this); + DeviceHelper.initializeDeviceId(this); + RsaHelper.initialiseRsaKeys(this); + SslHelper.initialiseCertificate(this); + PluginFactory.initPluginInfo(this); + NotificationHelper.initializeChannels(this); + LifecycleHelper.initializeObserver(); + loadRememberedDevicesFromSettings(); + } + + @Override + public void onTerminate() { + Log.d("KdeConnect/Application", "onTerminate"); + super.onTerminate(); + } + + public void addDeviceListChangedCallback(String key, DeviceListChangedCallback callback) { + deviceListChangedCallbacks.put(key, callback); + } + + public void removeDeviceListChangedCallback(String key) { + deviceListChangedCallbacks.remove(key); + } + + private void onDeviceListChanged() { + for (DeviceListChangedCallback callback : deviceListChangedCallbacks.values()) { + callback.onDeviceListChanged(); + } + } + + public ConcurrentHashMap getDevices() { + return devices; + } + + public Device getDevice(String id) { + if (id == null) { + return null; + } + return devices.get(id); + } + + public T getDevicePlugin(String deviceId, Class pluginClass) { + if (deviceId == null) { + return null; + } + Device device = devices.get(deviceId); + if (device == null) { + return null; + } + return device.getPlugin(pluginClass); + } + + public static KdeConnect getInstance() { + return instance; + } + + private void loadRememberedDevicesFromSettings() { + //Log.e("BackgroundService", "Loading remembered trusted devices"); + SharedPreferences preferences = getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); + Set trustedDevices = preferences.getAll().keySet(); + for (String deviceId : trustedDevices) { + //Log.e("BackgroundService", "Loading device "+deviceId); + if (preferences.getBoolean(deviceId, false)) { + Device device = new Device(this, deviceId); + devices.put(deviceId, device); + device.addPairingCallback(devicePairingCallback); + } + } + } + + private final Device.PairingCallback devicePairingCallback = new Device.PairingCallback() { + @Override + public void incomingRequest() { + onDeviceListChanged(); + } + + @Override + public void pairingSuccessful() { + onDeviceListChanged(); + } + + @Override + public void pairingFailed(String error) { + onDeviceListChanged(); + } + + @Override + public void unpaired() { + onDeviceListChanged(); + } + }; + + private final BaseLinkProvider.ConnectionReceiver connectionListener = new BaseLinkProvider.ConnectionReceiver() { + @Override + public void onConnectionReceived(final NetworkPacket identityPacket, final BaseLink link) { + String deviceId = identityPacket.getString("deviceId"); + Device device = devices.get(deviceId); + if (device != null) { + Log.i("KDE/Application", "addLink, known device: " + deviceId); + device.addLink(identityPacket, link); + } else { + Log.i("KDE/Application", "addLink,unknown device: " + deviceId); + device = new Device(KdeConnect.this, identityPacket, link); + devices.put(deviceId, device); + device.addPairingCallback(devicePairingCallback); + } + onDeviceListChanged(); + } + + @Override + public void onConnectionLost(BaseLink link) { + Device d = devices.get(link.getDeviceId()); + Log.i("KDE/onConnectionLost", "removeLink, deviceId: " + link.getDeviceId()); + if (d != null) { + d.removeLink(link); + if (!d.isReachable() && !d.isPaired()) { + //Log.e("onConnectionLost","Removing connection device because it was not paired"); + devices.remove(link.getDeviceId()); + d.removePairingCallback(devicePairingCallback); + } + } else { + //Log.d("KDE/onConnectionLost","Removing connection to unknown device"); + } + onDeviceListChanged(); + } + }; + public BaseLinkProvider.ConnectionReceiver getConnectionListener() { + return connectionListener; + } + +} diff --git a/src/org/kde/kdeconnect/KdeConnectBroadcastReceiver.java b/src/org/kde/kdeconnect/KdeConnectBroadcastReceiver.java index e7c742e6..b0b4a239 100644 --- a/src/org/kde/kdeconnect/KdeConnectBroadcastReceiver.java +++ b/src/org/kde/kdeconnect/KdeConnectBroadcastReceiver.java @@ -15,7 +15,6 @@ import android.util.Log; public class KdeConnectBroadcastReceiver extends BroadcastReceiver { - public void onReceive(Context context, Intent intent) { //Log.e("KdeConnect", "Broadcast event: "+intent.getAction()); @@ -25,9 +24,7 @@ public class KdeConnectBroadcastReceiver extends BroadcastReceiver { switch (action) { case Intent.ACTION_MY_PACKAGE_REPLACED: Log.i("KdeConnect", "MyUpdateReceiver"); - BackgroundService.RunCommand(context, service -> { - - }); + BackgroundService.Start(context); break; case Intent.ACTION_PACKAGE_REPLACED: Log.i("KdeConnect", "UpdateReceiver"); @@ -35,27 +32,20 @@ public class KdeConnectBroadcastReceiver extends BroadcastReceiver { Log.i("KdeConnect", "Ignoring, it's not me!"); return; } - BackgroundService.RunCommand(context, service -> { - - }); + BackgroundService.Start(context); break; case Intent.ACTION_BOOT_COMPLETED: Log.i("KdeConnect", "KdeConnectBroadcastReceiver"); - BackgroundService.RunCommand(context, service -> { - - }); + BackgroundService.Start(context); break; case WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION: case WifiManager.WIFI_STATE_CHANGED_ACTION: case ConnectivityManager.CONNECTIVITY_ACTION: Log.i("KdeConnect", "Connection state changed, trying to connect"); - BackgroundService.RunCommand(context, service -> { - service.onDeviceListChanged(); - service.onNetworkChange(); - }); + BackgroundService.ForceRefreshConnections(context); break; case Intent.ACTION_SCREEN_ON: - BackgroundService.RunCommand(context, BackgroundService::onNetworkChange); + BackgroundService.ForceRefreshConnections(context); break; default: Log.i("BroadcastReceiver", "Ignoring broadcast event: " + intent.getAction()); diff --git a/src/org/kde/kdeconnect/Plugins/BigscreenPlugin/BigscreenActivity.java b/src/org/kde/kdeconnect/Plugins/BigscreenPlugin/BigscreenActivity.java index 41fed159..072f67a0 100644 --- a/src/org/kde/kdeconnect/Plugins/BigscreenPlugin/BigscreenActivity.java +++ b/src/org/kde/kdeconnect/Plugins/BigscreenPlugin/BigscreenActivity.java @@ -17,10 +17,9 @@ import android.view.View; import androidx.appcompat.app.AppCompatActivity; -import org.kde.kdeconnect.BackgroundService; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect.UserInterface.MainActivity; import org.kde.kdeconnect.UserInterface.PermissionsAlertDialogFragment; -import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.databinding.ActivityBigscreenBinding; @@ -49,28 +48,32 @@ public class BigscreenActivity extends AppCompatActivity { binding.micButton.setVisibility(View.INVISIBLE); } - BackgroundService.RunWithPlugin(this, deviceId, BigscreenPlugin.class, plugin -> runOnUiThread(() -> { - binding.leftButton.setOnClickListener(v -> plugin.sendLeft()); - binding.rightButton.setOnClickListener(v -> plugin.sendRight()); - binding.upButton.setOnClickListener(v -> plugin.sendUp()); - binding.downButton.setOnClickListener(v -> plugin.sendDown()); - binding.selectButton.setOnClickListener(v -> plugin.sendSelect()); - binding.homeButton.setOnClickListener(v -> plugin.sendHome()); - binding.micButton.setOnClickListener(v -> { - if (plugin.hasMicPermission()) { - activateSTT(); - } else { - new PermissionsAlertDialogFragment.Builder() - .setTitle(plugin.getDisplayName()) - .setMessage(R.string.bigscreen_optional_permission_explanation) - .setPositiveButton(R.string.ok) - .setNegativeButton(R.string.cancel) - .setPermissions(new String[]{Manifest.permission.RECORD_AUDIO}) - .setRequestCode(MainActivity.RESULT_NEEDS_RELOAD) - .create().show(getSupportFragmentManager(), null); - } - }); - })); + BigscreenPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, BigscreenPlugin.class); + if (plugin == null) { + finish(); + return; + } + + binding.leftButton.setOnClickListener(v -> plugin.sendLeft()); + binding.rightButton.setOnClickListener(v -> plugin.sendRight()); + binding.upButton.setOnClickListener(v -> plugin.sendUp()); + binding.downButton.setOnClickListener(v -> plugin.sendDown()); + binding.selectButton.setOnClickListener(v -> plugin.sendSelect()); + binding.homeButton.setOnClickListener(v -> plugin.sendHome()); + binding.micButton.setOnClickListener(v -> { + if (plugin.hasMicPermission()) { + activateSTT(); + } else { + new PermissionsAlertDialogFragment.Builder() + .setTitle(plugin.getDisplayName()) + .setMessage(R.string.bigscreen_optional_permission_explanation) + .setPositiveButton(R.string.ok) + .setNegativeButton(R.string.cancel) + .setPermissions(new String[]{Manifest.permission.RECORD_AUDIO}) + .setRequestCode(MainActivity.RESULT_NEEDS_RELOAD) + .create().show(getSupportFragmentManager(), null); + } + }); } public void activateSTT() { @@ -89,9 +92,12 @@ public class BigscreenActivity extends AppCompatActivity { .getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); if (result.get(0) != null) { final String deviceId = getIntent().getStringExtra("deviceId"); - BackgroundService.RunWithPlugin(this, deviceId, BigscreenPlugin.class, plugin -> - runOnUiThread(() -> plugin.sendSTT(result.get(0))) - ); + BigscreenPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, BigscreenPlugin.class); + if (plugin == null) { + finish(); + return; + } + plugin.sendSTT(result.get(0)); } } } diff --git a/src/org/kde/kdeconnect/Plugins/ClibpoardPlugin/ClipboardTileService.kt b/src/org/kde/kdeconnect/Plugins/ClibpoardPlugin/ClipboardTileService.kt index 030c9d64..53825d04 100644 --- a/src/org/kde/kdeconnect/Plugins/ClibpoardPlugin/ClipboardTileService.kt +++ b/src/org/kde/kdeconnect/Plugins/ClibpoardPlugin/ClipboardTileService.kt @@ -10,7 +10,7 @@ import android.content.Intent import android.os.Build import android.service.quicksettings.TileService import androidx.annotation.RequiresApi -import org.kde.kdeconnect.BackgroundService +import org.kde.kdeconnect.KdeConnect @RequiresApi(Build.VERSION_CODES.N) class ClipboardTileService : TileService() { @@ -20,12 +20,9 @@ class ClipboardTileService : TileService() { startActivityAndCollapse(Intent(this, ClipboardFloatingActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK var ids : List = emptyList() - val service = BackgroundService.getInstance() - if (service != null) { - ids = service.devices.values - .filter { it.isReachable && it.isPaired } - .map { it.deviceId } - } + ids = KdeConnect.getInstance().devices.values + .filter { it.isReachable && it.isPaired } + .map { it.deviceId } putExtra("connectedDeviceIds", ArrayList(ids)) }) } diff --git a/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneActivity.java b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneActivity.java index ff92361a..502249dd 100644 --- a/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneActivity.java +++ b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneActivity.java @@ -12,8 +12,7 @@ import android.view.WindowManager; import androidx.appcompat.app.AppCompatActivity; -import org.kde.kdeconnect.BackgroundService; -import org.kde.kdeconnect.UserInterface.ThemeUtil; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect_tp.databinding.ActivityFindMyPhoneBinding; import java.util.Objects; @@ -21,7 +20,7 @@ import java.util.Objects; public class FindMyPhoneActivity extends AppCompatActivity { static final String EXTRA_DEVICE_ID = "deviceId"; - private FindMyPhonePlugin plugin; + String deviceId; @Override protected void onCreate(Bundle savedInstanceState) { @@ -39,8 +38,7 @@ public class FindMyPhoneActivity extends AppCompatActivity { finish(); } - String deviceId = getIntent().getStringExtra(EXTRA_DEVICE_ID); - plugin = BackgroundService.getInstance().getDevice(deviceId).getPlugin(FindMyPhonePlugin.class); + deviceId = getIntent().getStringExtra(EXTRA_DEVICE_ID); Window window = this.getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | @@ -53,11 +51,10 @@ public class FindMyPhoneActivity extends AppCompatActivity { @Override protected void onStart() { super.onStart(); - /* - For whatever reason when Android launches this activity as a SystemAlertWindow it calls: - onCreate(), onStart(), onResume(), onStop(), onStart(), onResume(). - When using BackgroundService.RunWithPlugin we get into concurrency problems and sometimes no sound will be played - */ + FindMyPhonePlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, FindMyPhonePlugin.class); + if (plugin == null) { + return; + } plugin.startPlaying(); plugin.hideNotification(); } @@ -65,7 +62,10 @@ public class FindMyPhoneActivity extends AppCompatActivity { @Override protected void onStop() { super.onStop(); - + FindMyPhonePlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, FindMyPhonePlugin.class); + if (plugin == null) { + return; + } plugin.stopPlaying(); } } diff --git a/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhonePlugin.java b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhonePlugin.java index 3d0df416..4c7fefa4 100644 --- a/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhonePlugin.java +++ b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhonePlugin.java @@ -25,8 +25,8 @@ import androidx.core.content.ContextCompat; import org.apache.commons.lang3.ArrayUtils; import org.kde.kdeconnect.Helpers.DeviceHelper; +import org.kde.kdeconnect.Helpers.LifecycleHelper; import org.kde.kdeconnect.Helpers.NotificationHelper; -import org.kde.kdeconnect.MyApplication; import org.kde.kdeconnect.NetworkPacket; import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.PluginFactory; @@ -107,7 +107,7 @@ public class FindMyPhonePlugin extends Plugin { @Override public boolean onPacketReceived(NetworkPacket np) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || MyApplication.isInForeground()) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || LifecycleHelper.isInForeground()) { Intent intent = new Intent(context, FindMyPhoneActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(FindMyPhoneActivity.EXTRA_DEVICE_ID, device.getDeviceId()); diff --git a/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneReceiver.java b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneReceiver.java index afea3961..9595e474 100644 --- a/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneReceiver.java +++ b/src/org/kde/kdeconnect/Plugins/FindMyPhonePlugin/FindMyPhoneReceiver.java @@ -5,7 +5,7 @@ import android.content.Context; import android.content.Intent; import android.util.Log; -import org.kde.kdeconnect.BackgroundService; +import org.kde.kdeconnect.KdeConnect; public class FindMyPhoneReceiver extends BroadcastReceiver { final static String ACTION_FOUND_IT = "org.kde.kdeconnect.Plugins.FindMyPhonePlugin.foundIt"; @@ -29,7 +29,10 @@ public class FindMyPhoneReceiver extends BroadcastReceiver { } String deviceId = intent.getStringExtra(EXTRA_DEVICE_ID); - - BackgroundService.RunWithPlugin(context, deviceId, FindMyPhonePlugin.class, FindMyPhonePlugin::stopPlaying); + FindMyPhonePlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, FindMyPhonePlugin.class); + if (plugin == null) { + return; + } + plugin.stopPlaying(); } } diff --git a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/ComposeSendActivity.kt b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/ComposeSendActivity.kt index e14155f7..b648ac08 100644 --- a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/ComposeSendActivity.kt +++ b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/ComposeSendActivity.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.preference.PreferenceManager import com.google.accompanist.themeadapter.material3.Mdc3Theme -import org.kde.kdeconnect.BackgroundService +import org.kde.kdeconnect.KdeConnect import org.kde.kdeconnect.NetworkPacket import org.kde.kdeconnect.UserInterface.compose.KdeTextButton import org.kde.kdeconnect.UserInterface.compose.KdeTextField @@ -72,9 +72,12 @@ class ComposeSendActivity : AppCompatActivity() { } catch (e: Exception) { Log.e("KDE/ComposeSend", "Exception", e) } - BackgroundService.RunWithPlugin( - this, deviceId, MousePadPlugin::class.java - ) { plugin: MousePadPlugin -> plugin.sendKeyboardPacket(np) } + val plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MousePadPlugin::class.java) + if (plugin == null) { + finish(); + return; + } + plugin.sendKeyboardPacket(np); } private fun sendComposed() { diff --git a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/KeyListenerView.java b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/KeyListenerView.java index 50aca849..1ba4a9be 100644 --- a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/KeyListenerView.java +++ b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/KeyListenerView.java @@ -14,7 +14,7 @@ import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; -import org.kde.kdeconnect.BackgroundService; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect.NetworkPacket; public class KeyListenerView extends View { @@ -89,7 +89,11 @@ public class KeyListenerView extends View { } private void sendKeyPressPacket(final NetworkPacket np) { - BackgroundService.RunWithPlugin(getContext(), deviceId, MousePadPlugin.class, plugin -> plugin.sendKeyboardPacket(np)); + MousePadPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MousePadPlugin.class); + if (plugin == null) { + return; + } + plugin.sendKeyboardPacket(np); } @Override diff --git a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java index 72b1d640..b6d1115e 100644 --- a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java +++ b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java @@ -22,10 +22,12 @@ import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.Toast; + import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import androidx.preference.PreferenceManager; -import org.kde.kdeconnect.BackgroundService; + +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect.UserInterface.PluginSettingsActivity; import org.kde.kdeconnect_tp.R; @@ -115,7 +117,12 @@ public class MousePadActivity final float nX = X; final float nY = Y; - BackgroundService.RunWithPlugin(this, deviceId, MousePadPlugin.class, plugin -> plugin.sendMouseDelta(nX, nY)); + MousePadPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MousePadPlugin.class); + if (plugin == null) { + finish(); + return; + } + plugin.sendMouseDelta(nX, nY); } @Override @@ -235,24 +242,30 @@ public class MousePadActivity startActivity(intent); return true; } else if (id == R.id.menu_show_keyboard) { - BackgroundService.RunWithPlugin(this, deviceId, MousePadPlugin.class, plugin -> { - if (plugin.isKeyboardEnabled()) { - showKeyboard(); - } else { - Toast toast = Toast.makeText(this, R.string.mousepad_keyboard_input_not_supported, Toast.LENGTH_SHORT); - toast.show(); - } - }); + MousePadPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MousePadPlugin.class); + if (plugin == null) { + finish(); + return true; + } + if (plugin.isKeyboardEnabled()) { + showKeyboard(); + } else { + Toast toast = Toast.makeText(this, R.string.mousepad_keyboard_input_not_supported, Toast.LENGTH_SHORT); + toast.show(); + } return true; } else if (id == R.id.menu_open_compose_send) { - BackgroundService.RunWithPlugin(this, deviceId, MousePadPlugin.class, plugin -> { - if (plugin.isKeyboardEnabled()) { - showCompose(); - } else { - Toast toast = Toast.makeText(this, R.string.mousepad_keyboard_input_not_supported, Toast.LENGTH_SHORT); - toast.show(); - } - }); + MousePadPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MousePadPlugin.class); + if (plugin == null) { + finish(); + return true; + } + if (plugin.isKeyboardEnabled()) { + showCompose(); + } else { + Toast toast = Toast.makeText(this, R.string.mousepad_keyboard_input_not_supported, Toast.LENGTH_SHORT); + toast.show(); + } return true; } else { return super.onOptionsItemSelected(item); @@ -288,20 +301,23 @@ public class MousePadActivity mCurrentX = event.getX(); mCurrentY = event.getY(); - BackgroundService.RunWithPlugin(this, deviceId, MousePadPlugin.class, plugin -> { - float deltaX = (mCurrentX - mPrevX) * displayDpiMultiplier * mCurrentSensitivity; - float deltaY = (mCurrentY - mPrevY) * displayDpiMultiplier * mCurrentSensitivity; + MousePadPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MousePadPlugin.class); + if (plugin == null) { + finish(); + return true; + } - // Run the mouse delta through the pointer acceleration profile - mPointerAccelerationProfile.touchMoved(deltaX, deltaY, event.getEventTime()); - mouseDelta = mPointerAccelerationProfile.commitAcceleratedMouseDelta(mouseDelta); + float deltaX = (mCurrentX - mPrevX) * displayDpiMultiplier * mCurrentSensitivity; + float deltaY = (mCurrentY - mPrevY) * displayDpiMultiplier * mCurrentSensitivity; - plugin.sendMouseDelta(mouseDelta.x, mouseDelta.y); + // Run the mouse delta through the pointer acceleration profile + mPointerAccelerationProfile.touchMoved(deltaX, deltaY, event.getEventTime()); + mouseDelta = mPointerAccelerationProfile.commitAcceleratedMouseDelta(mouseDelta); - mPrevX = mCurrentX; - mPrevY = mCurrentY; - }); + plugin.sendMouseDelta(mouseDelta.x, mouseDelta.y); + mPrevX = mCurrentX; + mPrevY = mCurrentY; break; } @@ -361,7 +377,12 @@ public class MousePadActivity @Override public void onLongPress(MotionEvent e) { getWindow().getDecorView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); - BackgroundService.RunWithPlugin(this, deviceId, MousePadPlugin.class, MousePadPlugin::sendSingleHold); + MousePadPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MousePadPlugin.class); + if (plugin == null) { + finish(); + return; + } + plugin.sendSingleHold(); } @Override @@ -388,7 +409,12 @@ public class MousePadActivity @Override public boolean onDoubleTap(MotionEvent e) { - BackgroundService.RunWithPlugin(this, deviceId, MousePadPlugin.class, MousePadPlugin::sendDoubleClick); + MousePadPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MousePadPlugin.class); + if (plugin == null) { + finish(); + return true; + } + plugin.sendDoubleClick(); return true; } @@ -438,19 +464,39 @@ public class MousePadActivity private void sendLeftClick() { - BackgroundService.RunWithPlugin(this, deviceId, MousePadPlugin.class, MousePadPlugin::sendLeftClick); + MousePadPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MousePadPlugin.class); + if (plugin == null) { + finish(); + return; + } + plugin.sendLeftClick(); } private void sendMiddleClick() { - BackgroundService.RunWithPlugin(this, deviceId, MousePadPlugin.class, MousePadPlugin::sendMiddleClick); + MousePadPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MousePadPlugin.class); + if (plugin == null) { + finish(); + return; + } + plugin.sendMiddleClick(); } private void sendRightClick() { - BackgroundService.RunWithPlugin(this, deviceId, MousePadPlugin.class, MousePadPlugin::sendRightClick); + MousePadPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MousePadPlugin.class); + if (plugin == null) { + finish(); + return; + } + plugin.sendRightClick(); } private void sendScroll(final float y) { - BackgroundService.RunWithPlugin(this, deviceId, MousePadPlugin.class, plugin -> plugin.sendScroll(0, y)); + MousePadPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MousePadPlugin.class); + if (plugin == null) { + finish(); + return; + } + plugin.sendScroll(0, y); } private void showKeyboard() { diff --git a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/SendKeystrokesToHostActivity.java b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/SendKeystrokesToHostActivity.java index 62849198..7fee2c83 100644 --- a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/SendKeystrokesToHostActivity.java +++ b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/SendKeystrokesToHostActivity.java @@ -19,11 +19,11 @@ import androidx.appcompat.app.AppCompatActivity; import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Helpers.SafeTextChecker; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect.NetworkPacket; import org.kde.kdeconnect.UserInterface.List.EntryItemWithIcon; import org.kde.kdeconnect.UserInterface.List.ListAdapter; import org.kde.kdeconnect.UserInterface.List.SectionItem; -import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.databinding.ActivitySendkeystrokesBinding; @@ -89,7 +89,7 @@ public class SendKeystrokesToHostActivity extends AppCompatActivity { // If we trust the sending app, check if there is only one device paired / reachable... if (contentIsOkay) { - List reachableDevices = BackgroundService.getInstance().getDevices().values().stream() + List reachableDevices = KdeConnect.getInstance().getDevices().values().stream() .filter(Device::isReachable) .limit(2) // we only need the first two; if its more than one, we need to show the user the device-selection .collect(Collectors.toList()); @@ -103,16 +103,9 @@ public class SendKeystrokesToHostActivity extends AppCompatActivity { } } - - // subscribe to new connected devices - BackgroundService.RunCommand(this, service -> { - service.onNetworkChange(); - service.addDeviceListChangedCallback("SendKeystrokesToHostActivity", unused -> updateDeviceList()); - }); - - // list all currently connected devices + KdeConnect.getInstance().addDeviceListChangedCallback("SendKeystrokesToHostActivity", () -> runOnUiThread(this::updateDeviceList)); + BackgroundService.ForceRefreshConnections(this); // force a network re-discover updateDeviceList(); - } else { Toast.makeText(getApplicationContext(), R.string.sendkeystrokes_wrong_data, Toast.LENGTH_LONG).show(); finish(); @@ -122,7 +115,7 @@ public class SendKeystrokesToHostActivity extends AppCompatActivity { @Override protected void onStop() { - BackgroundService.RunCommand(this, service -> service.removeDeviceListChangedCallback("SendKeystrokesToHostActivity")); + KdeConnect.getInstance().removeDeviceListChangedCallback("SendKeystrokesToHostActivity"); super.onStop(); } @@ -131,7 +124,12 @@ public class SendKeystrokesToHostActivity extends AppCompatActivity { if (binding.textToSend.getText() != null && (toSend = binding.textToSend.getText().toString().trim()).length() > 0) { final NetworkPacket np = new NetworkPacket(MousePadPlugin.PACKET_TYPE_MOUSEPAD_REQUEST); np.set("key", toSend); - BackgroundService.RunWithPlugin(this, deviceId.getDeviceId(), MousePadPlugin.class, plugin -> plugin.sendKeyboardPacket(np)); + MousePadPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId.getDeviceId(), MousePadPlugin.class); + if (plugin == null) { + finish(); + return; + } + plugin.sendKeyboardPacket(np); Toast.makeText( getApplicationContext(), getString(R.string.sendkeystrokes_sent_text, toSend, deviceId.getName()), @@ -143,41 +141,37 @@ public class SendKeystrokesToHostActivity extends AppCompatActivity { private void updateDeviceList() { - BackgroundService.RunCommand(this, service -> { + Collection devices = KdeConnect.getInstance().getDevices().values(); + final ArrayList devicesList = new ArrayList<>(); + final ArrayList items = new ArrayList<>(); - Collection devices = service.getDevices().values(); - final ArrayList devicesList = new ArrayList<>(); - final ArrayList items = new ArrayList<>(); + SectionItem section = new SectionItem(getString(R.string.sendkeystrokes_send_to)); + items.add(section); - SectionItem section = new SectionItem(getString(R.string.sendkeystrokes_send_to)); - items.add(section); - - for (Device d : devices) { - if (d.isReachable() && d.isPaired()) { - devicesList.add(d); - items.add(new EntryItemWithIcon(d.getName(), d.getIcon())); - section.isEmpty = false; - } + for (Device d : devices) { + if (d.isReachable() && d.isPaired()) { + devicesList.add(d); + items.add(new EntryItemWithIcon(d.getName(), d.getIcon())); + section.isEmpty = false; } - runOnUiThread(() -> { - binding.devicesList.setAdapter(new ListAdapter(SendKeystrokesToHostActivity.this, items)); - binding.devicesList.setOnItemClickListener((adapterView, view, i, l) -> { - Device device = devicesList.get(i - 1); // NOTE: -1 because of the title! - sendKeys(device); - this.finish(); // close the activity - }); - }); + } - // only one device is connected and we trust the text to send -> send it and close the activity. - // Usually we already check it in `onStart` - but if the BackgroundService was not started/connected to the host - // it will not have the deviceList in memory. Use this callback as second chance (but it will flicker a bit, because the activity might - // already been visible and get closed again quickly) - if (devicesList.size() == 1 && contentIsOkay) { - Device device = devicesList.get(0); - sendKeys(device); - this.finish(); // close the activity - } + binding.devicesList.setAdapter(new ListAdapter(SendKeystrokesToHostActivity.this, items)); + binding.devicesList.setOnItemClickListener((adapterView, view, i, l) -> { + Device device = devicesList.get(i - 1); // NOTE: -1 because of the title! + sendKeys(device); + this.finish(); // close the activity }); + + // only one device is connected and we trust the text to send -> send it and close the activity. + // Usually we already check it in `onStart` - but if the BackgroundService was not started/connected to the host + // it will not have the deviceList in memory. Use this callback as second chance (but it will flicker a bit, because the activity might + // already been visible and get closed again quickly) + if (devicesList.size() == 1 && contentIsOkay) { + Device device = devicesList.get(0); + sendKeys(device); + this.finish(); // close the activity + } } } diff --git a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaNotificationReceiver.java b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaNotificationReceiver.java index 18a4cdfa..c5283860 100644 --- a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaNotificationReceiver.java +++ b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaNotificationReceiver.java @@ -11,8 +11,7 @@ import android.content.Context; import android.content.Intent; import android.support.v4.media.session.MediaSessionCompat; -import org.kde.kdeconnect.BackgroundService; -import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.KdeConnect; /** * Called when the mpris media notification's buttons are pressed @@ -29,7 +28,7 @@ public class MprisMediaNotificationReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - //First case: buttons send by other applications via the media session APIs + //First case: buttons send by other applications via the media session APIs. They don't target a specific device. if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) { //Route these buttons to the media session, which will handle them MediaSessionCompat mediaSession = MprisMediaSession.getMediaSession(); @@ -39,13 +38,10 @@ public class MprisMediaNotificationReceiver extends BroadcastReceiver { //Second case: buttons on the notification, which we created ourselves //Get the correct device, the mpris plugin and the mpris player - BackgroundService service = BackgroundService.getInstance(); - if (service == null) return; - Device device = service.getDevice(intent.getStringExtra(EXTRA_DEVICE_ID)); - if (device == null) return; - MprisPlugin mpris = device.getPlugin(MprisPlugin.class); - if (mpris == null) return; - MprisPlugin.MprisPlayer player = mpris.getPlayerStatus(intent.getStringExtra(EXTRA_MPRIS_PLAYER)); + String deviceId = intent.getStringExtra(EXTRA_DEVICE_ID); + MprisPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MprisPlugin.class); + if (plugin == null) return; + MprisPlugin.MprisPlayer player = plugin.getPlayerStatus(intent.getStringExtra(EXTRA_MPRIS_PLAYER)); if (player == null) return; //Forward the action to the player @@ -65,7 +61,9 @@ public class MprisMediaNotificationReceiver extends BroadcastReceiver { case ACTION_CLOSE_NOTIFICATION: //The user dismissed the notification: actually handle its removal correctly MprisMediaSession.getInstance().closeMediaNotification(); + break; } } } + } diff --git a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaSession.java b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaSession.java index 7580d297..4a2c9167 100644 --- a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaSession.java +++ b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaSession.java @@ -14,6 +14,7 @@ import android.content.SharedPreferences; import android.graphics.Bitmap; import android.media.AudioManager; import android.os.Build; +import android.os.Handler; import android.preference.PreferenceManager; import android.service.notification.StatusBarNotification; import android.support.v4.media.MediaMetadataCompat; @@ -27,9 +28,9 @@ import androidx.core.app.TaskStackBuilder; import androidx.core.content.ContextCompat; import androidx.media.app.NotificationCompat.MediaStyle; -import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Helpers.NotificationHelper; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationReceiver; import org.kde.kdeconnect.Plugins.SystemVolumePlugin.SystemVolumePlugin; import org.kde.kdeconnect.Plugins.SystemVolumePlugin.SystemVolumeProvider; @@ -112,20 +113,20 @@ public class MprisMediaSession implements *

* Can be called multiple times, once for each device * - * @param _context The context - * @param mpris The mpris plugin - * @param device The device id + * @param context The context + * @param plugin The mpris plugin + * @param device The device id */ - public void onCreate(Context _context, MprisPlugin mpris, String device) { + public void onCreate(Context context, MprisPlugin plugin, String device) { if (mprisDevices.isEmpty()) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(_context); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); prefs.registerOnSharedPreferenceChangeListener(this); } - context = _context; + this.context = context; mprisDevices.add(device); - mpris.setPlayerListUpdatedHandler("media_notification", this::updateMediaNotification); - mpris.setPlayerStatusUpdatedHandler("media_notification", this::updateMediaNotification); + plugin.setPlayerListUpdatedHandler("media_notification", this::updateMediaNotification); + plugin.setPlayerStatusUpdatedHandler("media_notification", this::updateMediaNotification); NotificationReceiver.RunCommand(context, service -> { @@ -137,8 +138,6 @@ public class MprisMediaSession implements onListenerConnected(service); } }); - - updateMediaNotification(); } /** @@ -146,13 +145,13 @@ public class MprisMediaSession implements *

* Can be called multiple times, once for each device * - * @param mpris The mpris plugin + * @param plugin The mpris plugin * @param device The device id */ - public void onDestroy(MprisPlugin mpris, String device) { + public void onDestroy(MprisPlugin plugin, String device) { mprisDevices.remove(device); - mpris.removePlayerStatusUpdatedHandler("media_notification"); - mpris.removePlayerListUpdatedHandler("media_notification"); + plugin.removePlayerStatusUpdatedHandler("media_notification"); + plugin.removePlayerListUpdatedHandler("media_notification"); updateMediaNotification(); if (mprisDevices.isEmpty()) { @@ -166,21 +165,19 @@ public class MprisMediaSession implements *

* Prefers playing devices/mpris players, but tries to keep displaying the same * player and device, while possible. - * - * @param service The background service */ - private void updateCurrentPlayer(BackgroundService service) { - Pair player = findPlayer(service); + private void updateCurrentPlayer() { + Pair player = findPlayer(); //Update the last-displayed device and player notificationDevice = player.first == null ? null : player.first.getDeviceId(); notificationPlayer = player.second; } - private Pair findPlayer(BackgroundService service) { + private Pair findPlayer() { //First try the previously displayed player (if still playing) or the previous displayed device (otherwise) if (notificationDevice != null && mprisDevices.contains(notificationDevice)) { - Device device = service.getDevice(notificationDevice); + Device device = KdeConnect.getInstance().getDevice(notificationDevice); MprisPlugin.MprisPlayer player; if (notificationPlayer != null && notificationPlayer.isPlaying()) { @@ -194,7 +191,7 @@ public class MprisMediaSession implements } // Try a different player from another device - for (Device otherDevice : service.getDevices().values()) { + for (Device otherDevice : KdeConnect.getInstance().getDevices().values()) { MprisPlugin.MprisPlayer player = getPlayerFromDevice(otherDevice, null); if (player != null) { return new Pair<>(otherDevice, player); @@ -205,7 +202,7 @@ public class MprisMediaSession implements // This will succeed if it's paused: // that allows pausing and subsequently resuming via the notification if (notificationDevice != null && mprisDevices.contains(notificationDevice)) { - Device device = service.getDevice(notificationDevice); + Device device = KdeConnect.getInstance().getDevice(notificationDevice); MprisPlugin.MprisPlayer player = getPlayerFromDevice(device, notificationPlayer); if (player != null) { @@ -244,212 +241,211 @@ public class MprisMediaSession implements } private void updateRemoteDeviceVolumeControl() { - // Volume control feature is only available from Lollipop onwards - BackgroundService.RunWithPlugin(context, notificationDevice, SystemVolumePlugin.class, plugin -> { - SystemVolumeProvider systemVolumeProvider = SystemVolumeProvider.fromPlugin(plugin); - systemVolumeProvider.addStateListener(this); - systemVolumeProvider.startTrackingVolumeKeys(); - }); + SystemVolumePlugin plugin = KdeConnect.getInstance().getDevicePlugin(notificationDevice, SystemVolumePlugin.class); + if (plugin == null) { + return; + } + SystemVolumeProvider systemVolumeProvider = SystemVolumeProvider.fromPlugin(plugin); + systemVolumeProvider.addStateListener(this); + systemVolumeProvider.startTrackingVolumeKeys(); } /** * Update the media control notification */ private void updateMediaNotification() { - BackgroundService.RunCommand(context, service -> { - //If the user disabled the media notification, do not show it - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - if (!prefs.getBoolean(context.getString(R.string.mpris_notification_key), true)) { - closeMediaNotification(); - return; + + //If the user disabled the media notification, do not show it + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + if (!prefs.getBoolean(context.getString(R.string.mpris_notification_key), true)) { + closeMediaNotification(); + return; + } + + if (mediaSession == null) { + mediaSession = new MediaSessionCompat(context, MPRIS_MEDIA_SESSION_TAG); + mediaSession.setCallback(mediaSessionCallback, new Handler(context.getMainLooper())); + // Deprecated flags not required in Build.VERSION_CODES.O and later + mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); + } + + //Make sure our information is up-to-date + updateCurrentPlayer(); + + //If the player disappeared (and no other playing one found), just remove the notification + if (notificationPlayer == null) { + closeMediaNotification(); + return; + } + + updateRemoteDeviceVolumeControl(); + + MediaMetadataCompat.Builder metadata = new MediaMetadataCompat.Builder(); + + //Fallback because older KDE connect versions do not support getTitle() + if (!notificationPlayer.getTitle().isEmpty()) { + metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getTitle()); + } else { + metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getCurrentSong()); + } + if (!notificationPlayer.getArtist().isEmpty()) { + metadata.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, notificationPlayer.getArtist()); + metadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, notificationPlayer.getArtist()); + } + if (!notificationPlayer.getAlbum().isEmpty()) { + metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, notificationPlayer.getAlbum()); + } + if (notificationPlayer.getLength() > 0) { + metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, notificationPlayer.getLength()); + } + + Bitmap albumArt = notificationPlayer.getAlbumArt(); + if (albumArt != null) { + metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt); + } + + mediaSession.setMetadata(metadata.build()); + PlaybackStateCompat.Builder playbackState = new PlaybackStateCompat.Builder(); + + if (notificationPlayer.isPlaying()) { + playbackState.setState(PlaybackStateCompat.STATE_PLAYING, notificationPlayer.getPosition(), 1.0f); + } else { + playbackState.setState(PlaybackStateCompat.STATE_PAUSED, notificationPlayer.getPosition(), 0.0f); + } + + //Create all actions (previous/play/pause/next) + Intent iPlay = new Intent(context, MprisMediaNotificationReceiver.class); + iPlay.setAction(MprisMediaNotificationReceiver.ACTION_PLAY); + iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice); + iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayerName()); + PendingIntent piPlay = PendingIntent.getBroadcast(context, 0, iPlay, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); + NotificationCompat.Action.Builder aPlay = new NotificationCompat.Action.Builder( + R.drawable.ic_play_white, context.getString(R.string.mpris_play), piPlay); + + Intent iPause = new Intent(context, MprisMediaNotificationReceiver.class); + iPause.setAction(MprisMediaNotificationReceiver.ACTION_PAUSE); + iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice); + iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayerName()); + PendingIntent piPause = PendingIntent.getBroadcast(context, 0, iPause, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); + NotificationCompat.Action.Builder aPause = new NotificationCompat.Action.Builder( + R.drawable.ic_pause_white, context.getString(R.string.mpris_pause), piPause); + + Intent iPrevious = new Intent(context, MprisMediaNotificationReceiver.class); + iPrevious.setAction(MprisMediaNotificationReceiver.ACTION_PREVIOUS); + iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice); + iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayerName()); + PendingIntent piPrevious = PendingIntent.getBroadcast(context, 0, iPrevious, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); + NotificationCompat.Action.Builder aPrevious = new NotificationCompat.Action.Builder( + R.drawable.ic_previous_white, context.getString(R.string.mpris_previous), piPrevious); + + Intent iNext = new Intent(context, MprisMediaNotificationReceiver.class); + iNext.setAction(MprisMediaNotificationReceiver.ACTION_NEXT); + iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice); + iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayerName()); + PendingIntent piNext = PendingIntent.getBroadcast(context, 0, iNext, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); + NotificationCompat.Action.Builder aNext = new NotificationCompat.Action.Builder( + R.drawable.ic_next_white, context.getString(R.string.mpris_next), piNext); + + Intent iOpenActivity = new Intent(context, MprisActivity.class); + iOpenActivity.putExtra("deviceId", notificationDevice); + iOpenActivity.putExtra("player", notificationPlayer.getPlayerName()); + + PendingIntent piOpenActivity = TaskStackBuilder.create(context) + .addNextIntentWithParentStack(iOpenActivity) + .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); + + NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NotificationHelper.Channels.MEDIA_CONTROL); + + notification + .setAutoCancel(false) + .setContentIntent(piOpenActivity) + .setSmallIcon(R.drawable.ic_play_white) + .setShowWhen(false) + .setColor(ContextCompat.getColor(context, R.color.primary)) + .setVisibility(androidx.core.app.NotificationCompat.VISIBILITY_PUBLIC) + .setSubText(KdeConnect.getInstance().getDevice(notificationDevice).getName()); + + if (!notificationPlayer.getTitle().isEmpty()) { + notification.setContentTitle(notificationPlayer.getTitle()); + } else { + notification.setContentTitle(notificationPlayer.getCurrentSong()); + } + //Only set the notification body text if we have an author and/or album + if (!notificationPlayer.getArtist().isEmpty() && !notificationPlayer.getAlbum().isEmpty()) { + notification.setContentText(notificationPlayer.getArtist() + " - " + notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayerName() + ")"); + } else if (!notificationPlayer.getArtist().isEmpty()) { + notification.setContentText(notificationPlayer.getArtist() + " (" + notificationPlayer.getPlayerName() + ")"); + } else if (!notificationPlayer.getAlbum().isEmpty()) { + notification.setContentText(notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayerName() + ")"); + } else { + notification.setContentText(notificationPlayer.getPlayerName()); + } + + if (albumArt != null) { + notification.setLargeIcon(albumArt); + } + + if (!notificationPlayer.isPlaying()) { + Intent iCloseNotification = new Intent(context, MprisMediaNotificationReceiver.class); + iCloseNotification.setAction(MprisMediaNotificationReceiver.ACTION_CLOSE_NOTIFICATION); + iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice); + iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayerName()); + PendingIntent piCloseNotification = PendingIntent.getBroadcast(context, 0, iCloseNotification, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); + notification.setDeleteIntent(piCloseNotification); + } + + //Add media control actions + int numActions = 0; + long playbackActions = 0; + if (notificationPlayer.isGoPreviousAllowed()) { + notification.addAction(aPrevious.build()); + playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; + ++numActions; + } + if (notificationPlayer.isPlaying() && notificationPlayer.isPauseAllowed()) { + notification.addAction(aPause.build()); + playbackActions |= PlaybackStateCompat.ACTION_PAUSE; + ++numActions; + } + if (!notificationPlayer.isPlaying() && notificationPlayer.isPlayAllowed()) { + notification.addAction(aPlay.build()); + playbackActions |= PlaybackStateCompat.ACTION_PLAY; + ++numActions; + } + if (notificationPlayer.isGoNextAllowed()) { + notification.addAction(aNext.build()); + playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT; + ++numActions; + } + // Documentation says that this was added in Lollipop (21) but it seems to cause crashes on < Pie (28) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + if (notificationPlayer.isSeekAllowed()) { + playbackActions |= PlaybackStateCompat.ACTION_SEEK_TO; } + } + playbackState.setActions(playbackActions); + mediaSession.setPlaybackState(playbackState.build()); - //Make sure our information is up-to-date - updateCurrentPlayer(service); + //Only allow deletion if no music is notificationPlayer + notification.setOngoing(notificationPlayer.isPlaying()); - //If the player disappeared (and no other playing one found), just remove the notification - if (notificationPlayer == null) { - closeMediaNotification(); - return; - } + //Use the MediaStyle notification, so it feels like other media players. That also allows adding actions + MediaStyle mediaStyle = new MediaStyle(); + if (numActions == 1) { + mediaStyle.setShowActionsInCompactView(0); + } else if (numActions == 2) { + mediaStyle.setShowActionsInCompactView(0, 1); + } else if (numActions >= 3) { + mediaStyle.setShowActionsInCompactView(0, 1, 2); + } + mediaStyle.setMediaSession(mediaSession.getSessionToken()); + notification.setStyle(mediaStyle); + notification.setGroup("MprisMediaSession"); - //Update the metadata and playback status - if (mediaSession == null) { - mediaSession = new MediaSessionCompat(context, MPRIS_MEDIA_SESSION_TAG); - mediaSession.setCallback(mediaSessionCallback); - // Deprecated flags not required in Build.VERSION_CODES.O and later - mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); - } - - updateRemoteDeviceVolumeControl(); - - MediaMetadataCompat.Builder metadata = new MediaMetadataCompat.Builder(); - - //Fallback because older KDE connect versions do not support getTitle() - if (!notificationPlayer.getTitle().isEmpty()) { - metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getTitle()); - } else { - metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getCurrentSong()); - } - if (!notificationPlayer.getArtist().isEmpty()) { - metadata.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, notificationPlayer.getArtist()); - metadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, notificationPlayer.getArtist()); - } - if (!notificationPlayer.getAlbum().isEmpty()) { - metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, notificationPlayer.getAlbum()); - } - if (notificationPlayer.getLength() > 0) { - metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, notificationPlayer.getLength()); - } - - Bitmap albumArt = notificationPlayer.getAlbumArt(); - if (albumArt != null) { - metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt); - } - - mediaSession.setMetadata(metadata.build()); - PlaybackStateCompat.Builder playbackState = new PlaybackStateCompat.Builder(); - - if (notificationPlayer.isPlaying()) { - playbackState.setState(PlaybackStateCompat.STATE_PLAYING, notificationPlayer.getPosition(), 1.0f); - } else { - playbackState.setState(PlaybackStateCompat.STATE_PAUSED, notificationPlayer.getPosition(), 0.0f); - } - - //Create all actions (previous/play/pause/next) - Intent iPlay = new Intent(service, MprisMediaNotificationReceiver.class); - iPlay.setAction(MprisMediaNotificationReceiver.ACTION_PLAY); - iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice); - iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer()); - PendingIntent piPlay = PendingIntent.getBroadcast(service, 0, iPlay, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); - NotificationCompat.Action.Builder aPlay = new NotificationCompat.Action.Builder( - R.drawable.ic_play_white, service.getString(R.string.mpris_play), piPlay); - - Intent iPause = new Intent(service, MprisMediaNotificationReceiver.class); - iPause.setAction(MprisMediaNotificationReceiver.ACTION_PAUSE); - iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice); - iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer()); - PendingIntent piPause = PendingIntent.getBroadcast(service, 0, iPause, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); - NotificationCompat.Action.Builder aPause = new NotificationCompat.Action.Builder( - R.drawable.ic_pause_white, service.getString(R.string.mpris_pause), piPause); - - Intent iPrevious = new Intent(service, MprisMediaNotificationReceiver.class); - iPrevious.setAction(MprisMediaNotificationReceiver.ACTION_PREVIOUS); - iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice); - iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer()); - PendingIntent piPrevious = PendingIntent.getBroadcast(service, 0, iPrevious, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); - NotificationCompat.Action.Builder aPrevious = new NotificationCompat.Action.Builder( - R.drawable.ic_previous_white, service.getString(R.string.mpris_previous), piPrevious); - - Intent iNext = new Intent(service, MprisMediaNotificationReceiver.class); - iNext.setAction(MprisMediaNotificationReceiver.ACTION_NEXT); - iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice); - iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer()); - PendingIntent piNext = PendingIntent.getBroadcast(service, 0, iNext, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); - NotificationCompat.Action.Builder aNext = new NotificationCompat.Action.Builder( - R.drawable.ic_next_white, service.getString(R.string.mpris_next), piNext); - - Intent iOpenActivity = new Intent(service, MprisActivity.class); - iOpenActivity.putExtra("deviceId", notificationDevice); - iOpenActivity.putExtra("player", notificationPlayer.getPlayer()); - - PendingIntent piOpenActivity = TaskStackBuilder.create(context) - .addNextIntentWithParentStack(iOpenActivity) - .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); - - NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NotificationHelper.Channels.MEDIA_CONTROL); - - notification - .setAutoCancel(false) - .setContentIntent(piOpenActivity) - .setSmallIcon(R.drawable.ic_play_white) - .setShowWhen(false) - .setColor(ContextCompat.getColor(service, R.color.primary)) - .setVisibility(androidx.core.app.NotificationCompat.VISIBILITY_PUBLIC) - .setSubText(service.getDevice(notificationDevice).getName()); - - if (!notificationPlayer.getTitle().isEmpty()) { - notification.setContentTitle(notificationPlayer.getTitle()); - } else { - notification.setContentTitle(notificationPlayer.getCurrentSong()); - } - //Only set the notification body text if we have an author and/or album - if (!notificationPlayer.getArtist().isEmpty() && !notificationPlayer.getAlbum().isEmpty()) { - notification.setContentText(notificationPlayer.getArtist() + " - " + notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayer() + ")"); - } else if (!notificationPlayer.getArtist().isEmpty()) { - notification.setContentText(notificationPlayer.getArtist() + " (" + notificationPlayer.getPlayer() + ")"); - } else if (!notificationPlayer.getAlbum().isEmpty()) { - notification.setContentText(notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayer() + ")"); - } else { - notification.setContentText(notificationPlayer.getPlayer()); - } - - if (albumArt != null) { - notification.setLargeIcon(albumArt); - } - - if (!notificationPlayer.isPlaying()) { - Intent iCloseNotification = new Intent(service, MprisMediaNotificationReceiver.class); - iCloseNotification.setAction(MprisMediaNotificationReceiver.ACTION_CLOSE_NOTIFICATION); - iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice); - iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer()); - PendingIntent piCloseNotification = PendingIntent.getBroadcast(service, 0, iCloseNotification, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); - notification.setDeleteIntent(piCloseNotification); - } - - //Add media control actions - int numActions = 0; - long playbackActions = 0; - if (notificationPlayer.isGoPreviousAllowed()) { - notification.addAction(aPrevious.build()); - playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; - ++numActions; - } - if (notificationPlayer.isPlaying() && notificationPlayer.isPauseAllowed()) { - notification.addAction(aPause.build()); - playbackActions |= PlaybackStateCompat.ACTION_PAUSE; - ++numActions; - } - if (!notificationPlayer.isPlaying() && notificationPlayer.isPlayAllowed()) { - notification.addAction(aPlay.build()); - playbackActions |= PlaybackStateCompat.ACTION_PLAY; - ++numActions; - } - if (notificationPlayer.isGoNextAllowed()) { - notification.addAction(aNext.build()); - playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT; - ++numActions; - } - // Documentation says that this was added in Lollipop (21) but it seems to cause crashes on < Pie (28) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - if (notificationPlayer.isSeekAllowed()) { - playbackActions |= PlaybackStateCompat.ACTION_SEEK_TO; - } - } - playbackState.setActions(playbackActions); - mediaSession.setPlaybackState(playbackState.build()); - - //Only allow deletion if no music is notificationPlayer - notification.setOngoing(notificationPlayer.isPlaying()); - - //Use the MediaStyle notification, so it feels like other media players. That also allows adding actions - MediaStyle mediaStyle = new MediaStyle(); - if (numActions == 1) { - mediaStyle.setShowActionsInCompactView(0); - } else if (numActions == 2) { - mediaStyle.setShowActionsInCompactView(0, 1); - } else if (numActions >= 3) { - mediaStyle.setShowActionsInCompactView(0, 1, 2); - } - mediaStyle.setMediaSession(mediaSession.getSessionToken()); - notification.setStyle(mediaStyle); - notification.setGroup("MprisMediaSession"); - - //Display the notification - mediaSession.setActive(true); - final NotificationManager nm = ContextCompat.getSystemService(context, NotificationManager.class); - nm.notify(MPRIS_MEDIA_NOTIFICATION_ID, notification.build()); - }); + //Display the notification + mediaSession.setActive(true); + final NotificationManager nm = ContextCompat.getSystemService(context, NotificationManager.class); + nm.notify(MPRIS_MEDIA_NOTIFICATION_ID, notification.build()); } public void closeMediaNotification() { diff --git a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisNowPlayingFragment.java b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisNowPlayingFragment.java index 66115372..fa4c0edd 100644 --- a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisNowPlayingFragment.java +++ b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisNowPlayingFragment.java @@ -1,7 +1,6 @@ package org.kde.kdeconnect.Plugins.MprisPlugin; import android.content.ActivityNotFoundException; -import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; @@ -10,7 +9,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; -import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -28,12 +26,9 @@ import androidx.core.graphics.drawable.DrawableCompat; import androidx.fragment.app.Fragment; import org.apache.commons.lang3.ArrayUtils; -import org.kde.kdeconnect.Backends.BaseLink; -import org.kde.kdeconnect.Backends.BaseLinkProvider; -import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Helpers.VideoUrlsHelper; import org.kde.kdeconnect.Helpers.VolumeHelperKt; -import org.kde.kdeconnect.NetworkPacket; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.databinding.MprisControlBinding; import org.kde.kdeconnect_tp.databinding.MprisNowPlayingBinding; @@ -49,18 +44,9 @@ public class MprisNowPlayingFragment extends Fragment implements VolumeKeyListen private MprisNowPlayingBinding activityMprisBinding; private String deviceId; private Runnable positionSeekUpdateRunnable = null; + + private String targetPlayerName = ""; private MprisPlugin.MprisPlayer targetPlayer = null; - private final BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() { - @Override - public void onConnectionReceived(NetworkPacket identityPacket, BaseLink link) { - connectToPlugin(null); - } - - @Override - public void onConnectionLost(BaseLink link) { - - } - }; public static MprisNowPlayingFragment newInstance(String deviceId) { MprisNowPlayingFragment mprisNowPlayingFragment = new MprisNowPlayingFragment(); @@ -94,211 +80,217 @@ public class MprisNowPlayingFragment extends Fragment implements VolumeKeyListen @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + activityMprisBinding = MprisNowPlayingBinding.inflate(inflater); + mprisControlBinding = activityMprisBinding.mprisControl; - if (activityMprisBinding == null) { - activityMprisBinding = MprisNowPlayingBinding.inflate(inflater); - mprisControlBinding = activityMprisBinding.mprisControl; + deviceId = requireArguments().getString(MprisPlugin.DEVICE_ID_KEY); - String targetPlayerName = ""; - Intent activityIntent = requireActivity().getIntent(); - activityIntent.getStringExtra("player"); + targetPlayerName = ""; + Intent activityIntent = requireActivity().getIntent(); + if (activityIntent.hasExtra("player")) { + targetPlayerName = activityIntent.getStringExtra("player"); activityIntent.removeExtra("player"); + } else if (savedInstanceState != null) { + targetPlayerName = savedInstanceState.getString("targetPlayer"); + } - if (TextUtils.isEmpty(targetPlayerName)) { - if (savedInstanceState != null) { - targetPlayerName = savedInstanceState.getString("targetPlayer"); - } + connectToPlugin(); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(requireContext()); + String interval_time_str = prefs.getString(getString(R.string.mpris_time_key), + getString(R.string.mpris_time_default)); + final int interval_time = Integer.parseInt(interval_time_str); + + performActionOnClick(mprisControlBinding.loopButton, p -> { + switch (p.getLoopStatus()) { + case "None": + p.setLoopStatus("Track"); + break; + case "Track": + p.setLoopStatus("Playlist"); + break; + case "Playlist": + p.setLoopStatus("None"); + break; + } + }); + + performActionOnClick(mprisControlBinding.playButton, MprisPlugin.MprisPlayer::playPause); + + performActionOnClick(mprisControlBinding.shuffleButton, p -> p.setShuffle(!p.getShuffle())); + + performActionOnClick(mprisControlBinding.prevButton, MprisPlugin.MprisPlayer::previous); + + performActionOnClick(mprisControlBinding.rewButton, p -> targetPlayer.seek(interval_time * -1)); + + performActionOnClick(mprisControlBinding.ffButton, p -> p.seek(interval_time)); + + performActionOnClick(mprisControlBinding.nextButton, MprisPlugin.MprisPlayer::next); + + performActionOnClick(mprisControlBinding.stopButton, MprisPlugin.MprisPlayer::stop); + + mprisControlBinding.volumeSeek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { } - deviceId = requireArguments().getString(MprisPlugin.DEVICE_ID_KEY); + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(requireContext()); - String interval_time_str = prefs.getString(getString(R.string.mpris_time_key), - getString(R.string.mpris_time_default)); - final int interval_time = Integer.parseInt(interval_time_str); + @Override + public void onStopTrackingTouch(final SeekBar seekBar) { + if (targetPlayer == null) return; + targetPlayer.setVolume(seekBar.getProgress()); + } + }); - BackgroundService.RunCommand(requireContext(), service -> service.addConnectionListener(connectionReceiver)); - connectToPlugin(targetPlayerName); + positionSeekUpdateRunnable = () -> { + if (!isAdded()) return; // Fragment was already detached + if (targetPlayer != null) { + mprisControlBinding.positionSeek.setProgress((int) (targetPlayer.getPosition())); + } + positionSeekUpdateHandler.removeCallbacks(positionSeekUpdateRunnable); + positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 1000); + }; + positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 200); - performActionOnClick(mprisControlBinding.loopButton, p -> { - switch (p.getLoopStatus()) { - case "None": - p.setLoopStatus("Track"); - break; - case "Track": - p.setLoopStatus("Playlist"); - break; - case "Playlist": - p.setLoopStatus("None"); - break; + mprisControlBinding.positionSeek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean byUser) { + mprisControlBinding.progressTextview.setText(milisToProgress(progress)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + positionSeekUpdateHandler.removeCallbacks(positionSeekUpdateRunnable); + } + + @Override + public void onStopTrackingTouch(final SeekBar seekBar) { + if (targetPlayer != null) { + targetPlayer.setPosition(seekBar.getProgress()); } - }); + positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 200); + } + }); - performActionOnClick(mprisControlBinding.playButton, MprisPlugin.MprisPlayer::playPause); - - performActionOnClick(mprisControlBinding.shuffleButton, p -> p.setShuffle(!p.getShuffle())); - - performActionOnClick(mprisControlBinding.prevButton, MprisPlugin.MprisPlayer::previous); - - performActionOnClick(mprisControlBinding.rewButton, p -> targetPlayer.seek(interval_time * -1)); - - performActionOnClick(mprisControlBinding.ffButton, p -> p.seek(interval_time)); - - performActionOnClick(mprisControlBinding.nextButton, MprisPlugin.MprisPlayer::next); - - performActionOnClick(mprisControlBinding.stopButton, MprisPlugin.MprisPlayer::stop); - - mprisControlBinding.volumeSeek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int i, boolean b) { - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - } - - @Override - public void onStopTrackingTouch(final SeekBar seekBar) { - BackgroundService.RunCommand(requireContext(), service -> { - if (targetPlayer == null) return; - targetPlayer.setVolume(seekBar.getProgress()); - }); - } - }); - - positionSeekUpdateRunnable = () -> { - Context context = getContext(); - if (context == null) return; // Fragment was already detached - BackgroundService.RunCommand(context, service -> { - if (targetPlayer != null) { - mprisControlBinding.positionSeek.setProgress((int) (targetPlayer.getPosition())); - } - positionSeekUpdateHandler.removeCallbacks(positionSeekUpdateRunnable); - positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 1000); - }); - }; - positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 200); - - mprisControlBinding.positionSeek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean byUser) { - mprisControlBinding.progressTextview.setText(milisToProgress(progress)); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - positionSeekUpdateHandler.removeCallbacks(positionSeekUpdateRunnable); - } - - @Override - public void onStopTrackingTouch(final SeekBar seekBar) { - BackgroundService.RunCommand(requireContext(), service -> { - if (targetPlayer != null) { - targetPlayer.setPosition(seekBar.getProgress()); - } - positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 200); - }); - } - }); - - mprisControlBinding.nowPlayingTextview.setSelected(true); - - } + mprisControlBinding.nowPlayingTextview.setSelected(true); return activityMprisBinding.getRoot(); } - private void connectToPlugin(final String targetPlayerName) { - BackgroundService.RunWithPlugin(requireContext(), deviceId, MprisPlugin.class, mpris -> { - targetPlayer = mpris.getPlayerStatus(targetPlayerName); - - mpris.setPlayerStatusUpdatedHandler("activity", () -> requireActivity().runOnUiThread(() -> updatePlayerStatus(mpris))); - mpris.setPlayerListUpdatedHandler("activity", () -> { - final List playerList = mpris.getPlayerList(); - final ArrayAdapter adapter = new ArrayAdapter<>(requireContext(), - android.R.layout.simple_spinner_item, - playerList.toArray(ArrayUtils.EMPTY_STRING_ARRAY) - ); - - adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - requireActivity().runOnUiThread(() -> { - mprisControlBinding.playerSpinner.setAdapter(adapter); - - if (playerList.isEmpty()) { - mprisControlBinding.noPlayers.setVisibility(View.VISIBLE); - mprisControlBinding.playerSpinner.setVisibility(View.GONE); - mprisControlBinding.nowPlayingTextview.setText(""); - } else { - mprisControlBinding.noPlayers.setVisibility(View.GONE); - mprisControlBinding.playerSpinner.setVisibility(View.VISIBLE); - } - - mprisControlBinding.playerSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(AdapterView arg0, View arg1, int pos, long id) { - - if (pos >= playerList.size()) return; - - String player = playerList.get(pos); - if (targetPlayer != null && player.equals(targetPlayer.getPlayer())) { - return; //Player hasn't actually changed - } - targetPlayer = mpris.getPlayerStatus(player); - updatePlayerStatus(mpris); - - if (targetPlayer != null && targetPlayer.isPlaying()) { - MprisMediaSession.getInstance().playerSelected(targetPlayer); - } - } - - @Override - public void onNothingSelected(AdapterView arg0) { - targetPlayer = null; - } - }); - - if (targetPlayer == null) { - //If no player is selected, try to select a playing player - targetPlayer = mpris.getPlayingPlayer(); - } - //Try to select the specified player - if (targetPlayer != null) { - int targetIndex = adapter.getPosition(targetPlayer.getPlayer()); - if (targetIndex >= 0) { - mprisControlBinding.playerSpinner.setSelection(targetIndex); - } else { - targetPlayer = null; - } - } - //If no player selected, select the first one (if any) - if (targetPlayer == null && !playerList.isEmpty()) { - targetPlayer = mpris.getPlayerStatus(playerList.get(0)); - mprisControlBinding.playerSpinner.setSelection(0); - } - updatePlayerStatus(mpris); - }); - }); - }); - } - @Override - public void onDestroy() { - super.onDestroy(); - BackgroundService.RunCommand(requireContext(), service -> service.removeConnectionListener(connectionReceiver)); + public void onDestroyView() { + disconnectFromPlugin(); + super.onDestroyView(); } - private void performActionOnClick(View v, MprisPlayerCallback l) { - v.setOnClickListener(view -> BackgroundService.RunCommand(requireContext(), service -> { - if (targetPlayer == null) return; - l.performAction(targetPlayer); + private void disconnectFromPlugin() { + MprisPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MprisPlugin.class); + if (plugin != null) { + plugin.removePlayerListUpdatedHandler("activity"); + plugin.removePlayerStatusUpdatedHandler("activity"); + } + } + + private void connectToPlugin() { + MprisPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MprisPlugin.class); + if (plugin == null) { + if (isAdded()) { + requireActivity().finish(); + } + return; + } + targetPlayer = plugin.getPlayerStatus(targetPlayerName); + + plugin.setPlayerStatusUpdatedHandler("activity", () -> requireActivity().runOnUiThread(() -> { + updatePlayerStatus(plugin); + })); + plugin.setPlayerListUpdatedHandler("activity", () -> requireActivity().runOnUiThread(() -> { + final List playerList = plugin.getPlayerList(); + final ArrayAdapter adapter = new ArrayAdapter<>(requireContext(), + android.R.layout.simple_spinner_item, + playerList.toArray(ArrayUtils.EMPTY_STRING_ARRAY) + ); + + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + + mprisControlBinding.playerSpinner.setAdapter(adapter); + + if (playerList.isEmpty()) { + mprisControlBinding.noPlayers.setVisibility(View.VISIBLE); + mprisControlBinding.playerSpinner.setVisibility(View.GONE); + mprisControlBinding.nowPlayingTextview.setText(""); + } else { + mprisControlBinding.noPlayers.setVisibility(View.GONE); + mprisControlBinding.playerSpinner.setVisibility(View.VISIBLE); + } + + mprisControlBinding.playerSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView arg0, View arg1, int pos, long id) { + + if (pos >= playerList.size()) return; + + String player = playerList.get(pos); + if (targetPlayer != null && player.equals(targetPlayer.getPlayerName())) { + return; //Player hasn't actually changed + } + targetPlayer = plugin.getPlayerStatus(player); + targetPlayerName = targetPlayer.getPlayerName(); + updatePlayerStatus(plugin); + + if (targetPlayer != null && targetPlayer.isPlaying()) { + MprisMediaSession.getInstance().playerSelected(targetPlayer); + } + } + + @Override + public void onNothingSelected(AdapterView arg0) { + targetPlayer = null; + } + }); + + if (targetPlayer == null) { + //If no player is selected, try to select a playing player + targetPlayer = plugin.getPlayingPlayer(); + } + //Try to select the specified player + if (targetPlayer != null) { + int targetIndex = adapter.getPosition(targetPlayer.getPlayerName()); + if (targetIndex >= 0) { + mprisControlBinding.playerSpinner.setSelection(targetIndex); + } else { + targetPlayer = null; + } + } + //If no player selected, select the first one (if any) + if (targetPlayer == null && !playerList.isEmpty()) { + targetPlayer = plugin.getPlayerStatus(playerList.get(0)); + mprisControlBinding.playerSpinner.setSelection(0); + } + updatePlayerStatus(plugin); })); } - private void updatePlayerStatus(MprisPlugin mpris) { + private void performActionOnClick(View v, MprisPlayerCallback l) { + v.setOnClickListener(view -> { + if (targetPlayer == null) return; + l.performAction(targetPlayer); + }); + } + + private void updatePlayerStatus(MprisPlugin plugin) { + if (!isAdded()) { + //Fragment is not attached to an activity. We will crash if we try to do anything here. + return; + } + MprisPlugin.MprisPlayer playerStatus = targetPlayer; if (playerStatus == null) { //No player with that name found, just display "empty" data - playerStatus = mpris.getEmptyPlayer(); + playerStatus = plugin.getEmptyPlayer(); } String song = playerStatus.getCurrentSong(); @@ -433,7 +425,7 @@ public class MprisNowPlayingFragment extends Fragment implements VolumeKeyListen @Override public void onSaveInstanceState(@NonNull Bundle outState) { if (targetPlayer != null) { - outState.putString("targetPlayer", targetPlayer.getPlayer()); + outState.putString("targetPlayer", targetPlayerName); } } diff --git a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisPlugin.java b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisPlugin.java index 2f2e6773..6b79fd6a 100644 --- a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisPlugin.java +++ b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisPlugin.java @@ -11,8 +11,6 @@ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.Message; import android.util.Log; import androidx.core.content.ContextCompat; @@ -77,12 +75,12 @@ public class MprisPlugin extends Plugin { return album; } - public String getPlayer() { + public String getPlayerName() { return player; } boolean isSpotify() { - return getPlayer().equalsIgnoreCase("spotify"); + return getPlayerName().equalsIgnoreCase("spotify"); } public String getLoopStatus() { @@ -165,55 +163,55 @@ public class MprisPlugin extends Plugin { public void playPause() { if (isPauseAllowed() || isPlayAllowed()) { - MprisPlugin.this.sendCommand(getPlayer(), "action", "PlayPause"); + sendCommand(getPlayerName(), "action", "PlayPause"); } } public void play() { if (isPlayAllowed()) { - MprisPlugin.this.sendCommand(getPlayer(), "action", "Play"); + sendCommand(getPlayerName(), "action", "Play"); } } public void pause() { if (isPauseAllowed()) { - MprisPlugin.this.sendCommand(getPlayer(), "action", "Pause"); + sendCommand(getPlayerName(), "action", "Pause"); } } public void stop() { - MprisPlugin.this.sendCommand(getPlayer(), "action", "Stop"); + sendCommand(getPlayerName(), "action", "Stop"); } public void previous() { if (isGoPreviousAllowed()) { - MprisPlugin.this.sendCommand(getPlayer(), "action", "Previous"); + sendCommand(getPlayerName(), "action", "Previous"); } } public void next() { if (isGoNextAllowed()) { - MprisPlugin.this.sendCommand(getPlayer(), "action", "Next"); + sendCommand(getPlayerName(), "action", "Next"); } } public void setLoopStatus(String loopStatus) { - MprisPlugin.this.sendCommand(getPlayer(), "setLoopStatus", loopStatus); + sendCommand(getPlayerName(), "setLoopStatus", loopStatus); } public void setShuffle(boolean shuffle) { - MprisPlugin.this.sendCommand(getPlayer(), "setShuffle", shuffle); + sendCommand(getPlayerName(), "setShuffle", shuffle); } public void setVolume(int volume) { if (isSetVolumeAllowed()) { - MprisPlugin.this.sendCommand(getPlayer(), "setVolume", volume); + sendCommand(getPlayerName(), "setVolume", volume); } } public void setPosition(int position) { if (isSeekAllowed()) { - MprisPlugin.this.sendCommand(getPlayer(), "SetPosition", position); + sendCommand(getPlayerName(), "SetPosition", position); lastPosition = position; lastPositionTime = System.currentTimeMillis(); @@ -222,7 +220,7 @@ public class MprisPlugin extends Plugin { public void seek(int offset) { if (isSeekAllowed()) { - MprisPlugin.this.sendCommand(getPlayer(), "Seek", offset); + sendCommand(getPlayerName(), "Seek", offset); } } } @@ -522,7 +520,7 @@ public class MprisPlugin extends Plugin { if (player.albumArtUrl.equals(url)) { NetworkPacket np = new NetworkPacket(PACKET_TYPE_MPRIS_REQUEST); - np.set("player", player.getPlayer()); + np.set("player", player.getPlayerName()); np.set("albumArtUrl", url); device.sendPacket(np); return true; diff --git a/src/org/kde/kdeconnect/Plugins/PhotoPlugin/PhotoActivity.java b/src/org/kde/kdeconnect/Plugins/PhotoPlugin/PhotoActivity.java index 5a88ed7f..1a50a346 100644 --- a/src/org/kde/kdeconnect/Plugins/PhotoPlugin/PhotoActivity.java +++ b/src/org/kde/kdeconnect/Plugins/PhotoPlugin/PhotoActivity.java @@ -9,7 +9,7 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.FileProvider; -import org.kde.kdeconnect.BackgroundService; +import org.kde.kdeconnect.KdeConnect; import java.io.File; import java.io.IOException; @@ -58,13 +58,17 @@ public class PhotoActivity extends AppCompatActivity { @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); - BackgroundService.RunWithPlugin(this, getIntent().getStringExtra("deviceId"), PhotoPlugin.class, plugin -> { - if (resultCode == -1) { - plugin.sendPhoto(photoURI); - } else { - plugin.sendCancel(); - } - }); + String deviceId = getIntent().getStringExtra("deviceId"); + PhotoPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, PhotoPlugin.class); + if (plugin == null) { + finish(); + return; + } + if (resultCode == -1) { + plugin.sendPhoto(photoURI); + } else { + plugin.sendCancel(); + } finish(); } } diff --git a/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.java b/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.java index 4f1e434d..94bda929 100644 --- a/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.java +++ b/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.java @@ -24,8 +24,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import androidx.media.VolumeProviderCompat; -import org.kde.kdeconnect.BackgroundService; -import org.kde.kdeconnect.UserInterface.ThemeUtil; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.databinding.ActivityPresenterBinding; @@ -90,16 +89,14 @@ public class PresenterActivity extends AppCompatActivity implements SensorEventL Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); - final String deviceId = getIntent().getStringExtra("deviceId"); + String deviceId = getIntent().getStringExtra("deviceId"); + this.plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, PresenterPlugin.class); - BackgroundService.RunWithPlugin(this, deviceId, PresenterPlugin.class, plugin -> runOnUiThread(() -> { - this.plugin = plugin; - binding.nextButton.setOnClickListener(v -> plugin.sendNext()); - binding.previousButton.setOnClickListener(v -> plugin.sendPrevious()); - if (plugin.isPointerSupported()) { - enablePointer(); - } - })); + binding.nextButton.setOnClickListener(v -> plugin.sendNext()); + binding.previousButton.setOnClickListener(v -> plugin.sendPrevious()); + if (plugin.isPointerSupported()) { + enablePointer(); + } } @Override public boolean onCreateOptionsMenu(Menu menu) { diff --git a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java index ffb623ff..df37fe4b 100644 --- a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java +++ b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java @@ -23,9 +23,8 @@ import androidx.core.content.ContextCompat; import org.json.JSONException; import org.json.JSONObject; -import org.kde.kdeconnect.BackgroundService; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect.UserInterface.List.ListAdapter; -import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.databinding.ActivityRunCommandBinding; @@ -38,38 +37,42 @@ import java.util.Objects; public class RunCommandActivity extends AppCompatActivity { private ActivityRunCommandBinding binding; private String deviceId; - private final RunCommandPlugin.CommandsChangedCallback commandsChangedCallback = this::updateView; + private final RunCommandPlugin.CommandsChangedCallback commandsChangedCallback = () -> runOnUiThread(this::updateView); private List commandItems; private void updateView() { - BackgroundService.RunWithPlugin(this, deviceId, RunCommandPlugin.class, plugin -> runOnUiThread(() -> { - registerForContextMenu(binding.runCommandsList); + RunCommandPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, RunCommandPlugin.class); + if (plugin == null) { + finish(); + return; + } - commandItems = new ArrayList<>(); - for (JSONObject obj : plugin.getCommandList()) { - try { - commandItems.add(new CommandEntry(obj.getString("name"), - obj.getString("command"), obj.getString("key"))); - } catch (JSONException e) { - Log.e("RunCommand", "Error parsing JSON", e); - } + registerForContextMenu(binding.runCommandsList); + + commandItems = new ArrayList<>(); + for (JSONObject obj : plugin.getCommandList()) { + try { + commandItems.add(new CommandEntry(obj.getString("name"), + obj.getString("command"), obj.getString("key"))); + } catch (JSONException e) { + Log.e("RunCommand", "Error parsing JSON", e); } + } - Collections.sort(commandItems, Comparator.comparing(CommandEntry::getName)); + Collections.sort(commandItems, Comparator.comparing(CommandEntry::getName)); - ListAdapter adapter = new ListAdapter(RunCommandActivity.this, commandItems); + ListAdapter adapter = new ListAdapter(RunCommandActivity.this, commandItems); - binding.runCommandsList.setAdapter(adapter); - binding.runCommandsList.setOnItemClickListener((adapterView, view1, i, l) -> - plugin.runCommand(commandItems.get(i).getKey())); + binding.runCommandsList.setAdapter(adapter); + binding.runCommandsList.setOnItemClickListener((adapterView, view1, i, l) -> + plugin.runCommand(commandItems.get(i).getKey())); - String text = getString(R.string.addcommand_explanation); - if (!plugin.canAddCommand()) { - text += "\n" + getString(R.string.addcommand_explanation2); - } - binding.addComandExplanation.setText(text); - binding.addComandExplanation.setVisibility(commandItems.isEmpty() ? View.VISIBLE : View.GONE); - })); + String text = getString(R.string.addcommand_explanation); + if (!plugin.canAddCommand()) { + text += "\n" + getString(R.string.addcommand_explanation2); + } + binding.addComandExplanation.setText(text); + binding.addComandExplanation.setVisibility(commandItems.isEmpty() ? View.VISIBLE : View.GONE); } @Override @@ -84,27 +87,22 @@ public class RunCommandActivity extends AppCompatActivity { getSupportActionBar().setDisplayShowHomeEnabled(true); deviceId = getIntent().getStringExtra("deviceId"); - - boolean canAddCommands = false; - try { - canAddCommands = BackgroundService.getInstance().getDevice(deviceId).getPlugin(RunCommandPlugin.class).canAddCommand(); - } catch (Exception ignore) { + RunCommandPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId,RunCommandPlugin.class); + if (plugin != null) { + if (plugin.canAddCommand()) { + binding.addCommandButton.show(); + } else { + binding.addCommandButton.hide(); + } + binding.addCommandButton.setOnClickListener(v -> { + plugin.sendSetupPacket(); + new AlertDialog.Builder(RunCommandActivity.this) + .setTitle(R.string.add_command) + .setMessage(R.string.add_command_description) + .setPositiveButton(R.string.ok, null) + .show(); + }); } - - if (canAddCommands) { - binding.addCommandButton.show(); - } else { - binding.addCommandButton.hide(); - } - - binding.addCommandButton.setOnClickListener(v -> BackgroundService.RunWithPlugin(RunCommandActivity.this, deviceId, RunCommandPlugin.class, plugin -> { - plugin.sendSetupPacket(); - new AlertDialog.Builder(RunCommandActivity.this) - .setTitle(R.string.add_command) - .setMessage(R.string.add_command_description) - .setPositiveButton(R.string.ok, null) - .show(); - })); updateView(); } @@ -134,14 +132,23 @@ public class RunCommandActivity extends AppCompatActivity { protected void onResume() { super.onResume(); - BackgroundService.RunWithPlugin(this, deviceId, RunCommandPlugin.class, plugin -> plugin.addCommandsUpdatedCallback(commandsChangedCallback)); + RunCommandPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, RunCommandPlugin.class); + if (plugin == null) { + finish(); + return; + } + plugin.addCommandsUpdatedCallback(commandsChangedCallback); } @Override protected void onPause() { super.onPause(); - BackgroundService.RunWithPlugin(this, deviceId, RunCommandPlugin.class, plugin -> plugin.removeCommandsUpdatedCallback(commandsChangedCallback)); + RunCommandPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, RunCommandPlugin.class); + if (plugin == null) { + return; + } + plugin.removeCommandsUpdatedCallback(commandsChangedCallback); } @Override diff --git a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandControlsProviderService.kt b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandControlsProviderService.kt index ccfb45d9..73cb74b8 100644 --- a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandControlsProviderService.kt +++ b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandControlsProviderService.kt @@ -23,8 +23,8 @@ import io.reactivex.Flowable import io.reactivex.processors.ReplayProcessor import org.json.JSONArray import org.json.JSONException -import org.kde.kdeconnect.BackgroundService import org.kde.kdeconnect.Device +import org.kde.kdeconnect.KdeConnect import org.kde.kdeconnect.UserInterface.MainActivity import org.kde.kdeconnect_tp.R import org.reactivestreams.FlowAdapters @@ -92,12 +92,10 @@ class RunCommandControlsProviderService : ControlsProviderService() { if (action is CommandAction) { val commandEntry = getCommandByControlId(controlId) if (commandEntry != null) { - val plugin = BackgroundService.getInstance().getDevice(controlId.split(":")[0]).getPlugin(RunCommandPlugin::class.java) + val deviceId = controlId.split(":")[0] + val plugin = KdeConnect.getInstance().getDevicePlugin(deviceId ,RunCommandPlugin::class.java) if (plugin != null) { - BackgroundService.RunCommand(this) { - plugin.runCommand(commandEntry.key) - } - + plugin.runCommand(commandEntry.key) consumer.accept(ControlAction.RESPONSE_OK) } else { consumer.accept(ControlAction.RESPONSE_FAIL) @@ -141,9 +139,7 @@ class RunCommandControlsProviderService : ControlsProviderService() { private fun getAllCommandsList(): List { val commandList = mutableListOf() - val service = BackgroundService.getInstance() ?: return commandList - - for (device in service.devices.values) { + for (device in KdeConnect.getInstance().devices.values) { if (!device.isReachable) { commandList.addAll(getSavedCommandsList(device)) continue @@ -169,9 +165,7 @@ class RunCommandControlsProviderService : ControlsProviderService() { private fun getCommandByControlId(controlId: String): CommandEntryWithDevice? { val controlIdParts = controlId.split(":") - val service = BackgroundService.getInstance() ?: return null - - val device = service.getDevice(controlIdParts[0]) + val device = KdeConnect.getInstance().getDevice(controlIdParts[0]) if (device == null || !device.isPaired) return null diff --git a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandUrlActivity.java b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandUrlActivity.java index 6f740587..ca7d821b 100644 --- a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandUrlActivity.java +++ b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandUrlActivity.java @@ -10,9 +10,8 @@ import android.widget.TextView; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; -import org.kde.kdeconnect.UserInterface.ThemeUtil; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect_tp.R; public class RunCommandUrlActivity extends AppCompatActivity { @@ -26,40 +25,38 @@ public class RunCommandUrlActivity extends AppCompatActivity { Uri uri = getIntent().getData(); String deviceId = uri.getPathSegments().get(0); - BackgroundService.RunCommand(this, service -> { - final Device device = service.getDevice(deviceId); + final Device device = KdeConnect.getInstance().getDevice(deviceId); - if(device == null) { - error(R.string.runcommand_nosuchdevice); - return; + if(device == null) { + error(R.string.runcommand_nosuchdevice); + return; + } + + if (!device.isPaired()) { + error(R.string.runcommand_notpaired); + return; + } + + if (!device.isReachable()) { + error(R.string.runcommand_notreachable); + return; + } + + final RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class); + if (plugin == null) { + error(R.string.runcommand_noruncommandplugin); + return; + } + + plugin.runCommand(uri.getPathSegments().get(1)); + RunCommandUrlActivity.this.finish(); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + Vibrator vibrator = getSystemService(Vibrator.class); + if(vibrator != null && vibrator.hasVibrator()) { + vibrator.vibrate(100); } - - if (!device.isPaired()) { - error(R.string.runcommand_notpaired); - return; - } - - if (!device.isReachable()) { - error(R.string.runcommand_notreachable); - return; - } - - final RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class); - if (plugin == null) { - error(R.string.runcommand_noruncommandplugin); - return; - } - - plugin.runCommand(uri.getPathSegments().get(1)); - RunCommandUrlActivity.this.finish(); - - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { - Vibrator vibrator = RunCommandUrlActivity.this.getSystemService(Vibrator.class); - if(vibrator != null && vibrator.hasVibrator()) { - vibrator.vibrate(100); - } - } - }); + } } catch (Exception e) { Log.e("RuncommandPlugin", "Exception", e); } diff --git a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidget.java b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidget.java index 3034f824..363ab9e6 100644 --- a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidget.java +++ b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidget.java @@ -12,10 +12,12 @@ import android.util.Log; import android.view.View; import android.widget.RemoteViews; -import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect_tp.R; +import java.util.concurrent.ConcurrentHashMap; + public class RunCommandWidget extends AppWidgetProvider { public static final String RUN_COMMAND_ACTION = "RUN_COMMAND_ACTION"; @@ -35,18 +37,16 @@ public class RunCommandWidget extends AppWidgetProvider { final String targetCommand = intent.getStringExtra(TARGET_COMMAND); final String targetDevice = intent.getStringExtra(TARGET_DEVICE); - BackgroundService.RunCommand(context, service -> { - RunCommandPlugin plugin = service.getDevice(targetDevice).getPlugin(RunCommandPlugin.class); + RunCommandPlugin plugin = KdeConnect.getInstance().getDevicePlugin(targetDevice, RunCommandPlugin.class); - if (plugin != null) { - try { + if (plugin != null) { + try { - plugin.runCommand(targetCommand); - } catch (Exception ex) { - Log.e("RunCommandWidget", "Error running command", ex); - } + plugin.runCommand(targetCommand); + } catch (Exception ex) { + Log.e("RunCommandWidget", "Error running command", ex); } - }); + } } else if (intent != null && TextUtils.equals(intent.getAction(), SET_CURRENT_DEVICE)) { setCurrentDevice(context); } @@ -70,14 +70,13 @@ public class RunCommandWidget extends AppWidgetProvider { private void updateWidget(final Context context) { - if (getCurrentDevice() == null || !getCurrentDevice().isReachable()) { + Device device = getCurrentDevice(); - BackgroundService.RunCommand(context, service -> { - if (service.getDevices().size() > 0) - currentDeviceId = service.getDevices().elements().nextElement().getDeviceId(); - - updateWidgetImpl(context); - }); + if (device == null || !device.isReachable()) { + ConcurrentHashMap devices = KdeConnect.getInstance().getDevices(); + if (devices.size() > 0) { + currentDeviceId = devices.elements().nextElement().getDeviceId(); + } } updateWidgetImpl(context); @@ -99,12 +98,13 @@ public class RunCommandWidget extends AppWidgetProvider { pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); views.setOnClickPendingIntent(R.id.runcommandWidgetTitleHeader, pendingIntent); - if (getCurrentDevice() == null || !getCurrentDevice().isReachable()) { + Device device = getCurrentDevice(); + if (device == null || !device.isReachable()) { views.setTextViewText(R.id.runcommandWidgetTitle, context.getString(R.string.kde_connect)); views.setViewVisibility(R.id.run_commands_list, View.GONE); views.setViewVisibility(R.id.not_reachable_message, View.VISIBLE); } else { - views.setTextViewText(R.id.runcommandWidgetTitle, getCurrentDevice().getName()); + views.setTextViewText(R.id.runcommandWidgetTitle, device.getName()); views.setViewVisibility(R.id.run_commands_list, View.VISIBLE); views.setViewVisibility(R.id.not_reachable_message, View.GONE); } @@ -129,21 +129,15 @@ public class RunCommandWidget extends AppWidgetProvider { Log.e("RunCommandWidget", "Error updating widget", ex); } - if (BackgroundService.getInstance() != null) { - BackgroundService.getInstance().addDeviceListChangedCallback("RunCommandWidget", unused -> { - Intent updateWidget = new Intent(context, RunCommandWidget.class); - context.sendBroadcast(updateWidget); - }); - } + + KdeConnect.getInstance().addDeviceListChangedCallback("RunCommandWidget", () -> { + Intent updateWidget = new Intent(context, RunCommandWidget.class); + context.sendBroadcast(updateWidget); + }); } public static Device getCurrentDevice() { - - try { - return BackgroundService.getInstance().getDevice(currentDeviceId); - } catch (Exception ex) { - return null; - } + return KdeConnect.getInstance().getDevice(currentDeviceId); } public static void setCurrentDevice(final String DeviceId) { diff --git a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDeviceSelector.java b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDeviceSelector.java index 3291612f..0047f86a 100644 --- a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDeviceSelector.java +++ b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDeviceSelector.java @@ -6,10 +6,9 @@ import android.view.Window; import androidx.appcompat.app.AppCompatActivity; -import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect.UserInterface.List.ListAdapter; -import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.databinding.WidgetRemoteCommandPluginDialogBinding; import java.util.Comparator; @@ -27,25 +26,23 @@ public class RunCommandWidgetDeviceSelector extends AppCompatActivity { WidgetRemoteCommandPluginDialogBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); - BackgroundService.RunCommand(this, service -> runOnUiThread(() -> { - final List deviceItems = service.getDevices().values().stream() - .filter(Device::isPaired).filter(Device::isReachable) - .map(device -> new CommandEntry(device.getName(), null, device.getDeviceId())) - .sorted(Comparator.comparing(CommandEntry::getName)) - .collect(Collectors.toList()); + final List deviceItems = KdeConnect.getInstance().getDevices().values().stream() + .filter(Device::isPaired).filter(Device::isReachable) + .map(device -> new CommandEntry(device.getName(), null, device.getDeviceId())) + .sorted(Comparator.comparing(CommandEntry::getName)) + .collect(Collectors.toList()); - ListAdapter adapter = new ListAdapter(RunCommandWidgetDeviceSelector.this, deviceItems); + ListAdapter adapter = new ListAdapter(RunCommandWidgetDeviceSelector.this, deviceItems); - binding.runCommandsDeviceList.setAdapter(adapter); - binding.runCommandsDeviceList.setOnItemClickListener((adapterView, viewContent, i, l) -> { - CommandEntry entry = deviceItems.get(i); - RunCommandWidget.setCurrentDevice(entry.getKey()); + binding.runCommandsDeviceList.setAdapter(adapter); + binding.runCommandsDeviceList.setOnItemClickListener((adapterView, viewContent, i, l) -> { + CommandEntry entry = deviceItems.get(i); + RunCommandWidget.setCurrentDevice(entry.getKey()); - Intent updateWidget = new Intent(RunCommandWidgetDeviceSelector.this, RunCommandWidget.class); - RunCommandWidgetDeviceSelector.this.sendBroadcast(updateWidget); + Intent updateWidget = new Intent(RunCommandWidgetDeviceSelector.this, RunCommandWidget.class); + RunCommandWidgetDeviceSelector.this.sendBroadcast(updateWidget); - finish(); - }); - })); + finish(); + }); } } \ No newline at end of file diff --git a/src/org/kde/kdeconnect/Plugins/SftpPlugin/SftpSettingsFragment.java b/src/org/kde/kdeconnect/Plugins/SftpPlugin/SftpSettingsFragment.java index 63c8cfe9..4ebd6355 100644 --- a/src/org/kde/kdeconnect/Plugins/SftpPlugin/SftpSettingsFragment.java +++ b/src/org/kde/kdeconnect/Plugins/SftpPlugin/SftpSettingsFragment.java @@ -30,8 +30,8 @@ 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.KdeConnect; import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.UserInterface.PluginSettingsActivity; import org.kde.kdeconnect.UserInterface.PluginSettingsFragment; @@ -326,21 +326,11 @@ public class SftpSettingsFragment addStoragePreferences(preferenceCategory); - Device device = getDeviceOrThrow(); + Device device = KdeConnect.getInstance().getDevice(getDeviceId()); device.reloadPluginsFromSettings(); } - private Device getDeviceOrThrow() { - Device device = BackgroundService.getInstance().getDevice(getDeviceId()); - - if (device == null) { - throw new RuntimeException("SftpSettingsFragment.getDeviceOrThrow(): No device with id: " + getDeviceId()); - } - - return device; - } - @Override public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) { SftpPlugin.StorageInfo newStorageInfo = (SftpPlugin.StorageInfo) newValue; diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/SendFileActivity.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/SendFileActivity.java index 9fc9f0c3..d53c6fab 100644 --- a/src/org/kde/kdeconnect/Plugins/SharePlugin/SendFileActivity.java +++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/SendFileActivity.java @@ -16,8 +16,7 @@ import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; -import org.kde.kdeconnect.BackgroundService; -import org.kde.kdeconnect.UserInterface.ThemeUtil; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect_tp.R; import java.util.ArrayList; @@ -69,7 +68,12 @@ public class SendFileActivity extends AppCompatActivity { if (uris.isEmpty()) { Log.w("SendFileActivity", "No files to send?"); } else { - BackgroundService.RunWithPlugin(this, mDeviceId, SharePlugin.class, plugin -> plugin.sendUriList(uris)); + SharePlugin plugin = KdeConnect.getInstance().getDevicePlugin(mDeviceId, SharePlugin.class); + if (plugin == null) { + finish(); + return; + } + plugin.sendUriList(uris); } } finish(); diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java index 0425b721..fca49de3 100644 --- a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java +++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java @@ -17,10 +17,10 @@ import androidx.appcompat.app.AppCompatActivity; import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect.UserInterface.List.EntryItemWithIcon; import org.kde.kdeconnect.UserInterface.List.ListAdapter; import org.kde.kdeconnect.UserInterface.List.SectionItem; -import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.databinding.ActivityShareBinding; @@ -42,19 +42,17 @@ public class ShareActivity extends AppCompatActivity { @Override public boolean onOptionsItemSelected(final MenuItem item) { if (item.getItemId() == R.id.menu_refresh) { - updateDeviceListAction(); + refreshDevicesAction(); return true; } else { return super.onOptionsItemSelected(item); } } - private void updateDeviceListAction() { - updateDeviceList(); - BackgroundService.RunCommand(ShareActivity.this, BackgroundService::onNetworkChange); + private void refreshDevicesAction() { + BackgroundService.ForceRefreshConnections(this); binding.devicesListLayout.refreshListLayout.setRefreshing(true); - binding.devicesListLayout.refreshListLayout.postDelayed(() -> { binding.devicesListLayout.refreshListLayout.setRefreshing(false); }, 1500); @@ -69,31 +67,29 @@ public class ShareActivity extends AppCompatActivity { return; } - BackgroundService.RunCommand(this, service -> { + Collection devices = KdeConnect.getInstance().getDevices().values(); + final ArrayList devicesList = new ArrayList<>(); + final ArrayList items = new ArrayList<>(); - Collection devices = service.getDevices().values(); - final ArrayList devicesList = new ArrayList<>(); - final ArrayList items = new ArrayList<>(); + SectionItem section = new SectionItem(getString(R.string.share_to)); + items.add(section); - SectionItem section = new SectionItem(getString(R.string.share_to)); - items.add(section); - - for (Device d : devices) { - if (d.isReachable() && d.isPaired()) { - devicesList.add(d); - items.add(new EntryItemWithIcon(d.getName(), d.getIcon())); - section.isEmpty = false; - } + for (Device d : devices) { + if (d.isReachable() && d.isPaired()) { + devicesList.add(d); + items.add(new EntryItemWithIcon(d.getName(), d.getIcon())); + section.isEmpty = false; } + } - runOnUiThread(() -> { - binding.devicesListLayout.devicesList.setAdapter(new ListAdapter(ShareActivity.this, items)); - binding.devicesListLayout.devicesList.setOnItemClickListener((adapterView, view, i, l) -> { - Device device = devicesList.get(i - 1); //NOTE: -1 because of the title! - BackgroundService.RunWithPlugin(this, device.getDeviceId(), SharePlugin.class, plugin -> plugin.share(intent)); - finish(); - }); - }); + binding.devicesListLayout.devicesList.setAdapter(new ListAdapter(ShareActivity.this, items)); + binding.devicesListLayout.devicesList.setOnItemClickListener((adapterView, view, i, l) -> { + Device device = devicesList.get(i - 1); //NOTE: -1 because of the title! + SharePlugin plugin = KdeConnect.getInstance().getDevicePlugin(device.getDeviceId(), SharePlugin.class); + if (plugin != null) { + plugin.share(intent); + } + finish(); }); } @@ -109,7 +105,7 @@ public class ShareActivity extends AppCompatActivity { getSupportActionBar().setDisplayShowHomeEnabled(true); ActionBar actionBar = getSupportActionBar(); - binding.devicesListLayout.refreshListLayout.setOnRefreshListener(this::updateDeviceListAction); + binding.devicesListLayout.refreshListLayout.setOnRefreshListener(this::refreshDevicesAction); if (actionBar != null) { actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM); } @@ -123,22 +119,21 @@ public class ShareActivity extends AppCompatActivity { final String deviceId = intent.getStringExtra("deviceId"); if (deviceId != null) { - BackgroundService.RunWithPlugin(this, deviceId, SharePlugin.class, plugin -> { + SharePlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, SharePlugin.class); + if (plugin != null) { plugin.share(intent); - finish(); - }); + } + finish(); } else { - BackgroundService.RunCommand(this, service -> { - service.onNetworkChange(); - service.addDeviceListChangedCallback("ShareActivity", unused -> updateDeviceList()); - }); + KdeConnect.getInstance().addDeviceListChangedCallback("ShareActivity", () -> runOnUiThread(this::updateDeviceList)); + BackgroundService.ForceRefreshConnections(this); // force a network re-discover updateDeviceList(); } } @Override protected void onStop() { - BackgroundService.RunCommand(this, service -> service.removeDeviceListChangedCallback("ShareActivity")); + KdeConnect.getInstance().removeDeviceListChangedCallback("ShareActivity"); super.onStop(); } } diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareBroadcastReceiver.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareBroadcastReceiver.java index 11ade479..d474b491 100644 --- a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareBroadcastReceiver.java +++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareBroadcastReceiver.java @@ -11,7 +11,7 @@ import android.content.Context; import android.content.Intent; import android.util.Log; -import org.kde.kdeconnect.BackgroundService; +import org.kde.kdeconnect.KdeConnect; public class ShareBroadcastReceiver extends BroadcastReceiver { @Override @@ -35,6 +35,10 @@ public class ShareBroadcastReceiver extends BroadcastReceiver { long jobId = intent.getLongExtra(SharePlugin.CANCEL_SHARE_BACKGROUND_JOB_ID_EXTRA, -1); String deviceId = intent.getStringExtra(SharePlugin.CANCEL_SHARE_DEVICE_ID_EXTRA); - BackgroundService.RunWithPlugin(context, deviceId, SharePlugin.class, plugin -> plugin.cancelJob(jobId)); + SharePlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, SharePlugin.class); + if (plugin == null) { + return; + } + plugin.cancelJob(jobId); } } diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareChooserTargetService.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareChooserTargetService.java index 4391be25..cd348d34 100644 --- a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareChooserTargetService.java +++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareChooserTargetService.java @@ -14,8 +14,8 @@ import android.service.chooser.ChooserTarget; import android.service.chooser.ChooserTargetService; import android.util.Log; -import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect_tp.R; import java.util.ArrayList; @@ -26,7 +26,7 @@ public class ShareChooserTargetService extends ChooserTargetService { public List onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) { Log.d("DirectShare", "invoked"); final List targets = new ArrayList<>(); - for (Device d : BackgroundService.getInstance().getDevices().values()) { + for (Device d : KdeConnect.getInstance().getDevices().values()) { if (d.isReachable() && d.isPaired()) { Log.d("DirectShare", d.getName()); final String targetName = d.getName(); diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java index d58b6be5..8fddd65d 100644 --- a/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java +++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java @@ -15,7 +15,6 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.util.Log; @@ -157,7 +156,7 @@ public class SharePlugin extends Plugin { Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - IntentHelper.startActivityFromBackground(context, browserIntent, url); + IntentHelper.startActivityFromBackgroundOrCreateNotification(context, browserIntent, url); } private void receiveText(NetworkPacket np) { diff --git a/src/org/kde/kdeconnect/Plugins/SystemVolumePlugin/SinkItemHolder.java b/src/org/kde/kdeconnect/Plugins/SystemVolumePlugin/SinkItemHolder.java index 8cd15983..111c818a 100644 --- a/src/org/kde/kdeconnect/Plugins/SystemVolumePlugin/SinkItemHolder.java +++ b/src/org/kde/kdeconnect/Plugins/SystemVolumePlugin/SinkItemHolder.java @@ -11,7 +11,6 @@ import androidx.annotation.NonNull; import androidx.core.util.Consumer; import androidx.recyclerview.widget.RecyclerView; -import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.databinding.ListItemSystemvolumeBinding; @@ -49,8 +48,7 @@ class SinkItemHolder extends RecyclerView.ViewHolder @Override public void onProgressChanged(final SeekBar seekBar, int i, boolean triggeredByUser) { if (triggeredByUser) { - BackgroundService.RunCommand(seekBar.getContext(), - service -> plugin.sendVolume(sink.getName(), seekBar.getProgress())); + plugin.sendVolume(sink.getName(), seekBar.getProgress()); } } @@ -62,8 +60,7 @@ class SinkItemHolder extends RecyclerView.ViewHolder @Override public void onStopTrackingTouch(final SeekBar seekBar) { seekBarTracking.accept(false); - BackgroundService.RunCommand(seekBar.getContext(), - service -> plugin.sendVolume(sink.getName(), seekBar.getProgress())); + plugin.sendVolume(sink.getName(), seekBar.getProgress()); } @Override diff --git a/src/org/kde/kdeconnect/Plugins/SystemVolumePlugin/SystemVolumeFragment.java b/src/org/kde/kdeconnect/Plugins/SystemVolumePlugin/SystemVolumeFragment.java index cde61738..9a30328d 100644 --- a/src/org/kde/kdeconnect/Plugins/SystemVolumePlugin/SystemVolumeFragment.java +++ b/src/org/kde/kdeconnect/Plugins/SystemVolumePlugin/SystemVolumeFragment.java @@ -6,7 +6,6 @@ package org.kde.kdeconnect.Plugins.SystemVolumePlugin; -import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -20,8 +19,8 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; -import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Helpers.VolumeHelperKt; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect.Plugins.MprisPlugin.MprisPlugin; import org.kde.kdeconnect.Plugins.MprisPlugin.VolumeKeyListener; import org.kde.kdeconnect_tp.R; @@ -73,19 +72,15 @@ public class SystemVolumeFragment recyclerView.setAdapter(recyclerAdapter); } + connectToPlugin(getDeviceId()); + return systemVolumeFragmentBinding.getRoot(); } @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - connectToPlugin(getDeviceId()); - } - - @Override - public void onDetach() { - super.onDetach(); + public void onDestroyView() { disconnectFromPlugin(getDeviceId()); + super.onDestroyView(); } @Override @@ -99,16 +94,21 @@ public class SystemVolumeFragment } private void connectToPlugin(final String deviceId) { - BackgroundService.RunWithPlugin(requireActivity(), deviceId, SystemVolumePlugin.class, plugin -> { - this.plugin = plugin; - plugin.addSinkListener(SystemVolumeFragment.this); - plugin.requestSinkList(); - }); + SystemVolumePlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, SystemVolumePlugin.class); + if (plugin == null) { + return; + } + this.plugin = plugin; + plugin.addSinkListener(SystemVolumeFragment.this); + plugin.requestSinkList(); } private void disconnectFromPlugin(final String deviceId) { - BackgroundService.RunWithPlugin(requireActivity(), deviceId, SystemVolumePlugin.class, plugin -> - plugin.removeSinkListener(SystemVolumeFragment.this)); + SystemVolumePlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, SystemVolumePlugin.class); + if (plugin == null) { + return; + } + plugin.removeSinkListener(SystemVolumeFragment.this); } @Override diff --git a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.kt b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.kt index be058050..a7c53a16 100644 --- a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.kt +++ b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.kt @@ -18,11 +18,11 @@ import androidx.fragment.app.Fragment import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.dialog.MaterialAlertDialogBuilder -import org.kde.kdeconnect.BackgroundService import org.kde.kdeconnect.Device import org.kde.kdeconnect.Device.PairingCallback import org.kde.kdeconnect.Device.PluginsChangedListener import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper +import org.kde.kdeconnect.KdeConnect import org.kde.kdeconnect.Plugins.BatteryPlugin.BatteryPlugin import org.kde.kdeconnect.Plugins.Plugin import org.kde.kdeconnect.UserInterface.List.PluginAdapter @@ -98,9 +98,7 @@ class DeviceFragment : Fragment() { // ...and for when pairing doesn't (or can't) work errorBinding = deviceBinding.pairError - BackgroundService.RunCommand(mActivity) { - device = it.getDevice(deviceId) - } + device = KdeConnect.getInstance().getDevice(deviceId) requirePairingBinding().pairButton.setOnClickListener { with(requirePairingBinding()) { @@ -128,36 +126,35 @@ class DeviceFragment : Fragment() { mActivity?.onDeviceSelected(null) } setHasOptionsMenu(true) - BackgroundService.RunCommand(mActivity) { service: BackgroundService -> - device = service.getDevice(deviceId) ?: let { - Log.e(TAG, "Trying to display a device fragment but the device is not present") - mActivity?.onDeviceSelected(null) - return@RunCommand - } - mActivity?.supportActionBar?.title = device?.name - device?.addPairingCallback(pairingCallback) - device?.addPluginsChangedListener(pluginsChangedListener) - refreshUI() - } requireDeviceBinding().pluginsList.layoutManager = GridLayoutManager(requireContext(), resources.getInteger(R.integer.plugins_columns)) requireDeviceBinding().permissionsList.layoutManager = LinearLayoutManager(requireContext()) + device?.apply { + mActivity?.supportActionBar?.title = name + addPairingCallback(pairingCallback) + addPluginsChangedListener(pluginsChangedListener) + } ?: run { // device is null + Log.e(TAG, "Trying to display a device fragment but the device is not present") + mActivity?.onDeviceSelected(null) + } + + refreshUI() + return deviceBinding.root } private val pluginsChangedListener = PluginsChangedListener { refreshUI() } override fun onDestroyView() { - BackgroundService.RunCommand(mActivity) { service: BackgroundService -> - val device = service.getDevice(deviceId) ?: return@RunCommand - device.removePluginsChangedListener(pluginsChangedListener) - device.removePairingCallback(pairingCallback) + device?.apply { + removePluginsChangedListener(pluginsChangedListener) + removePairingCallback(pairingCallback) } - super.onDestroyView() pairingBinding = null errorBinding = null deviceBinding = null + super.onDestroyView() } override fun onPrepareOptionsMenu(menu: Menu) { diff --git a/src/org/kde/kdeconnect/UserInterface/MainActivity.kt b/src/org/kde/kdeconnect/UserInterface/MainActivity.kt index 8b6ced4c..9b707bc3 100644 --- a/src/org/kde/kdeconnect/UserInterface/MainActivity.kt +++ b/src/org/kde/kdeconnect/UserInterface/MainActivity.kt @@ -25,6 +25,7 @@ import org.apache.commons.lang3.ArrayUtils import org.kde.kdeconnect.BackgroundService import org.kde.kdeconnect.Device import org.kde.kdeconnect.Helpers.DeviceHelper +import org.kde.kdeconnect.KdeConnect import org.kde.kdeconnect.Plugins.SharePlugin.ShareSettingsFragment import org.kde.kdeconnect.UserInterface.About.AboutFragment.Companion.newInstance import org.kde.kdeconnect.UserInterface.About.getApplicationAboutData @@ -194,16 +195,14 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener { private fun onPairResultFromNotification(deviceId: String?, pairStatus: String): String? { assert(deviceId != null) if (pairStatus != PAIRING_PENDING) { - BackgroundService.RunCommand(this) { service: BackgroundService -> - val device = service.getDevice(deviceId) - if (device == null) { - Log.w(this::class.simpleName, "Reject pairing - device no longer exists: $deviceId") - return@RunCommand - } - when (pairStatus) { - PAIRING_ACCEPTED -> device.acceptPairing() - PAIRING_REJECTED -> device.rejectPairing() - } + val device = KdeConnect.getInstance().getDevice(deviceId) + if (device == null) { + Log.w(this::class.simpleName, "Reject pairing - device no longer exists: $deviceId") + return null + } + when (pairStatus) { + PAIRING_ACCEPTED -> device.acceptPairing() + PAIRING_REJECTED -> device.rejectPairing() } } return if (pairStatus == PAIRING_ACCEPTED || pairStatus == PAIRING_PENDING) deviceId else null @@ -228,45 +227,41 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener { } private fun updateDeviceList() { - BackgroundService.RunCommand(this@MainActivity) { service: BackgroundService -> - val menu = mNavigationView.menu - menu.clear() - mMapMenuToDeviceId.clear() - val devicesMenu = menu.addSubMenu(R.string.devices) - var id = MENU_ENTRY_DEVICE_FIRST_ID - val devices: Collection = service.devices.values - for (device in devices) { - if (device.isReachable && device.isPaired) { - val item = devicesMenu.add(Menu.FIRST, id++, 1, device.name) - item.icon = device.icon - item.isCheckable = true - mMapMenuToDeviceId[item] = device.deviceId - } + val menu = mNavigationView.menu + menu.clear() + mMapMenuToDeviceId.clear() + val devicesMenu = menu.addSubMenu(R.string.devices) + var id = MENU_ENTRY_DEVICE_FIRST_ID + val devices: Collection = KdeConnect.getInstance().devices.values + for (device in devices) { + if (device.isReachable && device.isPaired) { + val item = devicesMenu.add(Menu.FIRST, id++, 1, device.name) + item.icon = device.icon + item.isCheckable = true + mMapMenuToDeviceId[item] = device.deviceId } - val addDeviceItem = devicesMenu.add(Menu.FIRST, MENU_ENTRY_ADD_DEVICE, 1000, R.string.pair_new_device) - addDeviceItem.setIcon(R.drawable.ic_action_content_add_circle_outline_32dp) - addDeviceItem.isCheckable = true - val settingsItem = menu.add(Menu.FIRST, MENU_ENTRY_SETTINGS, 1000, R.string.settings) - settingsItem.setIcon(R.drawable.ic_settings_white_32dp) - settingsItem.isCheckable = true - val aboutItem = menu.add(Menu.FIRST, MENU_ENTRY_ABOUT, 1000, R.string.about) - aboutItem.setIcon(R.drawable.ic_baseline_info_24) - aboutItem.isCheckable = true - - //Ids might have changed - if (mCurrentMenuEntry >= MENU_ENTRY_DEVICE_FIRST_ID) { - mCurrentMenuEntry = deviceIdToMenuEntryId(mCurrentDevice) - } - mNavigationView.setCheckedItem(mCurrentMenuEntry) } + val addDeviceItem = devicesMenu.add(Menu.FIRST, MENU_ENTRY_ADD_DEVICE, 1000, R.string.pair_new_device) + addDeviceItem.setIcon(R.drawable.ic_action_content_add_circle_outline_32dp) + addDeviceItem.isCheckable = true + val settingsItem = menu.add(Menu.FIRST, MENU_ENTRY_SETTINGS, 1000, R.string.settings) + settingsItem.setIcon(R.drawable.ic_settings_white_32dp) + settingsItem.isCheckable = true + val aboutItem = menu.add(Menu.FIRST, MENU_ENTRY_ABOUT, 1000, R.string.about) + aboutItem.setIcon(R.drawable.ic_baseline_info_24) + aboutItem.isCheckable = true + + //Ids might have changed + if (mCurrentMenuEntry >= MENU_ENTRY_DEVICE_FIRST_ID) { + mCurrentMenuEntry = deviceIdToMenuEntryId(mCurrentDevice) + } + mNavigationView.setCheckedItem(mCurrentMenuEntry) } override fun onStart() { super.onStart() - BackgroundService.RunCommand(this) { service: BackgroundService -> - service.onNetworkChange() - service.addDeviceListChangedCallback(this::class.simpleName) { updateDeviceList() } - } + BackgroundService.Start(applicationContext); + KdeConnect.getInstance().addDeviceListChangedCallback(this::class.simpleName) { runOnUiThread { updateDeviceList() } } updateDeviceList() onBackPressedDispatcher.addCallback(mainFragmentCallback) onBackPressedDispatcher.addCallback(closeDrawerCallback) @@ -274,10 +269,10 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener { } override fun onStop() { - BackgroundService.RunCommand(this) { service: BackgroundService -> service.removeDeviceListChangedCallback(this::class.simpleName) } - super.onStop() + KdeConnect.getInstance().removeDeviceListChangedCallback(this::class.simpleName) mainFragmentCallback.remove() closeDrawerCallback.remove() + super.onStop() } @JvmOverloads @@ -315,11 +310,9 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener { @Deprecated("Deprecated in Java") override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when { - requestCode == RESULT_NEEDS_RELOAD -> BackgroundService.RunCommand(this) { service: BackgroundService -> - val device = service.getDevice(mCurrentDevice) - device.reloadPluginsFromSettings() + requestCode == RESULT_NEEDS_RELOAD -> { + KdeConnect.getInstance().getDevice(mCurrentDevice)?.reloadPluginsFromSettings() } - requestCode == STORAGE_LOCATION_CONFIGURED && resultCode == RESULT_OK && data != null -> { val uri = data.data ShareSettingsFragment.saveStorageLocationPreference(this, uri) @@ -344,17 +337,14 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener { } //New permission granted, reload plugins - BackgroundService.RunCommand(this) { service: BackgroundService -> - val device = service.getDevice(mCurrentDevice) - device.reloadPluginsFromSettings() - } + KdeConnect.getInstance().getDevice(mCurrentDevice)?.reloadPluginsFromSettings() } } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) { if (DeviceHelper.KEY_DEVICE_NAME_PREFERENCE == key) { mNavViewDeviceName.text = DeviceHelper.getDeviceName(this) - BackgroundService.RunCommand(this) { obj: BackgroundService -> obj.onNetworkChange() } //Re-send our identity packet + BackgroundService.ForceRefreshConnections(this) //Re-send our identity packet } } diff --git a/src/org/kde/kdeconnect/UserInterface/PairingFragment.java b/src/org/kde/kdeconnect/UserInterface/PairingFragment.java index 9af7334c..f6177fde 100644 --- a/src/org/kde/kdeconnect/UserInterface/PairingFragment.java +++ b/src/org/kde/kdeconnect/UserInterface/PairingFragment.java @@ -26,6 +26,7 @@ import androidx.fragment.app.Fragment; import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Helpers.TrustedNetworkHelper; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect.UserInterface.List.ListAdapter; import org.kde.kdeconnect.UserInterface.List.PairingDeviceItem; import org.kde.kdeconnect.UserInterface.List.SectionItem; @@ -59,7 +60,6 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb private TextView headerText; private TextView noWifiHeader; private TextView notTrustedText; - private boolean isConnectedToNonCellularNetwork = true; @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, @@ -73,7 +73,7 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb pairingExplanationTextBinding = PairingExplanationTextBinding.inflate(inflater); pairingExplanationTextNoWifiBinding = PairingExplanationTextNoWifiBinding.inflate(inflater); - devicesListBinding.refreshListLayout.setOnRefreshListener(this::updateDeviceListAction); + devicesListBinding.refreshListLayout.setOnRefreshListener(this::refreshDevicesAction); notTrustedText = pairingExplanationNotTrustedBinding.getRoot(); notTrustedText.setOnClickListener(null); @@ -105,136 +105,134 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb mActivity = ((MainActivity) getActivity()); } - private void updateDeviceListAction() { - updateDeviceList(); - BackgroundService.RunCommand(mActivity, BackgroundService::onNetworkChange); + private void refreshDevicesAction() { + BackgroundService.ForceRefreshConnections(requireContext()); + devicesListBinding.refreshListLayout.setRefreshing(true); - devicesListBinding.refreshListLayout.postDelayed(() -> { - // the view might be destroyed by now - if (devicesListBinding == null) { - return; + if (devicesListBinding != null) { // the view might be destroyed by now + devicesListBinding.refreshListLayout.setRefreshing(false); } - - devicesListBinding.refreshListLayout.setRefreshing(false); }, 1500); } private void updateDeviceList() { - BackgroundService.RunCommand(mActivity, service -> mActivity.runOnUiThread(() -> { + if (!isAdded()) { + //Fragment is not attached to an activity. We will crash if we try to do anything here. + return; + } - if (!isAdded()) { - //Fragment is not attached to an activity. We will crash if we try to do anything here. - return; - } + if (listRefreshCalledThisFrame) { + // This makes sure we don't try to call list.getFirstVisiblePosition() + // twice per frame, because the second time the list hasn't been drawn + // yet and it would always return 0. + return; + } + listRefreshCalledThisFrame = true; - if (listRefreshCalledThisFrame) { - // This makes sure we don't try to call list.getFirstVisiblePosition() - // twice per frame, because the second time the list hasn't been drawn - // yet and it would always return 0. - return; - } - listRefreshCalledThisFrame = true; + //Check if we're on Wi-Fi/Local network. If we still see a device, don't do anything special + BackgroundService service = BackgroundService.getInstance(); + if (service == null) { + updateConnectivityInfoHeader(true); + } else { + service.isConnectedToNonCellularNetwork().observe(this, this::updateConnectivityInfoHeader); + } - Collection devices = service.getDevices().values(); - boolean someDevicesReachable = false; + try { + final ArrayList items = new ArrayList<>(); + + SectionItem connectedSection; + Resources res = getResources(); + + connectedSection = new SectionItem(res.getString(R.string.category_connected_devices)); + items.add(connectedSection); + + Collection devices = KdeConnect.getInstance().getDevices().values(); for (Device device : devices) { - if (device.isReachable()) { - someDevicesReachable = true; + if (device.isReachable() && device.isPaired()) { + items.add(new PairingDeviceItem(device, PairingFragment.this)); + connectedSection.isEmpty = false; } } - - devicesListBinding.devicesList.removeHeaderView(headerText); - devicesListBinding.devicesList.removeHeaderView(noWifiHeader); - devicesListBinding.devicesList.removeHeaderView(notTrustedText); - - //Check if we're on Wi-Fi/Local network. If we still see a device, don't do anything special - if (someDevicesReachable || isConnectedToNonCellularNetwork) { - if (TrustedNetworkHelper.isTrustedNetwork(getContext())) { - devicesListBinding.devicesList.addHeaderView(headerText); - } else { - devicesListBinding.devicesList.addHeaderView(notTrustedText); - } - } else { - devicesListBinding.devicesList.addHeaderView(noWifiHeader); + if (connectedSection.isEmpty) { + items.remove(items.size() - 1); //Remove connected devices section if empty } - try { - final ArrayList items = new ArrayList<>(); - - SectionItem connectedSection; - Resources res = getResources(); - - connectedSection = new SectionItem(res.getString(R.string.category_connected_devices)); - items.add(connectedSection); - for (Device device : devices) { - if (device.isReachable() && device.isPaired()) { - items.add(new PairingDeviceItem(device, PairingFragment.this)); - connectedSection.isEmpty = false; - } + SectionItem availableSection = new SectionItem(res.getString(R.string.category_not_paired_devices)); + items.add(availableSection); + for (Device device : devices) { + if (device.isReachable() && !device.isPaired()) { + items.add(new PairingDeviceItem(device, PairingFragment.this)); + availableSection.isEmpty = false; } - if (connectedSection.isEmpty) { - items.remove(items.size() - 1); //Remove connected devices section if empty - } - - SectionItem availableSection = new SectionItem(res.getString(R.string.category_not_paired_devices)); - items.add(availableSection); - for (Device device : devices) { - if (device.isReachable() && !device.isPaired()) { - items.add(new PairingDeviceItem(device, PairingFragment.this)); - availableSection.isEmpty = false; - } - } - if (availableSection.isEmpty && !connectedSection.isEmpty) { - items.remove(items.size() - 1); //Remove remembered devices section if empty - } - - SectionItem rememberedSection = new SectionItem(res.getString(R.string.category_remembered_devices)); - items.add(rememberedSection); - for (Device device : devices) { - if (!device.isReachable() && device.isPaired()) { - items.add(new PairingDeviceItem(device, PairingFragment.this)); - rememberedSection.isEmpty = false; - } - } - if (rememberedSection.isEmpty) { - items.remove(items.size() - 1); //Remove remembered devices section if empty - } - - //Store current scroll - int index = devicesListBinding.devicesList.getFirstVisiblePosition(); - View v = devicesListBinding.devicesList.getChildAt(0); - int top = (v == null) ? 0 : (v.getTop() - devicesListBinding.devicesList.getPaddingTop()); - - devicesListBinding.devicesList.setAdapter(new ListAdapter(mActivity, items)); - - //Restore scroll - devicesListBinding.devicesList.setSelectionFromTop(index, top); - } catch (IllegalStateException e) { - //Ignore: The activity was closed while we were trying to update it - } finally { - listRefreshCalledThisFrame = false; + } + if (availableSection.isEmpty && !connectedSection.isEmpty) { + items.remove(items.size() - 1); //Remove remembered devices section if empty } - })); + SectionItem rememberedSection = new SectionItem(res.getString(R.string.category_remembered_devices)); + items.add(rememberedSection); + for (Device device : devices) { + if (!device.isReachable() && device.isPaired()) { + items.add(new PairingDeviceItem(device, PairingFragment.this)); + rememberedSection.isEmpty = false; + } + } + if (rememberedSection.isEmpty) { + items.remove(items.size() - 1); //Remove remembered devices section if empty + } + + //Store current scroll + int index = devicesListBinding.devicesList.getFirstVisiblePosition(); + View v = devicesListBinding.devicesList.getChildAt(0); + int top = (v == null) ? 0 : (v.getTop() - devicesListBinding.devicesList.getPaddingTop()); + + devicesListBinding.devicesList.setAdapter(new ListAdapter(mActivity, items)); + + //Restore scroll + devicesListBinding.devicesList.setSelectionFromTop(index, top); + } catch (IllegalStateException e) { + //Ignore: The activity was closed while we were trying to update it + } finally { + listRefreshCalledThisFrame = false; + } } + void updateConnectivityInfoHeader(boolean isConnectedToNonCellularNetwork) { + Collection devices = KdeConnect.getInstance().getDevices().values(); + boolean someDevicesReachable = false; + for (Device device : devices) { + if (device.isReachable()) { + someDevicesReachable = true; + } + } + + devicesListBinding.devicesList.removeHeaderView(headerText); + devicesListBinding.devicesList.removeHeaderView(noWifiHeader); + devicesListBinding.devicesList.removeHeaderView(notTrustedText); + + if (someDevicesReachable || isConnectedToNonCellularNetwork) { + if (TrustedNetworkHelper.isTrustedNetwork(getContext())) { + devicesListBinding.devicesList.addHeaderView(headerText); + } else { + devicesListBinding.devicesList.addHeaderView(notTrustedText); + } + } else { + devicesListBinding.devicesList.addHeaderView(noWifiHeader); + } + } @Override public void onStart() { super.onStart(); - devicesListBinding.refreshListLayout.setEnabled(true); - BackgroundService.RunCommand(mActivity, service -> service.addDeviceListChangedCallback("PairingFragment", newIsConnectedToNonCellularNetwork -> { - isConnectedToNonCellularNetwork = newIsConnectedToNonCellularNetwork; - updateDeviceList(); - })); + KdeConnect.getInstance().addDeviceListChangedCallback("PairingFragment", () -> mActivity.runOnUiThread(this::updateDeviceList)); + BackgroundService.ForceRefreshConnections(requireContext()); // force a network re-discover updateDeviceList(); } @Override public void onStop() { + KdeConnect.getInstance().removeDeviceListChangedCallback("PairingFragment"); super.onStop(); - devicesListBinding.refreshListLayout.setEnabled(false); - BackgroundService.RunCommand(mActivity, service -> service.removeDeviceListChangedCallback("PairingFragment")); } @Override @@ -265,7 +263,7 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb public boolean onOptionsItemSelected(final MenuItem item) { int id = item.getItemId(); if (id == R.id.menu_refresh) { - updateDeviceListAction(); + refreshDevicesAction(); return true; } else if (id == R.id.menu_custom_device_list) { startActivity(new Intent(mActivity, CustomDevicesActivity.class)); diff --git a/src/org/kde/kdeconnect/UserInterface/PluginSettingsActivity.java b/src/org/kde/kdeconnect/UserInterface/PluginSettingsActivity.java index 3eb667d6..79d1f7fa 100644 --- a/src/org/kde/kdeconnect/UserInterface/PluginSettingsActivity.java +++ b/src/org/kde/kdeconnect/UserInterface/PluginSettingsActivity.java @@ -13,8 +13,8 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; -import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect_tp.R; @@ -55,7 +55,7 @@ public class PluginSettingsActivity Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragmentPlaceHolder); if (fragment == null) { if (pluginKey != null) { - Device device = BackgroundService.getInstance().getDevice(deviceId); + Device device = KdeConnect.getInstance().getDevice(deviceId); if (device != null) { Plugin plugin = device.getPluginIncludingWithoutPermissions(pluginKey); if (plugin != null) { diff --git a/src/org/kde/kdeconnect/UserInterface/PluginSettingsFragment.java b/src/org/kde/kdeconnect/UserInterface/PluginSettingsFragment.java index a0a80946..3c64e353 100644 --- a/src/org/kde/kdeconnect/UserInterface/PluginSettingsFragment.java +++ b/src/org/kde/kdeconnect/UserInterface/PluginSettingsFragment.java @@ -13,8 +13,8 @@ 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.KdeConnect; import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect_tp.R; @@ -56,7 +56,7 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat { this.pluginKey = getArguments().getString(ARG_PLUGIN_KEY); this.layout = getArguments().getInt(ARG_LAYOUT); - this.device = getDeviceOrThrow(getDeviceId()); + this.device = KdeConnect.getInstance().getDevice(getDeviceId()); this.plugin = device.getPluginIncludingWithoutPermissions(pluginKey); super.onCreate(savedInstanceState); @@ -85,13 +85,4 @@ public class PluginSettingsFragment extends PreferenceFragmentCompat { 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; - } } diff --git a/src/org/kde/kdeconnect/UserInterface/PluginSettingsListFragment.java b/src/org/kde/kdeconnect/UserInterface/PluginSettingsListFragment.java index e58d5a59..d7e844f4 100644 --- a/src/org/kde/kdeconnect/UserInterface/PluginSettingsListFragment.java +++ b/src/org/kde/kdeconnect/UserInterface/PluginSettingsListFragment.java @@ -15,8 +15,8 @@ import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceScreen; import androidx.recyclerview.widget.RecyclerView; -import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect_tp.R; @@ -77,22 +77,20 @@ public class PluginSettingsListFragment extends PreferenceFragmentCompat { final String deviceId = getArguments().getString(ARG_DEVICE_ID); - BackgroundService.RunCommand(requireContext(), service -> { - final Device device = service.getDevice(deviceId); - if (device == null) { - final FragmentActivity activity = requireActivity(); - activity.runOnUiThread(activity::finish); - return; - } - List plugins = device.getSupportedPlugins(); - PluginFactory.sortPluginList(plugins); + Device device = KdeConnect.getInstance().getDevice(deviceId); + if (device == null) { + final FragmentActivity activity = requireActivity(); + activity.runOnUiThread(activity::finish); + return; + } + List plugins = device.getSupportedPlugins(); + PluginFactory.sortPluginList(plugins); - for (final String pluginKey : plugins) { - //TODO: Use PreferenceManagers context - PluginPreference pref = new PluginPreference(requireContext(), pluginKey, device, callback); - preferenceScreen.addPreference(pref); - } - }); + for (final String pluginKey : plugins) { + //TODO: Use PreferenceManagers context + PluginPreference pref = new PluginPreference(requireContext(), pluginKey, device, callback); + preferenceScreen.addPreference(pref); + } } @NonNull diff --git a/src/org/kde/kdeconnect/UserInterface/SettingsFragment.java b/src/org/kde/kdeconnect/UserInterface/SettingsFragment.java index 0fb62fac..d6b204eb 100644 --- a/src/org/kde/kdeconnect/UserInterface/SettingsFragment.java +++ b/src/org/kde/kdeconnect/UserInterface/SettingsFragment.java @@ -125,8 +125,10 @@ public class SettingsFragment extends PreferenceFragmentCompat { final boolean isChecked = (Boolean) newValue; NotificationHelper.setPersistentNotificationEnabled(context, isChecked); - BackgroundService.RunCommand(context, - service -> service.changePersistentNotificationVisibility(isChecked)); + BackgroundService service = BackgroundService.getInstance(); + if (service != null) { + service.changePersistentNotificationVisibility(isChecked); + } NotificationHelper.setPersistentNotificationEnabled(context, isChecked);