mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-28 12:47:43 +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="ALLOW_USER_CONFIGURATION" value="false" />
|
||||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
||||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
<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" />
|
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
||||||
</configuration>
|
</configuration>
|
||||||
</facet>
|
</facet>
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:theme="@style/Theme.AppCompat"
|
android:theme="@style/Theme.AppCompat"
|
||||||
android:name="org.kde.connect.MainActivity"
|
android:name="org.kde.connect.UserInterface.MainActivity"
|
||||||
android:label="KDE Connect" >
|
android:label="KDE Connect" >
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@ -36,11 +36,12 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:theme="@style/Theme.AppCompat"
|
android:theme="@style/Theme.AppCompat"
|
||||||
android:name="org.kde.connect.DeviceActivity"
|
android:name="org.kde.connect.UserInterface.DeviceActivity"
|
||||||
android:label="Device"
|
android:label="Device"
|
||||||
android:parentActivityName=".MainActivity">
|
android:parentActivityName="org.kde.connect.UserInterface.MainActivity"
|
||||||
|
>
|
||||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value=".MainActivity" />
|
android:value="org.kde.connect.UserInterface.MainActivity" />
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
@ -48,17 +49,30 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:theme="@style/Theme.AppCompat"
|
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:label="MPRIS controls"
|
||||||
android:parentActivityName=".DeviceActivity">
|
>
|
||||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value=".DeviceActivity" />
|
android:value=".DeviceActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.kde.connect.SettingsActivity"
|
android:name="org.kde.connect.UserInterface.SettingsActivity"
|
||||||
android:label="KDE Connect Settings"
|
android:label="KDE Connect Settings"
|
||||||
android:parentActivityName=".DeviceActivity">
|
>
|
||||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value=".DeviceActivity" />
|
android:value=".DeviceActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
@ -68,7 +82,7 @@
|
|||||||
android:name="org.kde.connect.BackgroundService">
|
android:name="org.kde.connect.BackgroundService">
|
||||||
</service>
|
</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">
|
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.notification.NotificationListenerService" />
|
<action android:name="android.service.notification.NotificationListenerService" />
|
||||||
|
@ -8,17 +8,23 @@ import android.content.SharedPreferences;
|
|||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.kde.connect.ComputerLinks.BaseComputerLink;
|
import org.kde.connect.ComputerLinks.BaseComputerLink;
|
||||||
import org.kde.connect.LinkProviders.BaseLinkProvider;
|
import org.kde.connect.LinkProviders.BaseLinkProvider;
|
||||||
import org.kde.connect.LinkProviders.BroadcastTcpLinkProvider;
|
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.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
@ -29,12 +35,33 @@ public class BackgroundService extends Service {
|
|||||||
|
|
||||||
private HashMap<String, Device> devices = new HashMap<String, Device>();
|
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() {
|
private void loadRememberedDevicesFromSettings() {
|
||||||
SharedPreferences preferences = getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
SharedPreferences preferences = getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||||
Set<String> trustedDevices = preferences.getAll().keySet();
|
Set<String> trustedDevices = preferences.getAll().keySet();
|
||||||
for(String deviceId : trustedDevices) {
|
for(String deviceId : trustedDevices) {
|
||||||
if (preferences.getBoolean(deviceId, false)) {
|
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) {
|
if (device != null) {
|
||||||
Log.e("BackgroundService", "addLink, known device: "+deviceId);
|
Log.e("BackgroundService", "addLink, known device: "+deviceId);
|
||||||
if (!device.hasName()) device.setName(identityPackage.getString("deviceName"));
|
|
||||||
device.addLink(link);
|
device.addLink(link);
|
||||||
} else {
|
} else {
|
||||||
Log.e("BackgroundService", "addLink,unknown device: "+deviceId);
|
Log.e("BackgroundService", "addLink,unknown device: "+deviceId);
|
||||||
String name = identityPackage.getString("deviceName");
|
String name = identityPackage.getString("deviceName");
|
||||||
device = new Device(getBaseContext(), deviceId, name, link);
|
device = new Device(getBaseContext(), deviceId, name, link);
|
||||||
devices.put(deviceId, device);
|
devices.put(deviceId, device);
|
||||||
|
device.addPairingCallback(devicePairingCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (deviceListChangedCallback != null) deviceListChangedCallback.onDeviceListChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -85,13 +114,14 @@ public class BackgroundService extends Service {
|
|||||||
Log.e("onConnectionLost","removeLink, deviceId: "+link.getDeviceId());
|
Log.e("onConnectionLost","removeLink, deviceId: "+link.getDeviceId());
|
||||||
if (d != null) {
|
if (d != null) {
|
||||||
d.removeLink(link);
|
d.removeLink(link);
|
||||||
if (!d.isReachable() && !d.isTrusted()) {
|
if (!d.isReachable() && !d.isPaired()) {
|
||||||
devices.remove(link.getDeviceId());
|
devices.remove(link.getDeviceId());
|
||||||
|
d.removePairingCallback(devicePairingCallback);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e("onConnectionLost","Removing connection to unknown device, this should not happen");
|
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
|
//This will called only once, even if we launch the service intent several times
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
@ -144,6 +183,7 @@ public class BackgroundService extends Service {
|
|||||||
|
|
||||||
Log.i("BackgroundService","Service not started yet, initializing...");
|
Log.i("BackgroundService","Service not started yet, initializing...");
|
||||||
|
|
||||||
|
initializeRsaKeys();
|
||||||
loadRememberedDevicesFromSettings();
|
loadRememberedDevicesFromSettings();
|
||||||
registerLinkProviders();
|
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
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
Log.i("BackgroundService", "Destroying");
|
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();
|
session = future.getSession();
|
||||||
if (callback != null) callback.dispatchMessage(new Message());
|
if (callback != null) callback.dispatchMessage(new Message());
|
||||||
}
|
}
|
||||||
}).run();
|
}).start();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,34 +1,70 @@
|
|||||||
package org.kde.connect;
|
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.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.kde.connect.ComputerLinks.BaseComputerLink;
|
import org.kde.connect.ComputerLinks.BaseComputerLink;
|
||||||
import org.kde.connect.Plugins.Plugin;
|
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.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
public class Device implements BaseComputerLink.PackageReceiver {
|
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 ArrayList<BaseComputerLink> links = new ArrayList<BaseComputerLink>();
|
||||||
private HashMap<String, Plugin> plugins = new HashMap<String, Plugin>();
|
private HashMap<String, Plugin> plugins = new HashMap<String, Plugin>();
|
||||||
private HashMap<String, Plugin> failedPlugins = 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;
|
SharedPreferences settings;
|
||||||
|
|
||||||
//Remembered trusted device, we need to wait for a incoming devicelink to communicate
|
//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.context = context;
|
||||||
this.deviceId = deviceId;
|
this.deviceId = deviceId;
|
||||||
this.name = settings.getString("deviceName", null);
|
this.name = settings.getString("deviceName", "unknown device");
|
||||||
this.trusted = true;
|
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();
|
reloadPluginsFromSettings();
|
||||||
}
|
}
|
||||||
@ -53,8 +97,9 @@ public class Device implements BaseComputerLink.PackageReceiver {
|
|||||||
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.deviceId = deviceId;
|
this.deviceId = deviceId;
|
||||||
setName(name);
|
this.name = name;
|
||||||
setTrusted(false);
|
this.pairStatus = PairStatus.NotPaired;
|
||||||
|
this.publicKey = null;
|
||||||
|
|
||||||
addLink(dl);
|
addLink(dl);
|
||||||
}
|
}
|
||||||
@ -65,50 +110,154 @@ public class Device implements BaseComputerLink.PackageReceiver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name != null? name : "unknown device";
|
return name != null? name : "unknown device"; //TODO: i18n
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
settings.edit().putString("deviceName",name).commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDeviceId() {
|
public String getDeviceId() {
|
||||||
return deviceId;
|
return deviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isReachable() {
|
|
||||||
return !links.isEmpty();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Pairing-related functions
|
||||||
|
//
|
||||||
|
|
||||||
|
public boolean isPaired() {
|
||||||
|
return pairStatus == PairStatus.Paired;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTrusted() {
|
public boolean isPairRequested() {
|
||||||
return trusted;
|
return pairStatus == PairStatus.Requested;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTrusted(boolean b) {
|
public void addPairingCallback(PairingCallback callback) {
|
||||||
trusted = b;
|
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);
|
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
boolean wasTrusted = preferences.getBoolean(deviceId, false);
|
|
||||||
|
|
||||||
if (trusted && !wasTrusted) {
|
|
||||||
preferences.edit().putBoolean(deviceId, true).commit();
|
|
||||||
} else if(!trusted && wasTrusted) {
|
|
||||||
preferences.edit().remove(deviceId).commit();
|
preferences.edit().remove(deviceId).commit();
|
||||||
}
|
|
||||||
|
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
|
||||||
|
np.set("pair", false);
|
||||||
|
sendPackage(np);
|
||||||
|
|
||||||
|
for (PairingCallback cb : pairingCallback) cb.unpaired();
|
||||||
|
|
||||||
reloadPluginsFromSettings();
|
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) {
|
public void addLink(BaseComputerLink link) {
|
||||||
|
|
||||||
links.add(link);
|
links.add(link);
|
||||||
@ -140,15 +289,154 @@ public class Device implements BaseComputerLink.PackageReceiver {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPackageReceived(NetworkPackage np) {
|
public void onPackageReceived(NetworkPackage 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()) {
|
for (Plugin plugin : plugins.values()) {
|
||||||
//Log.e("onPackageReceived",plugin.toString());
|
|
||||||
plugin.onPackageReceived(np);
|
plugin.onPackageReceived(np);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean sendPackage(final NetworkPackage 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>() {
|
new AsyncTask<Void,Void,Void>() {
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... voids) {
|
protected Void doInBackground(Void... voids) {
|
||||||
@ -160,12 +448,14 @@ public class Device implements BaseComputerLink.PackageReceiver {
|
|||||||
return null;
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
}.execute();
|
}.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);
|
failedPlugins.remove(name);
|
||||||
plugins.put(name, plugin);
|
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);
|
//Log.e("removePlugin","removed " + name);
|
||||||
|
|
||||||
|
for (PluginsChangedListener listener : pluginsChangedListeners) {
|
||||||
|
listener.onPluginsChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPluginEnabled(String pluginName, boolean value) {
|
public void setPluginEnabled(String pluginName, boolean value) {
|
||||||
settings.edit().putBoolean(pluginName,value).commit();
|
settings.edit().putBoolean(pluginName,value).commit();
|
||||||
if (value) addPlugin(pluginName);
|
if (value) addPlugin(pluginName);
|
||||||
else removePlugin(pluginName);
|
else removePlugin(pluginName);
|
||||||
for (PluginsChangedListener listener : pluginsChangedListeners) {
|
|
||||||
listener.onPluginsChanged(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPluginEnabled(String pluginName) {
|
public boolean isPluginEnabled(String pluginName) {
|
||||||
@ -270,7 +567,7 @@ public class Device implements BaseComputerLink.PackageReceiver {
|
|||||||
|
|
||||||
for(String pluginName : availablePlugins) {
|
for(String pluginName : availablePlugins) {
|
||||||
boolean enabled = false;
|
boolean enabled = false;
|
||||||
if (isTrusted() && isReachable()) {
|
if (isPaired() && isReachable()) {
|
||||||
enabled = isPluginEnabled(pluginName);
|
enabled = isPluginEnabled(pluginName);
|
||||||
}
|
}
|
||||||
//Log.e("reloadPluginsFromSettings",pluginName+"->"+enabled);
|
//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() {
|
public HashMap<String,Plugin> getFailedPlugins() {
|
||||||
return failedPlugins;
|
return failedPlugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PluginsChangedListener {
|
public interface PluginsChangedListener {
|
||||||
void onPluginsChanged(Device device);
|
void onPluginsChanged(Device device);
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayList<PluginsChangedListener> pluginsChangedListeners = new ArrayList<PluginsChangedListener>();
|
private ArrayList<PluginsChangedListener> pluginsChangedListeners = new ArrayList<PluginsChangedListener>();
|
||||||
|
|
||||||
public void addPluginsChangedListener(PluginsChangedListener listener) {
|
public void addPluginsChangedListener(PluginsChangedListener listener) {
|
||||||
pluginsChangedListeners.add(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;
|
package org.kde.connect;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
|
||||||
public class NetworkPackage {
|
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_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_PING = "kdeconnect.ping";
|
||||||
public final static String PACKAGE_TYPE_TELEPHONY = "kdeconnect.telephony";
|
public final static String PACKAGE_TYPE_TELEPHONY = "kdeconnect.telephony";
|
||||||
public final static String PACKAGE_TYPE_BATTERY = "kdeconnect.battery";
|
public final static String PACKAGE_TYPE_BATTERY = "kdeconnect.battery";
|
||||||
@ -25,7 +37,6 @@ public class NetworkPackage {
|
|||||||
private long mId;
|
private long mId;
|
||||||
private String mType;
|
private String mType;
|
||||||
private JSONObject mBody;
|
private JSONObject mBody;
|
||||||
private int mVersion;
|
|
||||||
|
|
||||||
private NetworkPackage() {
|
private NetworkPackage() {
|
||||||
}
|
}
|
||||||
@ -34,17 +45,12 @@ public class NetworkPackage {
|
|||||||
mId = System.currentTimeMillis();
|
mId = System.currentTimeMillis();
|
||||||
mType = type;
|
mType = type;
|
||||||
mBody = new JSONObject();
|
mBody = new JSONObject();
|
||||||
mVersion = CURRENT_PACKAGE_VERSION;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return mType;
|
return mType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getVersion() {
|
|
||||||
return mVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Most commons getters and setters defined for convenience
|
//Most commons getters and setters defined for convenience
|
||||||
public String getString(String key) { return mBody.optString(key,""); }
|
public String getString(String key) { return mBody.optString(key,""); }
|
||||||
public String getString(String key, String defaultValue) { return mBody.optString(key,defaultValue); }
|
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("id",mId);
|
||||||
jo.put("type",mType);
|
jo.put("type",mType);
|
||||||
jo.put("body",mBody);
|
jo.put("body",mBody);
|
||||||
jo.put("version",mVersion);
|
|
||||||
} catch(Exception e) {
|
} 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);
|
Log.e("NetworkPackage.serialize",json);
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@ -112,16 +118,68 @@ public class NetworkPackage {
|
|||||||
np.mId = jo.getLong("id");
|
np.mId = jo.getLong("id");
|
||||||
np.mType = jo.getString("type");
|
np.mType = jo.getString("type");
|
||||||
np.mBody = jo.getJSONObject("body");
|
np.mBody = jo.getJSONObject("body");
|
||||||
np.mVersion = jo.getInt("version");
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (np.mVersion > CURRENT_PACKAGE_VERSION) {
|
|
||||||
Log.e("NetworkPackage.unserialize","Version "+np.mVersion+" greater than supported version "+CURRENT_PACKAGE_VERSION);
|
|
||||||
}
|
|
||||||
return np;
|
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) {
|
static public NetworkPackage createIdentityPackage(Context context) {
|
||||||
|
|
||||||
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_IDENTITY);
|
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_IDENTITY);
|
||||||
@ -130,6 +188,7 @@ public class NetworkPackage {
|
|||||||
try {
|
try {
|
||||||
np.mBody.put("deviceId", deviceId);
|
np.mBody.put("deviceId", deviceId);
|
||||||
np.mBody.put("deviceName", HumanDeviceNames.getDeviceName());
|
np.mBody.put("deviceName", HumanDeviceNames.getDeviceName());
|
||||||
|
np.mBody.put("protocolVersion", NetworkPackage.ProtocolVersion);
|
||||||
} catch (JSONException e) {
|
} 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.app.AlertDialog;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -8,9 +9,10 @@ import android.content.IntentFilter;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.BatteryManager;
|
import android.os.BatteryManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
import org.kde.connect.NetworkPackage;
|
import org.kde.connect.NetworkPackage;
|
||||||
import org.kde.connect.PluginFactory;
|
import org.kde.connect.Plugins.Plugin;
|
||||||
import org.kde.kdeconnect.R;
|
import org.kde.kdeconnect.R;
|
||||||
|
|
||||||
public class BatteryPlugin extends Plugin {
|
public class BatteryPlugin extends Plugin {
|
||||||
@ -112,4 +114,8 @@ public class BatteryPlugin extends Plugin {
|
|||||||
return null;
|
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.app.AlertDialog;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.widget.Button;
|
||||||
|
|
||||||
import org.kde.connect.NetworkPackage;
|
import org.kde.connect.NetworkPackage;
|
||||||
import org.kde.connect.PluginFactory;
|
import org.kde.connect.Plugins.Plugin;
|
||||||
import org.kde.kdeconnect.R;
|
import org.kde.kdeconnect.R;
|
||||||
|
|
||||||
public class ClipboardPlugin extends Plugin {
|
public class ClipboardPlugin extends Plugin {
|
||||||
@ -117,4 +117,8 @@ public class ClipboardPlugin extends Plugin {
|
|||||||
.create();
|
.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.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@ -13,9 +13,11 @@ import android.widget.SeekBar;
|
|||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import org.kde.connect.BackgroundService;
|
||||||
import org.kde.connect.ComputerLinks.BaseComputerLink;
|
import org.kde.connect.ComputerLinks.BaseComputerLink;
|
||||||
|
import org.kde.connect.Device;
|
||||||
import org.kde.connect.LinkProviders.BaseLinkProvider;
|
import org.kde.connect.LinkProviders.BaseLinkProvider;
|
||||||
import org.kde.connect.Plugins.MprisPlugin;
|
import org.kde.connect.NetworkPackage;
|
||||||
import org.kde.kdeconnect.R;
|
import org.kde.kdeconnect.R;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
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.app.AlertDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
import org.kde.connect.NetworkPackage;
|
import org.kde.connect.NetworkPackage;
|
||||||
import org.kde.connect.PluginFactory;
|
import org.kde.connect.Plugins.Plugin;
|
||||||
import org.kde.kdeconnect.R;
|
import org.kde.kdeconnect.R;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -196,4 +200,18 @@ public class MprisPlugin extends Plugin {
|
|||||||
return null;
|
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.app.Service;
|
||||||
import android.content.Context;
|
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.AlertDialog;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -13,11 +14,12 @@ import android.provider.Settings;
|
|||||||
import android.service.notification.StatusBarNotification;
|
import android.service.notification.StatusBarNotification;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
import org.kde.connect.Helpers.AppsHelper;
|
import org.kde.connect.Helpers.AppsHelper;
|
||||||
import org.kde.connect.Helpers.ImagesHelper;
|
import org.kde.connect.Helpers.ImagesHelper;
|
||||||
import org.kde.connect.NetworkPackage;
|
import org.kde.connect.NetworkPackage;
|
||||||
import org.kde.connect.NotificationReceiver;
|
import org.kde.connect.Plugins.Plugin;
|
||||||
import org.kde.kdeconnect.R;
|
import org.kde.kdeconnect.R;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@ -248,7 +250,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).run();
|
}).start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -279,9 +281,9 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
|||||||
|
|
||||||
if (Build.VERSION.SDK_INT < 18) {
|
if (Build.VERSION.SDK_INT < 18) {
|
||||||
return new AlertDialog.Builder(baseContext)
|
return new AlertDialog.Builder(baseContext)
|
||||||
.setTitle("Notifications Plugin")
|
.setTitle("Notifications Plugin") //TODO: i18n
|
||||||
.setMessage("This plugin is not compatible with Android prior 4.3")
|
.setMessage("This plugin is not compatible with Android prior 4.3") //TODO: i18n
|
||||||
.setPositiveButton("Ok",new DialogInterface.OnClickListener() {
|
.setPositiveButton("Ok",new DialogInterface.OnClickListener() { //TODO: i18n
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
public void onClick(DialogInterface dialogInterface, int i) {
|
||||||
|
|
||||||
@ -290,18 +292,18 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
|||||||
.create();
|
.create();
|
||||||
} else {
|
} else {
|
||||||
return new AlertDialog.Builder(baseContext)
|
return new AlertDialog.Builder(baseContext)
|
||||||
.setTitle("Notifications Plugin")
|
.setTitle("Notifications Plugin") //TODO: i18n
|
||||||
.setMessage("You need to grant permission to access notifications")
|
.setMessage("You need to grant permission to access notifications") //TODO: i18n
|
||||||
.setPositiveButton("Open settings",new DialogInterface.OnClickListener() {
|
.setPositiveButton("Open settings",new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@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");
|
Intent intent=new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
|
||||||
baseContext.startActivity(intent);
|
baseContext.startActivity(intent);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setNegativeButton("Cancel",new DialogInterface.OnClickListener() {
|
.setNegativeButton("Cancel",new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
public void onClick(DialogInterface dialogInterface, int i) { //TODO: i18n
|
||||||
//Do nothing
|
//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.R;
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.drawable.Drawable;
|
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.NetworkPackage;
|
||||||
import org.kde.connect.PluginFactory;
|
import org.kde.connect.Plugins.Plugin;
|
||||||
|
|
||||||
|
|
||||||
public class PingPlugin extends Plugin {
|
public class PingPlugin extends Plugin {
|
||||||
@ -60,7 +64,7 @@ public class PingPlugin extends Plugin {
|
|||||||
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_PING)) {
|
if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_PING)) {
|
||||||
//Log.e("PingPackageReceiver", "was a ping!");
|
//Log.e("PingPackageReceiver", "was a ping!");
|
||||||
|
|
||||||
Notification noti = new Notification.Builder(context)
|
Notification noti = new NotificationCompat.Builder(context)
|
||||||
.setContentTitle(device.getName())
|
.setContentTitle(device.getName())
|
||||||
.setContentText("Ping!")
|
.setContentText("Ping!")
|
||||||
.setTicker("Ping!")
|
.setTicker("Ping!")
|
||||||
@ -82,4 +86,17 @@ public class PingPlugin extends Plugin {
|
|||||||
return null;
|
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.content.Context;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
import org.kde.connect.Device;
|
import org.kde.connect.Device;
|
||||||
import org.kde.connect.NetworkPackage;
|
import org.kde.connect.NetworkPackage;
|
||||||
@ -60,22 +61,29 @@ public abstract class Plugin {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Finish any ongoing operations, remove listeners... so
|
* Finish any ongoing operations, remove listeners... so
|
||||||
* this object could be garbage collected
|
* this object could be garbage collected.
|
||||||
*/
|
*/
|
||||||
public abstract void onDestroy();
|
public abstract void onDestroy();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If onCreate returns false, should create a dialog explaining
|
* 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);
|
public abstract boolean onPackageReceived(NetworkPackage np);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If onCreate returns false, should create a dialog explaining
|
* 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);
|
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.content.Context;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
|
||||||
import android.preference.CheckBoxPreference;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.kde.connect.Plugins.BatteryPlugin;
|
import org.kde.connect.Device;
|
||||||
import org.kde.connect.Plugins.ClipboardPlugin;
|
import org.kde.connect.Plugins.BatteryPlugin.BatteryPlugin;
|
||||||
import org.kde.connect.Plugins.MprisPlugin;
|
import org.kde.connect.Plugins.ClibpoardPlugin.ClipboardPlugin;
|
||||||
import org.kde.connect.Plugins.NotificationsPlugin;
|
import org.kde.connect.Plugins.MprisPlugin.MprisPlugin;
|
||||||
import org.kde.connect.Plugins.PingPlugin;
|
import org.kde.connect.Plugins.NotificationsPlugin.NotificationsPlugin;
|
||||||
import org.kde.connect.Plugins.Plugin;
|
import org.kde.connect.Plugins.PingPlugin.PingPlugin;
|
||||||
import org.kde.connect.Plugins.TelephonyPlugin;
|
import org.kde.connect.Plugins.TelephonyPlugin.TelephonyPlugin;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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.app.AlertDialog;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -10,10 +11,11 @@ import android.os.Bundle;
|
|||||||
import android.telephony.SmsMessage;
|
import android.telephony.SmsMessage;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
import org.kde.connect.Helpers.ContactsHelper;
|
import org.kde.connect.Helpers.ContactsHelper;
|
||||||
import org.kde.connect.NetworkPackage;
|
import org.kde.connect.NetworkPackage;
|
||||||
import org.kde.connect.PluginFactory;
|
import org.kde.connect.Plugins.Plugin;
|
||||||
import org.kde.kdeconnect.R;
|
import org.kde.kdeconnect.R;
|
||||||
|
|
||||||
public class TelephonyPlugin extends Plugin {
|
public class TelephonyPlugin extends Plugin {
|
||||||
@ -182,4 +184,8 @@ public class TelephonyPlugin extends Plugin {
|
|||||||
return null;
|
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.content.Context;
|
||||||
import android.database.DataSetObserver;
|
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"
|
tools:context=".MainActivity"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<Button android:id="@+id/button1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Select plugins"/>
|
<ListView
|
||||||
<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"/>
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="fill_parent"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:id="@+id/buttons_list"
|
||||||
android:text="Plugins failed to load (tap for more info):"
|
android:layout_weight="1"
|
||||||
android:visibility="gone"
|
/>
|
||||||
android:id="@+id/textView"/>
|
|
||||||
|
|
||||||
<ListView
|
<ListView
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="fill_parent"
|
||||||
android:id="@+id/listView1"
|
android:id="@+id/errors_list"
|
||||||
android:layout_weight="1"/>
|
android:layout_weight="1"
|
||||||
|
/>
|
||||||
|
|
||||||
</LinearLayout>
|
</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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
@ -7,12 +7,8 @@
|
|||||||
android:paddingTop="@dimen/activity_vertical_margin"
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
tools:context=".MainActivity"
|
tools:context=".MainActivity"
|
||||||
|
android:id="@+id/listView1"
|
||||||
|
android:addStatesFromChildren="true"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<ListView
|
</ListView>
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:id="@+id/listView1"
|
|
||||||
android:layout_weight="1"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
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
|
<item
|
||||||
android:id="@+id/menu_load"
|
android:id="@+id/menu_refresh"
|
||||||
android:icon="@drawable/navigation_refresh"
|
android:icon="@drawable/navigation_refresh"
|
||||||
android:orderInCategory="200"
|
android:orderInCategory="200"
|
||||||
android:showAsAction="always"
|
kdeconnect:showAsAction="always"
|
||||||
android:title="Reconnect"/>
|
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>
|
</menu>
|
@ -15,6 +15,6 @@
|
|||||||
<string name="pref_plugin_notifications">Notification sync</string>
|
<string name="pref_plugin_notifications">Notification sync</string>
|
||||||
<string name="pref_plugin_notifications_desc">Access your notifications from other devices</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="plugin_not_available">This feature is not available in your Android version</string>
|
||||||
|
<string name="device_list_empty">No devices</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user