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