commit 29f9e8ddab0392b8ceab2ccc1b845300b7d9021a Author: Javier Rengel Date: Sun Sep 13 12:34:59 2015 +0100 First commit 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 0000000..9e8c365 Binary files /dev/null and b/settings.jpg differ 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', []); + } +};