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

889 lines
31 KiB
Java
Raw Normal View History

2014-11-16 23:14:06 -08:00
/*
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
2013-09-05 01:33:54 +02:00
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
2016-12-05 23:42:58 +01:00
import org.kde.kdeconnect.Helpers.NotificationHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect.UserInterface.MainActivity;
import org.kde.kdeconnect_tp.R;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
public class Device implements BaseLink.PacketReceiver {
private final Context context;
private final String deviceId;
private String name;
2014-01-16 09:51:32 +04:00
public PublicKey publicKey;
public Certificate certificate;
private int notificationId;
private int protocolVersion;
2015-09-08 15:05:32 -07:00
private DeviceType deviceType;
private PairStatus pairStatus;
private final CopyOnWriteArrayList<PairingCallback> pairingCallback = new CopyOnWriteArrayList<>();
2018-10-27 00:01:30 +02:00
private final Map<String, BasePairingHandler> pairingHandlers = new HashMap<>();
2015-09-08 15:05:32 -07:00
private final CopyOnWriteArrayList<BaseLink> links = new CopyOnWriteArrayList<>();
2015-09-08 15:05:32 -07:00
private List<String> m_supportedPlugins = new ArrayList<>();
private final ConcurrentHashMap<String, Plugin> plugins = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Plugin> pluginsWithoutPermissions = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Plugin> pluginsWithoutOptionalPermissions = new ConcurrentHashMap<>();
private Map<String, ArrayList<String>> pluginsByIncomingInterface = new HashMap<>();
2015-09-08 15:05:32 -07:00
private final SharedPreferences settings;
private final CopyOnWriteArrayList<PluginsChangedListener> pluginsChangedListeners = new CopyOnWriteArrayList<>();
public interface PluginsChangedListener {
void onPluginsChanged(Device device);
}
public enum PairStatus {
NotPaired,
Paired
}
public enum DeviceType {
Phone,
Tablet,
Computer,
Tv;
2018-10-26 23:53:58 +02:00
static DeviceType FromString(String s) {
if ("tablet".equals(s)) return Tablet;
if ("phone".equals(s)) return Phone;
if ("tv".equals(s)) return Tv;
return Computer; //Default
}
public String toString() {
switch (this) {
case Tablet:
return "tablet";
case Phone:
return "phone";
case Tv:
return "tv";
default:
return "desktop";
}
}
}
public interface PairingCallback {
void incomingRequest();
void pairingSuccessful();
void pairingFailed(String error);
void unpaired();
}
//Remembered trusted device, we need to wait for a incoming devicelink to communicate
Device(Context context, String deviceId) {
settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
//Log.e("Device","Constructor A");
this.context = context;
this.deviceId = deviceId;
this.name = settings.getString("deviceName", context.getString(R.string.unknown_device));
this.pairStatus = PairStatus.Paired;
this.protocolVersion = NetworkPacket.ProtocolVersion; //We don't know it yet
this.deviceType = DeviceType.FromString(settings.getString("deviceType", "desktop"));
try {
String publicKeyStr = settings.getString("publicKey", null);
if (publicKeyStr != null) {
byte[] publicKeyBytes = Base64.decode(publicKeyStr, 0);
publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
}
} catch (Exception e) {
e.printStackTrace();
Log.e("KDE/Device", "Exception deserializing stored public key for device");
}
//Assume every plugin is supported until addLink is called and we can get the actual list
m_supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins());
//Do not load plugins yet, the device is not present
//reloadPluginsFromSettings();
}
//Device known via an incoming connection sent to us via a devicelink, we know everything but we don't trust it yet
Device(Context context, NetworkPacket np, BaseLink dl) {
//Log.e("Device","Constructor B");
this.context = context;
this.deviceId = np.getString("deviceId");
this.name = context.getString(R.string.unknown_device); //We read it in addLink
this.pairStatus = PairStatus.NotPaired;
this.protocolVersion = 0;
this.deviceType = DeviceType.Computer;
this.publicKey = null;
settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
addLink(np, dl);
}
public String getName() {
return name != null ? name : context.getString(R.string.unknown_device);
}
public Drawable getIcon() {
int drawableId;
switch (deviceType) {
case Phone:
drawableId = R.drawable.ic_device_phone;
break;
case Tablet:
drawableId = R.drawable.ic_device_tablet;
break;
case Tv:
drawableId = R.drawable.ic_device_tv;
break;
default:
drawableId = R.drawable.ic_device_laptop;
}
return ContextCompat.getDrawable(context, drawableId);
}
public DeviceType getDeviceType() {
return deviceType;
}
public String getDeviceId() {
return deviceId;
}
2015-06-25 04:20:03 +05:30
public Context getContext() {
return context;
}
//Returns 0 if the version matches, < 0 if it is older or > 0 if it is newer
public int compareProtocolVersion() {
return protocolVersion - NetworkPacket.ProtocolVersion;
}
//
// Pairing-related functions
//
public boolean isPaired() {
return pairStatus == PairStatus.Paired;
}
/* Asks all pairing handlers that, is pair requested? */
public boolean isPairRequested() {
boolean pairRequested = false;
for (BasePairingHandler ph : pairingHandlers.values()) {
pairRequested = pairRequested || ph.isPairRequested();
}
return pairRequested;
}
/* Asks all pairing handlers that, is pair requested by peer? */
public boolean isPairRequestedByPeer() {
boolean pairRequestedByPeer = false;
for (BasePairingHandler ph : pairingHandlers.values()) {
pairRequestedByPeer = pairRequestedByPeer || ph.isPairRequestedByPeer();
}
return pairRequestedByPeer;
}
public void addPairingCallback(PairingCallback callback) {
pairingCallback.add(callback);
}
public void removePairingCallback(PairingCallback callback) {
pairingCallback.remove(callback);
}
public void requestPairing() {
2013-09-05 01:33:54 +02:00
Resources res = context.getResources();
if (isPaired()) {
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(res.getString(R.string.error_already_paired));
}
return;
}
if (!isReachable()) {
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(res.getString(R.string.error_not_reachable));
}
return;
}
for (BasePairingHandler ph : pairingHandlers.values()) {
ph.requestPairing();
}
}
public void unpair() {
for (BasePairingHandler ph : pairingHandlers.values()) {
ph.unpair();
}
unpairInternal(); // Even if there are no pairing handlers, unpair
}
/**
* This method does not send an unpair package, instead it unpairs internally by deleting trusted device info. . Likely to be called after sending package from
* pairing handler
*/
private void unpairInternal() {
//Log.e("Device","Unpairing (unpairInternal)");
pairStatus = PairStatus.NotPaired;
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
2015-01-31 00:16:06 -08:00
preferences.edit().remove(deviceId).apply();
2015-06-25 04:20:03 +05:30
SharedPreferences devicePreferences = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
devicePreferences.edit().clear().apply();
for (PairingCallback cb : pairingCallback) cb.unpaired();
reloadPluginsFromSettings();
}
/* This method should be called after pairing is done from pairing handler. Calling this method again should not create any problem as most of the things will get over writter*/
private void pairingDone() {
//Log.e("Device", "Storing as trusted, deviceId: "+deviceId);
hidePairingNotification();
pairStatus = PairStatus.Paired;
//Store as trusted device
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
preferences.edit().putBoolean(deviceId, true).apply();
SharedPreferences.Editor editor = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE).edit();
editor.putString("deviceName", name);
editor.putString("deviceType", deviceType.toString());
editor.apply();
reloadPluginsFromSettings();
for (PairingCallback cb : pairingCallback) {
cb.pairingSuccessful();
}
}
/* This method is called after accepting pair request form GUI */
public void acceptPairing() {
2015-06-25 04:20:03 +05:30
Log.i("KDE/Device", "Accepted pair request started by the other device");
for (BasePairingHandler ph : pairingHandlers.values()) {
ph.acceptPairing();
}
}
/* This method is called after rejecting pairing from GUI */
public void rejectPairing() {
Log.i("KDE/Device", "Rejected pair request started by the other device");
//Log.e("Device","Unpairing (rejectPairing)");
pairStatus = PairStatus.NotPaired;
for (BasePairingHandler ph : pairingHandlers.values()) {
ph.rejectPairing();
2015-06-25 04:20:03 +05:30
}
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_canceled_by_user));
}
}
//
// Notification related methods used during pairing
//
public int getNotificationId() {
return notificationId;
}
public void displayPairingNotification() {
2015-12-02 09:46:22 -08:00
hidePairingNotification();
notificationId = (int) System.currentTimeMillis();
Intent intent = new Intent(getContext(), MainActivity.class);
intent.putExtra(MainActivity.EXTRA_DEVICE_ID, getDeviceId());
intent.putExtra(MainActivity.PAIR_REQUEST_STATUS, MainActivity.PAIRING_PENDING);
PendingIntent pendingIntent = PendingIntent.getActivity(getContext(), 1, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Intent acceptIntent = new Intent(getContext(), MainActivity.class);
Intent rejectIntent = new Intent(getContext(), MainActivity.class);
acceptIntent.putExtra(MainActivity.EXTRA_DEVICE_ID, getDeviceId());
//acceptIntent.putExtra("notificationId", notificationId);
acceptIntent.putExtra(MainActivity.PAIR_REQUEST_STATUS, MainActivity.PAIRING_ACCEPTED);
rejectIntent.putExtra(MainActivity.EXTRA_DEVICE_ID, getDeviceId());
//rejectIntent.putExtra("notificationId", notificationId);
rejectIntent.putExtra(MainActivity.PAIR_REQUEST_STATUS, MainActivity.PAIRING_REJECTED);
PendingIntent acceptedPendingIntent = PendingIntent.getActivity(getContext(), 2, acceptIntent, PendingIntent.FLAG_ONE_SHOT);
PendingIntent rejectedPendingIntent = PendingIntent.getActivity(getContext(), 4, rejectIntent, PendingIntent.FLAG_ONE_SHOT);
Resources res = getContext().getResources();
final NotificationManager notificationManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE);
Notification noti = new NotificationCompat.Builder(getContext(), NotificationHelper.Channels.DEFAULT)
.setContentTitle(res.getString(R.string.pairing_request_from, getName()))
.setContentText(res.getString(R.string.tap_to_answer))
.setContentIntent(pendingIntent)
.setTicker(res.getString(R.string.pair_requested))
.setSmallIcon(R.drawable.ic_notification)
.addAction(R.drawable.ic_accept_pairing, res.getString(R.string.pairing_accept), acceptedPendingIntent)
.addAction(R.drawable.ic_reject_pairing, res.getString(R.string.pairing_reject), rejectedPendingIntent)
.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_ALL)
.build();
2016-12-05 23:42:58 +01:00
NotificationHelper.notifyCompat(notificationManager, notificationId, noti);
2016-12-05 23:42:58 +01:00
BackgroundService.addGuiInUseCounter(context);
}
2015-12-02 09:46:22 -08:00
public void hidePairingNotification() {
final NotificationManager notificationManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId);
2015-12-02 09:46:22 -08:00
BackgroundService.removeGuiInUseCounter(context);
}
//
// ComputerLink-related functions
//
public boolean isReachable() {
return !links.isEmpty();
}
public void addLink(NetworkPacket identityPacket, BaseLink link) {
//FilesHelper.LogOpenFileCount();
this.protocolVersion = identityPacket.getInt("protocolVersion");
if (identityPacket.has("deviceName")) {
this.name = identityPacket.getString("deviceName", this.name);
SharedPreferences.Editor editor = settings.edit();
editor.putString("deviceName", this.name);
editor.apply();
}
if (identityPacket.has("deviceType")) {
this.deviceType = DeviceType.FromString(identityPacket.getString("deviceType", "desktop"));
}
if (identityPacket.has("certificate")) {
String certificateString = identityPacket.getString("certificate");
try {
byte[] certificateBytes = Base64.decode(certificateString, 0);
certificate = SslHelper.parseCertificate(certificateBytes);
Log.i("KDE/Device", "Got certificate ");
} catch (Exception e) {
e.printStackTrace();
Log.e("KDE/Device", "Error getting certificate");
}
}
links.add(link);
try {
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context);
byte[] privateKeyBytes = Base64.decode(globalSettings.getString("privateKey", ""), 0);
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
link.setPrivateKey(privateKey);
} catch (Exception e) {
e.printStackTrace();
Log.e("KDE/Device", "Exception reading our own private key"); //Should not happen
}
Log.i("KDE/Device", "addLink " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size());
if (!pairingHandlers.containsKey(link.getName())) {
BasePairingHandler.PairingHandlerCallback callback = new BasePairingHandler.PairingHandlerCallback() {
@Override
public void incomingRequest() {
for (PairingCallback cb : pairingCallback) {
cb.incomingRequest();
}
}
@Override
public void pairingDone() {
Device.this.pairingDone();
}
@Override
public void pairingFailed(String error) {
for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(error);
}
}
@Override
public void unpaired() {
unpairInternal();
}
};
pairingHandlers.put(link.getName(), link.getPairingHandler(this, callback));
}
Set<String> outgoingCapabilities = identityPacket.getStringSet("outgoingCapabilities", null);
Set<String> incomingCapabilities = identityPacket.getStringSet("incomingCapabilities", null);
if (incomingCapabilities != null && outgoingCapabilities != null) {
m_supportedPlugins = new Vector<>(PluginFactory.pluginsForCapabilities(incomingCapabilities, outgoingCapabilities));
} else {
m_supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins());
}
link.addPacketReceiver(this);
reloadPluginsFromSettings();
}
public void removeLink(BaseLink link) {
//FilesHelper.LogOpenFileCount();
/* Remove pairing handler corresponding to that link too if it was the only link*/
boolean linkPresent = false;
for (BaseLink bl : links) {
if (bl.getName().equals(link.getName())) {
linkPresent = true;
break;
}
}
if (!linkPresent) {
pairingHandlers.remove(link.getName());
}
link.removePacketReceiver(this);
links.remove(link);
2015-06-25 04:20:03 +05:30
Log.i("KDE/Device", "removeLink: " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size());
if (links.isEmpty()) {
reloadPluginsFromSettings();
}
}
@Override
public void onPacketReceived(NetworkPacket np) {
hackToMakeRetrocompatiblePacketTypes(np);
if (NetworkPacket.PACKET_TYPE_PAIR.equals(np.getType())) {
2015-06-25 04:20:03 +05:30
Log.i("KDE/Device", "Pair package");
for (BasePairingHandler ph : pairingHandlers.values()) {
try {
ph.packageReceived(np);
} catch (Exception e) {
e.printStackTrace();
Log.e("PairingPacketReceived", "Exception");
}
}
} else if (isPaired()) {
//If capabilities are not supported, iterate all plugins
Collection<String> targetPlugins = pluginsByIncomingInterface.get(np.getType());
2016-06-02 13:56:46 +02:00
if (targetPlugins != null && !targetPlugins.isEmpty()) {
2016-05-31 21:02:17 +02:00
for (String pluginKey : targetPlugins) {
Plugin plugin = plugins.get(pluginKey);
try {
plugin.onPacketReceived(np);
2016-05-31 21:02:17 +02:00
} catch (Exception e) {
e.printStackTrace();
Log.e("KDE/Device", "Exception in " + plugin.getPluginKey() + "'s onPacketReceived()");
//try { Log.e("KDE/Device", "NetworkPacket:" + np.serialize()); } catch (Exception _) { }
2016-05-31 21:02:17 +02:00
}
}
2016-06-02 13:56:46 +02:00
} else {
2016-06-21 13:38:21 +02:00
Log.w("Device", "Ignoring packet with type " + np.getType() + " because no plugin can handle it");
}
} else {
//Log.e("KDE/onPacketReceived","Device not paired, will pass package to unpairedPacketListeners");
// If it is pair package, it should be captured by "if" at start
// If not and device is paired, it should be captured by isPaired
// Else unpair, this handles the situation when one device unpairs, but other dont know like unpairing when wi-fi is off
unpair();
//If capabilities are not supported, iterate all plugins
Collection<String> targetPlugins = pluginsByIncomingInterface.get(np.getType());
2016-06-02 13:56:46 +02:00
if (targetPlugins != null && !targetPlugins.isEmpty()) {
2016-05-31 21:02:17 +02:00
for (String pluginKey : targetPlugins) {
Plugin plugin = plugins.get(pluginKey);
try {
plugin.onUnpairedDevicePacketReceived(np);
2016-05-31 21:02:17 +02:00
} catch (Exception e) {
e.printStackTrace();
Log.e("KDE/Device", "Exception in " + plugin.getDisplayName() + "'s onPacketReceived() in unPairedPacketListeners");
2016-05-31 21:02:17 +02:00
}
}
2016-06-02 13:56:46 +02:00
} else {
Log.e("Device", "Ignoring packet with type " + np.getType() + " because no plugin can handle it");
}
}
}
public static abstract class SendPacketStatusCallback {
public abstract void onSuccess();
public abstract void onFailure(Throwable e);
public void onProgressChanged(int percent) {
}
}
2018-10-27 00:01:30 +02:00
private final SendPacketStatusCallback defaultCallback = new SendPacketStatusCallback() {
@Override
public void onSuccess() {
}
@Override
public void onFailure(Throwable e) {
if (e != null) {
e.printStackTrace();
} else {
Log.e("KDE/sendPacket", "Unknown (null) exception");
}
}
};
public void sendPacket(NetworkPacket np) {
sendPacket(np, defaultCallback);
}
public boolean sendPacketBlocking(NetworkPacket np) {
return sendPacketBlocking(np, defaultCallback);
}
//Async
public void sendPacket(final NetworkPacket np, final SendPacketStatusCallback callback) {
new Thread(() -> sendPacketBlocking(np, callback)).start();
}
public boolean sendPacketBlocking(final NetworkPacket np, final SendPacketStatusCallback callback) {
/*
if (!m_outgoingCapabilities.contains(np.getType()) && !NetworkPacket.protocolPacketTypes.contains(np.getType())) {
Log.e("Device/sendPacket", "Plugin tried to send an undeclared package: " + np.getType());
Log.w("Device/sendPacket", "Declared outgoing package types: " + Arrays.toString(m_outgoingCapabilities.toArray()));
}
*/
hackToMakeRetrocompatiblePacketTypes(np);
boolean useEncryption = (protocolVersion < LanLinkProvider.MIN_VERSION_WITH_SSL_SUPPORT && (!np.getType().equals(NetworkPacket.PACKET_TYPE_PAIR) && isPaired()));
boolean success = false;
//Make a copy to avoid concurrent modification exception if the original list changes
for (final BaseLink link : links) {
if (link == null)
continue; //Since we made a copy, maybe somebody destroyed the link in the meanwhile
if (useEncryption) {
success = link.sendPacketEncrypted(np, callback, publicKey);
} else {
success = link.sendPacket(np, callback);
}
if (success) break; //If the link didn't call sendSuccess(), try the next one
}
if (!success) {
Log.e("KDE/sendPacket", "No device link (of " + links.size() + " available) could send the package. Packet " + np.getType() + " to " + name + " lost!");
}
2015-01-31 00:16:06 -08:00
return success;
}
//
// Plugin-related functions
//
public <T extends Plugin> T getPlugin(Class<T> pluginClass) {
Plugin plugin = getPlugin(Plugin.getPluginKey(pluginClass));
return (T) plugin;
}
public Plugin getPlugin(String pluginKey) {
return plugins.get(pluginKey);
}
private synchronized boolean addPlugin(final String pluginKey) {
Plugin existing = plugins.get(pluginKey);
if (existing != null) {
if (existing.getMinSdk() > Build.VERSION.SDK_INT) {
Log.i("KDE/addPlugin", "Min API level not fulfilled " + pluginKey);
return false;
}
2015-09-08 15:05:32 -07:00
//Log.w("KDE/addPlugin","plugin already present:" + pluginKey);
if (existing.checkOptionalPermissions()) {
Log.i("KDE/addPlugin", "Optional Permissions OK " + pluginKey);
pluginsWithoutOptionalPermissions.remove(pluginKey);
} else {
Log.e("KDE/addPlugin", "No optional permission " + pluginKey);
pluginsWithoutOptionalPermissions.put(pluginKey, existing);
}
return true;
}
final Plugin plugin = PluginFactory.instantiatePluginForDevice(context, pluginKey, this);
if (plugin == null) {
Log.e("KDE/addPlugin", "could not instantiate plugin: " + pluginKey);
return false;
}
if (plugin.getMinSdk() > Build.VERSION.SDK_INT) {
Log.i("KDE/addPlugin", "Min API level not fulfilled" + pluginKey);
return false;
}
boolean success;
try {
success = plugin.onCreate();
} catch (Exception e) {
success = false;
e.printStackTrace();
}
if (!success) {
Log.e("KDE/addPlugin", "plugin failed to load " + pluginKey);
}
plugins.put(pluginKey, plugin);
if (!plugin.checkRequiredPermissions()) {
Log.e("KDE/addPlugin", "No permission " + pluginKey);
plugins.remove(pluginKey);
pluginsWithoutPermissions.put(pluginKey, plugin);
success = false;
} else {
Log.i("KDE/addPlugin", "Permissions OK " + pluginKey);
pluginsWithoutPermissions.remove(pluginKey);
if (plugin.checkOptionalPermissions()) {
Log.i("KDE/addPlugin", "Optional Permissions OK " + pluginKey);
pluginsWithoutOptionalPermissions.remove(pluginKey);
} else {
Log.e("KDE/addPlugin", "No optional permission " + pluginKey);
pluginsWithoutOptionalPermissions.put(pluginKey, plugin);
}
}
return success;
}
private synchronized boolean removePlugin(String pluginKey) {
Plugin plugin = plugins.remove(pluginKey);
if (plugin == null) {
return false;
}
try {
plugin.onDestroy();
//Log.e("removePlugin","removed " + pluginKey);
} catch (Exception e) {
e.printStackTrace();
Log.e("KDE/removePlugin", "Exception calling onDestroy for plugin " + pluginKey);
}
return true;
}
public void setPluginEnabled(String pluginKey, boolean value) {
settings.edit().putBoolean(pluginKey, value).apply();
2015-09-08 15:05:32 -07:00
reloadPluginsFromSettings();
}
public boolean isPluginEnabled(String pluginKey) {
boolean enabledByDefault = PluginFactory.getPluginInfo(pluginKey).isEnabledByDefault();
return settings.getBoolean(pluginKey, enabledByDefault);
}
public void reloadPluginsFromSettings() {
2015-09-08 15:05:32 -07:00
HashMap<String, ArrayList<String>> newPluginsByIncomingInterface = new HashMap<>();
for (String pluginKey : m_supportedPlugins) {
2015-09-08 15:05:32 -07:00
PluginFactory.PluginInfo pluginInfo = PluginFactory.getPluginInfo(pluginKey);
2015-09-08 15:05:32 -07:00
boolean pluginEnabled = false;
boolean listenToUnpaired = pluginInfo.listenToUnpaired();
if ((isPaired() || listenToUnpaired) && isReachable()) {
2015-09-08 15:05:32 -07:00
pluginEnabled = isPluginEnabled(pluginKey);
}
2015-09-08 15:05:32 -07:00
if (pluginEnabled) {
boolean success = addPlugin(pluginKey);
if (success) {
for (String packageType : pluginInfo.getSupportedPacketTypes()) {
2016-07-14 14:43:39 +02:00
packageType = hackToMakeRetrocompatiblePacketTypes(packageType);
ArrayList<String> plugins = newPluginsByIncomingInterface.get(packageType);
if (plugins == null) plugins = new ArrayList<>();
plugins.add(pluginKey);
newPluginsByIncomingInterface.put(packageType, plugins);
}
2015-09-08 15:05:32 -07:00
}
} else {
removePlugin(pluginKey);
}
2015-09-08 15:05:32 -07:00
}
pluginsByIncomingInterface = newPluginsByIncomingInterface;
onPluginsChanged();
}
public void onPluginsChanged() {
for (PluginsChangedListener listener : pluginsChangedListeners) {
listener.onPluginsChanged(Device.this);
}
}
public ConcurrentHashMap<String, Plugin> getLoadedPlugins() {
return plugins;
}
public ConcurrentHashMap<String, Plugin> getPluginsWithoutPermissions() {
return pluginsWithoutPermissions;
}
public ConcurrentHashMap<String, Plugin> getPluginsWithoutOptionalPermissions() {
return pluginsWithoutOptionalPermissions;
}
public void addPluginsChangedListener(PluginsChangedListener listener) {
pluginsChangedListeners.add(listener);
}
public void removePluginsChangedListener(PluginsChangedListener listener) {
pluginsChangedListeners.remove(listener);
}
public void disconnect() {
for (BaseLink link : links) {
link.disconnect();
}
}
public boolean deviceShouldBeKeptAlive() {
2016-06-17 09:59:31 +02:00
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 m_supportedPlugins;
}
2018-10-26 23:53:58 +02:00
private void hackToMakeRetrocompatiblePacketTypes(NetworkPacket np) {
if (protocolVersion >= 6) return;
np.mType = np.getType().replace(".request", "");
}
2018-10-26 23:53:58 +02:00
private String hackToMakeRetrocompatiblePacketTypes(String type) {
2016-07-14 14:43:39 +02:00
if (protocolVersion >= 6) return type;
return type.replace(".request", "");
}
}