2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-22 09:58:08 +00:00

Refactor BackgroundService

Added a new KdeConnect Application class that holds the Devices now, while
BackgroundService "only" takes care of the LinkProviders.

Since KdeConnect subclasses Application we have the guarantee that it will
exist as long as our process does, so we can use it as a singleton. This
removes the "BackgroundService.RunCommand" hack (which sent an Intent that
would awake BackgroundService in case it wasn't running already and then
call our code in a callback). This saves lots of round trips between the
system and us and makes things simpler (and stack traces useful) by making
the code sequential.

We already had an Application subclass that I moved to a new helper, which
now the KdeConnect class initializes together with all the other helpers.
This commit is contained in:
Albert Vaca Cintora 2023-05-24 19:26:54 +02:00
parent a6eea8e996
commit ae23413971
46 changed files with 1264 additions and 1385 deletions

View File

@ -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">
<receiver

View File

@ -53,11 +53,6 @@ public abstract class BaseLink {
return linkProvider;
}
//The daemon will periodically destroy unpaired links if this returns false
public boolean linkShouldBeKeptAlive() {
return false;
}
public void addPacketReceiver(PacketReceiver pr) {
receivers.add(pr);
}

View File

@ -186,11 +186,6 @@ public class BluetoothLink extends BaseLink {
}
}
@Override
public boolean linkShouldBeKeptAlive() {
return receivingThread.isAlive();
}
/*
public boolean isConnected() {
return socket.isConnected();

View File

@ -252,14 +252,4 @@ public class LanLink extends BaseLink {
packetReceived(np);
}
@Override
public boolean linkShouldBeKeptAlive() {
return true; //FIXME: Current implementation is broken, so for now we will keep links always established
//We keep the remotely initiated connections, since the remotes require them if they want to request
//pairing to us, or connections that are already paired.
//return (connectionSource == ConnectionStarted.Remotely);
}
}

View File

@ -14,12 +14,12 @@ import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Helpers.ThreadHelper;
import org.kde.kdeconnect.Helpers.TrustedNetworkHelper;
import org.kde.kdeconnect.KdeConnect;
import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.UserInterface.CustomDevicesActivity;
@ -196,13 +196,13 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
if (isDeviceTrusted && !SslHelper.isCertificateStored(context, deviceId)) {
//Device paired with and old version, we can't use it as we lack the certificate
BackgroundService.RunCommand(context, service -> {
Device device = service.getDevice(deviceId);
if (device == null) return;
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 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

View File

@ -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<T extends Plugin> {
void run(T plugin);
}
private final ConcurrentHashMap<String, DeviceListChangedCallback> deviceListChangedCallbacks = new ConcurrentHashMap<>();
private KdeConnect applicationInstance;
private final ArrayList<BaseLinkProvider> linkProviders = new ArrayList<>();
private final ConcurrentHashMap<String, Device> devices = new ConcurrentHashMap<>();
private final HashSet<Object> 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;
}
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);
// This indicates when connected over wifi/usb/bluetooth/(anything other than cellular)
private final MutableLiveData<Boolean> connectedToNonCellularNetwork = new MutableLiveData<>();
public LiveData<Boolean> isConnectedToNonCellularNetwork() {
return connectedToNonCellularNetwork;
}
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<String> 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<BaseLinkProvider> 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<String, Device> 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<Device> it = devices.values().iterator();
while (it.hasNext()) {
Device device = it.next();
Plugin plugin = PluginFactory.instantiatePluginForDevice(getBaseContext(), pluginKey, device);
if (plugin == null) {
continue;
}
plugin.copyGlobalToDeviceSpecificSettings(globalPrefs);
if (!it.hasNext()) {
plugin.removeSettings(globalPrefs);
}
}
}
}
}
public void changePersistentNotificationVisibility(boolean visible) {
NotificationManager nm = 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<String> connectedDevices = new ArrayList<>();
ArrayList<String> 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<InstanceCallback> 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 <T extends Plugin> void RunWithPlugin(final Context c, final String deviceId, final Class<T> pluginClass, final PluginCallback<T> cb) {
RunCommand(c, service -> {
Device device = service.getDevice(deviceId);
if (device == null) {
Log.e("BackgroundService", "Device " + deviceId + " not found");
return;
public void Stop() {
stopForeground(true);
}
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);
});
}
}

View File

@ -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<String> getSupportedPlugins() {
return supportedPlugins;
}

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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<String, Device> devices = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, DeviceListChangedCallback> 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<String, Device> getDevices() {
return devices;
}
public Device getDevice(String id) {
if (id == null) {
return null;
}
return devices.get(id);
}
public <T extends Plugin> T getDevicePlugin(String deviceId, Class<T> 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<String> 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;
}
}

View File

@ -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());

View File

@ -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,7 +48,12 @@ public class BigscreenActivity extends AppCompatActivity {
binding.micButton.setVisibility(View.INVISIBLE);
}
BackgroundService.RunWithPlugin(this, deviceId, BigscreenPlugin.class, plugin -> runOnUiThread(() -> {
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());
@ -70,7 +74,6 @@ public class BigscreenActivity extends AppCompatActivity {
.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));
}
}
}

View File

@ -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<String> = emptyList()
val service = BackgroundService.getInstance()
if (service != null) {
ids = service.devices.values
ids = KdeConnect.getInstance().devices.values
.filter { it.isReachable && it.isPaired }
.map { it.deviceId }
}
putExtra("connectedDeviceIds", ArrayList(ids))
})
}

View File

@ -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();
}
}

View File

@ -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());

View File

@ -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();
}
}

View File

@ -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() {

View File

@ -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

View File

@ -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 -> {
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 -> {
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,7 +301,12 @@ public class MousePadActivity
mCurrentX = event.getX();
mCurrentY = event.getY();
BackgroundService.RunWithPlugin(this, deviceId, MousePadPlugin.class, plugin -> {
MousePadPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, MousePadPlugin.class);
if (plugin == null) {
finish();
return true;
}
float deltaX = (mCurrentX - mPrevX) * displayDpiMultiplier * mCurrentSensitivity;
float deltaY = (mCurrentY - mPrevY) * displayDpiMultiplier * mCurrentSensitivity;
@ -300,8 +318,6 @@ public class MousePadActivity
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() {

View File

@ -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<Device> reachableDevices = BackgroundService.getInstance().getDevices().values().stream()
List<Device> 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,9 +141,7 @@ public class SendKeystrokesToHostActivity extends AppCompatActivity {
private void updateDeviceList() {
BackgroundService.RunCommand(this, service -> {
Collection<Device> devices = service.getDevices().values();
Collection<Device> devices = KdeConnect.getInstance().getDevices().values();
final ArrayList<Device> devicesList = new ArrayList<>();
final ArrayList<ListAdapter.Item> items = new ArrayList<>();
@ -159,14 +155,13 @@ public class SendKeystrokesToHostActivity extends AppCompatActivity {
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
@ -177,7 +172,6 @@ public class SendKeystrokesToHostActivity extends AppCompatActivity {
sendKeys(device);
this.finish(); // close the activity
}
});
}
}

View File

@ -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;
}
}
}
}

View File

@ -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
* <p>
* Can be called multiple times, once for each device
*
* @param _context The context
* @param mpris The mpris plugin
* @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
* <p>
* 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
* <p>
* 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<Device, MprisPlugin.MprisPlayer> player = findPlayer(service);
private void updateCurrentPlayer() {
Pair<Device, MprisPlugin.MprisPlayer> player = findPlayer();
//Update the last-displayed device and player
notificationDevice = player.first == null ? null : player.first.getDeviceId();
notificationPlayer = player.second;
}
private Pair<Device, MprisPlugin.MprisPlayer> findPlayer(BackgroundService service) {
private Pair<Device, MprisPlugin.MprisPlayer> 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,19 +241,20 @@ public class MprisMediaSession implements
}
private void updateRemoteDeviceVolumeControl() {
// Volume control feature is only available from Lollipop onwards
BackgroundService.RunWithPlugin(context, notificationDevice, SystemVolumePlugin.class, plugin -> {
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)) {
@ -264,8 +262,15 @@ public class MprisMediaSession implements
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(service);
updateCurrentPlayer();
//If the player disappeared (and no other playing one found), just remove the notification
if (notificationPlayer == null) {
@ -273,14 +278,6 @@ public class MprisMediaSession implements
return;
}
//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();
@ -317,41 +314,41 @@ public class MprisMediaSession implements
}
//Create all actions (previous/play/pause/next)
Intent iPlay = new Intent(service, MprisMediaNotificationReceiver.class);
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.getPlayer());
PendingIntent piPlay = PendingIntent.getBroadcast(service, 0, iPlay, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
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, service.getString(R.string.mpris_play), piPlay);
R.drawable.ic_play_white, context.getString(R.string.mpris_play), piPlay);
Intent iPause = new Intent(service, MprisMediaNotificationReceiver.class);
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.getPlayer());
PendingIntent piPause = PendingIntent.getBroadcast(service, 0, iPause, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
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, service.getString(R.string.mpris_pause), piPause);
R.drawable.ic_pause_white, context.getString(R.string.mpris_pause), piPause);
Intent iPrevious = new Intent(service, MprisMediaNotificationReceiver.class);
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.getPlayer());
PendingIntent piPrevious = PendingIntent.getBroadcast(service, 0, iPrevious, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
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, service.getString(R.string.mpris_previous), piPrevious);
R.drawable.ic_previous_white, context.getString(R.string.mpris_previous), piPrevious);
Intent iNext = new Intent(service, MprisMediaNotificationReceiver.class);
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.getPlayer());
PendingIntent piNext = PendingIntent.getBroadcast(service, 0, iNext, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
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, service.getString(R.string.mpris_next), piNext);
R.drawable.ic_next_white, context.getString(R.string.mpris_next), piNext);
Intent iOpenActivity = new Intent(service, MprisActivity.class);
Intent iOpenActivity = new Intent(context, MprisActivity.class);
iOpenActivity.putExtra("deviceId", notificationDevice);
iOpenActivity.putExtra("player", notificationPlayer.getPlayer());
iOpenActivity.putExtra("player", notificationPlayer.getPlayerName());
PendingIntent piOpenActivity = TaskStackBuilder.create(context)
.addNextIntentWithParentStack(iOpenActivity)
@ -364,9 +361,9 @@ public class MprisMediaSession implements
.setContentIntent(piOpenActivity)
.setSmallIcon(R.drawable.ic_play_white)
.setShowWhen(false)
.setColor(ContextCompat.getColor(service, R.color.primary))
.setColor(ContextCompat.getColor(context, R.color.primary))
.setVisibility(androidx.core.app.NotificationCompat.VISIBILITY_PUBLIC)
.setSubText(service.getDevice(notificationDevice).getName());
.setSubText(KdeConnect.getInstance().getDevice(notificationDevice).getName());
if (!notificationPlayer.getTitle().isEmpty()) {
notification.setContentTitle(notificationPlayer.getTitle());
@ -375,13 +372,13 @@ public class MprisMediaSession implements
}
//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() + ")");
notification.setContentText(notificationPlayer.getArtist() + " - " + notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayerName() + ")");
} else if (!notificationPlayer.getArtist().isEmpty()) {
notification.setContentText(notificationPlayer.getArtist() + " (" + notificationPlayer.getPlayer() + ")");
notification.setContentText(notificationPlayer.getArtist() + " (" + notificationPlayer.getPlayerName() + ")");
} else if (!notificationPlayer.getAlbum().isEmpty()) {
notification.setContentText(notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayer() + ")");
notification.setContentText(notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayerName() + ")");
} else {
notification.setContentText(notificationPlayer.getPlayer());
notification.setContentText(notificationPlayer.getPlayerName());
}
if (albumArt != null) {
@ -389,11 +386,11 @@ public class MprisMediaSession implements
}
if (!notificationPlayer.isPlaying()) {
Intent iCloseNotification = new Intent(service, MprisMediaNotificationReceiver.class);
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.getPlayer());
PendingIntent piCloseNotification = PendingIntent.getBroadcast(service, 0, iCloseNotification, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
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);
}
@ -449,7 +446,6 @@ public class MprisMediaSession implements
mediaSession.setActive(true);
final NotificationManager nm = ContextCompat.getSystemService(context, NotificationManager.class);
nm.notify(MPRIS_MEDIA_NOTIFICATION_ID, notification.build());
});
}
public void closeMediaNotification() {

View File

@ -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,32 +80,27 @@ public class MprisNowPlayingFragment extends Fragment implements VolumeKeyListen
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
if (activityMprisBinding == null) {
activityMprisBinding = MprisNowPlayingBinding.inflate(inflater);
mprisControlBinding = activityMprisBinding.mprisControl;
String targetPlayerName = "";
Intent activityIntent = requireActivity().getIntent();
activityIntent.getStringExtra("player");
activityIntent.removeExtra("player");
deviceId = requireArguments().getString(MprisPlugin.DEVICE_ID_KEY);
if (TextUtils.isEmpty(targetPlayerName)) {
if (savedInstanceState != null) {
targetPlayerName = "";
Intent activityIntent = requireActivity().getIntent();
if (activityIntent.hasExtra("player")) {
targetPlayerName = activityIntent.getStringExtra("player");
activityIntent.removeExtra("player");
} else if (savedInstanceState != null) {
targetPlayerName = savedInstanceState.getString("targetPlayer");
}
}
deviceId = requireArguments().getString(MprisPlugin.DEVICE_ID_KEY);
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);
BackgroundService.RunCommand(requireContext(), service -> service.addConnectionListener(connectionReceiver));
connectToPlugin(targetPlayerName);
performActionOnClick(mprisControlBinding.loopButton, p -> {
switch (p.getLoopStatus()) {
case "None":
@ -159,23 +140,18 @@ public class MprisNowPlayingFragment extends Fragment implements VolumeKeyListen
@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 (!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);
@ -192,36 +168,54 @@ public class MprisNowPlayingFragment extends Fragment implements VolumeKeyListen
@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);
}
return activityMprisBinding.getRoot();
}
private void connectToPlugin(final String targetPlayerName) {
BackgroundService.RunWithPlugin(requireContext(), deviceId, MprisPlugin.class, mpris -> {
targetPlayer = mpris.getPlayerStatus(targetPlayerName);
@Override
public void onDestroyView() {
disconnectFromPlugin();
super.onDestroyView();
}
mpris.setPlayerStatusUpdatedHandler("activity", () -> requireActivity().runOnUiThread(() -> updatePlayerStatus(mpris)));
mpris.setPlayerListUpdatedHandler("activity", () -> {
final List<String> playerList = mpris.getPlayerList();
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<String> playerList = plugin.getPlayerList();
final ArrayAdapter<String> 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()) {
@ -240,11 +234,12 @@ public class MprisNowPlayingFragment extends Fragment implements VolumeKeyListen
if (pos >= playerList.size()) return;
String player = playerList.get(pos);
if (targetPlayer != null && player.equals(targetPlayer.getPlayer())) {
if (targetPlayer != null && player.equals(targetPlayer.getPlayerName())) {
return; //Player hasn't actually changed
}
targetPlayer = mpris.getPlayerStatus(player);
updatePlayerStatus(mpris);
targetPlayer = plugin.getPlayerStatus(player);
targetPlayerName = targetPlayer.getPlayerName();
updatePlayerStatus(plugin);
if (targetPlayer != null && targetPlayer.isPlaying()) {
MprisMediaSession.getInstance().playerSelected(targetPlayer);
@ -259,11 +254,11 @@ public class MprisNowPlayingFragment extends Fragment implements VolumeKeyListen
if (targetPlayer == null) {
//If no player is selected, try to select a playing player
targetPlayer = mpris.getPlayingPlayer();
targetPlayer = plugin.getPlayingPlayer();
}
//Try to select the specified player
if (targetPlayer != null) {
int targetIndex = adapter.getPosition(targetPlayer.getPlayer());
int targetIndex = adapter.getPosition(targetPlayer.getPlayerName());
if (targetIndex >= 0) {
mprisControlBinding.playerSpinner.setSelection(targetIndex);
} else {
@ -272,33 +267,30 @@ public class MprisNowPlayingFragment extends Fragment implements VolumeKeyListen
}
//If no player selected, select the first one (if any)
if (targetPlayer == null && !playerList.isEmpty()) {
targetPlayer = mpris.getPlayerStatus(playerList.get(0));
targetPlayer = plugin.getPlayerStatus(playerList.get(0));
mprisControlBinding.playerSpinner.setSelection(0);
}
updatePlayerStatus(mpris);
});
});
});
}
@Override
public void onDestroy() {
super.onDestroy();
BackgroundService.RunCommand(requireContext(), service -> service.removeConnectionListener(connectionReceiver));
}
private void performActionOnClick(View v, MprisPlayerCallback l) {
v.setOnClickListener(view -> BackgroundService.RunCommand(requireContext(), service -> {
if (targetPlayer == null) return;
l.performAction(targetPlayer);
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);
}
}

View File

@ -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;

View File

@ -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 -> {
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();
}
}

View File

@ -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();
}
}));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {

View File

@ -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,11 +37,16 @@ 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<CommandEntry> commandItems;
private void updateView() {
BackgroundService.RunWithPlugin(this, deviceId, RunCommandPlugin.class, plugin -> runOnUiThread(() -> {
RunCommandPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, RunCommandPlugin.class);
if (plugin == null) {
finish();
return;
}
registerForContextMenu(binding.runCommandsList);
commandItems = new ArrayList<>();
@ -69,7 +73,6 @@ public class RunCommandActivity extends AppCompatActivity {
}
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) {
}
if (canAddCommands) {
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 -> BackgroundService.RunWithPlugin(RunCommandActivity.this, deviceId, RunCommandPlugin.class, plugin -> {
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();
}));
});
}
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

View File

@ -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)
}
consumer.accept(ControlAction.RESPONSE_OK)
} else {
consumer.accept(ControlAction.RESPONSE_FAIL)
@ -141,9 +139,7 @@ class RunCommandControlsProviderService : ControlsProviderService() {
private fun getAllCommandsList(): List<CommandEntryWithDevice> {
val commandList = mutableListOf<CommandEntryWithDevice>()
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

View File

@ -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,8 +25,7 @@ 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);
@ -54,12 +52,11 @@ public class RunCommandUrlActivity extends AppCompatActivity {
RunCommandUrlActivity.this.finish();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
Vibrator vibrator = RunCommandUrlActivity.this.getSystemService(Vibrator.class);
Vibrator vibrator = getSystemService(Vibrator.class);
if(vibrator != null && vibrator.hasVibrator()) {
vibrator.vibrate(100);
}
}
});
} catch (Exception e) {
Log.e("RuncommandPlugin", "Exception", e);
}

View File

@ -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,8 +37,7 @@ 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 {
@ -46,7 +47,6 @@ public class RunCommandWidget extends AppWidgetProvider {
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<String, Device> 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 -> {
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) {

View File

@ -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,8 +26,7 @@ public class RunCommandWidgetDeviceSelector extends AppCompatActivity {
WidgetRemoteCommandPluginDialogBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
BackgroundService.RunCommand(this, service -> runOnUiThread(() -> {
final List<CommandEntry> deviceItems = service.getDevices().values().stream()
final List<CommandEntry> 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))
@ -46,6 +44,5 @@ public class RunCommandWidgetDeviceSelector extends AppCompatActivity {
finish();
});
}));
}
}

View File

@ -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;

View File

@ -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();

View File

@ -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,9 +67,7 @@ public class ShareActivity extends AppCompatActivity {
return;
}
BackgroundService.RunCommand(this, service -> {
Collection<Device> devices = service.getDevices().values();
Collection<Device> devices = KdeConnect.getInstance().getDevices().values();
final ArrayList<Device> devicesList = new ArrayList<>();
final ArrayList<ListAdapter.Item> items = new ArrayList<>();
@ -86,15 +82,15 @@ public class ShareActivity extends AppCompatActivity {
}
}
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));
SharePlugin plugin = KdeConnect.getInstance().getDevicePlugin(device.getDeviceId(), SharePlugin.class);
if (plugin != null) {
plugin.share(intent);
}
finish();
});
});
});
}
@Override
@ -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();
});
} 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();
}
}

View File

@ -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);
}
}

View File

@ -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<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) {
Log.d("DirectShare", "invoked");
final List<ChooserTarget> 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();

View File

@ -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) {

View File

@ -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

View File

@ -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 -> {
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

View File

@ -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) {

View File

@ -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,18 +195,16 @@ 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)
val device = KdeConnect.getInstance().getDevice(deviceId)
if (device == null) {
Log.w(this::class.simpleName, "Reject pairing - device no longer exists: $deviceId")
return@RunCommand
return null
}
when (pairStatus) {
PAIRING_ACCEPTED -> device.acceptPairing()
PAIRING_REJECTED -> device.rejectPairing()
}
}
}
return if (pairStatus == PAIRING_ACCEPTED || pairStatus == PAIRING_PENDING) deviceId else null
}
@ -228,13 +227,12 @@ 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<Device> = service.devices.values
val devices: Collection<Device> = KdeConnect.getInstance().devices.values
for (device in devices) {
if (device.isReachable && device.isPaired) {
val item = devicesMenu.add(Menu.FIRST, id++, 1, device.name)
@ -259,14 +257,11 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
}
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
}
}

View File

@ -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,24 +105,18 @@ 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);
}
}, 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;
@ -136,27 +130,12 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
}
listRefreshCalledThisFrame = true;
Collection<Device> devices = service.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);
//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);
BackgroundService service = BackgroundService.getInstance();
if (service == null) {
updateConnectivityInfoHeader(true);
} else {
devicesListBinding.devicesList.addHeaderView(notTrustedText);
}
} else {
devicesListBinding.devicesList.addHeaderView(noWifiHeader);
service.isConnectedToNonCellularNetwork().observe(this, this::updateConnectivityInfoHeader);
}
try {
@ -167,6 +146,8 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
connectedSection = new SectionItem(res.getString(R.string.category_connected_devices));
items.add(connectedSection);
Collection<Device> devices = KdeConnect.getInstance().getDevices().values();
for (Device device : devices) {
if (device.isReachable() && device.isPaired()) {
items.add(new PairingDeviceItem(device, PairingFragment.this));
@ -215,26 +196,43 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
} finally {
listRefreshCalledThisFrame = false;
}
}));
}
void updateConnectivityInfoHeader(boolean isConnectedToNonCellularNetwork) {
Collection<Device> 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));

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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,8 +77,7 @@ public class PluginSettingsListFragment extends PreferenceFragmentCompat {
final String deviceId = getArguments().getString(ARG_DEVICE_ID);
BackgroundService.RunCommand(requireContext(), service -> {
final Device device = service.getDevice(deviceId);
Device device = KdeConnect.getInstance().getDevice(deviceId);
if (device == null) {
final FragmentActivity activity = requireActivity();
activity.runOnUiThread(activity::finish);
@ -92,7 +91,6 @@ public class PluginSettingsListFragment extends PreferenceFragmentCompat {
PluginPreference pref = new PluginPreference(requireContext(), pluginKey, device, callback);
preferenceScreen.addPreference(pref);
}
});
}
@NonNull

View File

@ -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);