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

427 lines
16 KiB
Java
Raw Normal View History

2014-11-16 23:14:06 -08:00
/*
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.Plugins.NotificationsPlugin;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Notification;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.widget.Button;
import org.kde.kdeconnect.Helpers.AppsHelper;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.DeviceActivity;
import org.kde.kdeconnect.UserInterface.SettingsActivity;
import org.kde.kdeconnect_tp.R;
public class NotificationsPlugin extends Plugin implements NotificationReceiver.NotificationListener {
@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 hasSettings() {
return true;
}
@Override
public void startPreferencesActivity(final SettingsActivity parentActivity) {
if (hasPermission()) {
Intent intent = new Intent(parentActivity, NotificationFilterActivity.class);
parentActivity.startActivity(intent);
} else {
new AlertDialog.Builder(parentActivity)
.setTitle(R.string.pref_plugin_notifications)
.setMessage(R.string.no_permissions)
.setPositiveButton(R.string.open_settings, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
parentActivity.startActivityForResult(intent, DeviceActivity.RESULT_NEEDS_RELOAD);
}
})
.setNegativeButton(R.string.cancel,new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//Do nothing
}
})
.create().show();
}
}
@Override
public boolean isEnabledByDefault() {
return true;
}
private boolean hasPermission() {
String notificationListenerList = Settings.Secure.getString(context.getContentResolver(), "enabled_notification_listeners");
return (notificationListenerList != null && notificationListenerList.contains(context.getPackageName()));
}
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+1, last);
if (nid.tag.length() == 0) nid.tag = null;
String idString = s.substring(last+1);
try {
nid.id = Integer.parseInt(idString);
} catch(Exception e) {
nid.id = 0;
}
2013-09-05 10:48:42 +02:00
//Log.e("NotificationId","unserialize: " + nid.packageName+ ", "+nid.tag+ ", "+nid.id);
return nid;
}
public String serialize() {
2013-09-05 10:48:42 +02:00
//Log.e("NotificationId","serialize: " + packageName+ ", "+tag+ ", "+id);
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);
}
}
@Override
public boolean onCreate() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
return false;
}
if (hasPermission()) {
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) {
sendNotification(notification, true);
}
} catch(Exception e) {
e.printStackTrace();
Log.e("NotificationsPlugin","Exception");
}
}
});
return true;
} else {
return false;
}
}
@Override
public void onDestroy() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
return;
}
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);
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) {
sendNotification(statusBarNotification, false);
}
public void sendNotification(StatusBarNotification statusBarNotification, boolean requestAnswer) {
Notification notification = statusBarNotification.getNotification();
AppDatabase appDatabase = new AppDatabase(context);
if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0
|| (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0
|| (notification.flags & Notification.FLAG_LOCAL_ONLY) != 0) {
//This is not a notification we want!
return;
}
appDatabase.open();
if (!appDatabase.isFilterEnabled(statusBarNotification.getPackageName())){
return;
// we dont want notification from this app
}
appDatabase.close();
NotificationId id = NotificationId.fromNotification(statusBarNotification);
String packageName = statusBarNotification.getPackageName();
String appName = AppsHelper.appNameLookup(context, packageName);
if (id.serialize().equals("com.facebook.orca::10012") && notification.tickerText == null && appName.equals("Messenger")) {
//HACK: Weird Facebook empty "Messenger" notification
return;
}
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_NOTIFICATION);
/*
//TODO: Add support for displaying app icons to desktop plasmoid and uncomment this piece of code
try {
Drawable drawableAppIcon = AppsHelper.appIconLookup(context, packageName);
Bitmap appIcon = ImagesHelper.drawableToBitmap(drawableAppIcon);
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
if (appIcon.getWidth() > 128) {
appIcon = Bitmap.createScaledBitmap(appIcon, 96, 96, true);
}
appIcon.compress(Bitmap.CompressFormat.PNG, 90, outStream);
byte[] bitmapData = outStream.toByteArray();
np.setPayload(bitmapData);
} catch(Exception e) {
e.printStackTrace();
Log.e("NotificationsPlugin","Error retrieving icon");
}
*/
np.set("id", id.serialize());
np.set("appName", appName == null? packageName : appName);
np.set("isClearable", statusBarNotification.isClearable());
np.set("ticker", getTickerText(notification));
2013-09-05 10:37:42 +02:00
np.set("time", Long.toString(statusBarNotification.getPostTime()));
if (requestAnswer) np.set("requestAnswer", true);
device.sendPackage(np);
}
/**
* Returns the ticker text of the notification.
* If device android version is KitKat or newer, the title and text of the notification is used
* instead the ticker text.
*/
private String getTickerText(Notification notification) {
final String TITLE_KEY = "android.title";
final String TEXT_KEY = "android.text";
String ticker = "";
if(notification != null) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
Bundle extras = notification.extras;
String extraTitle = extras.getString(TITLE_KEY);
String extraText = null;
Object extraTextExtra = extras.get(TEXT_KEY);
if (extraTextExtra != null) extraText = extraTextExtra.toString();
if (extraTitle != null && extraText != null && !extraText.isEmpty()) {
ticker = extraTitle + " " + extraText;
} else if (extraTitle != null) {
ticker = extraTitle;
} else if (extraText != null) {
ticker = extraText;
}
} catch(Exception e) {
Log.w("NotificationPlugin","problem parsing notification extras for " + notification.tickerText);
e.printStackTrace();
}
}
if (ticker.isEmpty()) {
ticker = (notification.tickerText != null)? notification.tickerText.toString() : "";
}
}
return ticker;
}
@Override
public boolean onPackageReceived(final NetworkPackage np) {
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_NOTIFICATION)) return false;
if (np.getBoolean("request")) {
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
2013-08-28 19:38:00 +02:00
private void sendCurrentNotifications(NotificationReceiver service) {
StatusBarNotification[] notifications = service.getActiveNotifications();
for (StatusBarNotification notification : notifications) {
sendNotification(notification, true);
}
}
2013-08-28 19:38:00 +02:00
@Override
public void onServiceStart(final NotificationReceiver service) {
try {
//If service just started, this call will throw an exception because the answer is not ready yet
sendCurrentNotifications(service);
} catch(Exception e) {
Log.e("onPackageReceived","Error when answering 'request': Service failed to start. Retrying in 100ms...");
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100);
Log.e("onPackageReceived","Error when answering 'request': Service failed to start. Retrying...");
sendCurrentNotifications(service);
} catch (Exception e) {
Log.e("onPackageReceived","Error when answering 'request': Service failed to start twice!");
e.printStackTrace();
}
}
}).start();
2013-08-28 19:38:00 +02:00
}
}
});
} else if (np.has("cancel")) {
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
@Override
public void onServiceStart(NotificationReceiver service) {
NotificationId dismissedId = NotificationId.unserialize(np.getString("cancel"));
service.cancelNotification(dismissedId.getPackageName(), dismissedId.getTag(), dismissedId.getId());
}
});
} else {
2013-09-05 10:48:42 +02:00
Log.w("NotificationsPlugin","Nothing to do");
}
return true;
}
@Override
public AlertDialog getErrorDialog(final Activity deviceActivity) {
if (Build.VERSION.SDK_INT < 18) {
return new AlertDialog.Builder(deviceActivity)
2013-09-05 01:33:54 +02:00
.setTitle(R.string.pref_plugin_notifications)
.setMessage(R.string.plugin_not_available)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
})
.create();
} else {
return new AlertDialog.Builder(deviceActivity)
2013-09-05 01:33:54 +02:00
.setTitle(R.string.pref_plugin_notifications)
.setMessage(R.string.no_permissions)
.setPositiveButton(R.string.open_settings, new DialogInterface.OnClickListener() {
@Override
2013-09-05 01:33:54 +02:00
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
deviceActivity.startActivityForResult(intent, DeviceActivity.RESULT_NEEDS_RELOAD);
}
})
2013-09-05 01:33:54 +02:00
.setNegativeButton(R.string.cancel,new DialogInterface.OnClickListener() {
@Override
2013-09-05 01:33:54 +02:00
public void onClick(DialogInterface dialogInterface, int i) {
//Do nothing
}
})
.create();
}
}
@Override
public Button getInterfaceButton(Activity activity) {
return null;
}
}