mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 09:58:08 +00:00
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
This commit is contained in:
parent
6b978f334b
commit
d5bc6ebcfa
@ -10,6 +10,7 @@
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
||||
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
|
||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
||||
</configuration>
|
||||
</facet>
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
<activity
|
||||
android:theme="@style/Theme.AppCompat"
|
||||
android:name="org.kde.connect.MainActivity"
|
||||
android:name="org.kde.connect.UserInterface.MainActivity"
|
||||
android:label="KDE Connect" >
|
||||
|
||||
<intent-filter>
|
||||
@ -36,11 +36,12 @@
|
||||
|
||||
<activity
|
||||
android:theme="@style/Theme.AppCompat"
|
||||
android:name="org.kde.connect.DeviceActivity"
|
||||
android:name="org.kde.connect.UserInterface.DeviceActivity"
|
||||
android:label="Device"
|
||||
android:parentActivityName=".MainActivity">
|
||||
android:parentActivityName="org.kde.connect.UserInterface.MainActivity"
|
||||
>
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".MainActivity" />
|
||||
android:value="org.kde.connect.UserInterface.MainActivity" />
|
||||
|
||||
</activity>
|
||||
|
||||
@ -48,17 +49,30 @@
|
||||
|
||||
<activity
|
||||
android:theme="@style/Theme.AppCompat"
|
||||
android:name="org.kde.connect.MprisActivity"
|
||||
android:name="org.kde.connect.UserInterface.PairActivity"
|
||||
android:label="Device"
|
||||
android:parentActivityName="org.kde.connect.UserInterface.MainActivity"
|
||||
>
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.connect.UserInterface.MainActivity" />
|
||||
|
||||
</activity>
|
||||
|
||||
|
||||
|
||||
<activity
|
||||
android:theme="@style/Theme.AppCompat"
|
||||
android:name="org.kde.connect.Plugins.MprisPlugin.MprisActivity"
|
||||
android:label="MPRIS controls"
|
||||
android:parentActivityName=".DeviceActivity">
|
||||
>
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".DeviceActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.kde.connect.SettingsActivity"
|
||||
android:name="org.kde.connect.UserInterface.SettingsActivity"
|
||||
android:label="KDE Connect Settings"
|
||||
android:parentActivityName=".DeviceActivity">
|
||||
>
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".DeviceActivity" />
|
||||
</activity>
|
||||
@ -68,7 +82,7 @@
|
||||
android:name="org.kde.connect.BackgroundService">
|
||||
</service>
|
||||
|
||||
<service android:name="org.kde.connect.NotificationReceiver"
|
||||
<service android:name="org.kde.connect.Plugins.NotificationsPlugin.NotificationReceiver"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
|
@ -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<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)) {
|
||||
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");
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -78,7 +78,7 @@ public class TcpComputerLink extends BaseComputerLink {
|
||||
session = future.getSession();
|
||||
if (callback != null) callback.dispatchMessage(new Message());
|
||||
}
|
||||
}).run();
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
|
@ -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> pairingCallback = new ArrayList<PairingCallback>();
|
||||
private Timer pairingTimer;
|
||||
|
||||
private ArrayList<BaseComputerLink> links = new ArrayList<BaseComputerLink>();
|
||||
private HashMap<String, Plugin> plugins = new HashMap<String, Plugin>();
|
||||
private HashMap<String, Plugin> failedPlugins = new HashMap<String, Plugin>();
|
||||
|
||||
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<Void,Void,Void>() {
|
||||
@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<String,Plugin> getLoadedPlugins() {
|
||||
return plugins;
|
||||
}
|
||||
|
||||
public HashMap<String,Plugin> getFailedPlugins() {
|
||||
return failedPlugins;
|
||||
}
|
||||
|
||||
interface PluginsChangedListener {
|
||||
public interface PluginsChangedListener {
|
||||
void onPluginsChanged(Device device);
|
||||
}
|
||||
|
||||
ArrayList<PluginsChangedListener> pluginsChangedListeners = new ArrayList<PluginsChangedListener>();
|
||||
private ArrayList<PluginsChangedListener> pluginsChangedListeners = new ArrayList<PluginsChangedListener>();
|
||||
|
||||
public void addPluginsChangedListener(PluginsChangedListener listener) {
|
||||
pluginsChangedListeners.add(listener);
|
||||
|
@ -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<String, Plugin> 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<String>(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();
|
||||
}
|
||||
}
|
@ -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<String, NioSessionComputerLink> visibleComputers = new HashMap<String, NioSessionComputerLink>();
|
||||
private HashMap<Long, NioSessionComputerLink> nioSessions = new HashMap<Long, NioSessionComputerLink>();
|
||||
|
||||
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<IoFuture>() {
|
||||
@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<Void,Void,Void>() {
|
||||
@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";
|
||||
}
|
||||
}
|
@ -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<Void, Void, Void> {
|
||||
@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<String, Device> 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<String>(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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package org.kde.connect;
|
||||
package org.kde.connect.Plugins.NotificationsPlugin;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
@ -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;
|
@ -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;
|
||||
}
|
||||
}
|
@ -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<Preference> localList;
|
||||
|
||||
public PreferenceListAdapter(ArrayList<Preference> 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;
|
||||
}
|
||||
}
|
@ -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<String> plugins = PluginFactory.getAvailablePlugins();
|
||||
|
||||
ArrayList<Preference> preferences = new ArrayList<Preference>();
|
||||
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));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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<String, Plugin> 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<String>(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<ListAdapter.Item> items = new ArrayList<ListAdapter.Item>();
|
||||
final Collection<Plugin> 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package org.kde.connect;
|
||||
package org.kde.connect.UserInterface;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.DataSetObserver;
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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<ListAdapter.Item> {
|
||||
|
||||
public interface Item {
|
||||
public View inflateView(LayoutInflater layoutInflater);
|
||||
}
|
||||
|
||||
private ArrayList<Item> items;
|
||||
private LayoutInflater layoutInflater;
|
||||
|
||||
public ListAdapter(Context context, ArrayList<Item> 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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
@ -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<Device> devices = service.getDevices().values();
|
||||
final ArrayList<ListAdapter.Item> items = new ArrayList<ListAdapter.Item>();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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<Preference> {
|
||||
|
||||
|
||||
private ArrayList<Preference> localList;
|
||||
|
||||
public PreferenceListAdapter(Context context, ArrayList<Preference> 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);
|
||||
}
|
||||
|
||||
}
|
@ -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<String> plugins = PluginFactory.getAvailablePlugins();
|
||||
|
||||
final ArrayList<Preference> preferences = new ArrayList<Preference>();
|
||||
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);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -9,23 +9,19 @@
|
||||
tools:context=".MainActivity"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Button android:id="@+id/button1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Select plugins"/>
|
||||
<Button android:id="@+id/button2" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Send Ping"/>
|
||||
<Button android:id="@+id/button3" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Mpris controls"/>
|
||||
<ListView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:id="@+id/buttons_list"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="Plugins failed to load (tap for more info):"
|
||||
android:visibility="gone"
|
||||
android:id="@+id/textView"/>
|
||||
|
||||
<ListView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/listView1"
|
||||
android:layout_weight="1"/>
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:id="@+id/errors_list"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@ -7,12 +7,8 @@
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context=".MainActivity"
|
||||
android:id="@+id/listView1"
|
||||
android:addStatesFromChildren="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ListView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/listView1"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
</LinearLayout>
|
||||
</ListView>
|
||||
|
44
KdeConnect/src/main/res/layout/activity_pair.xml
Normal file
44
KdeConnect/src/main/res/layout/activity_pair.xml
Normal file
@ -0,0 +1,44 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context=".MainActivity"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<ProgressBar
|
||||
android:visibility="gone"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/pair_progress" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="Device not paired"
|
||||
android:id="@+id/pair_message"
|
||||
android:layout_gravity="left|center_vertical" />
|
||||
|
||||
<Button android:id="@+id/pair_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Request pairing"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:id="@+id/pair_request"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<Button android:id="@+id/accept_button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Accept"
|
||||
android:layout_weight="1" />
|
||||
<Button android:id="@+id/reject_button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Reject"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Switch
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Trust"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/switch1"></Switch>
|
23
KdeConnect/src/main/res/layout/list_item_category.xml
Normal file
23
KdeConnect/src/main/res/layout/list_item_category.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/list_item_category_text"
|
||||
layout="@android:layout/preference_category" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dip"
|
||||
android:visibility="gone"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="@string/device_list_empty"
|
||||
android:id="@+id/list_item_category_empty_placeholder"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
|
||||
</LinearLayout>
|
47
KdeConnect/src/main/res/layout/list_item_entry.xml
Normal file
47
KdeConnect/src/main/res/layout/list_item_entry.xml
Normal file
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingRight="?android:attr/scrollbarSize">
|
||||
<!--
|
||||
<ImageView
|
||||
android:id="@+id/list_item_entry_drawable"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="fill_parent"
|
||||
android:src="@android:drawable/ic_menu_preferences"
|
||||
android:paddingLeft="9dp"/>
|
||||
-->
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dip"
|
||||
android:layout_marginRight="6dip"
|
||||
android:layout_marginTop="3dip"
|
||||
android:layout_marginBottom="3dip"
|
||||
android:layout_weight="1">
|
||||
|
||||
<TextView android:id="@+id/list_item_entry_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal" />
|
||||
|
||||
<!--
|
||||
<TextView android:id="@+id/list_item_entry_summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/list_item_entry_title"
|
||||
android:layout_alignLeft="@id/list_item_entry_title"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
-->
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/progressBar2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
</ProgressBar>
|
@ -1,19 +0,0 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_trusted"
|
||||
android:orderInCategory="200"
|
||||
android:actionLayout="@layout/barswitch"
|
||||
android:showAsAction="always"
|
||||
android:title="Trusted"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_plugins"
|
||||
android:icon="@drawable/action_settings"
|
||||
android:orderInCategory="250"
|
||||
android:showAsAction="always"
|
||||
android:title="Select features"/>
|
||||
|
||||
|
||||
</menu>
|
@ -1,10 +1,21 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:kdeconnect="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_load"
|
||||
android:id="@+id/menu_refresh"
|
||||
android:icon="@drawable/navigation_refresh"
|
||||
android:orderInCategory="200"
|
||||
android:showAsAction="always"
|
||||
android:title="Reconnect"/>
|
||||
kdeconnect:showAsAction="always"
|
||||
android:title="Reconnect"
|
||||
/>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_progress"
|
||||
android:icon="@drawable/navigation_refresh"
|
||||
android:orderInCategory="200"
|
||||
android:visible="false"
|
||||
kdeconnect:showAsAction="always"
|
||||
kdeconnect:actionViewClass="android.widget.ProgressBar"
|
||||
/>
|
||||
|
||||
</menu>
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="action_settings" >Settings</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
<string name="pref_plugin_telephony">Telephony notifier</string>
|
||||
<string name="pref_plugin_telephony_desc">Send notifications for SMS and calls</string>
|
||||
<string name="pref_plugin_battery">Battery report</string>
|
||||
@ -15,6 +15,6 @@
|
||||
<string name="pref_plugin_notifications">Notification sync</string>
|
||||
<string name="pref_plugin_notifications_desc">Access your notifications from other devices</string>
|
||||
<string name="plugin_not_available">This feature is not available in your Android version</string>
|
||||
|
||||
<string name="device_list_empty">No devices</string>
|
||||
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user