|
|
@@ -21,171 +21,352 @@
|
|
|
|
|
|
package de.appplant.cordova.plugin.localnotification;
|
|
|
|
|
|
-import java.util.Calendar;
|
|
|
-import java.util.Random;
|
|
|
-
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
+
|
|
|
+import org.apache.cordova.CallbackContext;
|
|
|
+import org.apache.cordova.CordovaInterface;
|
|
|
+import org.apache.cordova.CordovaPlugin;
|
|
|
+import org.apache.cordova.CordovaWebView;
|
|
|
+import org.apache.cordova.PluginResult;
|
|
|
+import org.json.JSONArray;
|
|
|
import org.json.JSONException;
|
|
|
import org.json.JSONObject;
|
|
|
|
|
|
-import android.annotation.SuppressLint;
|
|
|
-import android.support.v4.app.NotificationCompat;
|
|
|
-import android.support.v4.app.NotificationCompat.*;
|
|
|
+import android.app.AlarmManager;
|
|
|
import android.app.NotificationManager;
|
|
|
import android.app.PendingIntent;
|
|
|
-import android.content.BroadcastReceiver;
|
|
|
import android.content.Context;
|
|
|
import android.content.Intent;
|
|
|
-import android.graphics.Bitmap;
|
|
|
-import android.graphics.BitmapFactory;
|
|
|
-import android.net.Uri;
|
|
|
-import android.os.Build;
|
|
|
-import android.os.Bundle;
|
|
|
+import android.content.SharedPreferences;
|
|
|
+import android.content.SharedPreferences.Editor;
|
|
|
|
|
|
/**
|
|
|
- * The alarm receiver is triggered when a scheduled alarm is fired. This class
|
|
|
- * reads the information in the intent and displays this information in the
|
|
|
- * Android notification bar. The notification uses the default notification
|
|
|
- * sound and it vibrates the phone.
|
|
|
+ * This plugin utilizes the Android AlarmManager in combination with StatusBar
|
|
|
+ * notifications. When a local notification is scheduled the alarm manager takes
|
|
|
+ * care of firing the event. When the event is processed, a notification is put
|
|
|
+ * in the Android status bar.
|
|
|
*/
|
|
|
-public class Receiver extends BroadcastReceiver {
|
|
|
+public class LocalNotification extends CordovaPlugin {
|
|
|
|
|
|
- public static final String OPTIONS = "LOCAL_NOTIFICATION_OPTIONS";
|
|
|
+ protected final static String PLUGIN_NAME = "LocalNotification";
|
|
|
|
|
|
- private Context context;
|
|
|
- private Options options;
|
|
|
+ private static CordovaWebView webView = null;
|
|
|
+ private static Boolean deviceready = false;
|
|
|
+ protected static Context context = null;
|
|
|
+ protected static Boolean isInBackground = true;
|
|
|
+ private static ArrayList<String> eventQueue = new ArrayList<String>();
|
|
|
|
|
|
@Override
|
|
|
- public void onReceive (Context context, Intent intent) {
|
|
|
- Options options = null;
|
|
|
- Bundle bundle = intent.getExtras();
|
|
|
- JSONObject args;
|
|
|
+ public void initialize (CordovaInterface cordova, CordovaWebView webView) {
|
|
|
+ super.initialize(cordova, webView);
|
|
|
|
|
|
- try {
|
|
|
- args = new JSONObject(bundle.getString(OPTIONS));
|
|
|
- options = new Options(context).parse(args);
|
|
|
- } catch (JSONException e) {
|
|
|
- return;
|
|
|
+ LocalNotification.webView = super.webView;
|
|
|
+ LocalNotification.context = super.cordova.getActivity().getApplicationContext();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean execute (String action, final JSONArray args, CallbackContext callbackContext) throws JSONException {
|
|
|
+ if (action.equalsIgnoreCase("add")) {
|
|
|
+ cordova.getThreadPool().execute( new Runnable() {
|
|
|
+ public void run() {
|
|
|
+ JSONObject arguments = args.optJSONObject(0);
|
|
|
+ Options options = new Options(context).parse(arguments);
|
|
|
+
|
|
|
+ persist(options.getId(), args);
|
|
|
+ add(options, true);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
- this.context = context;
|
|
|
- this.options = options;
|
|
|
+ if (action.equalsIgnoreCase("cancel")) {
|
|
|
+ cordova.getThreadPool().execute( new Runnable() {
|
|
|
+ public void run() {
|
|
|
+ String id = args.optString(0);
|
|
|
|
|
|
- // The context may got lost if the app was not running before
|
|
|
- LocalNotification.setContext(context);
|
|
|
+ cancel(id);
|
|
|
+ unpersist(id);
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
- fireTriggerEvent();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
|
|
|
- if (options.getInterval() == 0) {
|
|
|
- LocalNotification.unpersist(options.getId());
|
|
|
- } else if (isFirstAlarmInFuture()) {
|
|
|
- return;
|
|
|
- } else {
|
|
|
- LocalNotification.add(options.moveDate(), false);
|
|
|
+ if (action.equalsIgnoreCase("cancelAll")) {
|
|
|
+ cordova.getThreadPool().execute( new Runnable() {
|
|
|
+ public void run() {
|
|
|
+ cancelAll();
|
|
|
+ unpersistAll();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (action.equalsIgnoreCase("isScheduled")) {
|
|
|
+ String id = args.optString(0);
|
|
|
+
|
|
|
+ isScheduled(id, callbackContext);
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (action.equalsIgnoreCase("getScheduledIds")) {
|
|
|
+ getScheduledIds(callbackContext);
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (action.equalsIgnoreCase("deviceready")) {
|
|
|
+ cordova.getThreadPool().execute( new Runnable() {
|
|
|
+ public void run() {
|
|
|
+ deviceready();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (action.equalsIgnoreCase("pause")) {
|
|
|
+ isInBackground = true;
|
|
|
+
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
- Builder notification = buildNotification();
|
|
|
+ if (action.equalsIgnoreCase("resume")) {
|
|
|
+ isInBackground = false;
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
|
|
|
- showNotification(notification);
|
|
|
+ // Returning false results in a "MethodNotFound" error.
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
- /*
|
|
|
- * If you set a repeating alarm at 11:00 in the morning and it
|
|
|
- * should trigger every morning at 08:00 o'clock, it will
|
|
|
- * immediately fire. E.g. Android tries to make up for the
|
|
|
- * 'forgotten' reminder for that day. Therefore we ignore the event
|
|
|
- * if Android tries to 'catch up'.
|
|
|
+ /**
|
|
|
+ * Calls all pending callbacks after the deviceready event has been fired.
|
|
|
*/
|
|
|
- private Boolean isFirstAlarmInFuture () {
|
|
|
- if (options.getInterval() > 0) {
|
|
|
- Calendar now = Calendar.getInstance();
|
|
|
- Calendar alarm = options.getCalendar();
|
|
|
-
|
|
|
- int alarmHour = alarm.get(Calendar.HOUR_OF_DAY);
|
|
|
- int alarmMin = alarm.get(Calendar.MINUTE);
|
|
|
- int currentHour = now.get(Calendar.HOUR_OF_DAY);
|
|
|
- int currentMin = now.get(Calendar.MINUTE);
|
|
|
-
|
|
|
- if (currentHour != alarmHour && currentMin != alarmMin) {
|
|
|
- return true;
|
|
|
- }
|
|
|
+ private static void deviceready () {
|
|
|
+ deviceready = true;
|
|
|
+
|
|
|
+ for (String js : eventQueue) {
|
|
|
+ webView.sendJavascript(js);
|
|
|
}
|
|
|
|
|
|
- return false;
|
|
|
+ eventQueue.clear();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Creates the notification.
|
|
|
+ * Set an alarm.
|
|
|
+ *
|
|
|
+ * @param options
|
|
|
+ * The options that can be specified per alarm.
|
|
|
+ * @param doFireEvent
|
|
|
+ * If the onadd callback shall be called.
|
|
|
*/
|
|
|
- @SuppressLint("NewApi")
|
|
|
- private Builder buildNotification () {
|
|
|
- Bitmap icon = BitmapFactory.decodeResource(context.getResources(), options.getIcon());
|
|
|
- Uri sound = options.getSound();
|
|
|
-
|
|
|
- Builder notification = new NotificationCompat.Builder(context)
|
|
|
- .setContentTitle(options.getTitle())
|
|
|
- .setContentText(options.getMessage())
|
|
|
- .setNumber(options.getBadge())
|
|
|
- .setTicker(options.getMessage())
|
|
|
- .setSmallIcon(options.getSmallIcon())
|
|
|
- .setLargeIcon(icon)
|
|
|
- .setAutoCancel(options.getAutoCancel())
|
|
|
- .setOngoing(options.getOngoing());
|
|
|
-
|
|
|
- if (sound != null) {
|
|
|
- notification.setSound(sound);
|
|
|
+ public static void add (Options options, boolean doFireEvent) {
|
|
|
+ long triggerTime = options.getDate();
|
|
|
+
|
|
|
+ Intent intent = new Intent(context, Receiver.class)
|
|
|
+ .setAction("" + options.getId())
|
|
|
+ .putExtra(Receiver.OPTIONS, options.getJSONObject().toString());
|
|
|
+
|
|
|
+ AlarmManager am = getAlarmManager();
|
|
|
+ PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
|
|
+
|
|
|
+ if (doFireEvent) {
|
|
|
+ fireEvent("add", options.getId(), options.getJSON());
|
|
|
}
|
|
|
|
|
|
- if (Build.VERSION.SDK_INT > 16) {
|
|
|
- notification.setStyle(new NotificationCompat.BigTextStyle()
|
|
|
- .bigText(options.getMessage()));
|
|
|
+ am.set(AlarmManager.RTC_WAKEUP, triggerTime, pi);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Cancel a specific notification that was previously registered.
|
|
|
+ *
|
|
|
+ * @param notificationId
|
|
|
+ * The original ID of the notification that was used when it was
|
|
|
+ * registered using add()
|
|
|
+ */
|
|
|
+ public static void cancel (String notificationId) {
|
|
|
+ /*
|
|
|
+ * Create an intent that looks similar, to the one that was registered
|
|
|
+ * using add. Making sure the notification id in the action is the same.
|
|
|
+ * Now we can search for such an intent using the 'getService' method
|
|
|
+ * and cancel it.
|
|
|
+ */
|
|
|
+ Intent intent = new Intent(context, Receiver.class)
|
|
|
+ .setAction("" + notificationId);
|
|
|
+
|
|
|
+ PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
|
|
+ AlarmManager am = getAlarmManager();
|
|
|
+ NotificationManager nc = getNotificationManager();
|
|
|
+
|
|
|
+ am.cancel(pi);
|
|
|
+
|
|
|
+ try {
|
|
|
+ nc.cancel(Integer.parseInt(notificationId));
|
|
|
+ } catch (Exception e) {}
|
|
|
+
|
|
|
+ fireEvent("cancel", notificationId, "");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Cancel all notifications that were created by this plugin.
|
|
|
+ *
|
|
|
+ * Android can only unregister a specific alarm. There is no such thing
|
|
|
+ * as cancelAll. Therefore we rely on the Shared Preferences which holds
|
|
|
+ * all our alarms to loop through these alarms and unregister them one
|
|
|
+ * by one.
|
|
|
+ */
|
|
|
+ public static void cancelAll() {
|
|
|
+ SharedPreferences settings = getSharedPreferences();
|
|
|
+ NotificationManager nc = getNotificationManager();
|
|
|
+ Map<String, ?> alarms = settings.getAll();
|
|
|
+ Set<String> alarmIds = alarms.keySet();
|
|
|
+
|
|
|
+ for (String alarmId : alarmIds) {
|
|
|
+ cancel(alarmId);
|
|
|
}
|
|
|
|
|
|
- setClickEvent(notification);
|
|
|
+ nc.cancelAll();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Checks wether a notification with an ID is scheduled.
|
|
|
+ *
|
|
|
+ * @param id
|
|
|
+ * The notification ID to be check.
|
|
|
+ * @param callbackContext
|
|
|
+ */
|
|
|
+ public static void isScheduled (String id, CallbackContext callbackContext) {
|
|
|
+ SharedPreferences settings = getSharedPreferences();
|
|
|
+ Map<String, ?> alarms = settings.getAll();
|
|
|
+ boolean isScheduled = alarms.containsKey(id);
|
|
|
+ PluginResult result = new PluginResult(PluginResult.Status.OK, isScheduled);
|
|
|
+
|
|
|
+ callbackContext.sendPluginResult(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Retrieves a list with all currently pending notifications.
|
|
|
+ *
|
|
|
+ * @param callbackContext
|
|
|
+ */
|
|
|
+ public static void getScheduledIds (CallbackContext callbackContext) {
|
|
|
+ SharedPreferences settings = getSharedPreferences();
|
|
|
+ Map<String, ?> alarms = settings.getAll();
|
|
|
+ Set<String> alarmIds = alarms.keySet();
|
|
|
+ JSONArray pendingIds = new JSONArray(alarmIds);
|
|
|
|
|
|
- return notification;
|
|
|
+ callbackContext.success(pendingIds);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Adds an onclick handler to the notification
|
|
|
+ * Persist the information of this alarm to the Android Shared Preferences.
|
|
|
+ * This will allow the application to restore the alarm upon device reboot.
|
|
|
+ * Also this is used by the cancelAll method.
|
|
|
+ *
|
|
|
+ * @param alarmId
|
|
|
+ * The Id of the notification that must be persisted.
|
|
|
+ * @param args
|
|
|
+ * The assumption is that parse has been called already.
|
|
|
*/
|
|
|
- private Builder setClickEvent (Builder notification) {
|
|
|
- Intent intent = new Intent(context, ReceiverActivity.class)
|
|
|
- .putExtra(OPTIONS, options.getJSONObject().toString())
|
|
|
- .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
|
|
+ public static void persist (String alarmId, JSONArray args) {
|
|
|
+ Editor editor = getSharedPreferences().edit();
|
|
|
|
|
|
- int requestCode = new Random().nextInt();
|
|
|
+ if (alarmId != null) {
|
|
|
+ editor.putString(alarmId, args.toString());
|
|
|
+ editor.apply();
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- PendingIntent contentIntent = PendingIntent.getActivity(context, requestCode, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
|
|
+ /**
|
|
|
+ * Remove a specific alarm from the Android shared Preferences.
|
|
|
+ *
|
|
|
+ * @param alarmId
|
|
|
+ * The Id of the notification that must be removed.
|
|
|
+ */
|
|
|
+ public static void unpersist (String alarmId) {
|
|
|
+ Editor editor = getSharedPreferences().edit();
|
|
|
|
|
|
- return notification.setContentIntent(contentIntent);
|
|
|
+ if (alarmId != null) {
|
|
|
+ editor.remove(alarmId);
|
|
|
+ editor.apply();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Shows the notification
|
|
|
+ * Clear all alarms from the Android shared Preferences.
|
|
|
*/
|
|
|
- @SuppressWarnings("deprecation")
|
|
|
- @SuppressLint("NewApi")
|
|
|
- private void showNotification (Builder notification) {
|
|
|
- NotificationManager mgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
|
- int id = 0;
|
|
|
+ public static void unpersistAll () {
|
|
|
+ Editor editor = getSharedPreferences().edit();
|
|
|
|
|
|
- try {
|
|
|
- id = Integer.parseInt(options.getId());
|
|
|
- } catch (Exception e) {}
|
|
|
+ editor.clear();
|
|
|
+ editor.apply();
|
|
|
+ }
|
|
|
|
|
|
- if (Build.VERSION.SDK_INT<16) {
|
|
|
- // build notification for HoneyComb to ICS
|
|
|
- mgr.notify(id, notification.getNotification());
|
|
|
- } else if (Build.VERSION.SDK_INT>15) {
|
|
|
- // Notification for Jellybean and above
|
|
|
- mgr.notify(id, notification.build());
|
|
|
+ /**
|
|
|
+ * Fires the given event.
|
|
|
+ *
|
|
|
+ * @param {String} event The Name of the event
|
|
|
+ * @param {String} id The ID of the notification
|
|
|
+ * @param {String} json A custom (JSON) string
|
|
|
+ */
|
|
|
+ public static void fireEvent (String event, String id, String json) {
|
|
|
+ String state = getApplicationState();
|
|
|
+ String params = "\"" + id + "\",\"" + state + "\",\\'" + JSONObject.quote(json) + "\\'.replace(/(^\"|\"$)/g, \\'\\')";
|
|
|
+ String js = "setTimeout('plugin.notification.local.on" + event + "(" + params + ")',0)";
|
|
|
+
|
|
|
+ // webview may available, but callbacks needs to be executed
|
|
|
+ // after deviceready
|
|
|
+ if (deviceready == false) {
|
|
|
+ eventQueue.add(js);
|
|
|
+ } else {
|
|
|
+ webView.sendJavascript(js);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Fires ontrigger event.
|
|
|
+ * Retrieves the application state
|
|
|
+ *
|
|
|
+ * @return {String}
|
|
|
+ * Either "background" or "foreground"
|
|
|
+ */
|
|
|
+ protected static String getApplicationState () {
|
|
|
+ return isInBackground ? "background" : "foreground";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Set the application context if not already set.
|
|
|
+ */
|
|
|
+ protected static void setContext (Context context) {
|
|
|
+ if (LocalNotification.context == null) {
|
|
|
+ LocalNotification.context = context;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The Local storage for the application.
|
|
|
+ */
|
|
|
+ protected static SharedPreferences getSharedPreferences () {
|
|
|
+ return context.getSharedPreferences(PLUGIN_NAME, Context.MODE_PRIVATE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The alarm manager for the application.
|
|
|
+ */
|
|
|
+ protected static AlarmManager getAlarmManager () {
|
|
|
+ return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The notification manager for the application.
|
|
|
*/
|
|
|
- private void fireTriggerEvent () {
|
|
|
- LocalNotification.fireEvent("trigger", options.getId(), options.getJSON());
|
|
|
+ protected static NotificationManager getNotificationManager () {
|
|
|
+ return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
|
}
|
|
|
-}
|
|
|
+}
|