first commit
This commit is contained in:
commit
0ab16f6747
11
LICENSE.txt
Normal file
11
LICENSE.txt
Normal file
@ -0,0 +1,11 @@
|
||||
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.
|
7
README.md
Normal file
7
README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Reply to notifications plugin for Cordova
|
||||
|
||||
This plugin relies heavily on the great work competed by Javier Rengel and their [NotificationListenerService plugin for Cordova](https://git.umycode.com/dave/NotificationListener-cordova)
|
||||
|
||||
This plugin was built with the explicit intent of being used in my 8x8 to Matrix bridge application and therefore will only store the notification intents from app "org.vom8x8.sipua".
|
||||
|
||||
If you find it useful please feel free to use it for your own purposes.
|
23
package.json
Normal file
23
package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"name": "cordova-plugin-reply-to-notification",
|
||||
"cordova_name": "cordova-plugin-reply-to-notification",
|
||||
"description": "Reply to notifications plugin for Cordova",
|
||||
"license": "Apache 2.0",
|
||||
"repo": "https://git.umycode.com/dave/cordova-plugin-reply-to-notification",
|
||||
"issue": "https://git.umycode.com/dave/cordova-plugin-reply-to-notification/issues",
|
||||
"keywords": [
|
||||
"notification",
|
||||
"listener",
|
||||
"android"
|
||||
],
|
||||
"platforms": [
|
||||
"android"
|
||||
],
|
||||
"engines": [
|
||||
{
|
||||
"name": "cordova",
|
||||
"version": ">=3.1.0"
|
||||
}
|
||||
]
|
||||
}
|
53
plugin.xml
Normal file
53
plugin.xml
Normal file
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<plugin
|
||||
xmlns="http://www.phonegap.com/ns/plugins/1.0"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
id="cordova-plugin-reply-to-notification"
|
||||
version="0.0.1">
|
||||
|
||||
<name>cordova-plugin-reply-to-notification</name>
|
||||
<description>Reply to notifications plugin for Cordova</description>
|
||||
<license>Apache 2.0</license>
|
||||
<keywords>notification, listener, android</keywords>
|
||||
|
||||
<repo>https://git.umycode.com/dave/cordova-plugin-reply-to-notification</repo>
|
||||
<issue>https://git.umycode.com/dave/cordova-plugin-reply-to-notification/issues</issue>
|
||||
|
||||
<engines>
|
||||
<engine name="cordova" version=">=3.1.0" />
|
||||
</engines>
|
||||
|
||||
<js-module src="www/reply-to-notification.js" name="ReplyToNotification">
|
||||
<clobbers target="replyToNotification" />
|
||||
</js-module>
|
||||
|
||||
<platform name="android">
|
||||
<config-file target="res/xml/config.xml" parent="/*">
|
||||
<feature name="ReplyToNotification">
|
||||
<param name="android-package" value="com.umycode.replyToNotification.NotificationCommands"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
<config-file target="AndroidManifest.xml" parent="/*">
|
||||
<uses-permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"/>
|
||||
</config-file>
|
||||
|
||||
<config-file target="AndroidManifest.xml" parent="/manifest/application">
|
||||
|
||||
<service android:name="com.umycode.replyToNotification.NotificationService"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" >
|
||||
<intent-filter>
|
||||
<action android:name="android.service.notification.NotificationListenerService" ></action>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
</config-file>
|
||||
|
||||
<source-file src="src/android/NotificationService.java" target-dir="src/com/umycode/replyToNotification"/>
|
||||
<source-file src="src/android/NotificationCommands.java" target-dir="src/com/umycode/replyToNotification"/>
|
||||
<source-file src="src/android/Action.java" target-dir="src/com/umycode/replyToNotification"/>
|
||||
<source-file src="src/android/RemoteInputParcel.java" target-dir="src/com/umycode/replyToNotification"/>
|
||||
|
||||
</platform>
|
||||
|
||||
</plugin>
|
117
src/android/Action.java
Normal file
117
src/android/Action.java
Normal file
@ -0,0 +1,117 @@
|
||||
package com.umycode.replyToNotification;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.umycode.replyToNotification.RemoteInputParcel;
|
||||
|
||||
public class Action implements Parcelable {
|
||||
|
||||
private final String text;
|
||||
private final String packageName;
|
||||
private final PendingIntent p;
|
||||
private final boolean isQuickReply;
|
||||
private final ArrayList<RemoteInputParcel> remoteInputs = new ArrayList<>();
|
||||
|
||||
public Action(Parcel in) {
|
||||
text = in.readString();
|
||||
packageName = in.readString();
|
||||
p = in.readParcelable(PendingIntent.class.getClassLoader());
|
||||
isQuickReply = in.readByte() != 0;
|
||||
in.readTypedList(remoteInputs, RemoteInputParcel.CREATOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(text);
|
||||
dest.writeString(packageName);
|
||||
dest.writeParcelable(p, flags);
|
||||
dest.writeByte((byte) (isQuickReply ? 1 : 0));
|
||||
dest.writeTypedList(remoteInputs);
|
||||
}
|
||||
|
||||
public Action(String text, String packageName, PendingIntent p, RemoteInput remoteInput, boolean isQuickReply) {
|
||||
this.text = text;
|
||||
this.packageName = packageName;
|
||||
this.p = p;
|
||||
this.isQuickReply = isQuickReply;
|
||||
remoteInputs.add(new RemoteInputParcel(remoteInput));
|
||||
}
|
||||
|
||||
public Action(NotificationCompat.Action action, String packageName, boolean isQuickReply) {
|
||||
this.text = action.title.toString();
|
||||
this.packageName = packageName;
|
||||
this.p = action.actionIntent;
|
||||
if(action.getRemoteInputs() != null) {
|
||||
int size = action.getRemoteInputs().length;
|
||||
for(int i = 0; i < size; i++)
|
||||
remoteInputs.add(new RemoteInputParcel(action.getRemoteInputs()[i]));
|
||||
}
|
||||
this.isQuickReply = isQuickReply;
|
||||
}
|
||||
|
||||
public void sendReply(Context context, String msg) throws PendingIntent.CanceledException {
|
||||
Intent intent = new Intent();
|
||||
Bundle bundle = new Bundle();
|
||||
ArrayList<RemoteInput> actualInputs = new ArrayList<>();
|
||||
|
||||
for (RemoteInputParcel input : remoteInputs) {
|
||||
Log.i("", "RemoteInput: " + input.getLabel());
|
||||
bundle.putCharSequence(input.getResultKey(), msg);
|
||||
RemoteInput.Builder builder = new RemoteInput.Builder(input.getResultKey());
|
||||
builder.setLabel(input.getLabel());
|
||||
builder.setChoices(input.getChoices());
|
||||
builder.setAllowFreeFormInput(input.isAllowFreeFormInput());
|
||||
builder.addExtras(input.getExtras());
|
||||
actualInputs.add(builder.build());
|
||||
}
|
||||
|
||||
RemoteInput[] inputs = actualInputs.toArray(new RemoteInput[actualInputs.size()]);
|
||||
RemoteInput.addResultsToIntent(inputs, intent, bundle);
|
||||
p.send(context, 0, intent);
|
||||
}
|
||||
|
||||
public ArrayList<RemoteInputParcel> getRemoteInputs() {
|
||||
return remoteInputs;
|
||||
}
|
||||
|
||||
public boolean isQuickReply() {
|
||||
return isQuickReply;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public PendingIntent getQuickReplyIntent() {
|
||||
return isQuickReply ? p : null;
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
|
||||
public Action createFromParcel(Parcel in) {
|
||||
return new Action(in);
|
||||
}
|
||||
public Action[] newArray(int size) {
|
||||
return new Action[size];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
326
src/android/NotificationCommands.java
Normal file
326
src/android/NotificationCommands.java
Normal file
@ -0,0 +1,326 @@
|
||||
package com.umycode.replyToNotification;
|
||||
|
||||
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;
|
||||
import java.util.Set;
|
||||
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationManagerCompat;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
import android.app.Notification;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import com.umycode.replyToNotification.Action;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.widget.Toast;
|
||||
import android.os.Parcel;
|
||||
import java.util.Arrays;
|
||||
import android.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
|
||||
|
||||
public class NotificationCommands extends CordovaPlugin {
|
||||
|
||||
protected static LinkedList<LinkedList<Action>> intent_stacks = new LinkedList<LinkedList<Action>>();
|
||||
protected static List<String> intent_rooms = new LinkedList<String>();
|
||||
|
||||
private static final String TAG = "NotificationCommands";
|
||||
|
||||
private static final String LISTEN = "listen";
|
||||
|
||||
private static final String[] REPLY_KEYWORDS = {"reply", "android.intent.extra.text","message_key"};
|
||||
private static final CharSequence REPLY_KEYWORD = "reply";
|
||||
private static final CharSequence INPUT_KEYWORD = "input";
|
||||
|
||||
// note that webView.isPaused() is not Xwalk compatible, so tracking it poor-man style
|
||||
private boolean isPaused;
|
||||
|
||||
private static CallbackContext listener;
|
||||
private static Context current_context;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
|
||||
|
||||
if (!NotificationManagerCompat.getEnabledListenerPackages (this.cordova.getActivity().getApplicationContext()).contains(this.cordova.getActivity().getApplicationContext().getPackageName())) {
|
||||
Toast.makeText(this.cordova.getActivity().getApplicationContext(), "Please Enable Notification Access", Toast.LENGTH_LONG).show();
|
||||
//service is not enabled try to enabled by calling...
|
||||
this.cordova.getActivity().getApplicationContext().startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS").addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
|
||||
callbackContext.error(TAG+". Need permissions to be granted.");
|
||||
System.exit(0);
|
||||
|
||||
return false;
|
||||
} else {
|
||||
Log.i(TAG, "Received action " + action);
|
||||
current_context = this.cordova.getActivity().getApplicationContext();
|
||||
|
||||
if (LISTEN.equals(action)) {
|
||||
setListener(callbackContext);
|
||||
return true;
|
||||
|
||||
}else if (action.equals("replytonotification")) {
|
||||
|
||||
if(args.length() != 0){
|
||||
String room_name = args.getJSONObject(0).getString("room_name");
|
||||
String message = args.getJSONObject(0).getString("message");
|
||||
|
||||
// Do we have an existing intent for this
|
||||
int indexOfRoomName = intent_rooms.indexOf(room_name);
|
||||
if (indexOfRoomName > -1) {
|
||||
try{
|
||||
replyToRoom(callbackContext,indexOfRoomName, message);
|
||||
|
||||
PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT);
|
||||
result.setKeepCallback(true);
|
||||
callbackContext.sendPluginResult(result);
|
||||
} catch (Exception e){
|
||||
Log.e(TAG, "Unable to send notification "+ e);
|
||||
|
||||
PluginResult result = new PluginResult(PluginResult.Status.OK, "Unable to send notification "+ e);
|
||||
result.setKeepCallback(true);
|
||||
callbackContext.sendPluginResult(result);
|
||||
}
|
||||
}else{
|
||||
Log.e(TAG, "Unable to send notification: Room has not been queued yet");
|
||||
|
||||
PluginResult result = new PluginResult(PluginResult.Status.OK, "Unable to send notification: Room has not been queued yet");
|
||||
result.setKeepCallback(true);
|
||||
callbackContext.sendPluginResult(result);
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
// Store the notification for later
|
||||
Action reply_result = getQuickReplyAction(n.getNotification(),json.getString("package"));
|
||||
|
||||
if(reply_result!=null && json.getString("package").equals("org.vom8x8.sipua") ){
|
||||
|
||||
// Store the intent
|
||||
if (json.has("conversationTitle")) {
|
||||
int indexOfRoomName = intent_rooms.indexOf(json.getString("conversationTitle"));
|
||||
if (indexOfRoomName > -1) {
|
||||
addIntentToList(indexOfRoomName,reply_result);
|
||||
}else{
|
||||
intent_rooms.add(json.getString("conversationTitle"));
|
||||
addIntentToList(-1,reply_result);
|
||||
}
|
||||
}else{
|
||||
int indexOfRoomName = intent_rooms.indexOf(json.getString("title"));
|
||||
if (indexOfRoomName > -1) {
|
||||
addIntentToList(indexOfRoomName,reply_result);
|
||||
}else{
|
||||
intent_rooms.add(json.getString("title"));
|
||||
addIntentToList(-1,reply_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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("package", n.getPackageName());
|
||||
|
||||
Set<String> keys = extras.keySet();
|
||||
/* Iterate over all keys of the bundle to give back all the information available */
|
||||
for (String key : keys) {
|
||||
try {
|
||||
String printKey = key;
|
||||
/* If key has a prefix android., this will be removed. */
|
||||
if(printKey.indexOf("android.")==0 && printKey.length()>8){
|
||||
printKey = printKey.substring(8,key.length());
|
||||
}
|
||||
// json.put(key, bundle.get(key)); see edit below
|
||||
json.put(printKey, JSONObject.wrap(extras.get(key)));
|
||||
} catch(JSONException e) {
|
||||
Log.d(TAG,e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
|
||||
public static Action getQuickReplyAction(Notification n, String packageName) {
|
||||
NotificationCompat.Action action = null;
|
||||
if(Build.VERSION.SDK_INT >= 24)
|
||||
action = getQuickReplyAction(n);
|
||||
if(action == null)
|
||||
action = getWearReplyAction(n);
|
||||
if(action == null)
|
||||
return null;
|
||||
return new Action(action, packageName, true);
|
||||
}
|
||||
|
||||
private static NotificationCompat.Action getQuickReplyAction(Notification n) {
|
||||
for(int i = 0; i < NotificationCompat.getActionCount(n); i++) {
|
||||
NotificationCompat.Action action = NotificationCompat.getAction(n, i);
|
||||
if(action.getRemoteInputs() != null) {
|
||||
for (int x = 0; x < action.getRemoteInputs().length; x++) {
|
||||
RemoteInput remoteInput = action.getRemoteInputs()[x];
|
||||
if (isKnownReplyKey(remoteInput.getResultKey()))
|
||||
return action;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static NotificationCompat.Action getWearReplyAction(Notification n) {
|
||||
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(n);
|
||||
for (NotificationCompat.Action action : wearableExtender.getActions()) {
|
||||
if(action.getRemoteInputs() != null) {
|
||||
for (int x = 0; x < action.getRemoteInputs().length; x++) {
|
||||
RemoteInput remoteInput = action.getRemoteInputs()[x];
|
||||
if (isKnownReplyKey(remoteInput.getResultKey()))
|
||||
return action;
|
||||
else if (remoteInput.getResultKey().toLowerCase().contains(INPUT_KEYWORD))
|
||||
return action;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isKnownReplyKey(String resultKey) {
|
||||
if(TextUtils.isEmpty(resultKey))
|
||||
return false;
|
||||
|
||||
resultKey = resultKey.toLowerCase();
|
||||
for(String keyword : REPLY_KEYWORDS)
|
||||
if(resultKey.contains(keyword))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void addIntentToList(int indexOfRoomName, Action reply_result){
|
||||
if(indexOfRoomName >= 0){
|
||||
// we are adding to an existing stack
|
||||
intent_stacks.get(indexOfRoomName).add(reply_result);
|
||||
|
||||
// is it too long now?
|
||||
if(intent_stacks.get(indexOfRoomName).size() > 10){
|
||||
Log.i(TAG, "Removing intent from stack ");
|
||||
intent_stacks.get(indexOfRoomName).removeFirst();
|
||||
}
|
||||
}else{
|
||||
intent_stacks.add(new LinkedList<>());
|
||||
intent_stacks.get(intent_stacks.size()-1).add(reply_result);
|
||||
|
||||
// is it too long now?
|
||||
if(intent_stacks.get(intent_stacks.size()-1).size() > 10){
|
||||
Log.i(TAG, "Removing intent from stack ");
|
||||
intent_stacks.get(intent_stacks.size()-1).removeFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void replyToRoom(CallbackContext callbackContext,int indexOfRoomName, String message) throws PendingIntent.CanceledException{
|
||||
try{
|
||||
if(intent_stacks.get(indexOfRoomName).size()>0){
|
||||
Action reply_result = intent_stacks.get(indexOfRoomName).get(0);
|
||||
reply_result.sendReply(current_context, message);
|
||||
}else{
|
||||
PluginResult result = new PluginResult(PluginResult.Status.OK, "Unable to send notification. No stored intents.");
|
||||
result.setKeepCallback(true);
|
||||
callbackContext.sendPluginResult(result);
|
||||
}
|
||||
} catch (Exception e){
|
||||
Log.e(TAG, "Unable to send notification "+ e);
|
||||
|
||||
// Can we try the next one?
|
||||
StringWriter sw = new StringWriter();
|
||||
e.printStackTrace(new PrintWriter(sw));
|
||||
String exceptionAsString = sw.toString();
|
||||
if(exceptionAsString.contains("CanceledException")){
|
||||
Log.i(TAG, "Removing intent from stack ");
|
||||
intent_stacks.get(indexOfRoomName).removeFirst();
|
||||
replyToRoom(callbackContext,indexOfRoomName, message);
|
||||
}else{
|
||||
PluginResult result = new PluginResult(PluginResult.Status.OK, "Unable to send notification "+ e);
|
||||
result.setKeepCallback(true);
|
||||
callbackContext.sendPluginResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
src/android/NotificationService.java
Normal file
87
src/android/NotificationService.java
Normal file
@ -0,0 +1,87 @@
|
||||
package com.umycode.replyToNotification;
|
||||
|
||||
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.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<StatusBarNotification> notifications ;
|
||||
public static boolean enabled = false;
|
||||
private static Context context = null;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Log.i(TAG, "onCreate");
|
||||
enabled = true;
|
||||
|
||||
notifications = new ArrayList<StatusBarNotification>();
|
||||
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);
|
||||
}
|
||||
}
|
88
src/android/RemoteInputParcel.java
Normal file
88
src/android/RemoteInputParcel.java
Normal file
@ -0,0 +1,88 @@
|
||||
package com.umycode.replyToNotification;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
|
||||
/**
|
||||
* Created by JJ on 05/08/15.
|
||||
*/
|
||||
public class RemoteInputParcel implements Parcelable {
|
||||
|
||||
private String label;
|
||||
private String resultKey;
|
||||
private String[] choices = new String[0];
|
||||
private boolean allowFreeFormInput;
|
||||
private Bundle extras;
|
||||
|
||||
|
||||
public RemoteInputParcel(RemoteInput input) {
|
||||
label = input.getLabel().toString();
|
||||
resultKey = input.getResultKey();
|
||||
charSequenceToStringArray(input.getChoices());
|
||||
allowFreeFormInput = input.getAllowFreeFormInput();
|
||||
extras = input.getExtras();
|
||||
}
|
||||
|
||||
public RemoteInputParcel(Parcel in) {
|
||||
label = in.readString();
|
||||
resultKey = in.readString();
|
||||
choices = in.createStringArray();
|
||||
allowFreeFormInput = in.readByte() != 0;
|
||||
extras = in.readParcelable(Bundle.class.getClassLoader());
|
||||
}
|
||||
|
||||
public void charSequenceToStringArray(CharSequence[] charSequence) {
|
||||
if(charSequence != null) {
|
||||
int size = charSequence.length;
|
||||
choices = new String[charSequence.length];
|
||||
for (int i = 0; i < size; i++)
|
||||
choices[i] = charSequence[i].toString();
|
||||
}
|
||||
}
|
||||
|
||||
public String getResultKey() {
|
||||
return resultKey;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public CharSequence[] getChoices() {
|
||||
return choices;
|
||||
}
|
||||
|
||||
public boolean isAllowFreeFormInput() {
|
||||
return allowFreeFormInput;
|
||||
}
|
||||
|
||||
public Bundle getExtras() {
|
||||
return extras;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(label);
|
||||
dest.writeString(resultKey);
|
||||
dest.writeStringArray(choices);
|
||||
dest.writeByte((byte) (allowFreeFormInput ? 1 : 0));
|
||||
dest.writeParcelable(extras, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
|
||||
public RemoteInputParcel createFromParcel(Parcel in) {
|
||||
return new RemoteInputParcel(in);
|
||||
}
|
||||
public RemoteInputParcel[] newArray(int size) {
|
||||
return new RemoteInputParcel[size];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
26
www/reply-to-notification.js
Normal file
26
www/reply-to-notification.js
Normal file
@ -0,0 +1,26 @@
|
||||
// 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, 'ReplyToNotification', 'listen', []);
|
||||
},
|
||||
replytonotification: function (arg0, success, error){
|
||||
console.log("Calling replytonotification method");
|
||||
cordova.exec(success, error, 'ReplyToNotification', 'replytonotification', [arg0]);
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user