From 29f9e8ddab0392b8ceab2ccc1b845300b7d9021a Mon Sep 17 00:00:00 2001 From: Javier Rengel Date: Sun, 13 Sep 2015 12:34:59 +0100 Subject: [PATCH] First commit --- CHANGES.txt | 3 + LICENSE.txt | 13 +++ README.md | 72 +++++++++++++++++ package.json | 23 ++++++ plugin.xml | 48 +++++++++++ settings.jpg | Bin 0 -> 32969 bytes src/android/NotificationCommands.java | 110 ++++++++++++++++++++++++++ src/android/NotificationService.java | 88 +++++++++++++++++++++ www/notification-listener.js | 24 ++++++ 9 files changed, 381 insertions(+) create mode 100644 CHANGES.txt create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 package.json create mode 100644 plugin.xml create mode 100644 settings.jpg create mode 100644 src/android/NotificationCommands.java create mode 100644 src/android/NotificationService.java create mode 100644 www/notification-listener.js diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..f5db758 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,3 @@ += 0.0.1 = + +In progress diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..4e472f5 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,13 @@ +Copyright 2015 Javier Rengel (Coconauts) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..788feaa --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# NotificationListenerService plugin for Cordova + +This is an implementation of the +[NotificationListenerService in Android](https://developer.android.com/reference/android/service/notification/NotificationListenerService.html) +for Cordova. + + A service that receives calls from the system when new notifications are posted or removed, or their ranking changed. + +Note: This plugin doesn't work for IOS or Windows Phone, feel free to create a pull request if you want to add that functionality to this project. + +## How to install + + cordova plugin add https://github.com/coconauts/NotificationListener-cordova + +## Enable notification listener service + +This service requires an special permission that must be enabled from settings on Android (Settings > Notifications > Notification access) + +![](/settings.jpg) + +Note: The app requires the following permission in your Manifest file on Android, which will be added automatically: + + android.permission.BIND_NOTIFICATION_LISTENER_SERVICE + +## How to use + +On Cordova initialization, add the callback for your notification-listener. +Then everytime you get a notification in your phone that callback in JS will be triggered with the notification data. + +``` +var app = { + initialize: function() { + console.log("Initializing app"); + this.bindEvents(); + }, + bindEvents: function() { + console.log("Binding events"); + document.addEventListener('deviceready', this.onDeviceReady, false); + }, + onDeviceReady: function() { + console.log("Device ready"); + + notificationListener.listen(function(n){ + console.log("Received notification " + JSON.stringify(n) ); + }, function(e){ + console.log("Notification Error " + e); + }); + } +}; +app.initialize(); +``` + +For a full example, please see our [WatchDuino2 repository](https://github.com/coconauts/watchduino2-companion-app) + +## Sample output +``` +Received notification +{ + "title":"Chuck Norris", + "package":"com.google.android.talk", + "text":"Hello world", + "textLines":"" +} +``` + +## Notification response format + +The notification response received by Javascript is a simplified object from the +[StatusBarNotification class](https://developer.android.com/reference/android/service/notification/StatusBarNotification.html) +in Android. + +Feel free to update the [notification parser](#TODO) inside this plugin if needed. diff --git a/package.json b/package.json new file mode 100644 index 0000000..617b345 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "version": "0.0.1", + "name": "net.coconauts.notification-listener", + "cordova_name": "Notification-listener", + "description": "Notification listener Plugin for Android", + "license": "Apache 2.0", + "repo": "https://github.com/coconauts/NotificationListener-cordova", + "issue": "https://github.com/coconauts/NotificationListener-cordova/issues", + "keywords": [ + "notification", + "listener", + "android" + ], + "platforms": [ + "android" + ], + "engines": [ + { + "name": "cordova", + "version": ">=3.1.0" + } + ] +} diff --git a/plugin.xml b/plugin.xml new file mode 100644 index 0000000..77b4545 --- /dev/null +++ b/plugin.xml @@ -0,0 +1,48 @@ + + + + Notification-listener + Notification listener Plugin for Android + Apache 2.0 + notification, listener, android + + https://github.com/coconauts/NotificationListener-cordova + https://github.com/coconauts/NotificationListener-cordova/issues + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/settings.jpg b/settings.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9e8c36503eabef9105e1cd200ce980433e9da192 GIT binary patch literal 32969 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 literal 0 HcmV?d00001 diff --git a/src/android/NotificationCommands.java b/src/android/NotificationCommands.java new file mode 100644 index 0000000..06cb1ad --- /dev/null +++ b/src/android/NotificationCommands.java @@ -0,0 +1,110 @@ +package net.coconauts.notificationListener; + +import android.view.Gravity; +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaPlugin; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import android.util.Log; +import org.apache.cordova.PluginResult; +import android.service.notification.StatusBarNotification; +import android.os.Bundle; + +public class NotificationCommands extends CordovaPlugin { + + private static final String TAG = "NotificationCommands"; + + private static final String LISTEN = "listen"; + + // note that webView.isPaused() is not Xwalk compatible, so tracking it poor-man style + private boolean isPaused; + + private static CallbackContext listener; + + @Override + public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException { + + Log.i(TAG, "Received action " + action); + + if (LISTEN.equals(action)) { + setListener(callbackContext); + return true; + } else { + callbackContext.error(TAG+". " + action + " is not a supported function."); + return false; + } + } + + @Override + public void onPause(boolean multitasking) { + this.isPaused = true; + } + + @Override + public void onResume(boolean multitasking) { + this.isPaused = false; + } + + public void setListener(CallbackContext callbackContext) { + Log.i("Notification", "Attaching callback context listener " + callbackContext); + listener = callbackContext; + + PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT); + result.setKeepCallback(true); + callbackContext.sendPluginResult(result); + } + + public static void notifyListener(StatusBarNotification n){ + if (listener == null) { + Log.e(TAG, "Must define listener first. Call notificationListener.listen(success,error) first"); + return; + } + try { + + JSONObject json = parse(n); + + PluginResult result = new PluginResult(PluginResult.Status.OK, json); + + Log.i(TAG, "Sending notification to listener " + json.toString()); + result.setKeepCallback(true); + + listener.sendPluginResult(result); + } catch (Exception e){ + Log.e(TAG, "Unable to send notification "+ e); + listener.error(TAG+". Unable to send message: "+e.getMessage()); + } + } + + + private static JSONObject parse(StatusBarNotification n) throws JSONException{ + + JSONObject json = new JSONObject(); + + Bundle extras = n.getNotification().extras; + + json.put("title", getExtra(extras, "android.title")); + json.put("package", n.getPackageName()); + json.put("text", getExtra(extras,"android.text")); + json.put("textLines", getExtraLines(extras, "android.textLines")); + + return json; + } + + private static String getExtraLines(Bundle extras, String extra){ + try { + CharSequence[] lines = extras.getCharSequenceArray(extra); + return lines[lines.length-1].toString(); + } catch( Exception e){ + Log.d(TAG, "Unable to get extra lines " + extra); + return ""; + } + } + private static String getExtra(Bundle extras, String extra){ + try { + return extras.get(extra).toString(); + } catch( Exception e){ + return ""; + } + } +} diff --git a/src/android/NotificationService.java b/src/android/NotificationService.java new file mode 100644 index 0000000..647cfb2 --- /dev/null +++ b/src/android/NotificationService.java @@ -0,0 +1,88 @@ +package net.coconauts.notificationListener; + +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; +import android.support.v4.app.NotificationCompat; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +public class NotificationService extends NotificationListenerService { + //http://developer.android.com/reference/android/service/notification/NotificationListenerService.html + + private static final String TAG = NotificationService.class.getSimpleName(); + + //TODO store this in config + private static final String IGNORE_PKG = "snapdragon,com.google.android.googlequicksearchbox"; + private static int notificationId = 1; + + private static List notifications ; + public static boolean enabled = false; + private static Context context ; + + @Override + public void onCreate() { + super.onCreate(); + Log.i(TAG, "onCreate"); + enabled = true; + + notifications = new ArrayList(); + context = this; + } + @Override + public void onDestroy() { + Log.i(TAG, "onDestroy"); + enabled = false; + } + @Override + public void onNotificationPosted(StatusBarNotification sbn) { + //Do not send notifications from this app (can cause an infinite loop) + Log.d(TAG, "notification package name " + sbn.getPackageName()); + + String pk = sbn.getPackageName(); + + if (pk.equals("android") || ignorePkg(pk) || sbn.isOngoing()) Log.d(TAG, "Ignore notification from pkg " + pk); + else { + NotificationCommands.notifyListener(sbn); + addNotification(sbn); + } + } + private boolean ignorePkg(String pk){ + for(String s: IGNORE_PKG.split(",")) if (pk.contains(s)) return true; + return false; + } + + @Override + public void onNotificationRemoved(StatusBarNotification sbn) { + //debugNotification(sbn); + } + + private void addNotification(StatusBarNotification msg){ + notifications.add(msg); + } + + public static void removeAll(){ + try { + for (StatusBarNotification n : notifications) remove(n); + notifications.clear(); + } catch (Exception e){ + Log.e(TAG, "Unable to remove notifications",e); + } + } + private static void remove(StatusBarNotification n){ + String ns = Context.NOTIFICATION_SERVICE; + NotificationManager nMgr = (NotificationManager) context.getApplicationContext().getSystemService(ns); + + int id = n.getId(); + String tag = n.getTag(); + Log.i("Cancelling notification ", tag + ", " + id); + nMgr.cancel(tag, id); + } +} diff --git a/www/notification-listener.js b/www/notification-listener.js new file mode 100644 index 0000000..ac967d1 --- /dev/null +++ b/www/notification-listener.js @@ -0,0 +1,24 @@ +// (c) 2015 Javier Rengel (Coconauts) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +"use strict"; + +module.exports = { + + // value must be an ArrayBuffer + listen: function (success, failure) { + console.log("Calling cordova listen method"); + cordova.exec(success, failure, 'NotificationListener', 'listen', []); + } +};