2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-22 09:58:08 +00:00
Albert Vaca d5bc6ebcfa Catch up with the KDE client
Implemented symmetric pairing
Implemented public key exchange
Implemented encryption
Implemented NetworkPackage protocol version 3
New GUI, less dependant on Android 3.0+ features
Plugins can now place a button in the user interface
Fixed not clickable plugins preferences page
Some refactoring
2013-09-03 22:13:51 +02:00

302 lines
10 KiB
Java

package org.kde.connect;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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.HashMap;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BackgroundService extends Service {
private ArrayList<BaseLinkProvider> linkProviders = new ArrayList<BaseLinkProvider>();
private HashMap<String, Device> devices = new HashMap<String, Device>();
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<String> trustedDevices = preferences.getAll().keySet();
for(String deviceId : trustedDevices) {
if (preferences.getBoolean(deviceId, false)) {
Device device = new Device(getBaseContext(), deviceId);
devices.put(deviceId,device);
device.addPairingCallback(devicePairingCallback);
}
}
}
public void registerLinkProviders() {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
if (settings.getBoolean("avahitcp_link", true)) {
//linkProviders.add(new AvahiTcpLinkProvider(this));
}
if (settings.getBoolean("broadcasttcp_link", true)) {
linkProviders.add(new BroadcastTcpLinkProvider(this));
}
}
public Device getDevice(String id) {
return devices.get(id);
}
private BaseLinkProvider.ConnectionReceiver deviceListener = new BaseLinkProvider.ConnectionReceiver() {
@Override
public void onConnectionReceived(final NetworkPackage identityPackage, final BaseComputerLink link) {
Log.e("BackgroundService", "Connection accepted!");
String deviceId = identityPackage.getString("deviceId");
Device device = devices.get(deviceId);
if (device != null) {
Log.e("BackgroundService", "addLink, known device: "+deviceId);
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
public void onConnectionLost(BaseComputerLink link) {
Device d = devices.get(link.getDeviceId());
Log.e("onConnectionLost","removeLink, deviceId: "+link.getDeviceId());
if (d != null) {
d.removeLink(link);
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();
}
};
public HashMap<String, Device> getDevices() {
return devices;
}
public void startDiscovery() {
Log.i("BackgroundService","StartDiscovery");
for (BaseLinkProvider a : linkProviders) {
a.onStart();
}
}
public void stopDiscovery() {
Log.i("BackgroundService","StopDiscovery");
for (BaseLinkProvider a : linkProviders) {
a.onStop();
}
}
public void onNetworkChange() {
Log.i("BackgroundService","OnNetworkChange");
for (BaseLinkProvider a : linkProviders) {
a.onNetworkChange();
}
}
public void addConnectionListener(BaseLinkProvider.ConnectionReceiver cr) {
Log.i("BackgroundService","Registering connection listener");
for (BaseLinkProvider a : linkProviders) {
a.addConnectionReceiver(cr);
}
}
public void removeConnectionListener(BaseLinkProvider.ConnectionReceiver cr) {
for (BaseLinkProvider a : linkProviders) {
a.removeConnectionReceiver(cr);
}
}
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() {
super.onCreate();
// Register screen on listener
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
registerReceiver(new KdeConnectBroadcastReceiver(), filter);
Log.i("BackgroundService","Service not started yet, initializing...");
initializeRsaKeys();
loadRememberedDevicesFromSettings();
registerLinkProviders();
//Link Providers need to be already registered
addConnectionListener(deviceListener);
startDiscovery();
}
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");
stopDiscovery();
super.onDestroy();
}
@Override
public IBinder onBind (Intent intent) {
return new Binder();
}
//To use the service from the gui
public interface InstanceCallback {
void onServiceStart(BackgroundService service);
}
private static ArrayList<InstanceCallback> callbacks = new ArrayList<InstanceCallback>();
private static final Lock mutex = new ReentrantLock(true);
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//This will be called for each intent launch, even if the service is already started and it is reused
Log.i("BackgroundService","onStartCommand");
mutex.lock();
for (InstanceCallback c : callbacks) {
c.onServiceStart(this);
}
callbacks.clear();
mutex.unlock();
return Service.START_STICKY;
}
public static void Start(Context c) {
RunCommand(c, null);
}
public static void RunCommand(Context c, final InstanceCallback callback) {
if (callback != null) {
mutex.lock();
callbacks.add(callback);
mutex.unlock();
}
Intent serviceIntent = new Intent(c, BackgroundService.class);
c.startService(serviceIntent);
}
}