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/>.
|
|
|
|
|
*/
|
|
|
|
|
|
2013-09-05 01:37:59 +02:00
|
|
|
|
package org.kde.kdeconnect.Plugins.NotificationsPlugin;
|
2013-08-19 19:57:29 +02:00
|
|
|
|
|
2013-09-03 17:58:59 +02:00
|
|
|
|
import android.app.Activity;
|
2013-08-19 19:57:29 +02:00
|
|
|
|
import android.app.AlertDialog;
|
|
|
|
|
import android.app.Notification;
|
|
|
|
|
import android.content.DialogInterface;
|
|
|
|
|
import android.content.Intent;
|
|
|
|
|
import android.os.Build;
|
2014-03-27 23:17:43 +01:00
|
|
|
|
import android.os.Bundle;
|
2013-08-19 19:57:29 +02:00
|
|
|
|
import android.provider.Settings;
|
|
|
|
|
import android.service.notification.StatusBarNotification;
|
|
|
|
|
import android.util.Log;
|
|
|
|
|
|
2013-09-05 01:37:59 +02:00
|
|
|
|
import org.kde.kdeconnect.Helpers.AppsHelper;
|
|
|
|
|
import org.kde.kdeconnect.NetworkPackage;
|
|
|
|
|
import org.kde.kdeconnect.Plugins.Plugin;
|
2014-09-20 10:52:03 +02:00
|
|
|
|
import org.kde.kdeconnect.UserInterface.DeviceActivity;
|
2015-01-10 00:31:07 -08:00
|
|
|
|
import org.kde.kdeconnect.UserInterface.SettingsActivity;
|
2013-09-05 01:35:12 +02:00
|
|
|
|
import org.kde.kdeconnect_tp.R;
|
2013-08-19 19:57:29 +02:00
|
|
|
|
|
|
|
|
|
public class NotificationsPlugin extends Plugin implements NotificationReceiver.NotificationListener {
|
2015-04-11 23:45:01 -07:00
|
|
|
|
/*
|
2015-01-31 00:16:06 -08:00
|
|
|
|
private boolean sendIcons = false;
|
2015-04-11 23:45:01 -07:00
|
|
|
|
*/
|
2013-08-19 19:57:29 +02:00
|
|
|
|
|
|
|
|
|
@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);
|
|
|
|
|
}
|
|
|
|
|
|
2014-09-16 15:45:31 +02:00
|
|
|
|
@Override
|
|
|
|
|
public boolean hasSettings() {
|
2015-01-10 00:22:53 -08:00
|
|
|
|
return true;
|
2014-09-16 15:45:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-01-10 00:31:07 -08:00
|
|
|
|
@Override
|
|
|
|
|
public void startPreferencesActivity(final SettingsActivity parentActivity) {
|
|
|
|
|
if (hasPermission()) {
|
|
|
|
|
Intent intent = new Intent(parentActivity, NotificationFilterActivity.class);
|
|
|
|
|
parentActivity.startActivity(intent);
|
|
|
|
|
} else {
|
2015-02-01 12:31:29 -08:00
|
|
|
|
getErrorDialog(parentActivity).show();
|
2015-01-10 00:31:07 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean hasPermission() {
|
|
|
|
|
String notificationListenerList = Settings.Secure.getString(context.getContentResolver(), "enabled_notification_listeners");
|
|
|
|
|
return (notificationListenerList != null && notificationListenerList.contains(context.getPackageName()));
|
|
|
|
|
}
|
2013-08-20 13:41:13 +02:00
|
|
|
|
|
2013-08-19 19:57:29 +02:00
|
|
|
|
@Override
|
|
|
|
|
public boolean onCreate() {
|
|
|
|
|
|
2014-03-15 17:31:44 +01:00
|
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2013-08-19 19:57:29 +02:00
|
|
|
|
|
2015-01-10 00:31:07 -08:00
|
|
|
|
if (hasPermission()) {
|
2013-08-19 19:57:29 +02:00
|
|
|
|
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) {
|
2014-01-10 19:29:50 +01:00
|
|
|
|
sendNotification(notification, true);
|
2013-08-19 19:57:29 +02:00
|
|
|
|
}
|
|
|
|
|
} catch(Exception e) {
|
|
|
|
|
Log.e("NotificationsPlugin","Exception");
|
2015-01-22 21:30:32 -08:00
|
|
|
|
e.printStackTrace();
|
2015-01-20 21:31:53 -08:00
|
|
|
|
}
|
2013-08-19 19:57:29 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return true;
|
|
|
|
|
} else {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onDestroy() {
|
2014-03-15 17:31:44 +01:00
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-19 19:57:29 +02:00
|
|
|
|
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onServiceStart(NotificationReceiver service) {
|
|
|
|
|
service.removeListener(NotificationsPlugin.this);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onNotificationRemoved(StatusBarNotification statusBarNotification) {
|
2015-08-10 00:28:16 -07:00
|
|
|
|
String id = statusBarNotification.getKey();
|
2013-08-20 13:41:13 +02:00
|
|
|
|
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_NOTIFICATION);
|
2015-08-10 00:28:16 -07:00
|
|
|
|
np.set("id", id);
|
2013-08-20 13:41:13 +02:00
|
|
|
|
np.set("isCancel", true);
|
|
|
|
|
device.sendPackage(np);
|
2013-08-19 19:57:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onNotificationPosted(StatusBarNotification statusBarNotification) {
|
2013-09-18 17:31:42 +02:00
|
|
|
|
sendNotification(statusBarNotification, false);
|
2013-08-22 04:38:04 +02:00
|
|
|
|
}
|
|
|
|
|
|
2013-09-18 17:31:42 +02:00
|
|
|
|
public void sendNotification(StatusBarNotification statusBarNotification, boolean requestAnswer) {
|
2013-08-19 19:57:29 +02:00
|
|
|
|
|
|
|
|
|
Notification notification = statusBarNotification.getNotification();
|
2015-01-10 00:22:53 -08:00
|
|
|
|
AppDatabase appDatabase = new AppDatabase(context);
|
2014-01-10 18:00:26 +01:00
|
|
|
|
|
2014-01-10 19:29:50 +01:00
|
|
|
|
if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0
|
2014-12-13 21:45:41 -08:00
|
|
|
|
|| (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0
|
|
|
|
|
|| (notification.flags & Notification.FLAG_LOCAL_ONLY) != 0) {
|
|
|
|
|
//This is not a notification we want!
|
2014-01-10 18:00:26 +01:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-10 00:22:53 -08:00
|
|
|
|
appDatabase.open();
|
2015-01-31 00:16:06 -08:00
|
|
|
|
if (!appDatabase.isEnabled(statusBarNotification.getPackageName())){
|
2015-01-10 00:22:53 -08:00
|
|
|
|
return;
|
|
|
|
|
// we dont want notification from this app
|
|
|
|
|
}
|
|
|
|
|
appDatabase.close();
|
|
|
|
|
|
2015-08-10 00:28:16 -07:00
|
|
|
|
String id = statusBarNotification.getKey();
|
2013-08-19 19:57:29 +02:00
|
|
|
|
String packageName = statusBarNotification.getPackageName();
|
|
|
|
|
String appName = AppsHelper.appNameLookup(context, packageName);
|
|
|
|
|
|
2015-08-10 00:28:16 -07:00
|
|
|
|
if ("com.facebook.orca".equals(packageName) && "10012".equals(statusBarNotification.getId()) && appName.equals("Messenger") && notification.tickerText == null) {
|
2015-01-20 23:10:36 -08:00
|
|
|
|
//HACK: Hide weird Facebook empty "Messenger" notification that is actually not shown in the phone
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (packageName.equals("com.google.android.googlequicksearchbox")) {
|
|
|
|
|
//HACK: Hide Google Now notifications that keep constantly popping up (and without text because we don't know how to read them properly)
|
2015-01-20 21:31:53 -08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_NOTIFICATION);
|
|
|
|
|
|
2015-01-20 23:10:53 -08:00
|
|
|
|
if (packageName.equals("org.kde.kdeconnect_tp"))
|
|
|
|
|
{
|
|
|
|
|
//Make our own notifications silent :)
|
|
|
|
|
np.set("silent", true);
|
|
|
|
|
np.set("requestAnswer", true); //For compatibility with old desktop versions of KDE Connect that don't support "silent"
|
|
|
|
|
}
|
2015-04-11 23:45:01 -07:00
|
|
|
|
/*
|
2015-01-31 00:16:06 -08:00
|
|
|
|
if (sendIcons) {
|
|
|
|
|
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");
|
2015-01-20 21:31:53 -08:00
|
|
|
|
}
|
2013-08-19 19:57:29 +02:00
|
|
|
|
}
|
2015-04-11 23:45:01 -07:00
|
|
|
|
*/
|
2015-08-10 00:28:16 -07:00
|
|
|
|
np.set("id", id);
|
2013-08-19 19:57:29 +02:00
|
|
|
|
np.set("appName", appName == null? packageName : appName);
|
|
|
|
|
np.set("isClearable", statusBarNotification.isClearable());
|
2014-03-27 23:17:43 +01:00
|
|
|
|
np.set("ticker", getTickerText(notification));
|
2013-09-05 10:37:42 +02:00
|
|
|
|
np.set("time", Long.toString(statusBarNotification.getPostTime()));
|
2015-01-20 23:10:53 -08:00
|
|
|
|
if (requestAnswer) {
|
|
|
|
|
np.set("requestAnswer", true);
|
|
|
|
|
np.set("silent", true);
|
|
|
|
|
}
|
2013-08-19 19:57:29 +02:00
|
|
|
|
|
|
|
|
|
device.sendPackage(np);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2014-03-27 23:17:43 +01:00
|
|
|
|
/**
|
|
|
|
|
* 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) {
|
2014-06-27 14:44:40 +02:00
|
|
|
|
try {
|
|
|
|
|
Bundle extras = notification.extras;
|
|
|
|
|
String extraTitle = extras.getString(TITLE_KEY);
|
2015-01-10 00:40:36 -08:00
|
|
|
|
String extraText = null;
|
|
|
|
|
Object extraTextExtra = extras.get(TEXT_KEY);
|
|
|
|
|
if (extraTextExtra != null) extraText = extraTextExtra.toString();
|
2014-06-27 14:44:40 +02:00
|
|
|
|
|
2015-01-10 00:40:36 -08:00
|
|
|
|
if (extraTitle != null && extraText != null && !extraText.isEmpty()) {
|
2014-06-27 14:44:40 +02:00
|
|
|
|
ticker = extraTitle + " ‐ " + extraText;
|
|
|
|
|
} else if (extraTitle != null) {
|
|
|
|
|
ticker = extraTitle;
|
|
|
|
|
} else if (extraText != null) {
|
|
|
|
|
ticker = extraText;
|
|
|
|
|
}
|
|
|
|
|
} catch(Exception e) {
|
2015-01-10 00:40:36 -08:00
|
|
|
|
Log.w("NotificationPlugin","problem parsing notification extras for " + notification.tickerText);
|
2014-06-27 14:44:40 +02:00
|
|
|
|
e.printStackTrace();
|
2014-03-27 23:17:43 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2013-08-19 19:57:29 +02:00
|
|
|
|
|
2014-03-29 01:47:15 +01:00
|
|
|
|
if (ticker.isEmpty()) {
|
2014-03-27 23:17:43 +01:00
|
|
|
|
ticker = (notification.tickerText != null)? notification.tickerText.toString() : "";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ticker;
|
|
|
|
|
}
|
2013-08-19 19:57:29 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
2013-08-20 13:41:13 +02:00
|
|
|
|
public boolean onPackageReceived(final NetworkPackage np) {
|
2013-08-19 19:57:29 +02:00
|
|
|
|
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_NOTIFICATION)) return false;
|
2015-04-11 23:45:01 -07:00
|
|
|
|
/*
|
2015-01-31 00:16:06 -08:00
|
|
|
|
if (np.getBoolean("sendIcons")) {
|
|
|
|
|
sendIcons = true;
|
|
|
|
|
}
|
2015-04-11 23:45:01 -07:00
|
|
|
|
*/
|
2013-08-19 19:57:29 +02:00
|
|
|
|
if (np.getBoolean("request")) {
|
|
|
|
|
|
|
|
|
|
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
2013-08-28 19:38:00 +02:00
|
|
|
|
private void sendCurrentNotifications(NotificationReceiver service) {
|
2013-08-19 19:57:29 +02:00
|
|
|
|
StatusBarNotification[] notifications = service.getActiveNotifications();
|
|
|
|
|
for (StatusBarNotification notification : notifications) {
|
2013-09-18 17:31:42 +02:00
|
|
|
|
sendNotification(notification, true);
|
2013-08-19 19:57:29 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
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);
|
2015-01-31 00:16:06 -08:00
|
|
|
|
Log.e("onPackageReceived","Error when answering 'request': Service failed to start. Retrying...");
|
2013-08-28 19:38:00 +02:00
|
|
|
|
sendCurrentNotifications(service);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e("onPackageReceived","Error when answering 'request': Service failed to start twice!");
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-09-03 17:58:59 +02:00
|
|
|
|
}).start();
|
2013-08-28 19:38:00 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2013-08-19 19:57:29 +02:00
|
|
|
|
});
|
|
|
|
|
|
2013-08-20 13:41:13 +02:00
|
|
|
|
} else if (np.has("cancel")) {
|
2013-08-19 19:57:29 +02:00
|
|
|
|
|
|
|
|
|
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onServiceStart(NotificationReceiver service) {
|
2015-08-10 00:28:16 -07:00
|
|
|
|
String dismissedId = np.getString("cancel");
|
|
|
|
|
service.cancelNotification(dismissedId);
|
2013-08-19 19:57:29 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
2013-09-05 10:48:42 +02:00
|
|
|
|
Log.w("NotificationsPlugin","Nothing to do");
|
2013-08-19 19:57:29 +02:00
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
2014-09-20 10:52:03 +02:00
|
|
|
|
public AlertDialog getErrorDialog(final Activity deviceActivity) {
|
2013-08-19 19:57:29 +02:00
|
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT < 18) {
|
2014-09-20 10:52:03 +02:00
|
|
|
|
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() {
|
2013-08-19 23:59:13 +02:00
|
|
|
|
@Override
|
|
|
|
|
public void onClick(DialogInterface dialogInterface, int i) {
|
2013-08-19 19:57:29 +02:00
|
|
|
|
|
2013-08-19 23:59:13 +02:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.create();
|
2013-08-19 19:57:29 +02:00
|
|
|
|
} else {
|
2014-09-20 10:52:03 +02:00
|
|
|
|
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() {
|
2013-08-19 23:59:13 +02:00
|
|
|
|
@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");
|
2014-09-20 10:52:03 +02:00
|
|
|
|
deviceActivity.startActivityForResult(intent, DeviceActivity.RESULT_NEEDS_RELOAD);
|
2013-08-19 23:59:13 +02:00
|
|
|
|
}
|
|
|
|
|
})
|
2013-09-05 01:33:54 +02:00
|
|
|
|
.setNegativeButton(R.string.cancel,new DialogInterface.OnClickListener() {
|
2013-08-19 23:59:13 +02:00
|
|
|
|
@Override
|
2013-09-05 01:33:54 +02:00
|
|
|
|
public void onClick(DialogInterface dialogInterface, int i) {
|
2013-08-19 23:59:13 +02:00
|
|
|
|
//Do nothing
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.create();
|
2013-08-19 19:57:29 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|