diff --git a/KdeConnect/KdeConnect.iml b/KdeConnect/KdeConnect.iml
index 4ab1b141..2666b857 100644
--- a/KdeConnect/KdeConnect.iml
+++ b/KdeConnect/KdeConnect.iml
@@ -10,6 +10,7 @@
+
diff --git a/KdeConnect/src/main/AndroidManifest.xml b/KdeConnect/src/main/AndroidManifest.xml
index 4ae06c14..8f5f8ca2 100644
--- a/KdeConnect/src/main/AndroidManifest.xml
+++ b/KdeConnect/src/main/AndroidManifest.xml
@@ -24,7 +24,7 @@
@@ -36,11 +36,12 @@
+ android:parentActivityName="org.kde.connect.UserInterface.MainActivity"
+ >
+ android:value="org.kde.connect.UserInterface.MainActivity" />
@@ -48,17 +49,30 @@
+
+
+
+
+
+
+
+ >
+ >
@@ -68,7 +82,7 @@
android:name="org.kde.connect.BackgroundService">
-
diff --git a/KdeConnect/src/main/java/org/kde/connect/BackgroundService.java b/KdeConnect/src/main/java/org/kde/connect/BackgroundService.java
index babc0c9e..36a5b1e6 100644
--- a/KdeConnect/src/main/java/org/kde/connect/BackgroundService.java
+++ b/KdeConnect/src/main/java/org/kde/connect/BackgroundService.java
@@ -8,17 +8,23 @@ import android.content.SharedPreferences;
import android.os.Binder;
import android.os.IBinder;
import android.preference.PreferenceManager;
+import android.util.Base64;
import android.util.Log;
import org.kde.connect.ComputerLinks.BaseComputerLink;
import org.kde.connect.LinkProviders.BaseLinkProvider;
import org.kde.connect.LinkProviders.BroadcastTcpLinkProvider;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+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.HashSet;
-import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -29,12 +35,33 @@ public class BackgroundService extends Service {
private HashMap devices = new HashMap();
+ Device.PairingCallback devicePairingCallback = new Device.PairingCallback() {
+ @Override
+ public void incomingRequest() {
+ if (deviceListChangedCallback != null) deviceListChangedCallback.onDeviceListChanged();
+ }
+ @Override
+ public void pairingSuccessful() {
+ if (deviceListChangedCallback != null) deviceListChangedCallback.onDeviceListChanged();
+ }
+ @Override
+ public void pairingFailed(String error) {
+ if (deviceListChangedCallback != null) deviceListChangedCallback.onDeviceListChanged();
+ }
+ @Override
+ public void unpaired() {
+ if (deviceListChangedCallback != null) deviceListChangedCallback.onDeviceListChanged();
+ }
+ };
+
private void loadRememberedDevicesFromSettings() {
SharedPreferences preferences = getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
Set trustedDevices = preferences.getAll().keySet();
for(String deviceId : trustedDevices) {
if (preferences.getBoolean(deviceId, false)) {
- devices.put(deviceId,new Device(getBaseContext(), deviceId));
+ Device device = new Device(getBaseContext(), deviceId);
+ devices.put(deviceId,device);
+ device.addPairingCallback(devicePairingCallback);
}
}
}
@@ -69,14 +96,16 @@ public class BackgroundService extends Service {
if (device != null) {
Log.e("BackgroundService", "addLink, known device: "+deviceId);
- if (!device.hasName()) device.setName(identityPackage.getString("deviceName"));
device.addLink(link);
} else {
Log.e("BackgroundService", "addLink,unknown device: "+deviceId);
String name = identityPackage.getString("deviceName");
device = new Device(getBaseContext(), deviceId, name, link);
devices.put(deviceId, device);
+ device.addPairingCallback(devicePairingCallback);
}
+
+ if (deviceListChangedCallback != null) deviceListChangedCallback.onDeviceListChanged();
}
@Override
@@ -85,13 +114,14 @@ public class BackgroundService extends Service {
Log.e("onConnectionLost","removeLink, deviceId: "+link.getDeviceId());
if (d != null) {
d.removeLink(link);
- if (!d.isReachable() && !d.isTrusted()) {
+ if (!d.isReachable() && !d.isPaired()) {
devices.remove(link.getDeviceId());
+ d.removePairingCallback(devicePairingCallback);
}
} else {
Log.e("onConnectionLost","Removing connection to unknown device, this should not happen");
}
-
+ if (deviceListChangedCallback != null) deviceListChangedCallback.onDeviceListChanged();
}
};
@@ -133,6 +163,15 @@ public class BackgroundService extends Service {
}
}
+ public interface DeviceListChangedCallback {
+ void onDeviceListChanged();
+ }
+ private DeviceListChangedCallback deviceListChangedCallback = null;
+ public void setDeviceListChangedCallback(DeviceListChangedCallback callback) {
+ this.deviceListChangedCallback = callback;
+ }
+
+
//This will called only once, even if we launch the service intent several times
@Override
public void onCreate() {
@@ -144,6 +183,7 @@ public class BackgroundService extends Service {
Log.i("BackgroundService","Service not started yet, initializing...");
+ initializeRsaKeys();
loadRememberedDevicesFromSettings();
registerLinkProviders();
@@ -153,6 +193,61 @@ public class BackgroundService extends Service {
}
+ private void initializeRsaKeys() {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
+
+ if (!settings.contains("publicKey") || !settings.contains("privateKey")) {
+
+ KeyPair keyPair;
+ try {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(2048);
+ keyPair = keyGen.genKeyPair();
+ } catch(Exception e) {
+ e.printStackTrace();
+ Log.e("initializeRsaKeys","Exception");
+ return;
+ }
+
+ byte[] publicKey = keyPair.getPublic().getEncoded();
+ byte[] privateKey = keyPair.getPrivate().getEncoded();
+
+ SharedPreferences.Editor edit = settings.edit();
+ edit.putString("publicKey",Base64.encodeToString(publicKey, 0).trim()+"\n");
+ edit.putString("privateKey",Base64.encodeToString(privateKey, 0));
+ edit.commit();
+
+ }
+
+
+/*
+ // Encryption and decryption test
+ //================================
+
+ try {
+
+ NetworkPackage np = NetworkPackage.createIdentityPackage(this);
+
+ SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(this);
+
+ byte[] publicKeyBytes = Base64.decode(globalSettings.getString("publicKey",""), 0);
+ PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
+
+ np.encrypt(publicKey);
+
+ byte[] privateKeyBytes = Base64.decode(globalSettings.getString("privateKey",""), 0);
+ PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
+
+ NetworkPackage decrypted = np.decrypt(privateKey);
+ Log.e("ENCRYPTION AND DECRYPTION TEST", decrypted.serialize());
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.e("ENCRYPTION AND DECRYPTION TEST","Exception: "+e);
+ }
+*/
+
+ }
+
@Override
public void onDestroy() {
Log.i("BackgroundService", "Destroying");
diff --git a/KdeConnect/src/main/java/org/kde/connect/ComputerLinks/LanComputerLink.java b/KdeConnect/src/main/java/org/kde/connect/ComputerLinks/LanComputerLink.java
new file mode 100644
index 00000000..c31542f4
--- /dev/null
+++ b/KdeConnect/src/main/java/org/kde/connect/ComputerLinks/LanComputerLink.java
@@ -0,0 +1,38 @@
+package org.kde.connect.ComputerLinks;
+
+import android.util.Log;
+
+import org.apache.mina.core.session.IoSession;
+import org.kde.connect.LinkProviders.BaseLinkProvider;
+import org.kde.connect.NetworkPackage;
+
+public class LanComputerLink extends BaseComputerLink {
+
+ private IoSession session = null;
+
+ public void disconnect() {
+ Log.e("NioSessionComputerLink","Disconnect: "+session.getRemoteAddress().toString());
+ session.close(true);
+ }
+
+ public LanComputerLink(IoSession session, String deviceId, BaseLinkProvider linkProvider) {
+ super(deviceId, linkProvider);
+ this.session = session;
+ }
+
+ @Override
+ public boolean sendPackage(NetworkPackage np) {
+ Log.e("TcpComputerLink", "sendPackage");
+ if (session == null) {
+ Log.e("TcpComputerLink","not yet connected");
+ return false;
+ } else {
+ session.write(np.serialize());
+ return true;
+ }
+ }
+
+ public void injectNetworkPackage(NetworkPackage np) {
+ packageReceived(np);
+ }
+}
diff --git a/KdeConnect/src/main/java/org/kde/connect/ComputerLinks/TcpComputerLink.java b/KdeConnect/src/main/java/org/kde/connect/ComputerLinks/TcpComputerLink.java
index a498c6cb..06684843 100644
--- a/KdeConnect/src/main/java/org/kde/connect/ComputerLinks/TcpComputerLink.java
+++ b/KdeConnect/src/main/java/org/kde/connect/ComputerLinks/TcpComputerLink.java
@@ -78,7 +78,7 @@ public class TcpComputerLink extends BaseComputerLink {
session = future.getSession();
if (callback != null) callback.dispatchMessage(new Message());
}
- }).run();
+ }).start();
}
diff --git a/KdeConnect/src/main/java/org/kde/connect/Device.java b/KdeConnect/src/main/java/org/kde/connect/Device.java
index c292469f..81fe8ccc 100644
--- a/KdeConnect/src/main/java/org/kde/connect/Device.java
+++ b/KdeConnect/src/main/java/org/kde/connect/Device.java
@@ -1,34 +1,70 @@
package org.kde.connect;
+import android.R;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
+import android.support.v4.app.NotificationCompat;
+import android.util.Base64;
import android.util.Log;
import org.kde.connect.ComputerLinks.BaseComputerLink;
import org.kde.connect.Plugins.Plugin;
+import org.kde.connect.Plugins.PluginFactory;
+import org.kde.connect.UserInterface.PairActivity;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
public class Device implements BaseComputerLink.PackageReceiver {
+ private Context context;
+
+ private String deviceId;
+ private String name;
+ private PublicKey publicKey;
+ private int notificationId;
+
+ private enum PairStatus {
+ NotPaired,
+ Requested,
+ RequestedByPeer,
+ Paired
+ }
+
+ public interface PairingCallback {
+ abstract void incomingRequest();
+ abstract void pairingSuccessful();
+ abstract void pairingFailed(String error);
+ abstract void unpaired();
+ }
+
+ private PairStatus pairStatus;
+ private ArrayList pairingCallback = new ArrayList();
+ private Timer pairingTimer;
+
private ArrayList links = new ArrayList();
private HashMap plugins = new HashMap();
private HashMap failedPlugins = new HashMap();
- private Context context;
- private String deviceId;
- private String name;
- private boolean trusted;
-
SharedPreferences settings;
//Remembered trusted device, we need to wait for a incoming devicelink to communicate
@@ -39,8 +75,16 @@ public class Device implements BaseComputerLink.PackageReceiver {
this.context = context;
this.deviceId = deviceId;
- this.name = settings.getString("deviceName", null);
- this.trusted = true;
+ this.name = settings.getString("deviceName", "unknown device");
+ this.pairStatus = PairStatus.Paired;
+
+ try {
+ byte[] publicKeyBytes = Base64.decode(settings.getString("publicKey", ""), 0);
+ publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.e("Device","Exception");
+ }
reloadPluginsFromSettings();
}
@@ -53,8 +97,9 @@ public class Device implements BaseComputerLink.PackageReceiver {
this.context = context;
this.deviceId = deviceId;
- setName(name);
- setTrusted(false);
+ this.name = name;
+ this.pairStatus = PairStatus.NotPaired;
+ this.publicKey = null;
addLink(dl);
}
@@ -65,50 +110,154 @@ public class Device implements BaseComputerLink.PackageReceiver {
}
public String getName() {
- return name != null? name : "unknown device";
- }
-
- public void setName(String name) {
- this.name = name;
- settings.edit().putString("deviceName",name).commit();
+ return name != null? name : "unknown device"; //TODO: i18n
}
public String getDeviceId() {
return deviceId;
}
- public boolean isReachable() {
- return !links.isEmpty();
+
+
+
+
+
+ //
+ // Pairing-related functions
+ //
+
+ public boolean isPaired() {
+ return pairStatus == PairStatus.Paired;
}
- public boolean isTrusted() {
- return trusted;
+ public boolean isPairRequested() {
+ return pairStatus == PairStatus.Requested;
}
- public void setTrusted(boolean b) {
- trusted = b;
+ public void addPairingCallback(PairingCallback callback) {
+ pairingCallback.add(callback);
+ if (pairStatus == PairStatus.RequestedByPeer) {
+ callback.incomingRequest();
+ }
+ }
+ public void removePairingCallback(PairingCallback callback) {
+ pairingCallback.remove(callback);
+ }
+
+ public void requestPairing() {
+
+ if (pairStatus == PairStatus.Paired) {
+ for (PairingCallback cb : pairingCallback) cb.pairingFailed("Device already paired"); //TODO: i18n
+ return;
+ }
+ if (pairStatus == PairStatus.Requested) {
+ for (PairingCallback cb : pairingCallback) cb.pairingFailed("Pairing already requested"); //TODO: i18n
+ return;
+ }
+ if (!isReachable()) {
+ for (PairingCallback cb : pairingCallback) cb.pairingFailed("Device not reachable"); //TODO: i18n
+ return;
+ }
+
+ //Send our own public key
+ NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
+ boolean success = sendPackage(np);
+
+ if (!success) {
+ for (PairingCallback cb : pairingCallback) cb.pairingFailed("Could not send package");
+ return;
+ }
+
+ pairingTimer = new Timer();
+ pairingTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ for (PairingCallback cb : pairingCallback) cb.pairingFailed("Timed out"); //TODO: i18n
+ pairStatus = PairStatus.NotPaired;
+ }
+ }, 20*1000);
+
+ pairStatus = PairStatus.Requested;
+
+ }
+
+ public int getNotificationId() {
+ return notificationId;
+ }
+
+ public void unpair() {
+
+ if (!isPaired()) return;
+
+ pairStatus = PairStatus.NotPaired;
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
+ preferences.edit().remove(deviceId).commit();
- boolean wasTrusted = preferences.getBoolean(deviceId, false);
+ NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
+ np.set("pair", false);
+ sendPackage(np);
- if (trusted && !wasTrusted) {
- preferences.edit().putBoolean(deviceId, true).commit();
- } else if(!trusted && wasTrusted) {
- preferences.edit().remove(deviceId).commit();
- }
+ for (PairingCallback cb : pairingCallback) cb.unpaired();
reloadPluginsFromSettings();
}
+ public void acceptPairing() {
+
+ Log.e("Device","Accepted pairing");
+
+ //Send our own public key
+ NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
+ boolean success = sendPackage(np);
+
+ if (!success) return;
+
+ pairStatus = PairStatus.Paired;
+
+ //Store as trusted device
+ String encodedPublicKey = Base64.encodeToString(publicKey.getEncoded(), 0);
+ SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
+ preferences.edit().putBoolean(deviceId,true).commit();
+
+ //Store device information needed to create a Device object in a future
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString("deviceName", getName());
+ editor.putString("publicKey", encodedPublicKey);
+ editor.commit();
+
+ reloadPluginsFromSettings();
+
+ for (PairingCallback cb : pairingCallback) cb.pairingSuccessful();
+
+ }
+
+ public void rejectPairing() {
+
+ Log.e("Device","Rejected pairing");
+
+ pairStatus = PairStatus.NotPaired;
+
+ NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
+ np.set("pair", false);
+ sendPackage(np);
+
+ for (PairingCallback cb : pairingCallback) cb.pairingFailed("Canceled by the user"); //TODO: i18n
+
+ }
+
//
- // Computer link-related functions
+ // ComputerLink-related functions
//
+ public boolean isReachable() {
+ return !links.isEmpty();
+ }
+
public void addLink(BaseComputerLink link) {
links.add(link);
@@ -140,15 +289,154 @@ public class Device implements BaseComputerLink.PackageReceiver {
@Override
public void onPackageReceived(NetworkPackage np) {
- for (Plugin plugin : plugins.values()) {
- //Log.e("onPackageReceived",plugin.toString());
- plugin.onPackageReceived(np);
+
+ if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR)) {
+
+ Log.e("Device","Pair package");
+
+ boolean wantsPair = np.getBoolean("pair");
+
+ if (wantsPair == isPaired()) {
+ if (pairStatus == PairStatus.Requested) {
+ pairStatus = PairStatus.NotPaired;
+ pairingTimer.cancel();
+ for (PairingCallback cb : pairingCallback) cb.pairingFailed("Canceled by other peer"); //TODO: i18n
+ }
+ return;
+ }
+
+ if (wantsPair) {
+
+ //Retrieve their public key
+ try {
+ String publicKeyContent = np.getString("publicKey").replace("-----BEGIN PUBLIC KEY-----\n","").replace("-----END PUBLIC KEY-----\n","");
+ byte[] publicKeyBytes = Base64.decode(publicKeyContent, 0);
+ Log.e("asdasd","key bytes: " + publicKeyBytes);
+ publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
+ } catch(Exception e) {
+ e.printStackTrace();
+ Log.e("Device","Pairing exception: Received incorrect key");
+ for (PairingCallback cb : pairingCallback) cb.pairingFailed("Incorrect key received"); //TODO: i18n
+ return;
+ }
+
+ if (pairStatus == PairStatus.Requested) { //We started pairing
+
+ Log.e("Pairing","Pair answer");
+
+ pairStatus = PairStatus.Paired;
+ pairingTimer.cancel();
+
+ //Store as trusted device
+ String encodedPublicKey = Base64.encodeToString(publicKey.getEncoded(), 0);
+ SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
+ preferences.edit().putBoolean(deviceId,true).commit();
+
+ //Store device information needed to create a Device object in a future
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putString("deviceName", getName());
+ editor.putString("publicKey", encodedPublicKey);
+ editor.commit();
+
+ reloadPluginsFromSettings();
+
+ for (PairingCallback cb : pairingCallback) cb.pairingSuccessful();
+
+ } else {
+
+ Log.e("Pairing","Pair request");
+
+ Intent intent = new Intent(context, PairActivity.class);
+ intent.putExtra("deviceId", deviceId);
+ PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+
+ Notification noti = new NotificationCompat.Builder(context)
+ .setContentTitle("Pairing request from" + getName()) //TODO: i18n
+ .setContentText("Tap to answer") //TODO: i18n
+ .setContentIntent(pendingIntent)
+ .setTicker("Pair requested") //TODO: i18n
+ .setSmallIcon(R.drawable.ic_menu_help)
+ .setAutoCancel(true)
+ .setDefaults(Notification.DEFAULT_SOUND)
+ .build();
+
+
+ final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationId = (int)System.currentTimeMillis();
+ notificationManager.notify(notificationId, noti);
+
+ pairingTimer = new Timer();
+ pairingTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ pairStatus = PairStatus.NotPaired;
+ notificationManager.cancel(notificationId);
+ }
+ }, 19*1000); //Time to show notification
+
+ pairStatus = PairStatus.RequestedByPeer;
+ for (PairingCallback cb : pairingCallback) cb.incomingRequest();
+
+ }
+ } else {
+ Log.e("Pairing","Unpair request");
+
+ if (pairStatus == PairStatus.Requested) {
+ pairingTimer.cancel();
+ for (PairingCallback cb : pairingCallback) cb.pairingFailed("Canceled by other peer"); //TODO: i18n
+ } else if (pairStatus == PairStatus.Paired) {
+ SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
+ preferences.edit().remove(deviceId).commit();
+ reloadPluginsFromSettings();
+ }
+
+ pairStatus = PairStatus.NotPaired;
+ for (PairingCallback cb : pairingCallback) cb.unpaired();
+
+ }
+ } else if (!isPaired()) {
+
+ //TODO: Notify the other side that we don't trust them
+ Log.e("onPackageReceived","Device not paired, ignoring package!");
+
+ } else {
+ if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_ENCRYPTED)) {
+
+ try {
+ //TODO: Do not read the key every time
+ SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context);
+ byte[] privateKeyBytes = Base64.decode(globalSettings.getString("privateKey",""), 0);
+ PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
+ np = np.decrypt(privateKey);
+ } catch(Exception e) {
+ e.printStackTrace();
+ Log.e("onPackageReceived","Exception reading the key needed to decrypt the package");
+ }
+
+ } else {
+ //TODO: The other side doesn't know that we are already paired, do something
+ Log.e("onPackageReceived","WARNING: Received unencrypted package from paired device!");
+ }
+
+ for (Plugin plugin : plugins.values()) {
+ plugin.onPackageReceived(np);
+ }
}
+
}
public boolean sendPackage(final NetworkPackage np) {
- Log.e("Device", "sendPackage "+np.getType()+". "+links.size()+" links available");
+
+ if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR) && isPaired()) {
+ try {
+ np.encrypt(publicKey);
+ } catch(Exception e) {
+ e.printStackTrace();
+ Log.e("Device","sendPackage exception - could not encrypt");
+ }
+ }
+
new AsyncTask() {
@Override
protected Void doInBackground(Void... voids) {
@@ -160,12 +448,14 @@ public class Device implements BaseComputerLink.PackageReceiver {
return null;
}
}
- Log.e("sendPackage","Error: Package could not be sent");
+ Log.e("sendPackage","Error: Package could not be sent ("+links.size()+" links available)");
return null;
}
}.execute();
- return true; //FIXME: Detect when unable to send a package and try again somehow
+ //TODO: Detect when unable to send a package and try again somehow
+
+ return !links.isEmpty();
}
@@ -216,6 +506,11 @@ public class Device implements BaseComputerLink.PackageReceiver {
failedPlugins.remove(name);
plugins.put(name, plugin);
+
+ for (PluginsChangedListener listener : pluginsChangedListeners) {
+ listener.onPluginsChanged(Device.this);
+ }
+
}
});
@@ -243,16 +538,18 @@ public class Device implements BaseComputerLink.PackageReceiver {
//Log.e("removePlugin","removed " + name);
+ for (PluginsChangedListener listener : pluginsChangedListeners) {
+ listener.onPluginsChanged(this);
+ }
+
return true;
+
}
public void setPluginEnabled(String pluginName, boolean value) {
settings.edit().putBoolean(pluginName,value).commit();
if (value) addPlugin(pluginName);
else removePlugin(pluginName);
- for (PluginsChangedListener listener : pluginsChangedListeners) {
- listener.onPluginsChanged(this);
- }
}
public boolean isPluginEnabled(String pluginName) {
@@ -270,7 +567,7 @@ public class Device implements BaseComputerLink.PackageReceiver {
for(String pluginName : availablePlugins) {
boolean enabled = false;
- if (isTrusted() && isReachable()) {
+ if (isPaired() && isReachable()) {
enabled = isPluginEnabled(pluginName);
}
//Log.e("reloadPluginsFromSettings",pluginName+"->"+enabled);
@@ -286,15 +583,19 @@ public class Device implements BaseComputerLink.PackageReceiver {
}
}
+ public HashMap getLoadedPlugins() {
+ return plugins;
+ }
+
public HashMap getFailedPlugins() {
return failedPlugins;
}
- interface PluginsChangedListener {
+ public interface PluginsChangedListener {
void onPluginsChanged(Device device);
}
- ArrayList pluginsChangedListeners = new ArrayList();
+ private ArrayList pluginsChangedListeners = new ArrayList();
public void addPluginsChangedListener(PluginsChangedListener listener) {
pluginsChangedListeners.add(listener);
diff --git a/KdeConnect/src/main/java/org/kde/connect/DeviceActivity.java b/KdeConnect/src/main/java/org/kde/connect/DeviceActivity.java
deleted file mode 100644
index e9d0ef47..00000000
--- a/KdeConnect/src/main/java/org/kde/connect/DeviceActivity.java
+++ /dev/null
@@ -1,174 +0,0 @@
-package org.kde.connect;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.preference.CheckBoxPreference;
-import android.preference.Preference;
-import android.preference.PreferenceManager;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.CompoundButton;
-import android.widget.ListView;
-import android.widget.Switch;
-
-import org.kde.connect.Plugins.PingPlugin;
-import org.kde.connect.Plugins.Plugin;
-import org.kde.kdeconnect.R;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class DeviceActivity extends Activity {
-
- private String deviceId;
- private Device.PluginsChangedListener pluginsChangedListener = new Device.PluginsChangedListener() {
- @Override
- public void onPluginsChanged(final Device device) {
-
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- Log.e("MainActivity", "updateComputerList");
-
- final HashMap plugins = device.getFailedPlugins();
- final String[] ids = plugins.keySet().toArray(new String[plugins.size()]);
- String[] names = new String[plugins.size()];
- for(int i = 0; i < ids.length; i++) {
- Plugin p = plugins.get(ids[i]);
- names[i] = p.getDisplayName();
- }
-
- ListView list = (ListView)findViewById(R.id.listView1);
-
- list.setAdapter(new ArrayAdapter(DeviceActivity.this, android.R.layout.simple_list_item_1, names));
-
- list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView> adapterView, View view, int position, long id) {
- Plugin p = plugins.get(ids[position]);
- p.getErrorDialog(DeviceActivity.this).show();
- }
- });
-
- findViewById(R.id.textView).setVisibility(plugins.size() > 0? View.VISIBLE : View.GONE);
-
- }
- });
-
-
-
- }
- };
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.device, menu);
-
- MenuItem item = menu.findItem(R.id.menu_trusted);
- final Switch toggle = (Switch)item.getActionView();
- BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
- @Override
- public void onServiceStart(BackgroundService service) {
- final Device device = service.getDevice(deviceId);
-
- toggle.setChecked(device.isTrusted());
- toggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton compoundButton, final boolean b) {
- device.setTrusted(b);
- }
- });
- }
- });
-
- return true;
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_device);
-
-/*
- ActionBar actionBar = getSupportActionBar();
- actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME
- | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
- actionBar.setDisplayHomeAsUpEnabled(true);
- //actionBar.setIcon()
-*/
-
- deviceId = getIntent().getStringExtra("deviceId");
-
- BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
- @Override
- public void onServiceStart(BackgroundService service) {
- Device device = service.getDevice(deviceId);
- setTitle(device.getName());
- device.addPluginsChangedListener(pluginsChangedListener);
- pluginsChangedListener.onPluginsChanged(device);
- }
- });
-
- findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- Intent intent = new Intent(DeviceActivity.this, SettingsActivity.class);
- intent.putExtra("deviceId", deviceId);
- startActivity(intent);
- }
- });
-
-
- findViewById(R.id.button2).setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
- @Override
- public void onServiceStart(BackgroundService service) {
- Device device = service.getDevice(deviceId);
- device.sendPackage(new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PING));
- }
- });
-
- }
- });
-
- findViewById(R.id.button3).setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
- @Override
- public void onServiceStart(BackgroundService service) {
- Intent intent = new Intent(DeviceActivity.this, MprisActivity.class);
- intent.putExtra("deviceId", deviceId);
- startActivity(intent);
- }
- });
- }
- });
-
-
-
- }
-
- @Override
- protected void onDestroy() {
- BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
- @Override
- public void onServiceStart(BackgroundService service) {
- Device device = service.getDevice(deviceId);
- device.removePluginsChangedListener(pluginsChangedListener);
- }
- });
- super.onDestroy();
- }
-}
diff --git a/KdeConnect/src/main/java/org/kde/connect/LinkProviders/LanLinkProvider.java b/KdeConnect/src/main/java/org/kde/connect/LinkProviders/LanLinkProvider.java
new file mode 100644
index 00000000..dc572d0c
--- /dev/null
+++ b/KdeConnect/src/main/java/org/kde/connect/LinkProviders/LanLinkProvider.java
@@ -0,0 +1,285 @@
+package org.kde.connect.LinkProviders;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import org.apache.mina.core.future.ConnectFuture;
+import org.apache.mina.core.future.IoFuture;
+import org.apache.mina.core.future.IoFutureListener;
+import org.apache.mina.core.service.IoHandler;
+import org.apache.mina.core.service.IoHandlerAdapter;
+import org.apache.mina.core.session.IoSession;
+import org.apache.mina.filter.codec.ProtocolCodecFilter;
+import org.apache.mina.filter.codec.textline.LineDelimiter;
+import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
+import org.apache.mina.transport.socket.nio.NioDatagramAcceptor;
+import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
+import org.apache.mina.transport.socket.nio.NioSocketConnector;
+import org.kde.connect.ComputerLinks.BaseComputerLink;
+import org.kde.connect.ComputerLinks.NioSessionComputerLink;
+import org.kde.connect.NetworkPackage;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+
+public class LanLinkProvider extends BaseLinkProvider {
+
+ private final static int port = 1714;
+
+ private Context context;
+ private HashMap visibleComputers = new HashMap();
+ private HashMap nioSessions = new HashMap();
+
+ private NioSocketAcceptor tcpAcceptor = null;
+ private NioDatagramAcceptor udpAcceptor = null;
+
+ private final IoHandler tcpHandler = new IoHandlerAdapter() {
+ @Override
+ public void sessionClosed(IoSession session) throws Exception {
+
+ NioSessionComputerLink brokenLink = nioSessions.remove(session.getId());
+ if (brokenLink != null) {
+ connectionLost(brokenLink);
+ String deviceId = brokenLink.getDeviceId();
+ if (visibleComputers.get(deviceId) == brokenLink) {
+ visibleComputers.remove(deviceId);
+ connectionLost(brokenLink);
+ }
+ }
+
+ }
+
+ @Override
+ public void messageReceived(IoSession session, Object message) throws Exception {
+ super.messageReceived(session, message);
+
+ //Log.e("BroadcastTcpLinkProvider","Incoming package, address: "+session.getRemoteAddress()).toString());
+
+ String theMessage = (String) message;
+ NetworkPackage np = NetworkPackage.unserialize(theMessage);
+
+ NioSessionComputerLink prevLink = nioSessions.get(session.getId());
+
+ if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
+ String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId");
+ if (np.getString("deviceId").equals(myId)) {
+ return;
+ }
+ NioSessionComputerLink link = new NioSessionComputerLink(session, np.getString("deviceId"), LanLinkProvider.this);
+ nioSessions.put(session.getId(),link);
+ addLink(np, link);
+ } else {
+ if (prevLink == null) {
+ Log.e("BroadcastTcpLinkProvider","2 Expecting an identity package");
+ } else {
+ prevLink.injectNetworkPackage(np);
+ }
+ }
+
+ }
+ };
+
+ private IoHandler udpHandler = new IoHandlerAdapter() {
+ @Override
+ public void messageReceived(IoSession udpSession, Object message) throws Exception {
+ super.messageReceived(udpSession, message);
+
+ Log.e("BroadcastTcpLinkProvider", "Udp message received (" + message.getClass() + ") " + message.toString());
+
+ NetworkPackage np = null;
+
+ try {
+ //We should receive a string thanks to the TextLineCodecFactory filter
+ String theMessage = (String) message;
+ np = NetworkPackage.unserialize(theMessage);
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.e("BroadcastTcpLinkProvider", "Could not unserialize package");
+ }
+
+ if (np != null) {
+
+ final NetworkPackage identityPackage = np;
+ if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
+ Log.e("BroadcastTcpLinkProvider", "1 Expecting an identity package");
+ return;
+ } else {
+ String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId");
+ if (np.getString("deviceId").equals(myId)) {
+ return;
+ }
+ }
+
+ Log.e("BroadcastTcpLinkProvider", "It is an identity package, creating link");
+
+ try {
+ final InetSocketAddress address = (InetSocketAddress) udpSession.getRemoteAddress();
+
+ final NioSocketConnector connector = new NioSocketConnector();
+ connector.setHandler(tcpHandler);
+ //TextLineCodecFactory will split incoming data delimited by the given string
+ connector.getFilterChain().addLast("codec",
+ new ProtocolCodecFilter(
+ new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX)
+ )
+ );
+ connector.getSessionConfig().setKeepAlive(true);
+
+ int tcpPort = np.getInt("tcpPort",port);
+ ConnectFuture future = connector.connect(new InetSocketAddress(address.getAddress(), tcpPort));
+ future.addListener(new IoFutureListener() {
+ @Override
+ public void operationComplete(IoFuture ioFuture) {
+ IoSession session = ioFuture.getSession();
+
+ Log.e("BroadcastTcpLinkProvider", "Connection successful: " + session.isConnected());
+
+ NioSessionComputerLink link = new NioSessionComputerLink(session, identityPackage.getString("deviceId"), LanLinkProvider.this);
+
+ NetworkPackage np2 = NetworkPackage.createIdentityPackage(context);
+ link.sendPackage(np2);
+
+ nioSessions.put(session.getId(), link);
+ addLink(identityPackage, link);
+ }
+ });
+
+ } catch (Exception e) {
+ Log.e("BroadcastTcpLinkProvider","Exception!!");
+ e.printStackTrace();
+ }
+
+ }
+ }
+ };
+
+ private void addLink(NetworkPackage identityPackage, NioSessionComputerLink link) {
+ String deviceId = identityPackage.getString("deviceId");
+ Log.e("BroadcastTcpLinkProvider","addLink to "+deviceId);
+ BaseComputerLink oldLink = visibleComputers.get(deviceId);
+ visibleComputers.put(deviceId, link);
+ connectionAccepted(identityPackage, link);
+ if (oldLink != null) {
+ Log.e("BroadcastTcpLinkProvider","Removing old connection to same device");
+ connectionLost(oldLink);
+ }
+ }
+
+ public LanLinkProvider(Context context) {
+
+ this.context = context;
+
+ //This handles the case when I'm the new device in the network and somebody answers my introduction package
+ tcpAcceptor = new NioSocketAcceptor();
+ tcpAcceptor.setHandler(tcpHandler);
+ tcpAcceptor.getSessionConfig().setKeepAlive(true);
+ tcpAcceptor.getSessionConfig().setReuseAddress(true);
+ tcpAcceptor.setCloseOnDeactivation(false);
+ //TextLineCodecFactory will split incoming data delimited by the given string
+ tcpAcceptor.getFilterChain().addLast("codec",
+ new ProtocolCodecFilter(
+ new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX)
+ )
+ );
+
+
+ udpAcceptor = new NioDatagramAcceptor();
+ udpAcceptor.getSessionConfig().setReuseAddress(true); //Share port if existing
+ //TextLineCodecFactory will split incoming data delimited by the given string
+ udpAcceptor.getFilterChain().addLast("codec",
+ new ProtocolCodecFilter(
+ new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX)
+ )
+ );
+
+ }
+
+ @Override
+ public void onStart() {
+
+ //This handles the case when I'm the existing device in the network and receive a "hello" UDP package
+
+ udpAcceptor.setHandler(udpHandler);
+
+ try {
+ udpAcceptor.bind(new InetSocketAddress(port));
+ } catch(Exception e) {
+ Log.e("BroadcastTcpLinkProvider", "Error: Could not bind udp socket");
+ e.printStackTrace();
+ }
+
+ boolean success = false;
+ int tcpPort = port;
+ while(!success) {
+ try {
+ tcpAcceptor.bind(new InetSocketAddress(tcpPort));
+ success = true;
+ } catch(Exception e) {
+ tcpPort++;
+ }
+ }
+
+ Log.e("BroadcastTcpLinkProvider","Using tcpPort "+tcpPort);
+
+ //I'm on a new network, let's be polite and introduce myself
+ final int finalTcpPort = tcpPort;
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... voids) {
+
+ try {
+ NetworkPackage identity = NetworkPackage.createIdentityPackage(context);
+ identity.set("tcpPort",finalTcpPort);
+ byte[] b = identity.serialize().getBytes("UTF-8");
+ DatagramPacket packet = new DatagramPacket(b, b.length, InetAddress.getByAddress(new byte[]{-1,-1,-1,-1}), port);
+ DatagramSocket socket = new DatagramSocket();
+ socket.setReuseAddress(true);
+ socket.setBroadcast(true);
+ socket.send(packet);
+ Log.e("BroadcastTcpLinkProvider","Udp identity package sent");
+ } catch(Exception e) {
+ e.printStackTrace();
+ Log.e("BroadcastTcpLinkProvider","Sending udp identity package failed");
+ }
+
+ return null;
+
+ }
+
+ }.execute();
+
+ }
+
+ @Override
+ public void onNetworkChange() {
+
+ Log.e("BroadcastTcpLinkProvider","OnNetworkChange: " + (udpAcceptor != null));
+
+ onStop();
+ onStart();
+
+ }
+
+ @Override
+ public void onStop() {
+
+ udpAcceptor.unbind();
+ tcpAcceptor.unbind();
+
+ }
+
+ @Override
+ public int getPriority() {
+ return 1000;
+ }
+
+ @Override
+ public String getName() {
+ return "BroadcastTcpLinkProvider";
+ }
+}
diff --git a/KdeConnect/src/main/java/org/kde/connect/MainActivity.java b/KdeConnect/src/main/java/org/kde/connect/MainActivity.java
deleted file mode 100644
index 847f8c88..00000000
--- a/KdeConnect/src/main/java/org/kde/connect/MainActivity.java
+++ /dev/null
@@ -1,159 +0,0 @@
-package org.kde.connect;
-
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-
-import org.kde.connect.ComputerLinks.BaseComputerLink;
-import org.kde.connect.LinkProviders.BaseLinkProvider;
-import org.kde.kdeconnect.R;
-
-import java.util.HashMap;
-
-public class MainActivity extends Activity {
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.main, menu);
- return true;
- }
-
- private MenuItem menuItem;
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_load:
- BackgroundService.RunCommand(MainActivity.this, new BackgroundService.InstanceCallback() {
- @Override
- public void onServiceStart(BackgroundService service) {
- service.onNetworkChange();
- }
- });
- if (Build.VERSION.SDK_INT >= 11) {
- menuItem = item;
- menuItem.setActionView(R.layout.progressbar);
- if (Build.VERSION.SDK_INT >= 14) {
- menuItem.expandActionView();
- }
- TestTask task = new TestTask();
- task.execute();
- }
- break;
- default:
- break;
- }
- return true;
- }
-
- private class TestTask extends AsyncTask {
- @Override
- protected Void doInBackground(Void... params) {
- try {
- Thread.sleep(1500);
- } catch (InterruptedException e) {
- }
- return null;
- }
- @Override
- protected void onPostExecute(Void result) {
- if (Build.VERSION.SDK_INT >= 14)
- menuItem.collapseActionView();
- menuItem.setActionView(null);
- }
- };
-
- void updateComputerList() {
- Log.e("MainActivity","updateComputerList");
-
- BackgroundService.RunCommand(MainActivity.this, new BackgroundService.InstanceCallback() {
- @Override
- public void onServiceStart(BackgroundService service) {
-
- HashMap devices = service.getDevices();
- final String[] ids = devices.keySet().toArray(new String[devices.size()]);
- final String[] names = new String[devices.size()];
- for(int i = 0; i < ids.length; i++) {
- Device d = devices.get(ids[i]);
- names[i] = d.getName() + " " + d.isTrusted() + " " + d.isReachable();
- }
-
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- ListView list = (ListView)findViewById(R.id.listView1);
- list.setAdapter(new ArrayAdapter(MainActivity.this, android.R.layout.simple_list_item_1, names));
- list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView> adapterView, View view, int position, long id) {
- Intent intent = new Intent(MainActivity.this, DeviceActivity.class);
- intent.putExtra("deviceId", ids[position]);
- startActivity(intent);
- }
- });
- }
- });
-
- }
- });
- };
-
- BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() {
- @Override
- public void onConnectionReceived(NetworkPackage identityPackage, BaseComputerLink link) {
- updateComputerList();
- }
-
- @Override
- public void onConnectionLost(BaseComputerLink link) {
- updateComputerList();
- }
- };
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-/*
- ActionBar actionBar = getSupportActionBar();
- actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME
- | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
-*/
-
- BackgroundService.RunCommand(MainActivity.this, new BackgroundService.InstanceCallback() {
- @Override
- public void onServiceStart(BackgroundService service) {
- service.onNetworkChange();
-
- service.addConnectionListener(connectionReceiver);
- }
- });
-
- updateComputerList();
-
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- BackgroundService.RunCommand(MainActivity.this, new BackgroundService.InstanceCallback() {
- @Override
- public void onServiceStart(BackgroundService service) {
- service.removeConnectionListener(connectionReceiver);
- }
- });
- }
-
-}
diff --git a/KdeConnect/src/main/java/org/kde/connect/NetworkPackage.java b/KdeConnect/src/main/java/org/kde/connect/NetworkPackage.java
index 39287152..a14e6ffe 100644
--- a/KdeConnect/src/main/java/org/kde/connect/NetworkPackage.java
+++ b/KdeConnect/src/main/java/org/kde/connect/NetworkPackage.java
@@ -1,20 +1,32 @@
package org.kde.connect;
import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
import android.provider.Settings;
+import android.util.Base64;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
+import java.nio.charset.Charset;
+import java.security.PrivateKey;
+import java.security.PublicKey;
import java.util.ArrayList;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+
public class NetworkPackage {
- private final static int CURRENT_PACKAGE_VERSION = 1;
+ public final static int ProtocolVersion = 3;
public final static String PACKAGE_TYPE_IDENTITY = "kdeconnect.identity";
+ public final static String PACKAGE_TYPE_PAIR = "kdeconnect.pair";
+ public final static String PACKAGE_TYPE_ENCRYPTED = "kdeconnect.encrypted";
public final static String PACKAGE_TYPE_PING = "kdeconnect.ping";
public final static String PACKAGE_TYPE_TELEPHONY = "kdeconnect.telephony";
public final static String PACKAGE_TYPE_BATTERY = "kdeconnect.battery";
@@ -25,7 +37,6 @@ public class NetworkPackage {
private long mId;
private String mType;
private JSONObject mBody;
- private int mVersion;
private NetworkPackage() {
}
@@ -34,17 +45,12 @@ public class NetworkPackage {
mId = System.currentTimeMillis();
mType = type;
mBody = new JSONObject();
- mVersion = CURRENT_PACKAGE_VERSION;
}
public String getType() {
return mType;
}
- public int getVersion() {
- return mVersion;
- }
-
//Most commons getters and setters defined for convenience
public String getString(String key) { return mBody.optString(key,""); }
public String getString(String key, String defaultValue) { return mBody.optString(key,defaultValue); }
@@ -96,10 +102,10 @@ public class NetworkPackage {
jo.put("id",mId);
jo.put("type",mType);
jo.put("body",mBody);
- jo.put("version",mVersion);
} catch(Exception e) {
}
- String json = jo.toString()+"\n";
+ //QJSon does not escape slashes, but Java JSONObject does. Converting to QJson format.
+ String json = jo.toString().replace("\\/","/")+"\n";
Log.e("NetworkPackage.serialize",json);
return json;
}
@@ -112,16 +118,68 @@ public class NetworkPackage {
np.mId = jo.getLong("id");
np.mType = jo.getString("type");
np.mBody = jo.getJSONObject("body");
- np.mVersion = jo.getInt("version");
} catch (Exception e) {
return null;
}
- if (np.mVersion > CURRENT_PACKAGE_VERSION) {
- Log.e("NetworkPackage.unserialize","Version "+np.mVersion+" greater than supported version "+CURRENT_PACKAGE_VERSION);
- }
return np;
}
+
+
+ public void encrypt(PublicKey publicKey) throws Exception {
+
+ String serialized = serialize();
+
+ int chunkSize = 128;
+
+ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+
+ JSONArray chunks = new JSONArray();
+ while (serialized.length() > 0) {
+ if (serialized.length() < chunkSize) {
+ chunkSize = serialized.length();
+ }
+ String chunk = serialized.substring(0, chunkSize);
+ serialized = serialized.substring(chunkSize);
+ byte[] chunkBytes = chunk.getBytes(Charset.defaultCharset());
+ byte[] encryptedChunk;
+ encryptedChunk = cipher.doFinal(chunkBytes);
+ chunks.put(Base64.encodeToString(encryptedChunk, Base64.NO_WRAP));
+ }
+
+ mId = System.currentTimeMillis();
+ mType = NetworkPackage.PACKAGE_TYPE_ENCRYPTED;
+ mBody = new JSONObject();
+ try {
+ mBody.put("data", chunks);
+ }catch(Exception e){
+ e.printStackTrace();
+ Log.e("NetworkPackage","Exception");
+ }
+
+ Log.e("NetworkPackage", "Encrypted " + chunks.length()+" chunks");
+
+ }
+
+ public NetworkPackage decrypt(PrivateKey privateKey) throws Exception {
+
+ JSONArray chunks = mBody.getJSONArray("data");
+
+ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+
+ String decryptedJson = "";
+ for (int i = 0; i < chunks.length(); i++) {
+ byte[] encryptedChunk = Base64.decode(chunks.getString(i), Base64.NO_WRAP);
+ String decryptedChunk = new String(cipher.doFinal(encryptedChunk));
+ decryptedJson += decryptedChunk;
+ }
+
+ return unserialize(decryptedJson);
+ }
+
+
static public NetworkPackage createIdentityPackage(Context context) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_IDENTITY);
@@ -130,6 +188,7 @@ public class NetworkPackage {
try {
np.mBody.put("deviceId", deviceId);
np.mBody.put("deviceName", HumanDeviceNames.getDeviceName());
+ np.mBody.put("protocolVersion", NetworkPackage.ProtocolVersion);
} catch (JSONException e) {
}
@@ -137,5 +196,18 @@ public class NetworkPackage {
}
+ static public NetworkPackage createPublicKeyPackage(Context context) {
+
+ NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
+
+ np.set("pair", true);
+
+ SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context);
+ String publicKey = "-----BEGIN PUBLIC KEY-----\n" + globalSettings.getString("publicKey", "").trim()+ "\n-----END PUBLIC KEY-----\n";
+ np.set("publicKey", publicKey);
+
+ return np;
+
+ }
}
diff --git a/KdeConnect/src/main/java/org/kde/connect/Plugins/BatteryPlugin.java b/KdeConnect/src/main/java/org/kde/connect/Plugins/BatteryPlugin/BatteryPlugin.java
similarity index 93%
rename from KdeConnect/src/main/java/org/kde/connect/Plugins/BatteryPlugin.java
rename to KdeConnect/src/main/java/org/kde/connect/Plugins/BatteryPlugin/BatteryPlugin.java
index a21519da..8a4b6a28 100644
--- a/KdeConnect/src/main/java/org/kde/connect/Plugins/BatteryPlugin.java
+++ b/KdeConnect/src/main/java/org/kde/connect/Plugins/BatteryPlugin/BatteryPlugin.java
@@ -1,5 +1,6 @@
-package org.kde.connect.Plugins;
+package org.kde.connect.Plugins.BatteryPlugin;
+import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -8,9 +9,10 @@ import android.content.IntentFilter;
import android.graphics.drawable.Drawable;
import android.os.BatteryManager;
import android.util.Log;
+import android.widget.Button;
import org.kde.connect.NetworkPackage;
-import org.kde.connect.PluginFactory;
+import org.kde.connect.Plugins.Plugin;
import org.kde.kdeconnect.R;
public class BatteryPlugin extends Plugin {
@@ -112,4 +114,8 @@ public class BatteryPlugin extends Plugin {
return null;
}
+ @Override
+ public Button getInterfaceButton(Activity activity) {
+ return null;
+ }
}
diff --git a/KdeConnect/src/main/java/org/kde/connect/Plugins/ClipboardPlugin.java b/KdeConnect/src/main/java/org/kde/connect/Plugins/ClibpoardPlugin/ClipboardPlugin.java
similarity index 92%
rename from KdeConnect/src/main/java/org/kde/connect/Plugins/ClipboardPlugin.java
rename to KdeConnect/src/main/java/org/kde/connect/Plugins/ClibpoardPlugin/ClipboardPlugin.java
index 143cb761..f21c5c0e 100644
--- a/KdeConnect/src/main/java/org/kde/connect/Plugins/ClipboardPlugin.java
+++ b/KdeConnect/src/main/java/org/kde/connect/Plugins/ClibpoardPlugin/ClipboardPlugin.java
@@ -1,17 +1,17 @@
-package org.kde.connect.Plugins;
+package org.kde.connect.Plugins.ClibpoardPlugin;
+import android.app.Activity;
import android.app.AlertDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Build;
-import android.util.Log;
+import android.widget.Button;
import org.kde.connect.NetworkPackage;
-import org.kde.connect.PluginFactory;
+import org.kde.connect.Plugins.Plugin;
import org.kde.kdeconnect.R;
public class ClipboardPlugin extends Plugin {
@@ -117,4 +117,8 @@ public class ClipboardPlugin extends Plugin {
.create();
}
+ @Override
+ public Button getInterfaceButton(Activity activity) {
+ return null;
+ }
}
diff --git a/KdeConnect/src/main/java/org/kde/connect/MprisActivity.java b/KdeConnect/src/main/java/org/kde/connect/Plugins/MprisPlugin/MprisActivity.java
similarity index 98%
rename from KdeConnect/src/main/java/org/kde/connect/MprisActivity.java
rename to KdeConnect/src/main/java/org/kde/connect/Plugins/MprisPlugin/MprisActivity.java
index 0e6c301c..6ecad2e9 100644
--- a/KdeConnect/src/main/java/org/kde/connect/MprisActivity.java
+++ b/KdeConnect/src/main/java/org/kde/connect/Plugins/MprisPlugin/MprisActivity.java
@@ -1,4 +1,4 @@
-package org.kde.connect;
+package org.kde.connect.Plugins.MprisPlugin;
import android.app.Activity;
import android.os.Bundle;
@@ -13,9 +13,11 @@ import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
+import org.kde.connect.BackgroundService;
import org.kde.connect.ComputerLinks.BaseComputerLink;
+import org.kde.connect.Device;
import org.kde.connect.LinkProviders.BaseLinkProvider;
-import org.kde.connect.Plugins.MprisPlugin;
+import org.kde.connect.NetworkPackage;
import org.kde.kdeconnect.R;
import java.util.ArrayList;
diff --git a/KdeConnect/src/main/java/org/kde/connect/Plugins/MprisPlugin.java b/KdeConnect/src/main/java/org/kde/connect/Plugins/MprisPlugin/MprisPlugin.java
similarity index 88%
rename from KdeConnect/src/main/java/org/kde/connect/Plugins/MprisPlugin.java
rename to KdeConnect/src/main/java/org/kde/connect/Plugins/MprisPlugin/MprisPlugin.java
index b5d44c59..4590d184 100644
--- a/KdeConnect/src/main/java/org/kde/connect/Plugins/MprisPlugin.java
+++ b/KdeConnect/src/main/java/org/kde/connect/Plugins/MprisPlugin/MprisPlugin.java
@@ -1,14 +1,18 @@
-package org.kde.connect.Plugins;
+package org.kde.connect.Plugins.MprisPlugin;
+import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
+import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
+import android.view.View;
+import android.widget.Button;
import org.kde.connect.NetworkPackage;
-import org.kde.connect.PluginFactory;
+import org.kde.connect.Plugins.Plugin;
import org.kde.kdeconnect.R;
import java.util.ArrayList;
@@ -196,4 +200,18 @@ public class MprisPlugin extends Plugin {
return null;
}
+ @Override
+ public Button getInterfaceButton(final Activity activity) {
+ Button b = new Button(activity);
+ b.setText("Open remote control"); //TODO: i18n
+ b.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(activity, MprisActivity.class);
+ intent.putExtra("deviceId", device.getDeviceId());
+ activity.startActivity(intent);
+ }
+ });
+ return b;
+ }
}
diff --git a/KdeConnect/src/main/java/org/kde/connect/NotificationReceiver.java b/KdeConnect/src/main/java/org/kde/connect/Plugins/NotificationsPlugin/NotificationReceiver.java
similarity index 97%
rename from KdeConnect/src/main/java/org/kde/connect/NotificationReceiver.java
rename to KdeConnect/src/main/java/org/kde/connect/Plugins/NotificationsPlugin/NotificationReceiver.java
index f016a37a..dfb4726c 100644
--- a/KdeConnect/src/main/java/org/kde/connect/NotificationReceiver.java
+++ b/KdeConnect/src/main/java/org/kde/connect/Plugins/NotificationsPlugin/NotificationReceiver.java
@@ -1,4 +1,4 @@
-package org.kde.connect;
+package org.kde.connect.Plugins.NotificationsPlugin;
import android.app.Service;
import android.content.Context;
diff --git a/KdeConnect/src/main/java/org/kde/connect/Plugins/NotificationsPlugin.java b/KdeConnect/src/main/java/org/kde/connect/Plugins/NotificationsPlugin/NotificationsPlugin.java
similarity index 95%
rename from KdeConnect/src/main/java/org/kde/connect/Plugins/NotificationsPlugin.java
rename to KdeConnect/src/main/java/org/kde/connect/Plugins/NotificationsPlugin/NotificationsPlugin.java
index 9995c148..062a57e6 100644
--- a/KdeConnect/src/main/java/org/kde/connect/Plugins/NotificationsPlugin.java
+++ b/KdeConnect/src/main/java/org/kde/connect/Plugins/NotificationsPlugin/NotificationsPlugin.java
@@ -1,5 +1,6 @@
-package org.kde.connect.Plugins;
+package org.kde.connect.Plugins.NotificationsPlugin;
+import android.app.Activity;
import android.app.AlertDialog;
import android.app.Notification;
import android.content.Context;
@@ -13,11 +14,12 @@ import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.Base64;
import android.util.Log;
+import android.widget.Button;
import org.kde.connect.Helpers.AppsHelper;
import org.kde.connect.Helpers.ImagesHelper;
import org.kde.connect.NetworkPackage;
-import org.kde.connect.NotificationReceiver;
+import org.kde.connect.Plugins.Plugin;
import org.kde.kdeconnect.R;
import java.io.ByteArrayOutputStream;
@@ -248,7 +250,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
e.printStackTrace();
}
}
- }).run();
+ }).start();
}
}
});
@@ -279,9 +281,9 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
if (Build.VERSION.SDK_INT < 18) {
return new AlertDialog.Builder(baseContext)
- .setTitle("Notifications Plugin")
- .setMessage("This plugin is not compatible with Android prior 4.3")
- .setPositiveButton("Ok",new DialogInterface.OnClickListener() {
+ .setTitle("Notifications Plugin") //TODO: i18n
+ .setMessage("This plugin is not compatible with Android prior 4.3") //TODO: i18n
+ .setPositiveButton("Ok",new DialogInterface.OnClickListener() { //TODO: i18n
@Override
public void onClick(DialogInterface dialogInterface, int i) {
@@ -290,18 +292,18 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
.create();
} else {
return new AlertDialog.Builder(baseContext)
- .setTitle("Notifications Plugin")
- .setMessage("You need to grant permission to access notifications")
+ .setTitle("Notifications Plugin") //TODO: i18n
+ .setMessage("You need to grant permission to access notifications") //TODO: i18n
.setPositiveButton("Open settings",new DialogInterface.OnClickListener() {
@Override
- public void onClick(DialogInterface dialogInterface, int i) {
+ public void onClick(DialogInterface dialogInterface, int i) { //TODO: i18n
Intent intent=new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
baseContext.startActivity(intent);
}
})
.setNegativeButton("Cancel",new DialogInterface.OnClickListener() {
@Override
- public void onClick(DialogInterface dialogInterface, int i) {
+ public void onClick(DialogInterface dialogInterface, int i) { //TODO: i18n
//Do nothing
}
})
@@ -309,5 +311,9 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
}
}
+ @Override
+ public Button getInterfaceButton(Activity activity) {
+ return null;
+ }
}
diff --git a/KdeConnect/src/main/java/org/kde/connect/Plugins/PingPlugin.java b/KdeConnect/src/main/java/org/kde/connect/Plugins/PingPlugin/PingPlugin.java
similarity index 73%
rename from KdeConnect/src/main/java/org/kde/connect/Plugins/PingPlugin.java
rename to KdeConnect/src/main/java/org/kde/connect/Plugins/PingPlugin/PingPlugin.java
index 39cf8896..f064201d 100644
--- a/KdeConnect/src/main/java/org/kde/connect/Plugins/PingPlugin.java
+++ b/KdeConnect/src/main/java/org/kde/connect/Plugins/PingPlugin/PingPlugin.java
@@ -1,15 +1,19 @@
-package org.kde.connect.Plugins;
+package org.kde.connect.Plugins.PingPlugin;
import android.R;
+import android.app.Activity;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.drawable.Drawable;
-import android.util.Log;
+import android.support.v4.app.NotificationCompat;
+import android.view.View;
+import android.widget.Button;
+import org.kde.connect.Device;
import org.kde.connect.NetworkPackage;
-import org.kde.connect.PluginFactory;
+import org.kde.connect.Plugins.Plugin;
public class PingPlugin extends Plugin {
@@ -60,7 +64,7 @@ public class PingPlugin extends Plugin {
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_PING)) {
//Log.e("PingPackageReceiver", "was a ping!");
- Notification noti = new Notification.Builder(context)
+ Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(device.getName())
.setContentText("Ping!")
.setTicker("Ping!")
@@ -82,4 +86,17 @@ public class PingPlugin extends Plugin {
return null;
}
+ @Override
+ public Button getInterfaceButton(Activity activity) {
+ Button b = new Button(activity);
+ b.setText("Send ping"); //TODO: i18n
+ b.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ device.sendPackage(new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PING));
+ }
+ });
+ return b;
+ }
+
}
diff --git a/KdeConnect/src/main/java/org/kde/connect/Plugins/Plugin.java b/KdeConnect/src/main/java/org/kde/connect/Plugins/Plugin.java
index 7a951d6b..83567aed 100644
--- a/KdeConnect/src/main/java/org/kde/connect/Plugins/Plugin.java
+++ b/KdeConnect/src/main/java/org/kde/connect/Plugins/Plugin.java
@@ -6,6 +6,7 @@ import android.app.Application;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.widget.Button;
import org.kde.connect.Device;
import org.kde.connect.NetworkPackage;
@@ -60,22 +61,29 @@ public abstract class Plugin {
/**
* Finish any ongoing operations, remove listeners... so
- * this object could be garbage collected
+ * this object could be garbage collected.
*/
public abstract void onDestroy();
/**
* If onCreate returns false, should create a dialog explaining
- * the problem (and how to fix it, if possible) to the user
+ * the problem (and how to fix it, if possible) to the user.
*/
public abstract boolean onPackageReceived(NetworkPackage np);
/**
* If onCreate returns false, should create a dialog explaining
- * the problem (and how to fix it, if possible) to the user
+ * the problem (and how to fix it, if possible) to the user.
*/
public abstract AlertDialog getErrorDialog(Context baseContext);
- //TODO: Add a getInterfaceButton to show in the device activity
+ /**
+ * Creates a button that will be displayed in the user interface
+ * It can open an activity or perform any other action that the
+ * plugin would wants to expose to the user. Return null if no
+ * button should be displayed.
+ */
+ public abstract Button getInterfaceButton(Activity activity);
+
}
diff --git a/KdeConnect/src/main/java/org/kde/connect/PluginFactory.java b/KdeConnect/src/main/java/org/kde/connect/Plugins/PluginFactory.java
similarity index 89%
rename from KdeConnect/src/main/java/org/kde/connect/PluginFactory.java
rename to KdeConnect/src/main/java/org/kde/connect/Plugins/PluginFactory.java
index d13ffedf..e0c77b67 100644
--- a/KdeConnect/src/main/java/org/kde/connect/PluginFactory.java
+++ b/KdeConnect/src/main/java/org/kde/connect/Plugins/PluginFactory.java
@@ -1,19 +1,17 @@
-package org.kde.connect;
+package org.kde.connect.Plugins;
import android.content.Context;
import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.preference.CheckBoxPreference;
import android.util.Log;
-import org.kde.connect.Plugins.BatteryPlugin;
-import org.kde.connect.Plugins.ClipboardPlugin;
-import org.kde.connect.Plugins.MprisPlugin;
-import org.kde.connect.Plugins.NotificationsPlugin;
-import org.kde.connect.Plugins.PingPlugin;
-import org.kde.connect.Plugins.Plugin;
-import org.kde.connect.Plugins.TelephonyPlugin;
+import org.kde.connect.Device;
+import org.kde.connect.Plugins.BatteryPlugin.BatteryPlugin;
+import org.kde.connect.Plugins.ClibpoardPlugin.ClipboardPlugin;
+import org.kde.connect.Plugins.MprisPlugin.MprisPlugin;
+import org.kde.connect.Plugins.NotificationsPlugin.NotificationsPlugin;
+import org.kde.connect.Plugins.PingPlugin.PingPlugin;
+import org.kde.connect.Plugins.TelephonyPlugin.TelephonyPlugin;
import java.util.Map;
import java.util.Set;
diff --git a/KdeConnect/src/main/java/org/kde/connect/Plugins/TelephonyPlugin.java b/KdeConnect/src/main/java/org/kde/connect/Plugins/TelephonyPlugin/TelephonyPlugin.java
similarity index 95%
rename from KdeConnect/src/main/java/org/kde/connect/Plugins/TelephonyPlugin.java
rename to KdeConnect/src/main/java/org/kde/connect/Plugins/TelephonyPlugin/TelephonyPlugin.java
index 175bde16..f89d3248 100644
--- a/KdeConnect/src/main/java/org/kde/connect/Plugins/TelephonyPlugin.java
+++ b/KdeConnect/src/main/java/org/kde/connect/Plugins/TelephonyPlugin/TelephonyPlugin.java
@@ -1,5 +1,6 @@
-package org.kde.connect.Plugins;
+package org.kde.connect.Plugins.TelephonyPlugin;
+import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -10,10 +11,11 @@ import android.os.Bundle;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.util.Log;
+import android.widget.Button;
import org.kde.connect.Helpers.ContactsHelper;
import org.kde.connect.NetworkPackage;
-import org.kde.connect.PluginFactory;
+import org.kde.connect.Plugins.Plugin;
import org.kde.kdeconnect.R;
public class TelephonyPlugin extends Plugin {
@@ -182,4 +184,8 @@ public class TelephonyPlugin extends Plugin {
return null;
}
+ @Override
+ public Button getInterfaceButton(Activity activity) {
+ return null;
+ }
}
diff --git a/KdeConnect/src/main/java/org/kde/connect/PreferenceListAdapter.java b/KdeConnect/src/main/java/org/kde/connect/PreferenceListAdapter.java
deleted file mode 100644
index 4efbc956..00000000
--- a/KdeConnect/src/main/java/org/kde/connect/PreferenceListAdapter.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package org.kde.connect;
-
-import android.content.Context;
-import android.database.DataSetObserver;
-import android.graphics.drawable.Drawable;
-import android.preference.Preference;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.ListAdapter;
-import android.widget.TextView;
-
-import org.kde.kdeconnect.R;
-
-import java.util.ArrayList;
-
-public class PreferenceListAdapter implements ListAdapter {
-
-
- private ArrayList localList;
-
- public PreferenceListAdapter(ArrayList list) {
- super();
- Log.e("PreferenceListAdapter", ""+list.size());
- localList = list;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- View v = localList.get(position).getView(convertView, parent);
- v.setEnabled(true);
- v.setFocusable(true);
- return v;
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public void unregisterDataSetObserver(DataSetObserver observer) {
- } // Empty
-
- @Override
- public void registerDataSetObserver(DataSetObserver observer) {
- } // Empty
-
- @Override
- public boolean isEmpty() {
- return localList.size() == 0;
- }
-
- @Override
- public boolean hasStableIds() {
- return false;
- }
-
- @Override
- public int getViewTypeCount() {
- return localList.size();
- }
-
- @Override
- public int getItemViewType(int position) {
- return 0;
- } // Empty
-
- @Override
- public Object getItem(int position) {
- return localList.get(position);
- } // Empty
-
- @Override
- public int getCount() {
- return localList.size();
- }
-
- @Override
- public boolean isEnabled(int i) {
- return false;
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return false;
- }
- }
diff --git a/KdeConnect/src/main/java/org/kde/connect/SettingsActivity.java b/KdeConnect/src/main/java/org/kde/connect/SettingsActivity.java
deleted file mode 100644
index f1b6d992..00000000
--- a/KdeConnect/src/main/java/org/kde/connect/SettingsActivity.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package org.kde.connect;
-
-import android.app.ListActivity;
-import android.content.SharedPreferences;
-import android.os.Build;
-import android.os.Bundle;
-import android.preference.CheckBoxPreference;
-import android.preference.Preference;
-import android.preference.PreferenceActivity;
-import android.preference.PreferenceManager;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-
-import org.kde.kdeconnect.R;
-
-import java.util.AbstractCollection;
-import java.util.ArrayList;
-import java.util.Set;
-
-public class SettingsActivity extends ListActivity {
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
-/*
- ActionBar actionBar = getActionBar();
- actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME
- | ActionBar.DISPLAY_SHOW_TITLE);
- actionBar.setDisplayHomeAsUpEnabled(true);*/
-
- getListView().setItemsCanFocus(true);
- getListView().setFocusable(false);
- getListView().setEnabled(true);
- getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
- getListView().setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
- getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
-
- final String deviceId = getIntent().getStringExtra("deviceId");
- BackgroundService.RunCommand(getApplicationContext(), new BackgroundService.InstanceCallback() {
- @Override
- public void onServiceStart(BackgroundService service) {
-
- final Device device = service.getDevice(deviceId);
- Set plugins = PluginFactory.getAvailablePlugins();
-
- ArrayList preferences = new ArrayList();
- for (final String pluginName : plugins) {
- Log.e("SettingsActivity", pluginName);
- CheckBoxPreference pref = new CheckBoxPreference(getBaseContext());
- PluginFactory.PluginInfo info = PluginFactory.getPluginInfo(getBaseContext(), pluginName);
- pref.setKey(pluginName);
- pref.setTitle(info.getDisplayName());
- pref.setSummary(info.getDescription());
- pref.setSelectable(true);
- pref.setEnabled(true);
- pref.setChecked(device.isPluginEnabled(pluginName));
- pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- Log.e("CLICK","CLICK");
- return false;
- }
- });
- pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- device.setPluginEnabled(pluginName, (Boolean)newValue);
- return true;
- }
- });
- preferences.add(pref);
- }
-
- setListAdapter(new PreferenceListAdapter(preferences));
- }
- });
-
-
-
- }
-
-
-}
\ No newline at end of file
diff --git a/KdeConnect/src/main/java/org/kde/connect/UserInterface/DeviceActivity.java b/KdeConnect/src/main/java/org/kde/connect/UserInterface/DeviceActivity.java
new file mode 100644
index 00000000..8567d592
--- /dev/null
+++ b/KdeConnect/src/main/java/org/kde/connect/UserInterface/DeviceActivity.java
@@ -0,0 +1,151 @@
+package org.kde.connect.UserInterface;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import org.kde.connect.BackgroundService;
+import org.kde.connect.Device;
+import org.kde.connect.Plugins.Plugin;
+import org.kde.connect.UserInterface.List.ButtonItem;
+import org.kde.connect.UserInterface.List.ListAdapter;
+import org.kde.connect.UserInterface.List.SectionItem;
+import org.kde.kdeconnect.R;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+
+public class DeviceActivity extends ActionBarActivity {
+
+ private String deviceId;
+ private Device device;
+
+ private Device.PluginsChangedListener pluginsChangedListener = new Device.PluginsChangedListener() {
+ @Override
+ public void onPluginsChanged(final Device device) {
+
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Log.e("MainActivity", "updateComputerList");
+
+ //Errors list
+ final HashMap failedPlugins = device.getFailedPlugins();
+ final String[] ids = failedPlugins.keySet().toArray(new String[failedPlugins.size()]);
+ String[] names = new String[failedPlugins.size()];
+ for(int i = 0; i < ids.length; i++) {
+ Plugin p = failedPlugins.get(ids[i]);
+ names[i] = p.getDisplayName();
+ }
+ ListView errorList = (ListView)findViewById(R.id.errors_list);
+ if (!failedPlugins.isEmpty() && errorList.getHeaderViewsCount() == 0) {
+ TextView header = new TextView(DeviceActivity.this);
+ header.setPadding(0,24,0,0);
+ header.setText("Plugins failed to load (tap for more info):"); //TODO: i18n
+ errorList.addHeaderView(header);
+ }
+ errorList.setAdapter(new ArrayAdapter(DeviceActivity.this, android.R.layout.simple_list_item_1, names));
+ errorList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> adapterView, View view, int position, long id) {
+ Plugin p = failedPlugins.get(ids[position - 1]); //Header is position 0, so we have to substract one
+ p.getErrorDialog(DeviceActivity.this).show();
+ }
+ });
+
+ //Buttons list
+ ArrayList items = new ArrayList();
+ final Collection plugins = device.getLoadedPlugins().values();
+ for (Plugin p : plugins) {
+ Button b = p.getInterfaceButton(DeviceActivity.this);
+ if (b != null) {
+ items.add(new SectionItem(p.getDisplayName()));
+ items.add(new ButtonItem(b));
+ }
+ }
+ ListView buttonsList = (ListView)findViewById(R.id.buttons_list);
+ buttonsList.setAdapter(new ListAdapter(DeviceActivity.this, items));
+
+ }
+ });
+
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_device);
+
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+
+ deviceId = getIntent().getStringExtra("deviceId");
+
+ BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(BackgroundService service) {
+ device = service.getDevice(deviceId);
+ setTitle(device.getName());
+ device.addPluginsChangedListener(pluginsChangedListener);
+ pluginsChangedListener.onPluginsChanged(device);
+ }
+ });
+
+ }
+
+ @Override
+ protected void onDestroy() {
+ BackgroundService.RunCommand(DeviceActivity.this, new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(BackgroundService service) {
+ Device device = service.getDevice(deviceId);
+ device.removePluginsChangedListener(pluginsChangedListener);
+ }
+ });
+ super.onDestroy();
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ menu.clear();
+ if (device.isPaired()) {
+ //TODO: i18n
+ menu.add("Select plugins").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ Intent intent = new Intent(DeviceActivity.this, SettingsActivity.class);
+ intent.putExtra("deviceId", deviceId);
+ startActivity(intent);
+ return true;
+ }
+ });
+ //TODO: i18n
+ menu.add("Unpair").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ device.unpair();
+ finish();
+ return true;
+ }
+ });
+ return true;
+ } else {
+ return false;
+ }
+
+ }
+}
diff --git a/KdeConnect/src/main/java/org/kde/connect/ImageListAdapter.java b/KdeConnect/src/main/java/org/kde/connect/UserInterface/ImageListAdapter.java
similarity index 98%
rename from KdeConnect/src/main/java/org/kde/connect/ImageListAdapter.java
rename to KdeConnect/src/main/java/org/kde/connect/UserInterface/ImageListAdapter.java
index 4dbdd96f..6a76335b 100644
--- a/KdeConnect/src/main/java/org/kde/connect/ImageListAdapter.java
+++ b/KdeConnect/src/main/java/org/kde/connect/UserInterface/ImageListAdapter.java
@@ -1,4 +1,4 @@
-package org.kde.connect;
+package org.kde.connect.UserInterface;
import android.content.Context;
import android.database.DataSetObserver;
diff --git a/KdeConnect/src/main/java/org/kde/connect/UserInterface/List/ButtonItem.java b/KdeConnect/src/main/java/org/kde/connect/UserInterface/List/ButtonItem.java
new file mode 100644
index 00000000..4a81460e
--- /dev/null
+++ b/KdeConnect/src/main/java/org/kde/connect/UserInterface/List/ButtonItem.java
@@ -0,0 +1,21 @@
+package org.kde.connect.UserInterface.List;
+
+import android.app.Activity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+
+public class ButtonItem implements ListAdapter.Item {
+
+ private final Button button;
+
+ public ButtonItem(Button b) {
+ this.button = b;
+ }
+
+ @Override
+ public View inflateView(LayoutInflater layoutInflater) {
+ return button;
+ }
+
+}
diff --git a/KdeConnect/src/main/java/org/kde/connect/UserInterface/List/DeviceItem.java b/KdeConnect/src/main/java/org/kde/connect/UserInterface/List/DeviceItem.java
new file mode 100644
index 00000000..d7148275
--- /dev/null
+++ b/KdeConnect/src/main/java/org/kde/connect/UserInterface/List/DeviceItem.java
@@ -0,0 +1,49 @@
+package org.kde.connect.UserInterface.List;
+
+
+import android.app.Activity;
+import android.content.Intent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import org.kde.connect.Device;
+import org.kde.connect.UserInterface.DeviceActivity;
+import org.kde.connect.UserInterface.PairActivity;
+import org.kde.kdeconnect.R;
+
+public class DeviceItem implements ListAdapter.Item {
+
+ private final Device device;
+ private final Activity activity;
+
+ public DeviceItem(Activity activity, Device device) {
+ this.device = device;
+ this.activity = activity;
+ }
+
+ @Override
+ public View inflateView(LayoutInflater layoutInflater) {
+ View v = layoutInflater.inflate(R.layout.list_item_entry, null);
+
+ TextView titleView = (TextView)v.findViewById(R.id.list_item_entry_title);
+ if (titleView != null) titleView.setText(device.getName());
+
+ v.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent;
+ if (device.isPaired()) {
+ intent = new Intent(activity, DeviceActivity.class);
+ } else {
+ intent = new Intent(activity, PairActivity.class);
+ }
+ intent.putExtra("deviceId", device.getDeviceId());
+ activity.startActivity(intent);
+ }
+ });
+
+ return v;
+ }
+
+}
diff --git a/KdeConnect/src/main/java/org/kde/connect/UserInterface/List/ListAdapter.java b/KdeConnect/src/main/java/org/kde/connect/UserInterface/List/ListAdapter.java
new file mode 100644
index 00000000..85ce0fbe
--- /dev/null
+++ b/KdeConnect/src/main/java/org/kde/connect/UserInterface/List/ListAdapter.java
@@ -0,0 +1,34 @@
+package org.kde.connect.UserInterface.List;
+
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+
+public class ListAdapter extends ArrayAdapter {
+
+ public interface Item {
+ public View inflateView(LayoutInflater layoutInflater);
+ }
+
+ private ArrayList- items;
+ private LayoutInflater layoutInflater;
+
+ public ListAdapter(Context context, ArrayList
- items) {
+ super(context, 0, items);
+ this.items = items;
+ layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
+
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final Item i = items.get(position);
+ return i.inflateView(layoutInflater);
+ }
+
+}
diff --git a/KdeConnect/src/main/java/org/kde/connect/UserInterface/List/SectionItem.java b/KdeConnect/src/main/java/org/kde/connect/UserInterface/List/SectionItem.java
new file mode 100644
index 00000000..c1af2bee
--- /dev/null
+++ b/KdeConnect/src/main/java/org/kde/connect/UserInterface/List/SectionItem.java
@@ -0,0 +1,38 @@
+package org.kde.connect.UserInterface.List;
+
+import android.app.Activity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import org.kde.kdeconnect.R;
+
+public class SectionItem implements ListAdapter.Item {
+
+ private final String title;
+ public boolean isEmpty;
+
+ public SectionItem(String title) {
+ this.title = title;
+ }
+
+ @Override
+ public View inflateView(LayoutInflater layoutInflater) {
+
+ View v = layoutInflater.inflate(R.layout.list_item_category, null);
+
+ v.setOnClickListener(null);
+ v.setOnLongClickListener(null);
+ v.setLongClickable(false);
+
+ TextView sectionView = (TextView) v.findViewById(R.id.list_item_category_text);
+ sectionView.setText(title);
+
+ if (isEmpty) {
+ v.findViewById(R.id.list_item_category_empty_placeholder).setVisibility(View.VISIBLE);
+ }
+
+ return v;
+
+ }
+}
diff --git a/KdeConnect/src/main/java/org/kde/connect/UserInterface/MainActivity.java b/KdeConnect/src/main/java/org/kde/connect/UserInterface/MainActivity.java
new file mode 100644
index 00000000..a84009d1
--- /dev/null
+++ b/KdeConnect/src/main/java/org/kde/connect/UserInterface/MainActivity.java
@@ -0,0 +1,190 @@
+package org.kde.connect.UserInterface;
+
+
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import org.kde.connect.BackgroundService;
+import org.kde.connect.ComputerLinks.BaseComputerLink;
+import org.kde.connect.Device;
+import org.kde.connect.LinkProviders.BaseLinkProvider;
+import org.kde.connect.NetworkPackage;
+import org.kde.connect.UserInterface.List.ListAdapter;
+import org.kde.connect.UserInterface.List.DeviceItem;
+import org.kde.connect.UserInterface.List.SectionItem;
+import org.kde.kdeconnect.R;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class MainActivity extends ActionBarActivity {
+
+ //
+ // Action bar
+ //
+
+ MenuItem menuProgress;
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.main, menu);
+ menuProgress = menu.findItem(R.id.menu_progress);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_refresh:
+ BackgroundService.RunCommand(MainActivity.this, new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(BackgroundService service) {
+ service.onNetworkChange();
+ }
+ });
+ item.setVisible(false);
+ menuProgress.setVisible(true);
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try { Thread.sleep(1500); } catch (InterruptedException e) { }
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ menuProgress.setVisible(false);
+ item.setVisible(true);
+ }
+ });
+ }
+ }).start();
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
+
+ }
+
+
+
+ //
+ // Device list
+ //
+
+ void updateComputerList() {
+ Log.e("MainActivity","updateComputerList");
+
+ BackgroundService.RunCommand(MainActivity.this, new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(final BackgroundService service) {
+
+ Collection devices = service.getDevices().values();
+ final ArrayList items = new ArrayList();
+
+ SectionItem section;
+
+ section = new SectionItem("Connected devices"); //TODO: i18n
+ section.isEmpty = true;
+ items.add(section);
+ for(Device d : devices) {
+ if (d.isReachable() && d.isPaired()) {
+ items.add(new DeviceItem(MainActivity.this, d));
+ section.isEmpty = false;
+ }
+ }
+
+ section = new SectionItem("Not paired devices"); //TODO: i18n
+ section.isEmpty = true;
+ items.add(section);
+ for(Device d : devices) {
+ if (d.isReachable() && !d.isPaired()) {
+ items.add(new DeviceItem(MainActivity.this, d));
+ section.isEmpty = false;
+ }
+ }
+
+ section = new SectionItem("Remembered devices"); //TODO: i18n
+ section.isEmpty = true;
+ items.add(section);
+ for(Device d : devices) {
+ if (!d.isReachable() && d.isPaired()) {
+ items.add(new DeviceItem(MainActivity.this, d));
+ section.isEmpty = false;
+ }
+ }
+ if (section.isEmpty) {
+ items.remove(items.size()-1); //Remove this section
+ }
+
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ListView list = (ListView)findViewById(R.id.listView1);
+ list.setAdapter(new ListAdapter(MainActivity.this, items));
+ list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
+ view.callOnClick();
+ }
+ });
+ }
+ });
+
+ }
+ });
+ };
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ BackgroundService.RunCommand(MainActivity.this, new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(BackgroundService service) {
+ service.onNetworkChange();
+ service.setDeviceListChangedCallback(new BackgroundService.DeviceListChangedCallback() {
+ @Override
+ public void onDeviceListChanged() {
+ updateComputerList();
+ }
+ });
+ }
+ });
+ }
+
+ @Override
+ protected void onStop() {
+ BackgroundService.RunCommand(MainActivity.this, new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(BackgroundService service) {
+ service.setDeviceListChangedCallback(null);
+ }
+ });
+ super.onStop();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ updateComputerList();
+ }
+
+}
diff --git a/KdeConnect/src/main/java/org/kde/connect/UserInterface/PairActivity.java b/KdeConnect/src/main/java/org/kde/connect/UserInterface/PairActivity.java
new file mode 100644
index 00000000..b9a7b442
--- /dev/null
+++ b/KdeConnect/src/main/java/org/kde/connect/UserInterface/PairActivity.java
@@ -0,0 +1,148 @@
+package org.kde.connect.UserInterface;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import org.kde.connect.BackgroundService;
+import org.kde.connect.Device;
+import org.kde.kdeconnect.R;
+
+public class PairActivity extends ActionBarActivity {
+
+ private String deviceId;
+ private Device device = null;
+
+ private Device.PairingCallback pairingCallback = new Device.PairingCallback() {
+
+ @Override
+ public void incomingRequest() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ((TextView) findViewById(R.id.pair_message)).setText("Pairing requested"); //TODO: i18n
+ findViewById(R.id.pair_progress).setVisibility(View.GONE);
+ findViewById(R.id.pair_button).setVisibility(View.GONE);
+ findViewById(R.id.pair_request).setVisibility(View.VISIBLE);
+ }
+ });
+ NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(device.getNotificationId());
+ }
+
+ @Override
+ public void pairingSuccessful() {
+ finish();
+ }
+
+ @Override
+ public void pairingFailed(final String error) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ((TextView) findViewById(R.id.pair_message)).setText(error);
+ findViewById(R.id.pair_progress).setVisibility(View.GONE);
+ findViewById(R.id.pair_button).setVisibility(View.VISIBLE);
+ findViewById(R.id.pair_request).setVisibility(View.GONE);
+ }
+ });
+ }
+
+ @Override
+ public void unpaired() {
+
+ }
+
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_pair);
+
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+
+ deviceId = getIntent().getStringExtra("deviceId");
+
+ BackgroundService.RunCommand(PairActivity.this, new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(BackgroundService service) {
+ device = service.getDevice(deviceId);
+ setTitle(device.getName());
+ NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(device.getNotificationId());
+ }
+
+ });
+
+ final Button pairButton = (Button)findViewById(R.id.pair_button);
+ pairButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ pairButton.setVisibility(View.GONE);
+ ((TextView) findViewById(R.id.pair_message)).setText("");
+ findViewById(R.id.pair_progress).setVisibility(View.VISIBLE);
+ BackgroundService.RunCommand(PairActivity.this, new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(BackgroundService service) {
+ device = service.getDevice(deviceId);
+ device.requestPairing();
+ }
+ });
+ }
+ });
+
+ findViewById(R.id.accept_button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ BackgroundService.RunCommand(PairActivity.this, new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(BackgroundService service) {
+ device.acceptPairing();
+ finish();
+ }
+ });
+ }
+ });
+
+ findViewById(R.id.reject_button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ BackgroundService.RunCommand(PairActivity.this, new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(BackgroundService service) {
+ device.rejectPairing();
+ finish();
+ }
+ });
+ }
+ });
+
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ BackgroundService.RunCommand(PairActivity.this, new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(BackgroundService service) {
+ device.addPairingCallback(pairingCallback);
+ }
+ });
+ }
+
+ @Override
+ protected void onStop() {
+ if (device != null) device.removePairingCallback(pairingCallback);
+ super.onStop();
+ }
+
+}
\ No newline at end of file
diff --git a/KdeConnect/src/main/java/org/kde/connect/UserInterface/PreferenceListAdapter.java b/KdeConnect/src/main/java/org/kde/connect/UserInterface/PreferenceListAdapter.java
new file mode 100644
index 00000000..0a3b50e6
--- /dev/null
+++ b/KdeConnect/src/main/java/org/kde/connect/UserInterface/PreferenceListAdapter.java
@@ -0,0 +1,37 @@
+package org.kde.connect.UserInterface;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.graphics.drawable.Drawable;
+import android.preference.Preference;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import org.kde.kdeconnect.R;
+
+import java.util.ArrayList;
+
+public class PreferenceListAdapter extends ArrayAdapter {
+
+
+ private ArrayList localList;
+
+ public PreferenceListAdapter(Context context, ArrayList items) {
+ super(context,0, items);
+ Log.e("PreferenceListAdapter", ""+items.size());
+ localList = items;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Preference preference = localList.get(position);
+ return preference.getView(convertView, parent);
+ }
+
+}
diff --git a/KdeConnect/src/main/java/org/kde/connect/UserInterface/SettingsActivity.java b/KdeConnect/src/main/java/org/kde/connect/UserInterface/SettingsActivity.java
new file mode 100644
index 00000000..7a32e538
--- /dev/null
+++ b/KdeConnect/src/main/java/org/kde/connect/UserInterface/SettingsActivity.java
@@ -0,0 +1,74 @@
+package org.kde.connect.UserInterface;
+
+import android.app.ListActivity;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.support.v7.appcompat.R;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+
+import org.kde.connect.BackgroundService;
+import org.kde.connect.Device;
+import org.kde.connect.Plugins.PluginFactory;
+
+import java.util.ArrayList;
+import java.util.Set;
+
+public class SettingsActivity extends ListActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final String deviceId = getIntent().getStringExtra("deviceId");
+ BackgroundService.RunCommand(getApplicationContext(), new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(BackgroundService service) {
+
+ final Device device = service.getDevice(deviceId);
+ Set plugins = PluginFactory.getAvailablePlugins();
+
+ final ArrayList preferences = new ArrayList();
+ for (final String pluginName : plugins) {
+ Log.e("SettingsActivity", pluginName);
+ CheckBoxPreference pref = new CheckBoxPreference(getBaseContext());
+ PluginFactory.PluginInfo info = PluginFactory.getPluginInfo(getBaseContext(), pluginName);
+ pref.setKey(pluginName);
+ pref.setTitle(info.getDisplayName());
+ pref.setSummary(info.getDescription());
+ pref.setChecked(device.isPluginEnabled(pluginName));
+ preferences.add(pref);
+ }
+
+ setListAdapter(new PreferenceListAdapter(SettingsActivity.this, preferences));
+ getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
+
+ CheckBoxPreference pref = (CheckBoxPreference)preferences.get(i);
+
+ boolean enabled = device.isPluginEnabled(pref.getKey());
+ device.setPluginEnabled(pref.getKey(), !enabled);
+
+ pref.setChecked(!enabled);
+
+ getListAdapter().getView(i, view, null); //This will refresh the view (yes, this is the way to do it)
+
+ }
+ });
+
+ getListView().setPadding(16,16,16,16);
+
+ }
+ });
+
+
+
+ }
+
+
+}
\ No newline at end of file
diff --git a/KdeConnect/src/main/res/layout/activity_device.xml b/KdeConnect/src/main/res/layout/activity_device.xml
index f2d589dd..92d7d69c 100644
--- a/KdeConnect/src/main/res/layout/activity_device.xml
+++ b/KdeConnect/src/main/res/layout/activity_device.xml
@@ -9,23 +9,19 @@
tools:context=".MainActivity"
android:orientation="vertical">
-
-
-
+
-
-
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:id="@+id/errors_list"
+ android:layout_weight="1"
+ />
diff --git a/KdeConnect/src/main/res/layout/activity_main.xml b/KdeConnect/src/main/res/layout/activity_main.xml
index 2dcb3dbf..996dc57c 100644
--- a/KdeConnect/src/main/res/layout/activity_main.xml
+++ b/KdeConnect/src/main/res/layout/activity_main.xml
@@ -1,4 +1,4 @@
-
-
-
-
+
diff --git a/KdeConnect/src/main/res/layout/activity_pair.xml b/KdeConnect/src/main/res/layout/activity_pair.xml
new file mode 100644
index 00000000..b6febf71
--- /dev/null
+++ b/KdeConnect/src/main/res/layout/activity_pair.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/KdeConnect/src/main/res/layout/barswitch.xml b/KdeConnect/src/main/res/layout/barswitch.xml
deleted file mode 100644
index f944621b..00000000
--- a/KdeConnect/src/main/res/layout/barswitch.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
\ No newline at end of file
diff --git a/KdeConnect/src/main/res/layout/list_item_category.xml b/KdeConnect/src/main/res/layout/list_item_category.xml
new file mode 100644
index 00000000..3d259bc8
--- /dev/null
+++ b/KdeConnect/src/main/res/layout/list_item_category.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
diff --git a/KdeConnect/src/main/res/layout/list_item_entry.xml b/KdeConnect/src/main/res/layout/list_item_entry.xml
new file mode 100644
index 00000000..4f3333d0
--- /dev/null
+++ b/KdeConnect/src/main/res/layout/list_item_entry.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/KdeConnect/src/main/res/layout/progressbar.xml b/KdeConnect/src/main/res/layout/progressbar.xml
deleted file mode 100644
index eda4b924..00000000
--- a/KdeConnect/src/main/res/layout/progressbar.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/KdeConnect/src/main/res/menu/device.xml b/KdeConnect/src/main/res/menu/device.xml
deleted file mode 100644
index 2aca8eae..00000000
--- a/KdeConnect/src/main/res/menu/device.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
\ No newline at end of file
diff --git a/KdeConnect/src/main/res/menu/main.xml b/KdeConnect/src/main/res/menu/main.xml
index 09515c66..067d0585 100644
--- a/KdeConnect/src/main/res/menu/main.xml
+++ b/KdeConnect/src/main/res/menu/main.xml
@@ -1,10 +1,21 @@
-