2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-22 01:51:47 +00:00

Implemented notifications plugin (Android 4.3 required)

Now plugins are almost self-contained (PluginFactory still needs to know about them)
Regression: Plugins settings page does not work
Now plugins that fail to load can display a message to the user explaining the problem
Clipboard plugin now compares if content has changed before sending it
This commit is contained in:
Albert Vaca 2013-08-19 19:57:29 +02:00
parent 92cbf99891
commit b22d753410
27 changed files with 1190 additions and 141 deletions

View File

@ -66,7 +66,7 @@
<excludeFolder url="file://$MODULE_DIR$/build/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android 4.2.2" jdkType="Android SDK" />
<orderEntry type="jdk" jdkName="Android 4.3 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="android-support-v4" level="project" />
<orderEntry type="library" exported="" name="mina-core-2.0.7" level="project" />

View File

@ -16,8 +16,8 @@ dependencies {
}
android {
compileSdkVersion 17
buildToolsVersion "17.0.0"
compileSdkVersion 18
buildToolsVersion "18.0.1"
defaultConfig {
minSdkVersion 7

View File

@ -68,6 +68,13 @@
android:name="org.kde.connect.BackgroundService">
</service>
<service android:name="org.kde.connect.NotificationReceiver"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
<receiver android:name="org.kde.connect.KdeConnectBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED"></action>

View File

@ -32,7 +32,7 @@ public class BackgroundService extends Service {
Set<String> trustedDevices = preferences.getStringSet("trusted", new HashSet<String>());
for(String deviceId : trustedDevices) {
Log.e("loadRememberedDevicesFromSettings",deviceId);
devices.put(deviceId,new Device(getApplicationContext(), deviceId));
devices.put(deviceId,new Device(getBaseContext(), deviceId));
}
}
@ -74,7 +74,7 @@ public class BackgroundService extends Service {
device.addLink(link);
} else {
Log.i("BackgroundService", "unknown device");
Device device = new Device(getApplicationContext(), deviceId, name, link);
Device device = new Device(getBaseContext(), deviceId, name, link);
devices.put(deviceId, device);
}
}

View File

@ -49,6 +49,6 @@ public abstract class BaseComputerLink {
}
//TO OVERRIDE
public abstract boolean sendPackage(NetworkPackage np); //Should be async
public abstract boolean sendPackage(NetworkPackage np);
}

View File

@ -20,6 +20,7 @@ public class Device implements BaseComputerLink.PackageReceiver {
private ArrayList<BaseComputerLink> links = new ArrayList<BaseComputerLink>();
private HashMap<String, Plugin> plugins = new HashMap<String, Plugin>();
private HashMap<String, Plugin> failedPlugins = new HashMap<String, Plugin>();
private Context context;
private String deviceId;
@ -184,7 +185,7 @@ public class Device implements BaseComputerLink.PackageReceiver {
return plugins.get(name);
}
public Plugin addPlugin(String name) {
private Plugin addPlugin(String name) {
Plugin existing = plugins.get(name);
if (existing != null) {
Log.e("addPlugin","plugin already present:" + name);
@ -194,12 +195,18 @@ public class Device implements BaseComputerLink.PackageReceiver {
Plugin plugin = PluginFactory.instantiatePluginForDevice(context, name, this);
if (plugin == null) {
Log.e("addPlugin","could not create plugin: "+name);
failedPlugins.put(name, plugin);
return null;
}
try {
plugin.onCreate();
boolean success = plugin.onCreate();
if (!success) {
failedPlugins.put(name, plugin);
return null;
}
} catch (Exception e) {
failedPlugins.put(name, plugin);
Log.e("addPlugin","Exception calling onCreate for "+name);
e.printStackTrace();
return null;
@ -207,14 +214,22 @@ public class Device implements BaseComputerLink.PackageReceiver {
Log.e("addPlugin",name);
failedPlugins.remove(name);
plugins.put(name, plugin);
return plugin;
}
public boolean removePlugin(String name) {
private boolean removePlugin(String name) {
Plugin plugin = plugins.remove(name);
Plugin failedPlugin = failedPlugins.remove(name);
if (plugin == null) {
return false;
if (failedPlugin == null) {
return false;
}
plugin = failedPlugin;
}
try {
@ -230,21 +245,32 @@ public class Device implements BaseComputerLink.PackageReceiver {
return true;
}
public void setPluginEnabled(String key, boolean value) {
settings.edit().putBoolean(key,value).commit();
if (value) addPlugin(key);
else removePlugin(key);
public void setPluginEnabled(String pluginName, boolean value) {
settings.edit().putBoolean(pluginName,value).commit();
if (value) addPlugin(pluginName);
else removePlugin(pluginName);
for (PluginsChangedListener listener : pluginsChangedListeners) {
listener.onPluginsChanged(this);
}
}
public boolean isPluginEnabled(String pluginName) {
boolean enabledByDefault = PluginFactory.getPluginInfo(context, pluginName).isEnabledByDefault();
boolean enabled = settings.getBoolean(pluginName, enabledByDefault);
return enabled;
}
public void reloadPluginsFromSettings() {
failedPlugins.clear();
Set<String> availablePlugins = PluginFactory.getAvailablePlugins();
for(String pluginName : availablePlugins) {
boolean enabled = false;
if (isTrusted() && isReachable()) {
boolean enabledByDefault = PluginFactory.isPluginEnabledByDefault(pluginName);
enabled = settings.getBoolean(pluginName, enabledByDefault);
enabled = isPluginEnabled(pluginName);
}
//Log.e("reloadPluginsFromSettings",pluginName+"->"+enabled);
if (enabled) {
@ -254,20 +280,27 @@ public class Device implements BaseComputerLink.PackageReceiver {
}
}
for (PluginsChangedListener listener : pluginsChangedListeners) {
listener.onPluginsChanged(this);
}
}
public void readPluginPreferences(SharedPreferences outSettings) {
SharedPreferences.Editor editor = outSettings.edit();
public HashMap<String,Plugin> getFailedPlugins() {
return failedPlugins;
}
Set<String> availablePlugins = PluginFactory.getAvailablePlugins();
for(String pluginName : availablePlugins) {
boolean enabledByDefault = PluginFactory.isPluginEnabledByDefault(pluginName);
boolean enabled = settings.getBoolean(pluginName, enabledByDefault);
editor.putBoolean(pluginName, enabled);
//Log.e("readPluginPreferences",pluginName+"->"+enabled);
}
interface PluginsChangedListener {
void onPluginsChanged(Device device);
}
editor.commit();
ArrayList<PluginsChangedListener> pluginsChangedListeners = new ArrayList<PluginsChangedListener>();
public void addPluginsChangedListener(PluginsChangedListener listener) {
pluginsChangedListeners.add(listener);
}
public void removePluginsChangedListener(PluginsChangedListener listener) {
pluginsChangedListeners.remove(listener);
}
}

View File

@ -2,21 +2,71 @@ 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) {
@ -63,6 +113,8 @@ public class DeviceActivity extends Activity {
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
setTitle(device.getName());
device.addPluginsChangedListener(pluginsChangedListener);
pluginsChangedListener.onPluginsChanged(device);
}
});
@ -83,8 +135,7 @@ public class DeviceActivity extends Activity {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(deviceId);
PingPlugin pi = (PingPlugin) device.getPlugin("plugin_ping");
if (pi != null) pi.sendPing();
device.sendPackage(new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PING));
}
});
@ -105,6 +156,19 @@ public class DeviceActivity extends Activity {
}
});
}
@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();
}
}

View File

@ -0,0 +1,61 @@
package org.kde.connect.Helpers;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.ContactsContract.PhoneLookup;
import android.util.Log;
public class AppsHelper {
public static String appNameLookup(Context context, String packageName) {
try {
PackageManager pm = context.getPackageManager();
ApplicationInfo ai = pm.getApplicationInfo( packageName, 0);
return pm.getApplicationLabel(ai).toString();
} catch (final PackageManager.NameNotFoundException e) {
e.printStackTrace();
Log.e("AppsHelper","Could not resolve name "+packageName);
return null;
}
}
public static Drawable appIconLookup(Context context, String packageName) {
try {
PackageManager pm = context.getPackageManager();
ApplicationInfo ai = pm.getApplicationInfo( packageName, 0);
Drawable drawable = pm.getApplicationIcon(ai);
return drawable;
} catch (final PackageManager.NameNotFoundException e) {
e.printStackTrace();
Log.e("AppsHelper","Could not find icon for "+packageName);
return null;
}
}
}

View File

@ -1,4 +1,4 @@
package org.kde.connect;
package org.kde.connect.Helpers;
import android.content.Context;
import android.database.Cursor;

View File

@ -0,0 +1,23 @@
package org.kde.connect.Helpers;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
public class ImagesHelper {
public static Bitmap drawableToBitmap (Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable)drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}

View File

@ -0,0 +1,95 @@
package org.kde.connect;
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
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 ImageListAdapter implements ListAdapter {
static class ImageListElement {
String text;
Drawable icon;
}
private ArrayList<ImageListElement> localList = new ArrayList<ImageListElement>();
public ImageListAdapter(ArrayList<ImageListElement> list) {
super();
localList = list;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE );
convertView = inflater.inflate(R.layout.imagelist_element, null);
}
ImageListElement data = localList.get(position);
((TextView) convertView.findViewById(R.id.txt)).setText(data.text);
((ImageView) convertView.findViewById(R.id.img)).setImageDrawable(data.icon);
return convertView;
}
@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;
}
}

View File

@ -0,0 +1,76 @@
package org.kde.connect;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import java.util.ArrayList;
public class NotificationReceiver extends NotificationListenerService {
public interface NotificationListener {
void onNotificationPosted(StatusBarNotification statusBarNotification);
void onNotificationRemoved(StatusBarNotification statusBarNotification);
}
private ArrayList<NotificationListener> listeners = new ArrayList<NotificationListener>();
public void addListener(NotificationListener listener) {
listeners.add(listener);
}
public void removeListener(NotificationListener listener) {
listeners.remove(listener);
}
@Override
public void onNotificationPosted(StatusBarNotification statusBarNotification) {
Log.e("NotificationReceiver.onNotificationPosted","listeners: " + listeners.size());
for(NotificationListener listener : listeners) {
listener.onNotificationPosted(statusBarNotification);
}
}
@Override
public void onNotificationRemoved(StatusBarNotification statusBarNotification) {
for(NotificationListener listener : listeners) {
listener.onNotificationRemoved(statusBarNotification);
}
}
//To use the service from the outer (name)space
//This will be called for each intent launch, even if the service is already started and is reused
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("NotificationReceiver", "onStartCommand");
for (InstanceCallback c : callbacks) {
c.onServiceStart(this);
}
callbacks.clear();
return Service.START_STICKY;
}
public interface InstanceCallback {
void onServiceStart(NotificationReceiver service);
}
private static ArrayList<InstanceCallback> callbacks = new ArrayList<InstanceCallback>();
public static void Start(Context c) {
RunCommand(c, null);
}
public static void RunCommand(Context c, final InstanceCallback callback) {
if (callback != null) callbacks.add(callback);
Intent serviceIntent = new Intent(c, NotificationReceiver.class);
c.startService(serviceIntent);
}
}

View File

@ -2,12 +2,15 @@ package org.kde.connect;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.preference.CheckBoxPreference;
import android.util.Log;
import org.kde.connect.Plugins.BatteryPlugin;
import org.kde.connect.Plugins.ClipboardPlugin;
import org.kde.connect.Plugins.MprisPlugin;
import org.kde.connect.Plugins.NotificationsPlugin;
import org.kde.connect.Plugins.PingPlugin;
import org.kde.connect.Plugins.Plugin;
import org.kde.connect.Plugins.TelephonyPlugin;
@ -18,33 +21,81 @@ import java.util.TreeMap;
public class PluginFactory {
public static class PluginInfo {
public PluginInfo(String pluginName, String displayName, String description, Drawable icon, boolean enabledByDefault) {
this.pluginName = pluginName;
this.displayName = displayName;
this.description = description;
this.icon = icon;
this.enabledByDefault = enabledByDefault;
}
public String getPluginName() {
return pluginName;
}
public String getDisplayName() {
return displayName;
}
public String getDescription() {
return description;
}
public Drawable getIcon() {
return icon;
}
public boolean isEnabledByDefault() {
return enabledByDefault;
}
private String pluginName;
private String displayName;
private String description;
private final Drawable icon;
private boolean enabledByDefault;
}
private static final Map<String, Class> availablePlugins = new TreeMap<String, Class>();
private static final Map<String, PluginInfo> availablePluginsInfo = new TreeMap<String, PluginInfo>();
static {
//Note that settings to enable the plugins must have tha same names as keys than here
availablePlugins.put("plugin_battery", BatteryPlugin.class);
availablePlugins.put("plugin_clipboard", ClipboardPlugin.class);
availablePlugins.put("plugin_mpris", MprisPlugin.class);
availablePlugins.put("plugin_ping", PingPlugin.class);
availablePlugins.put("plugin_telephony", TelephonyPlugin.class);
//TODO: Avoid this factory having to know every plugin
PluginFactory.registerPlugin(TelephonyPlugin.class);
PluginFactory.registerPlugin(PingPlugin.class);
PluginFactory.registerPlugin(MprisPlugin.class);
PluginFactory.registerPlugin(ClipboardPlugin.class);
PluginFactory.registerPlugin(BatteryPlugin.class);
PluginFactory.registerPlugin(NotificationsPlugin.class);
}
public static PluginInfo getPluginInfo(Context context, String pluginName) {
PluginInfo info = availablePluginsInfo.get(pluginName); //Is it cached?
if (info != null) return info;
try {
Plugin p = ((Plugin)availablePlugins.get(pluginName).newInstance());
p.setContext(context, null);
info = new PluginInfo(pluginName, p.getDisplayName(), p.getDescription(), p.getIcon(), p.isEnabledByDefault());
availablePluginsInfo.put(pluginName, info); //Cache it
return info;
} catch(Exception e) {
e.printStackTrace();
Log.e("PluginFactory","getPluginInfo exception");
return null;
}
}
public static Set<String> getAvailablePlugins() {
return availablePlugins.keySet();
}
public static boolean isPluginEnabledByDefault(String pluginName) {
if (pluginName.equals("plugin_clibpoard"))
return (Build.VERSION.SDK_INT > 10 && Build.VERSION.SDK_INT != 18);
else
return true;
}
public static Plugin instantiatePluginForDevice(Context context, String name, Device device) {
Class c = availablePlugins.get(name);
public static Plugin instantiatePluginForDevice(Context context, String pluginName, Device device) {
Class c = availablePlugins.get(pluginName);
if (c == null) {
Log.e("PluginFactory", "Plugin not found: "+name);
Log.e("PluginFactory", "Plugin not found: "+pluginName);
return null;
}
@ -54,10 +105,21 @@ public class PluginFactory {
return plugin;
} catch(Exception e) {
e.printStackTrace();
Log.e("PluginFactory", "Could not instantiate plugin: "+name);
Log.e("PluginFactory", "Could not instantiate plugin: "+pluginName);
return null;
}
}
public static void registerPlugin(Class pluginClass) {
try {
//I hate this but I need to create an instance because abstract static functions can't be declared
String pluginName = ((Plugin)pluginClass.newInstance()).getPluginName();
availablePlugins.put(pluginName, pluginClass);
} catch(Exception e) {
Log.e("PluginFactory","addPlugin exception");
e.printStackTrace();
}
}
}

View File

@ -1,13 +1,17 @@
package org.kde.connect.Plugins;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.Drawable;
import android.os.BatteryManager;
import android.util.Log;
import org.kde.connect.NetworkPackage;
import org.kde.connect.PluginFactory;
import org.kde.kdeconnect.R;
public class BatteryPlugin extends Plugin {
@ -15,6 +19,36 @@ public class BatteryPlugin extends Plugin {
private IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
/*static {
PluginFactory.registerPlugin(BatteryPlugin.class);
}*/
@Override
public String getPluginName() {
return "plugin_battery";
}
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_battery);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_battery_desc);
}
@Override
public Drawable getIcon() {
return context.getResources().getDrawable(R.drawable.icon);
}
@Override
public boolean isEnabledByDefault() {
return true;
}
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@ -73,4 +107,9 @@ public class BatteryPlugin extends Plugin {
return true;
}
@Override
public AlertDialog getErrorDialog(Context baseContext) {
return null;
}
}

View File

@ -1,28 +1,67 @@
package org.kde.connect.Plugins;
import android.app.AlertDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.Log;
import org.kde.connect.NetworkPackage;
import org.kde.connect.PluginFactory;
import org.kde.kdeconnect.R;
public class ClipboardPlugin extends Plugin {
private boolean ignore_next_clipboard_change = false;
private String currentContent;
/*static {
PluginFactory.registerPlugin(ClipboardPlugin.class);
}*/
@Override
public String getPluginName() {
return "plugin_clipboard";
}
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_clipboard);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_clipboard_desc);
}
@Override
public Drawable getIcon() {
return context.getResources().getDrawable(R.drawable.icon);
}
@Override
public boolean isEnabledByDefault() {
return true;
}
private ClipboardManager cm;
private ClipboardManager.OnPrimaryClipChangedListener listener = new ClipboardManager.OnPrimaryClipChangedListener() {
@Override
public void onPrimaryClipChanged() {
try {
if (ignore_next_clipboard_change) {
ignore_next_clipboard_change = false;
return;
}
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_CLIPBOARD);
ClipData.Item item = cm.getPrimaryClip().getItemAt(0);
np.set("content",item.coerceToText(context).toString());
device.sendPackage(np);
String content = item.coerceToText(context).toString();
if (!content.equals(currentContent)) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_CLIPBOARD);
np.set("content", content);
device.sendPackage(np);
currentContent = content;
}
} catch(Exception e) {
//Probably clipboard was not text
}
@ -33,6 +72,11 @@ public class ClipboardPlugin extends Plugin {
public boolean onCreate() {
cm = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
if (Build.VERSION.SDK_INT == 18) {
return false;
}
cm.addPrimaryClipChangedListener(listener);
return true;
@ -43,6 +87,7 @@ public class ClipboardPlugin extends Plugin {
public void onDestroy() {
cm.removePrimaryClipChangedListener(listener);
}
@Override
@ -51,10 +96,24 @@ public class ClipboardPlugin extends Plugin {
return false;
}
ignore_next_clipboard_change = true;
cm.setText(np.getString("content"));
currentContent = np.getString("content");
cm.setText(currentContent);
return true;
}
@Override
public AlertDialog getErrorDialog(Context baseContext) {
return new AlertDialog.Builder(baseContext)
.setTitle("ClipBoard Plugin")
.setMessage("This plugin is not compatible with Android 4.3")
.setPositiveButton("Ok",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
})
.create();
}
}

View File

@ -1,10 +1,15 @@
package org.kde.connect.Plugins;
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import org.kde.connect.NetworkPackage;
import org.kde.connect.PluginFactory;
import org.kde.kdeconnect.R;
import java.util.ArrayList;
@ -21,6 +26,34 @@ public class MprisPlugin extends Plugin {
private String player = "";
private boolean playing = false;
/*static {
PluginFactory.registerPlugin(MprisPlugin.class);
}*/
@Override
public String getPluginName() {
return "plugin_mpris";
}
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_mpris);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_mpris_desc);
}
@Override
public Drawable getIcon() {
return context.getResources().getDrawable(R.drawable.icon);
}
@Override
public boolean isEnabledByDefault() {
return true;
}
@Override
public boolean onCreate() {
@ -158,5 +191,9 @@ public class MprisPlugin extends Plugin {
device.sendPackage(np);
}
@Override
public AlertDialog getErrorDialog(Context baseContext) {
return null;
}
}

View File

@ -0,0 +1,285 @@
package org.kde.connect.Plugins;
import android.Manifest;
import android.app.AlertDialog;
import android.app.Notification;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.Base64;
import android.util.Log;
import org.kde.connect.Helpers.AppsHelper;
import org.kde.connect.Helpers.ImagesHelper;
import org.kde.connect.NetworkPackage;
import org.kde.connect.NotificationReceiver;
import org.kde.connect.PluginFactory;
import org.kde.kdeconnect.R;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
public class NotificationsPlugin extends Plugin implements NotificationReceiver.NotificationListener {
private NotificationId lastId;
/*static {
PluginFactory.registerPlugin(NotificationsPlugin.class);
}*/
@Override
public String getPluginName() {
return "plugin_notifications";
}
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_notifications);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_notifications_desc);
}
@Override
public Drawable getIcon() {
return context.getResources().getDrawable(R.drawable.icon);
}
@Override
public boolean isEnabledByDefault() {
return true;
}
static class NotificationId {
String packageName;
String tag;
int id;
public static NotificationId fromNotification(StatusBarNotification statusBarNotification) {
NotificationId nid = new NotificationId();
nid.packageName = statusBarNotification.getPackageName();
nid.tag = statusBarNotification.getTag();
nid.id = statusBarNotification.getId();
return nid;
}
public static NotificationId unserialize(String s) {
NotificationId nid = new NotificationId();
int first = s.indexOf(':');
int last = s.lastIndexOf(':');
nid.packageName = s.substring(0, first);
nid.tag = s.substring(first, last);
if (nid.tag.length() == 0) nid.tag = null;
nid.id = Integer.parseInt(s.substring(last));
return nid;
}
public String serialize() {
String safePackageName = (packageName == null)? "" : packageName;
String safeTag = (tag == null)? "" : tag;
return safePackageName+":"+safeTag+":"+id;
}
public String getPackageName() {
return packageName;
}
public String getTag() {
return tag;
}
public int getId() {
return id;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof NotificationId)) return false;
NotificationId other = (NotificationId)o;
return other.getTag().equals(tag) && other.getId() == id && other.getPackageName().equals(packageName);
}
}
private boolean hasPermissions;
@Override
public boolean onCreate() {
Log.e("NotificationsPlugin", "onCreate");
if (Build.VERSION.SDK_INT < 18) return false;
//Check for permissions
String notificationListenerList = Settings.Secure.getString(context.getContentResolver(), "enabled_notification_listeners");
if (notificationListenerList != null && notificationListenerList.contains(context.getPackageName())) {
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
@Override
public void onServiceStart(NotificationReceiver service) {
try {
service.addListener(NotificationsPlugin.this);
/*
StatusBarNotification[] notifications = service.getActiveNotifications();
for (StatusBarNotification notification : notifications) {
onNotificationPosted(notification);
}
*/
} catch(Exception e) {
e.printStackTrace();
Log.e("NotificationsPlugin","Exception");
}
}
});
return true;
} else {
return false;
}
}
@Override
public void onDestroy() {
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
@Override
public void onServiceStart(NotificationReceiver service) {
service.removeListener(NotificationsPlugin.this);
}
});
}
@Override
public void onNotificationRemoved(StatusBarNotification statusBarNotification) {
NotificationId id = NotificationId.fromNotification(statusBarNotification);
if (!id.equals(lastId)) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_NOTIFICATION);
np.set("id", id.serialize());
np.set("isCancel", true);
device.sendPackage(np);
}
}
@Override
public void onNotificationPosted(StatusBarNotification statusBarNotification) {
Notification notification = statusBarNotification.getNotification();
NotificationId id = NotificationId.fromNotification(statusBarNotification);
PackageManager packageManager = context.getPackageManager();
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_NOTIFICATION);
String packageName = statusBarNotification.getPackageName();
String appName = AppsHelper.appNameLookup(context, packageName);
try {
Drawable drawableAppIcon = AppsHelper.appIconLookup(context, packageName);
Bitmap appIcon = ImagesHelper.drawableToBitmap(drawableAppIcon);
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
appIcon.compress(Bitmap.CompressFormat.PNG, 90, outStream);
byte[] bitmapData = outStream.toByteArray();
byte[] serializedBitmapData = Base64.encode(bitmapData, Base64.NO_WRAP);
String stringBitmapData = new String(serializedBitmapData, Charset.defaultCharset());
//Es super gran lol, millor ho fem quan puguem enviar arxius
//np.set("base64icon", stringBitmapData);
} catch(Exception e) {
e.printStackTrace();
Log.e("NotificationsPlugin","Error retrieveing icon");
}
np.set("id", id.serialize());
np.set("appName", appName == null? packageName : appName);
np.set("isClearable", statusBarNotification.isClearable());
np.set("ticker", notification.tickerText.toString());
np.set("time", new Long(statusBarNotification.getPostTime()).toString());
device.sendPackage(np);
}
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_NOTIFICATION)) return false;
if (np.getBoolean("request")) {
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
@Override
public void onServiceStart(NotificationReceiver service) {
StatusBarNotification[] notifications = service.getActiveNotifications();
for (StatusBarNotification notification : notifications) {
onNotificationPosted(notification);
}
}
});
} else if (np.getBoolean("isCancel")) {
lastId = NotificationId.unserialize(np.getString("id"));
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
@Override
public void onServiceStart(NotificationReceiver service) {
service.cancelNotification(lastId.getPackageName(), lastId.getTag(), lastId.getId());
}
});
} else {
Log.e("NotificationsPlugin","Nothing to do");
}
return true;
}
@Override
public AlertDialog getErrorDialog(final Context baseContext) {
if (Build.VERSION.SDK_INT < 18) {
return new AlertDialog.Builder(baseContext)
.setTitle("Notifications Plugin")
.setMessage("This plugin is not compatible with Android 4.3")
.setPositiveButton("Ok",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
})
.create();
} else {
return new AlertDialog.Builder(baseContext)
.setTitle("Notifications Plugin")
.setMessage("You need to grant permission to access notifications")
.setPositiveButton("Open settings",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent=new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
baseContext.startActivity(intent);
}
})
.setNegativeButton("Cancel",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//Do nothing
}
})
.create();
}
}
}

View File

@ -1,16 +1,48 @@
package org.kde.connect.Plugins;
import android.R;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Log;
import org.kde.connect.NetworkPackage;
import org.kde.connect.PluginFactory;
public class PingPlugin extends Plugin {
/*static {
PluginFactory.registerPlugin(PingPlugin.class);
}*/
@Override
public String getPluginName() {
return "plugin_ping";
}
@Override
public String getDisplayName() {
return context.getResources().getString(org.kde.kdeconnect.R.string.pref_plugin_ping);
}
@Override
public String getDescription() {
return context.getResources().getString(org.kde.kdeconnect.R.string.pref_plugin_ping_desc);
}
@Override
public Drawable getIcon() {
return context.getResources().getDrawable(org.kde.kdeconnect.R.drawable.icon);
}
@Override
public boolean isEnabledByDefault() {
return true;
}
@Override
public boolean onCreate() {
return true;
@ -45,10 +77,9 @@ public class PingPlugin extends Plugin {
return false;
}
public void sendPing() {
Log.e("PingPlugin", "sendPing");
NetworkPackage lastPackage = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PING);
device.sendPackage(lastPackage);
@Override
public AlertDialog getErrorDialog(Context baseContext) {
return null;
}
}

View File

@ -1,6 +1,11 @@
package org.kde.connect.Plugins;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Application;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import org.kde.connect.Device;
import org.kde.connect.NetworkPackage;
@ -15,9 +20,60 @@ public abstract class Plugin {
this.context = context;
}
//Functions to override
/**
* Return the internal plugin name, that will be used as a
* unique key to distinguish it. This function can not access
* this.context nor this.device.
*/
public abstract String getPluginName();
/**
* Return the human-readable plugin name. This function can
* access this.context to provide translated text.
*/
public abstract String getDisplayName();
/**
* Return the human-readable description of this plugin. This
* function can access this.context to provide translated text.
*/
public abstract String getDescription();
/**
* Return an icon associated to this plugin. This function can
* access this.context to load the image from resources.
*/
public abstract Drawable getIcon();
/**
* Return true if this plugin should be enabled on new devices.
* This function can access this.context and perform compatibility
* checks with the Android version, but can not access this.device.
*/
public abstract boolean isEnabledByDefault();
/**
* Initialize the listeners and structures in your plugin.
* Should return true if initialization was successful.
*/
public abstract boolean onCreate();
/**
* Finish any ongoing operations, remove listeners... so
* this object could be garbage collected
*/
public abstract void onDestroy();
/**
* If onCreate returns false, should create a dialog explaining
* the problem (and how to fix it, if possible) to the user
*/
public abstract boolean onPackageReceived(NetworkPackage np);
/**
* If onCreate returns false, should create a dialog explaining
* the problem (and how to fix it, if possible) to the user
*/
public abstract AlertDialog getErrorDialog(Context baseContext);
}

View File

@ -1,19 +1,51 @@
package org.kde.connect.Plugins;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.util.Log;
import org.kde.connect.ContactsHelper;
import org.kde.connect.Helpers.ContactsHelper;
import org.kde.connect.NetworkPackage;
import org.kde.connect.PluginFactory;
import org.kde.kdeconnect.R;
public class TelephonyPlugin extends Plugin {
/*static {
PluginFactory.registerPlugin(TelephonyPlugin.class);
}*/
@Override
public String getPluginName() {
return "plugin_telephony";
}
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_telephony);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_telephony_desc);
}
@Override
public Drawable getIcon() {
return context.getResources().getDrawable(R.drawable.icon);
}
@Override
public boolean isEnabledByDefault() {
return true;
}
private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
@ -145,5 +177,9 @@ public class TelephonyPlugin extends Plugin {
return false;
}
@Override
public AlertDialog getErrorDialog(Context baseContext) {
return null;
}
}

View File

@ -0,0 +1,90 @@
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;
}
}

View File

@ -1,33 +1,25 @@
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;
public class SettingsActivity extends PreferenceActivity {
Device device = null;
SharedPreferences preferences;
private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
final boolean value = sharedPreferences.getBoolean(key,true);
BackgroundService.RunCommand(getApplicationContext(), new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
device.setPluginEnabled(key,value);
}
});
}
};
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Set;
public class SettingsActivity extends ListActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
@ -39,43 +31,56 @@ public class SettingsActivity extends PreferenceActivity {
| ActionBar.DISPLAY_SHOW_TITLE);
actionBar.setDisplayHomeAsUpEnabled(true);*/
preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
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) {
device = service.getDevice(deviceId);
//This activity displays the DefaultSharedPreferences, so let's update them from our device
device.readPluginPreferences(preferences);
addPreferencesFromResource(R.xml.settings);
final Device device = service.getDevice(deviceId);
Set<String> plugins = PluginFactory.getAvailablePlugins();
if (Build.VERSION.SDK_INT < 11 || Build.VERSION.SDK_INT == 18) {
CheckBoxPreference p = (CheckBoxPreference)findPreference("plugin_clipboard");
p.setEnabled(false);
p.setChecked(false);
p.setSelectable(false);
p.setSummary(R.string.plugin_not_available);
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);
}
preferences.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
setListAdapter(new PreferenceListAdapter(preferences));
}
});
}
@Override
public void onResume() {
super.onResume();
if (preferences != null) preferences.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
}
@Override
public void onPause() {
if (preferences != null) preferences.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener);
super.onPause();
}
}

View File

@ -13,4 +13,19 @@
<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_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Plugins failed to load (tap for more info):"
android:visibility="gone"
android:id="@+id/textView"/>
<ListView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/listView1"
android:layout_weight="1"/>
</LinearLayout>

View File

@ -0,0 +1,10 @@
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="12px"
android:paddingBottom="12px"
xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView android:layout_marginLeft="6px" android:layout_marginRight="8px" android:layout_height="wrap_content" android:maxHeight="32px" android:minHeight="32px" android:maxWidth="32px" android:minWidth="32px" android:src="@drawable/icon" android:layout_width="wrap_content" android:id="@+id/img" android:layout_gravity="center_vertical"></ImageView>
<TextView android:gravity="left" android:id="@+id/txt" android:layout_width="wrap_content" android:textSize="8pt" android:textColor="#000000" android:layout_height="wrap_content" android:layout_gravity="center_vertical" ></TextView>
</LinearLayout>

View File

@ -12,7 +12,8 @@
<string name="pref_plugin_mpris_desc">Control audio/video from your phone</string>
<string name="pref_plugin_ping">Ping</string>
<string name="pref_plugin_ping_desc">Send and receive pings</string>
<string name="pref_plugin_notifications">Notification sync</string>
<string name="pref_plugin_notifications_desc">Access your notifications from other devices</string>
<string name="plugin_not_available">This feature is not available in your Android version</string>

View File

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:key="plugin_telephony"
android:title="@string/pref_plugin_telephony"
android:summary="@string/pref_plugin_telephony_desc"
android:defaultValue="true" />
<CheckBoxPreference
android:key="plugin_battery"
android:title="@string/pref_plugin_battery"
android:summary="@string/pref_plugin_battery_desc"
android:defaultValue="true" />
<CheckBoxPreference
android:key="plugin_clipboard"
android:title="@string/pref_plugin_clipboard"
android:summary="@string/pref_plugin_clipboard_desc"
android:defaultValue="true" />
<CheckBoxPreference
android:key="plugin_mpris"
android:title="@string/pref_plugin_mpris"
android:summary="@string/pref_plugin_mpris_desc"
android:defaultValue="true" />
<CheckBoxPreference
android:key="plugin_ping"
android:title="@string/pref_plugin_ping"
android:summary="@string/pref_plugin_ping_desc"
android:defaultValue="true" />
</PreferenceScreen>