326 lines
11 KiB
Java
326 lines
11 KiB
Java
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);
|
|
}
|
|
}
|
|
}
|
|
} |