Sebastián Katzer 8 vuotta sitten
vanhempi
commit
6e0cb62698

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "cordova-plugin-local-notifications",
-  "version": "0.0.0",
+  "version": "0.0.9",
   "description": "Schedules and queries for local notifications",
   "cordova": {
     "id": "cordova-plugin-local-notifications",

+ 46 - 52
plugin.xml

@@ -24,7 +24,7 @@
 <plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
         xmlns:android="http://schemas.android.com/apk/res/android"
         id="cordova-plugin-local-notifications"
-        version="0.0.0">
+        version="0.9.0">
 
     <name>LocalNotification</name>
 
@@ -40,10 +40,11 @@
 
     <!-- cordova -->
     <engines>
-        <engine name="cordova" version=">=3.6.0" />
-        <engine name="cordova-plugman" version=">=4.3.0" />
-        <engine name="cordova-windows" version=">=4.2.0" />
-        <engine name="apple-ios" version=">=10.0.0" />
+        <engine name="cordova"         version=">=3.6.0"  />
+        <engine name="cordova-plugman" version=">=4.3.0"  />
+        <engine name="cordova-windows" version=">=4.2.0"  />
+        <engine name="apple-ios"       version=">=10.0.0" />
+        <engine name="cordova-android" version=">=6.0.0"  />
     </engines>
 
     <!-- dependencies -->
@@ -66,7 +67,6 @@
 
     <!-- ios -->
     <platform name="ios">
-
         <config-file target="config.xml" parent="/*">
             <feature name="LocalNotification">
                 <param name="ios-package" value="APPLocalNotification" onload="true" />
@@ -91,13 +91,11 @@
 
         <header-file src="src/ios/UNNotificationRequest+APPLocalNotification.h" />
         <source-file src="src/ios/UNNotificationRequest+APPLocalNotification.m" />
-
     </platform>
 
     <!-- android -->
-    <!-- <platform name="android">
-
-        <framework src="com.android.support:support-v4:+" value="gradle" />
+    <platform name="android">
+        <framework src="com.android.support:support-v4:26.+" value="gradle" />
 
         <config-file target="res/xml/config.xml" parent="/*">
             <feature name="LocalNotification">
@@ -106,7 +104,6 @@
         </config-file>
 
         <config-file target="AndroidManifest.xml" parent="/manifest/application">
-
             <receiver
                 android:name="de.appplant.cordova.plugin.localnotification.TriggerReceiver"
                 android:exported="false" />
@@ -118,33 +115,19 @@
             <activity
                 android:name="de.appplant.cordova.plugin.localnotification.ClickActivity"
                 android:launchMode="singleInstance"
-                android:theme="@android:style/Theme.NoDisplay"
-                android:exported="false" />
-
-            <receiver
-                android:name="de.appplant.cordova.plugin.notification.TriggerReceiver"
-                android:exported="false" />
-
-            <receiver
-                android:name="de.appplant.cordova.plugin.notification.ClearReceiver"
+                android:theme="@android:style/Theme.Translucent"
                 android:exported="false" />
-
+<!--
             <receiver android:name="de.appplant.cordova.plugin.localnotification.RestoreReceiver" android:exported="false" >
                 <intent-filter>
                     <action android:name="android.intent.action.BOOT_COMPLETED" />
                 </intent-filter>
             </receiver>
-
-            <activity
-                android:name="de.appplant.cordova.plugin.notification.ClickActivity"
-                android:launchMode="singleInstance"
-                android:theme="@android:style/Theme.NoDisplay"
-                android:exported="false" />
-
+ -->
         </config-file>
 
         <config-file target="AndroidManifest.xml" parent="/manifest">
-            <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+            <!-- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> -->
             <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
         </config-file>
 
@@ -163,41 +146,57 @@
         <source-file
             src="src/android/ClearReceiver.java"
             target-dir="src/de/appplant/cordova/plugin/localnotification" />
-
+<!--
         <source-file
             src="src/android/RestoreReceiver.java"
             target-dir="src/de/appplant/cordova/plugin/localnotification" />
+ -->
+        <source-file
+            src="src/android/notification/activity/AbstractClickActivity.java"
+            target-dir="src/de/appplant/cordova/plugin/notification/activity" />
 
         <source-file
-            src="src/android/notification/AbstractClearReceiver.java"
-            target-dir="src/de/appplant/cordova/plugin/notification" />
+            src="src/android/notification/activity/ClickActivity.java"
+            target-dir="src/de/appplant/cordova/plugin/notification/activity" />
 
         <source-file
-            src="src/android/notification/AbstractClickActivity.java"
-            target-dir="src/de/appplant/cordova/plugin/notification" />
+            src="src/android/notification/receiver/AbstractClearReceiver.java"
+            target-dir="src/de/appplant/cordova/plugin/notification/receiver" />
+<!--
+        <source-file
+            src="src/android/notification/receiver/AbstractRestoreReceiver.java"
+            target-dir="src/de/appplant/cordova/plugin/notification/receiver" />
+ -->
+        <source-file
+            src="src/android/notification/receiver/AbstractTriggerReceiver.java"
+            target-dir="src/de/appplant/cordova/plugin/notification/receiver" />
 
         <source-file
-            src="src/android/notification/AbstractRestoreReceiver.java"
-            target-dir="src/de/appplant/cordova/plugin/notification" />
+            src="src/android/notification/receiver/ClearReceiver.java"
+            target-dir="src/de/appplant/cordova/plugin/notification/receiver" />
 
         <source-file
-            src="src/android/notification/AbstractTriggerReceiver.java"
-            target-dir="src/de/appplant/cordova/plugin/notification" />
+            src="src/android/notification/receiver/TriggerReceiver.java"
+            target-dir="src/de/appplant/cordova/plugin/notification/receiver" />
 
         <source-file
-            src="src/android/notification/AssetUtil.java"
-            target-dir="src/de/appplant/cordova/plugin/notification" />
+            src="src/android/notification/trigger/DateTrigger.java"
+            target-dir="src/de/appplant/cordova/plugin/notification/trigger" />
 
         <source-file
-            src="src/android/notification/Builder.java"
-            target-dir="src/de/appplant/cordova/plugin/notification" />
+            src="src/android/notification/trigger/IntervalTrigger.java"
+            target-dir="src/de/appplant/cordova/plugin/notification/trigger" />
 
         <source-file
-            src="src/android/notification/ClearReceiver.java"
-            target-dir="src/de/appplant/cordova/plugin/notification" />
+            src="src/android/notification/trigger/MatchTrigger.java"
+            target-dir="src/de/appplant/cordova/plugin/notification/trigger" />
 
         <source-file
-            src="src/android/notification/ClickActivity.java"
+            src="src/android/notification/util/AssetUtil.java"
+            target-dir="src/de/appplant/cordova/plugin/notification/util" />
+
+        <source-file
+            src="src/android/notification/Builder.java"
             target-dir="src/de/appplant/cordova/plugin/notification" />
 
         <source-file
@@ -213,17 +212,12 @@
             target-dir="src/de/appplant/cordova/plugin/notification" />
 
         <source-file
-            src="src/android/notification/TriggerReceiver.java"
+            src="src/android/notification/Request.java"
             target-dir="src/de/appplant/cordova/plugin/notification" />
-
-    </platform> -->
+    </platform>
 
     <!-- windows -->
     <platform name="windows">
-        <config-file target="config.xml" parent="/*" >
-            <preference name="WindowsToastCapable" value="true" />
-        </config-file>
-
         <framework src="src/windows/lib.UW/ARM/LocalNotificationProxy.winmd" target-dir="x86" arch="x86" custom="true"/>
         <framework src="src/windows/lib.UW/ARM/LocalNotificationProxy.winmd" target-dir="x64" arch="x64" custom="true"/>
         <framework src="src/windows/lib.UW/ARM/LocalNotificationProxy.winmd" target-dir="ARM" arch="ARM" custom="true"/>

+ 2 - 3
src/android/ClearReceiver.java

@@ -29,13 +29,12 @@ import de.appplant.cordova.plugin.notification.Notification;
  * notification manually. It un-persists the cleared notification from the
  * shared preferences.
  */
-public class ClearReceiver extends de.appplant.cordova.plugin.notification.ClearReceiver {
+public class ClearReceiver extends de.appplant.cordova.plugin.notification.receiver.ClearReceiver {
 
     /**
      * Called when a local notification was cleared from outside of the app.
      *
-     * @param notification
-     *      Wrapper around the local notification
+     * @param notification Wrapper around the local notification
      */
     @Override
     public void onClear (Notification notification) {

+ 6 - 22
src/android/ClickActivity.java

@@ -21,22 +21,19 @@
 
 package de.appplant.cordova.plugin.localnotification;
 
-import de.appplant.cordova.plugin.notification.Builder;
 import de.appplant.cordova.plugin.notification.Notification;
-import de.appplant.cordova.plugin.notification.TriggerReceiver;
 
 /**
  * The receiver activity is triggered when a notification is clicked by a user.
  * The activity calls the background callback and brings the launch intent
  * up to foreground.
  */
-public class ClickActivity extends de.appplant.cordova.plugin.notification.ClickActivity {
+public class ClickActivity extends de.appplant.cordova.plugin.notification.activity.ClickActivity {
 
     /**
      * Called when local notification was clicked by the user.
      *
-     * @param notification
-     *      Wrapper around the local notification
+     * @param notification Wrapper around the local notification
      */
     @Override
     public void onClick(Notification notification) {
@@ -44,24 +41,11 @@ public class ClickActivity extends de.appplant.cordova.plugin.notification.Click
 
         super.onClick(notification);
 
-        if (notification.getOptions().isOngoing())
-            return;
+        // if (notification.getOptions().isOngoing())
+        //     return;
 
-        String event = notification.isRepeating() ? "clear" : "cancel";
-        LocalNotification.fireEvent(event, notification);
-    }
-
-    /**
-     * Build notification specified by options.
-     *
-     * @param builder
-     *      Notification builder
-     */
-    @Override
-    public Notification buildNotification (Builder builder) {
-        return builder
-                .setTriggerReceiver(TriggerReceiver.class)
-                .build();
+        // String event = notification.isRepeating() ? "clear" : "cancel";
+        // LocalNotification.fireEvent(event, notification);
     }
 
 }

+ 403 - 380
src/android/LocalNotification.java

@@ -39,6 +39,11 @@ import java.lang.Exception;
 
 import de.appplant.cordova.plugin.notification.Manager;
 import de.appplant.cordova.plugin.notification.Notification;
+import de.appplant.cordova.plugin.notification.Options;
+import de.appplant.cordova.plugin.notification.Request;
+
+// import de.appplant.cordova.plugin.notification.Manager;
+// import de.appplant.cordova.plugin.notification.Notification;
 
 /**
  * This plugin utilizes the Android AlarmManager in combination with local
@@ -129,73 +134,77 @@ public class LocalNotification extends CordovaPlugin {
     public boolean execute (final String action, final JSONArray args,
                             final CallbackContext command) throws JSONException {
 
-        Notification.setDefaultTriggerReceiver(TriggerReceiver.class);
-
         cordova.getThreadPool().execute(new Runnable() {
             public void run() {
-                if (action.equals("schedule")) {
+                if (action.equals("ready")) {
+                    deviceready();
+                } else
+                if (action.equalsIgnoreCase("check")) {
+                    check(command);
+                } else
+                if (action.equalsIgnoreCase("request")) {
+                    request(command);
+                } else
+                if (action.equalsIgnoreCase("schedule")) {
                     schedule(args);
                     command.success();
                 }
-                else if (action.equals("update")) {
-                    update(args);
-                    command.success();
-                }
-                else if (action.equals("cancel")) {
-                    cancel(args);
-                    command.success();
-                }
-                else if (action.equals("cancelAll")) {
-                    cancelAll();
-                    command.success();
-                }
-                else if (action.equals("clear")) {
-                    clear(args);
-                    command.success();
-                }
-                else if (action.equals("clearAll")) {
-                    clearAll();
-                    command.success();
-                }
-                else if (action.equals("isPresent")) {
-                    isPresent(args.optInt(0), command);
-                }
-                else if (action.equals("isScheduled")) {
-                    isScheduled(args.optInt(0), command);
-                }
-                else if (action.equals("isTriggered")) {
-                    isTriggered(args.optInt(0), command);
-                }
-                else if (action.equals("getAllIds")) {
-                    getAllIds(command);
-                }
-                else if (action.equals("getScheduledIds")) {
-                    getScheduledIds(command);
-                }
-                else if (action.equals("getTriggeredIds")) {
-                    getTriggeredIds(command);
-                }
-                else if (action.equals("getSingle")) {
-                    getSingle(args, command);
-                }
-                else if (action.equals("getSingleScheduled")) {
-                    getSingleScheduled(args, command);
-                }
-                else if (action.equals("getSingleTriggered")) {
-                    getSingleTriggered(args, command);
-                }
-                else if (action.equals("getAll")) {
-                    getAll(args, command);
-                }
-                else if (action.equals("getScheduled")) {
-                    getScheduled(args, command);
-                }
-                else if (action.equals("getTriggered")) {
-                    getTriggered(args, command);
-                }
-                else if (action.equals("deviceready")) {
-                    deviceready();
-                }
+                // if (action.equals("update")) {
+                //     update(args);
+                //     command.success();
+                // } else
+                // if (action.equals("cancel")) {
+                //     cancel(args);
+                //     command.success();
+                // } else
+                // if (action.equals("cancelAll")) {
+                //     cancelAll();
+                //     command.success();
+                // } else
+                // if (action.equals("clear")) {
+                //     clear(args);
+                //     command.success();
+                // } else
+                // if (action.equals("clearAll")) {
+                //     clearAll();
+                //     command.success();
+                // } else
+                // if (action.equals("isPresent")) {
+                //     isPresent(args.optInt(0), command);
+                // } else
+                // if (action.equals("isScheduled")) {
+                //     isScheduled(args.optInt(0), command);
+                // } else
+                // if (action.equals("isTriggered")) {
+                //     isTriggered(args.optInt(0), command);
+                // } else
+                // if (action.equals("getAllIds")) {
+                //     getAllIds(command);
+                // } else
+                // if (action.equals("getScheduledIds")) {
+                //     getScheduledIds(command);
+                // } else
+                // if (action.equals("getTriggeredIds")) {
+                //     getTriggeredIds(command);
+                // } else
+                // if (action.equals("getSingle")) {
+                //     getSingle(args, command);
+                // } else
+                // if (action.equals("getSingleScheduled")) {
+                //     getSingleScheduled(args, command);
+                // } else
+                // if (action.equals("getSingleTriggered")) {
+                //     getSingleTriggered(args, command);
+                // } else
+                // if (action.equals("getAll")) {
+                //     getAll(args, command);
+                // } else
+                // if (action.equals("getScheduled")) {
+                //     getScheduled(args, command);
+                // } else
+                // if (action.equals("getTriggered")) {
+                //     getTriggered(args, command);
+                // }
             }
         });
 
@@ -203,323 +212,341 @@ public class LocalNotification extends CordovaPlugin {
     }
 
     /**
-     * Schedule multiple local notifications.
-     *
-     * @param notifications
-     *      Properties for each local notification
-     */
-    private void schedule (JSONArray notifications) {
-        for (int i = 0; i < notifications.length(); i++) {
-            JSONObject options = notifications.optJSONObject(i);
-
-            try {
-                Notification notification =
-                        getNotificationMgr().schedule(options, TriggerReceiver.class);
-
-                fireEvent("schedule", notification);
-            }
-            catch(Exception generic) {
-                //silently ignore the exception
-                //on some samsung devices there is a known bug where a 500 alarms limit can crash the app
-                //http://developer.samsung.com/forum/board/thread/view.do?boardName=General&messageId=280286&listLines=15&startId=zzzzz%7E&searchSubId=0000000001
-
-            }
-        }
-    }
-
-    /**
-     * Update multiple local notifications.
+     * Ask if user has enabled permission for local notifications.
      *
-     * @param updates
-     *      Notification properties including their IDs
+     * @param command The callback context used when calling back into
+     *                JavaScript.
      */
-    private void update (JSONArray updates) {
-        for (int i = 0; i < updates.length(); i++) {
-            JSONObject update = updates.optJSONObject(i);
-            int id = update.optInt("id", 0);
-
-            Notification notification =
-                    getNotificationMgr().update(id, update, TriggerReceiver.class);
-
-            if (notification == null)
-                continue;
-
-            fireEvent("update", notification);
-        }
-    }
-
-    /**
-     * Cancel multiple local notifications.
-     *
-     * @param ids
-     *      Set of local notification IDs
-     */
-    private void cancel (JSONArray ids) {
-        for (int i = 0; i < ids.length(); i++) {
-            int id = ids.optInt(i, 0);
-
-            Notification notification =
-                    getNotificationMgr().cancel(id);
-
-            if (notification == null)
-                continue;
-
-            fireEvent("cancel", notification);
-        }
-    }
-
-    /**
-     * Cancel all scheduled notifications.
-     */
-    private void cancelAll() {
-        getNotificationMgr().cancelAll();
-        fireEvent("cancelall");
-    }
-
-    /**
-     * Clear multiple local notifications without canceling them.
-     *
-     * @param ids
-     *      Set of local notification IDs
-     */
-    private void clear(JSONArray ids){
-        for (int i = 0; i < ids.length(); i++) {
-            int id = ids.optInt(i, 0);
-
-            Notification notification =
-                    getNotificationMgr().clear(id);
-
-            if (notification == null)
-                continue;
-
-            fireEvent("clear", notification);
-        }
-    }
-
-    /**
-     * Clear all triggered notifications without canceling them.
-     */
-    private void clearAll() {
-        getNotificationMgr().clearAll();
-        fireEvent("clearall");
-    }
-
-    /**
-     * If a notification with an ID is present.
-     *
-     * @param id
-     *      Notification ID
-     * @param command
-     *      The callback context used when calling back into JavaScript.
-     */
-    private void isPresent (int id, CallbackContext command) {
-        boolean exist = getNotificationMgr().exist(id);
-
-        PluginResult result = new PluginResult(
-                PluginResult.Status.OK, exist);
+    private void check (CallbackContext command) {
+        boolean allowed = getNotificationMgr().hasPermission();
+        PluginResult result = new PluginResult(PluginResult.Status.OK, allowed);
 
         command.sendPluginResult(result);
     }
 
     /**
-     * If a notification with an ID is scheduled.
+     * Request permission for local notifications.
      *
-     * @param id
-     *      Notification ID
-     * @param command
-     *      The callback context used when calling back into JavaScript.
+     * @param command The callback context used when calling back into
+     *                JavaScript.
      */
-    private void isScheduled (int id, CallbackContext command) {
-        boolean exist = getNotificationMgr().exist(
-                id, Notification.Type.SCHEDULED);
-
-        PluginResult result = new PluginResult(
-                PluginResult.Status.OK, exist);
-
-        command.sendPluginResult(result);
+    private void request (CallbackContext command) {
+        check(command);
     }
 
     /**
-     * If a notification with an ID is triggered.
-     *
-     * @param id
-     *      Notification ID
-     * @param command
-     *      The callback context used when calling back into JavaScript.
-     */
-    private void isTriggered (int id, CallbackContext command) {
-        boolean exist = getNotificationMgr().exist(
-                id, Notification.Type.TRIGGERED);
-
-        PluginResult result = new PluginResult(
-                PluginResult.Status.OK, exist);
-
-        command.sendPluginResult(result);
-    }
-
-    /**
-     * Set of IDs from all existent notifications.
-     *
-     * @param command
-     *      The callback context used when calling back into JavaScript.
-     */
-    private void getAllIds (CallbackContext command) {
-        List<Integer> ids = getNotificationMgr().getIds();
-
-        command.success(new JSONArray(ids));
-    }
-
-    /**
-     * Set of IDs from all scheduled notifications.
-     *
-     * @param command
-     *      The callback context used when calling back into JavaScript.
-     */
-    private void getScheduledIds (CallbackContext command) {
-        List<Integer> ids = getNotificationMgr().getIdsByType(
-                Notification.Type.SCHEDULED);
-
-        command.success(new JSONArray(ids));
-    }
-
-    /**
-     * Set of IDs from all triggered notifications.
-     *
-     * @param command
-     *      The callback context used when calling back into JavaScript.
-     */
-    private void getTriggeredIds (CallbackContext command) {
-        List<Integer> ids = getNotificationMgr().getIdsByType(
-                Notification.Type.TRIGGERED);
-
-        command.success(new JSONArray(ids));
-    }
-
-    /**
-     * Options from local notification.
-     *
-     * @param ids
-     *      Set of local notification IDs
-     * @param command
-     *      The callback context used when calling back into JavaScript.
-     */
-    private void getSingle (JSONArray ids, CallbackContext command) {
-        getOptions(ids.optString(0), Notification.Type.ALL, command);
-    }
-
-    /**
-     * Options from scheduled notification.
-     *
-     * @param ids
-     *      Set of local notification IDs
-     * @param command
-     *      The callback context used when calling back into JavaScript.
-     */
-    private void getSingleScheduled (JSONArray ids, CallbackContext command) {
-        getOptions(ids.optString(0), Notification.Type.SCHEDULED, command);
-    }
-
-    /**
-     * Options from triggered notification.
-     *
-     * @param ids
-     *      Set of local notification IDs
-     * @param command
-     *      The callback context used when calling back into JavaScript.
-     */
-    private void getSingleTriggered (JSONArray ids, CallbackContext command) {
-        getOptions(ids.optString(0), Notification.Type.TRIGGERED, command);
-    }
-
-    /**
-     * Set of options from local notification.
-     *
-     * @param ids
-     *      Set of local notification IDs
-     * @param command
-     *      The callback context used when calling back into JavaScript.
-     */
-    private void getAll (JSONArray ids, CallbackContext command) {
-        getOptions(ids, Notification.Type.ALL, command);
-    }
-
-    /**
-     * Set of options from scheduled notifications.
-     *
-     * @param ids
-     *      Set of local notification IDs
-     * @param command
-     *      The callback context used when calling back into JavaScript.
-     */
-    private void getScheduled (JSONArray ids, CallbackContext command) {
-        getOptions(ids, Notification.Type.SCHEDULED, command);
-    }
-
-    /**
-     * Set of options from triggered notifications.
-     *
-     * @param ids
-     *      Set of local notification IDs
-     * @param command
-     *      The callback context used when calling back into JavaScript.
-     */
-    private void getTriggered (JSONArray ids, CallbackContext command) {
-        getOptions(ids, Notification.Type.TRIGGERED, command);
-    }
-
-    /**
-     * Options from local notification.
+     * Schedule multiple local notifications.
      *
-     * @param id
-     *      Set of local notification IDs
-     * @param type
-     *      The local notification life cycle type
-     * @param command
-     *      The callback context used when calling back into JavaScript.
+     * @param notifications The notifications to schedule.
      */
-    private void getOptions (String id, Notification.Type type,
-                             CallbackContext command) {
+    private void schedule (JSONArray notifications) {
+        Manager mgr = getNotificationMgr();
 
-        JSONArray ids = new JSONArray().put(id);
-        PluginResult result;
+        for (int i = 0; i < notifications.length(); i++) {
+            JSONObject dict = notifications.optJSONObject(i);
+            Options options = new Options(dict);
+            Request request = new Request(options);
 
-        List<JSONObject> options =
-                getNotificationMgr().getOptionsBy(type, toList(ids));
+            Notification notification =
+                    mgr.schedule(request, TriggerReceiver.class);
 
-        if (options.isEmpty()) {
-            // Status.NO_RESULT led to no callback invocation :(
-            // Status.OK        led to no NPE and crash
-            result = new PluginResult(PluginResult.Status.NO_RESULT);
-        } else {
-            result = new PluginResult(PluginResult.Status.OK, options.get(0));
+            fireEvent("schedule", notification);
         }
-
-        command.sendPluginResult(result);
     }
 
-    /**
-     * Set of options from local notifications.
-     *
-     * @param ids
-     *      Set of local notification IDs
-     * @param type
-     *      The local notification life cycle type
-     * @param command
-     *      The callback context used when calling back into JavaScript.
-     */
-    private void getOptions (JSONArray ids, Notification.Type type,
-                             CallbackContext command) {
-
-        List<JSONObject> options;
-
-        if (ids.length() == 0) {
-            options = getNotificationMgr().getOptionsByType(type);
-        } else {
-            options = getNotificationMgr().getOptionsBy(type, toList(ids));
-        }
-
-        command.success(new JSONArray(options));
-    }
+    // /**
+    //  * Update multiple local notifications.
+    //  *
+    //  * @param updates
+    //  *      Notification properties including their IDs
+    //  */
+    // private void update (JSONArray updates) {
+    //     for (int i = 0; i < updates.length(); i++) {
+    //         JSONObject update = updates.optJSONObject(i);
+    //         int id = update.optInt("id", 0);
+
+    //         Notification notification =
+    //                 getNotificationMgr().update(id, update, TriggerReceiver.class);
+
+    //         if (notification == null)
+    //             continue;
+
+    //         fireEvent("update", notification);
+    //     }
+    // }
+
+    // /**
+    //  * Cancel multiple local notifications.
+    //  *
+    //  * @param ids
+    //  *      Set of local notification IDs
+    //  */
+    // private void cancel (JSONArray ids) {
+    //     for (int i = 0; i < ids.length(); i++) {
+    //         int id = ids.optInt(i, 0);
+
+    //         Notification notification =
+    //                 getNotificationMgr().cancel(id);
+
+    //         if (notification == null)
+    //             continue;
+
+    //         fireEvent("cancel", notification);
+    //     }
+    // }
+
+    // /**
+    //  * Cancel all scheduled notifications.
+    //  */
+    // private void cancelAll() {
+    //     getNotificationMgr().cancelAll();
+    //     fireEvent("cancelall");
+    // }
+
+    // /**
+    //  * Clear multiple local notifications without canceling them.
+    //  *
+    //  * @param ids
+    //  *      Set of local notification IDs
+    //  */
+    // private void clear(JSONArray ids){
+    //     for (int i = 0; i < ids.length(); i++) {
+    //         int id = ids.optInt(i, 0);
+
+    //         Notification notification =
+    //                 getNotificationMgr().clear(id);
+
+    //         if (notification == null)
+    //             continue;
+
+    //         fireEvent("clear", notification);
+    //     }
+    // }
+
+    // /**
+    //  * Clear all triggered notifications without canceling them.
+    //  */
+    // private void clearAll() {
+    //     getNotificationMgr().clearAll();
+    //     fireEvent("clearall");
+    // }
+
+    // /**
+    //  * If a notification with an ID is present.
+    //  *
+    //  * @param id
+    //  *      Notification ID
+    //  * @param command
+    //  *      The callback context used when calling back into JavaScript.
+    //  */
+    // private void isPresent (int id, CallbackContext command) {
+    //     boolean exist = getNotificationMgr().exist(id);
+
+    //     PluginResult result = new PluginResult(
+    //             PluginResult.Status.OK, exist);
+
+    //     command.sendPluginResult(result);
+    // }
+
+    // /**
+    //  * If a notification with an ID is scheduled.
+    //  *
+    //  * @param id
+    //  *      Notification ID
+    //  * @param command
+    //  *      The callback context used when calling back into JavaScript.
+    //  */
+    // private void isScheduled (int id, CallbackContext command) {
+    //     boolean exist = getNotificationMgr().exist(
+    //             id, Notification.Type.SCHEDULED);
+
+    //     PluginResult result = new PluginResult(
+    //             PluginResult.Status.OK, exist);
+
+    //     command.sendPluginResult(result);
+    // }
+
+    // /**
+    //  * If a notification with an ID is triggered.
+    //  *
+    //  * @param id
+    //  *      Notification ID
+    //  * @param command
+    //  *      The callback context used when calling back into JavaScript.
+    //  */
+    // private void isTriggered (int id, CallbackContext command) {
+    //     boolean exist = getNotificationMgr().exist(
+    //             id, Notification.Type.TRIGGERED);
+
+    //     PluginResult result = new PluginResult(
+    //             PluginResult.Status.OK, exist);
+
+    //     command.sendPluginResult(result);
+    // }
+
+    // /**
+    //  * Set of IDs from all existent notifications.
+    //  *
+    //  * @param command
+    //  *      The callback context used when calling back into JavaScript.
+    //  */
+    // private void getAllIds (CallbackContext command) {
+    //     List<Integer> ids = getNotificationMgr().getIds();
+
+    //     command.success(new JSONArray(ids));
+    // }
+
+    // /**
+    //  * Set of IDs from all scheduled notifications.
+    //  *
+    //  * @param command
+    //  *      The callback context used when calling back into JavaScript.
+    //  */
+    // private void getScheduledIds (CallbackContext command) {
+    //     List<Integer> ids = getNotificationMgr().getIdsByType(
+    //             Notification.Type.SCHEDULED);
+
+    //     command.success(new JSONArray(ids));
+    // }
+
+    // /**
+    //  * Set of IDs from all triggered notifications.
+    //  *
+    //  * @param command
+    //  *      The callback context used when calling back into JavaScript.
+    //  */
+    // private void getTriggeredIds (CallbackContext command) {
+    //     List<Integer> ids = getNotificationMgr().getIdsByType(
+    //             Notification.Type.TRIGGERED);
+
+    //     command.success(new JSONArray(ids));
+    // }
+
+    // /**
+    //  * Options from local notification.
+    //  *
+    //  * @param ids
+    //  *      Set of local notification IDs
+    //  * @param command
+    //  *      The callback context used when calling back into JavaScript.
+    //  */
+    // private void getSingle (JSONArray ids, CallbackContext command) {
+    //     getOptions(ids.optString(0), Notification.Type.ALL, command);
+    // }
+
+    // /**
+    //  * Options from scheduled notification.
+    //  *
+    //  * @param ids
+    //  *      Set of local notification IDs
+    //  * @param command
+    //  *      The callback context used when calling back into JavaScript.
+    //  */
+    // private void getSingleScheduled (JSONArray ids, CallbackContext command) {
+    //     getOptions(ids.optString(0), Notification.Type.SCHEDULED, command);
+    // }
+
+    // /**
+    //  * Options from triggered notification.
+    //  *
+    //  * @param ids
+    //  *      Set of local notification IDs
+    //  * @param command
+    //  *      The callback context used when calling back into JavaScript.
+    //  */
+    // private void getSingleTriggered (JSONArray ids, CallbackContext command) {
+    //     getOptions(ids.optString(0), Notification.Type.TRIGGERED, command);
+    // }
+
+    // /**
+    //  * Set of options from local notification.
+    //  *
+    //  * @param ids
+    //  *      Set of local notification IDs
+    //  * @param command
+    //  *      The callback context used when calling back into JavaScript.
+    //  */
+    // private void getAll (JSONArray ids, CallbackContext command) {
+    //     getOptions(ids, Notification.Type.ALL, command);
+    // }
+
+    // /**
+    //  * Set of options from scheduled notifications.
+    //  *
+    //  * @param ids
+    //  *      Set of local notification IDs
+    //  * @param command
+    //  *      The callback context used when calling back into JavaScript.
+    //  */
+    // private void getScheduled (JSONArray ids, CallbackContext command) {
+    //     getOptions(ids, Notification.Type.SCHEDULED, command);
+    // }
+
+    // /**
+    //  * Set of options from triggered notifications.
+    //  *
+    //  * @param ids
+    //  *      Set of local notification IDs
+    //  * @param command
+    //  *      The callback context used when calling back into JavaScript.
+    //  */
+    // private void getTriggered (JSONArray ids, CallbackContext command) {
+    //     getOptions(ids, Notification.Type.TRIGGERED, command);
+    // }
+
+    // /**
+    //  * Options from local notification.
+    //  *
+    //  * @param id
+    //  *      Set of local notification IDs
+    //  * @param type
+    //  *      The local notification life cycle type
+    //  * @param command
+    //  *      The callback context used when calling back into JavaScript.
+    //  */
+    // private void getOptions (String id, Notification.Type type,
+    //                          CallbackContext command) {
+
+    //     JSONArray ids = new JSONArray().put(id);
+    //     PluginResult result;
+
+    //     List<JSONObject> options =
+    //             getNotificationMgr().getOptionsBy(type, toList(ids));
+
+    //     if (options.isEmpty()) {
+    //         // Status.NO_RESULT led to no callback invocation :(
+    //         // Status.OK        led to no NPE and crash
+    //         result = new PluginResult(PluginResult.Status.NO_RESULT);
+    //     } else {
+    //         result = new PluginResult(PluginResult.Status.OK, options.get(0));
+    //     }
+
+    //     command.sendPluginResult(result);
+    // }
+
+    // /**
+    //  * Set of options from local notifications.
+    //  *
+    //  * @param ids
+    //  *      Set of local notification IDs
+    //  * @param type
+    //  *      The local notification life cycle type
+    //  * @param command
+    //  *      The callback context used when calling back into JavaScript.
+    //  */
+    // private void getOptions (JSONArray ids, Notification.Type type,
+    //                          CallbackContext command) {
+
+    //     List<JSONObject> options;
+
+    //     if (ids.length() == 0) {
+    //         options = getNotificationMgr().getOptionsByType(type);
+    //     } else {
+    //         options = getNotificationMgr().getOptionsBy(type, toList(ids));
+    //     }
+
+    //     command.success(new JSONArray(options));
+    // }
 
     /**
      * Call all pending callbacks after the deviceready event has been fired.
@@ -548,10 +575,8 @@ public class LocalNotification extends CordovaPlugin {
     /**
      * Fire given event on JS side. Does inform all event listeners.
      *
-     * @param event
-     *      The event name
-     * @param notification
-     *      Optional local notification to pass the id and properties.
+     * @param event The event name
+     * @param notification Optional notification to pass the id and properties.
      */
     static void fireEvent (String event, Notification notification) {
         String state = getApplicationState();
@@ -570,8 +595,7 @@ public class LocalNotification extends CordovaPlugin {
     /**
      * Use this instead of deprecated sendJavascript
      *
-     * @param js
-     *       JS code snippet as string
+     * @param js JS code snippet as string
      */
     private static synchronized void sendJavascript(final String js) {
 
@@ -593,27 +617,26 @@ public class LocalNotification extends CordovaPlugin {
         }
     }
 
-    /**
-     * Convert JSON array of integers to List.
-     *
-     * @param ary
-     *      Array of integers
-     */
-    private List<Integer> toList (JSONArray ary) {
-        ArrayList<Integer> list = new ArrayList<Integer>();
+    // /**
+    //  * Convert JSON array of integers to List.
+    //  *
+    //  * @param ary
+    //  *      Array of integers
+    //  */
+    // private List<Integer> toList (JSONArray ary) {
+    //     ArrayList<Integer> list = new ArrayList<Integer>();
 
-        for (int i = 0; i < ary.length(); i++) {
-            list.add(ary.optInt(i));
-        }
+    //     for (int i = 0; i < ary.length(); i++) {
+    //         list.add(ary.optInt(i));
+    //     }
 
-        return list;
-    }
+    //     return list;
+    // }
 
     /**
      * Current application state.
      *
-     * @return
-     *      "background" or "foreground"
+     * @return "background" or "foreground"
      */
     static String getApplicationState () {
         return isInBackground ? "background" : "foreground";

+ 4 - 8
src/android/TriggerReceiver.java

@@ -30,16 +30,14 @@ import de.appplant.cordova.plugin.notification.Notification;
  * Android notification bar. The notification uses the default notification
  * sound and it vibrates the phone.
  */
-public class TriggerReceiver extends de.appplant.cordova.plugin.notification.TriggerReceiver {
+public class TriggerReceiver extends de.appplant.cordova.plugin.notification.receiver.TriggerReceiver {
 
     /**
      * Called when a local notification was triggered. Does present the local
      * notification, re-schedule the alarm if necessary and fire trigger event.
      *
-     * @param notification
-     *      Wrapper around the local notification
-     * @param updated
-     *      If an update has triggered or the original
+     * @param notification Wrapper around the local notification
+     * @param updated      If an update has triggered or the original
      */
     @Override
     public void onTrigger (Notification notification, boolean updated) {
@@ -53,13 +51,11 @@ public class TriggerReceiver extends de.appplant.cordova.plugin.notification.Tri
     /**
      * Build notification specified by options.
      *
-     * @param builder
-     *      Notification builder
+     * @param builder Notification builder
      */
     @Override
     public Notification buildNotification (Builder builder) {
         return builder
-                .setTriggerReceiver(TriggerReceiver.class)
                 .setClickActivity(ClickActivity.class)
                 .setClearReceiver(ClearReceiver.class)
                 .build();

+ 26 - 62
src/android/notification/Builder.java

@@ -24,13 +24,13 @@ package de.appplant.cordova.plugin.notification;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
-import android.net.Uri;
 import android.support.v4.app.NotificationCompat;
 
-import org.json.JSONObject;
-
 import java.util.Random;
 
+import de.appplant.cordova.plugin.notification.activity.ClickActivity;
+import de.appplant.cordova.plugin.notification.receiver.ClearReceiver;
+
 /**
  * Builder class for local notifications. Build fully configured local
  * notification specified by JSON object passed from JS side.
@@ -43,9 +43,6 @@ public class Builder {
     // Notification options passed by JS
     private final Options options;
 
-    // Receiver to handle the trigger event
-    private Class<?> triggerReceiver;
-
     // Receiver to handle the clear event
     private Class<?> clearReceiver = ClearReceiver.class;
 
@@ -55,43 +52,17 @@ public class Builder {
     /**
      * Constructor
      *
-     * @param context
-     *      Application context
-     * @param options
-     *      Notification options
-     */
-    public Builder(Context context, JSONObject options) {
-        this.context = context;
-        this.options = new Options(context).parse(options);
-    }
-
-    /**
-     * Constructor
-     *
-     * @param options
-     *      Notification options
+     * @param options Notification options
      */
     public Builder(Options options) {
         this.context = options.getContext();
         this.options = options;
     }
 
-    /**
-     * Set trigger receiver.
-     *
-     * @param receiver
-     *      Broadcast receiver
-     */
-    public Builder setTriggerReceiver(Class<?> receiver) {
-        this.triggerReceiver = receiver;
-        return this;
-    }
-
     /**
      * Set clear receiver.
      *
-     * @param receiver
-     *      Broadcast receiver
+     * @param receiver Broadcast receiver for the clear event.
      */
     public Builder setClearReceiver(Class<?> receiver) {
         this.clearReceiver = receiver;
@@ -101,8 +72,7 @@ public class Builder {
     /**
      * Set click activity.
      *
-     * @param activity
-     *      Activity
+     * @param activity The activity to handler the click event.
      */
     public Builder setClickActivity(Class<?> activity) {
         this.clickActivity = activity;
@@ -111,50 +81,45 @@ public class Builder {
 
     /**
      * Creates the notification with all its options passed through JS.
+     *
+     * @return The final notification to display.
      */
     public Notification build() {
-        Uri sound     = options.getSoundUri();
         int smallIcon = options.getSmallIcon();
-        int ledColor  = options.getLedColor();
         NotificationCompat.Builder builder;
 
-        builder = new NotificationCompat.Builder(context)
-                .setDefaults(0)
+        builder = new NotificationCompat.Builder(context, "group")
+                .setDefaults(options.getDefaults())
                 .setContentTitle(options.getTitle())
                 .setContentText(options.getText())
                 .setNumber(options.getBadgeNumber())
-                .setTicker(options.getText())
                 .setAutoCancel(options.isAutoClear())
-                .setOngoing(options.isOngoing())
-                .setColor(options.getColor());
-
-        if (ledColor != 0) {
-            builder.setLights(ledColor, 100, 100);
-        }
-
-        if (sound != null) {
-            builder.setSound(sound);
-        }
-
-        if (smallIcon == 0) {
-            builder.setSmallIcon(options.getIcon());
+                .setOngoing(options.isSticky())
+                .setColor(options.getColor())
+                .setSound(options.getSound())
+                .setVisibility(options.getVisibility())
+                .setPriority(options.getPriority())
+                .setShowWhen(options.getShowWhen())
+                .setLights(options.getLedColor(), options.getLedOn(), options.getLedOff());
+
+        if (smallIcon != 0) {
+            builder.setSmallIcon(smallIcon);
+            builder.setLargeIcon(options.getLargeIcon());
         } else {
-            builder.setSmallIcon(options.getSmallIcon());
-            builder.setLargeIcon(options.getIconBitmap());
+            builder.setSmallIcon(options.getIcon());
         }
 
         applyDeleteReceiver(builder);
         applyContentReceiver(builder);
 
-        return new Notification(context, options, builder, triggerReceiver);
+        return new Notification(context, options, builder);
     }
 
     /**
      * Set intent to handle the delete event. Will clean up some persisted
      * preferences.
      *
-     * @param builder
-     *      Local notification builder instance
+     * @param builder Local notification builder instance
      */
     private void applyDeleteReceiver(NotificationCompat.Builder builder) {
 
@@ -162,7 +127,7 @@ public class Builder {
             return;
 
         Intent intent = new Intent(context, clearReceiver)
-                .setAction(options.getIdStr())
+                .setAction(options.getIdentifier())
                 .putExtra(Options.EXTRA, options.toString());
 
         PendingIntent deleteIntent = PendingIntent.getBroadcast(
@@ -175,8 +140,7 @@ public class Builder {
      * Set intent to handle the click event. Will bring the app to
      * foreground.
      *
-     * @param builder
-     *      Local notification builder instance
+     * @param builder Local notification builder instance
      */
     private void applyContentReceiver(NotificationCompat.Builder builder) {
 

+ 406 - 354
src/android/notification/Manager.java

@@ -21,20 +21,30 @@
 
 package de.appplant.cordova.plugin.notification;
 
+import android.app.AlarmManager;
+import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
 import android.content.SharedPreferences;
+import android.os.Build;
+import android.support.v4.app.NotificationManagerCompat;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import static de.appplant.cordova.plugin.notification.Notification.PREF_KEY;
+import de.appplant.cordova.plugin.notification.receiver.TriggerReceiver;
+
+// import static de.appplant.cordova.plugin.notification.Notification.PREF_KEY;
 
 /**
  * Central way to access all or single local notifications set by specific
@@ -49,8 +59,7 @@ public class Manager {
     /**
      * Constructor
      *
-     * @param context
-     *      Application context
+     * @param context Application context
      */
     private Manager(Context context){
         this.context = context;
@@ -59,398 +68,441 @@ public class Manager {
     /**
      * Static method to retrieve class instance.
      *
-     * @param context
-     *      Application context
+     * @param context Application context
      */
     public static Manager getInstance(Context context) {
         return new Manager(context);
     }
 
     /**
-     * Schedule local notification specified by JSON object.
-     *
-     * @param options
-     *      JSON object with set of options
-     * @param receiver
-     *      Receiver to handle the trigger event
-     */
-    public Notification schedule (JSONObject options, Class<?> receiver) {
-        return schedule(new Options(context).parse(options), receiver);
-    }
-
-    /**
-     * Schedule local notification specified by options object.
-     *
-     * @param options
-     *      Set of notification options
-     * @param receiver
-     *      Receiver to handle the trigger event
-     */
-    public Notification schedule (Options options, Class<?> receiver) {
-        Notification notification = new Builder(options)
-                .setTriggerReceiver(receiver)
-                .build();
-
-        notification.schedule();
-
-        return notification;
-    }
-
-    /**
-     * Clear local notification specified by ID.
-     *
-     * @param id
-     *      The notification ID
-     * @param updates
-     *      JSON object with notification options
-     * @param receiver
-     *      Receiver to handle the trigger event
-     */
-    public Notification update (int id, JSONObject updates, Class<?> receiver) {
-        Notification notification = get(id);
-
-        if (notification == null)
-            return null;
-
-        notification.cancel();
-
-        JSONObject options = mergeJSONObjects(
-                notification.getOptions().getDict(), updates);
-
-        try {
-            options.put("updated", true);
-        } catch (JSONException ignore) {}
-
-        return schedule(options, receiver);
-    }
-
-    /**
-     * Clear local notification specified by ID.
-     *
-     * @param id
-     *      The notification ID
+     * Check if app has local notification permission.
      */
-    public Notification clear (int id) {
-        Notification notification = get(id);
-
-        if (notification != null) {
-            notification.clear();
-        }
-
-        return notification;
+    public boolean hasPermission () {
+        return getNotMgr().areNotificationsEnabled();
     }
 
     /**
-     * Clear local notification specified by ID.
+     * Schedule local notification specified by request.
      *
-     * @param id
-     *      The notification ID
+     * @param request Set of notification options.
+     * @param receiver Receiver to handle the trigger event.
      */
-    public Notification cancel (int id) {
-        Notification notification = get(id);
-
-        if (notification != null) {
-            notification.cancel();
-        }
-
-        return notification;
-    }
-
-    /**
-     * Clear all local notifications.
-     */
-    public void clearAll () {
-        List<Notification> notifications = getAll();
-
-        for (Notification notification : notifications) {
-            notification.clear();
-        }
+    public Notification schedule (Request request, Class<?> receiver) {
+        Options options  = request.getOptions();
+        AlarmManager mgr = getAlarmMgr();
 
-        getNotMgr().cancelAll();
-    }
+        do {
+            Date date = request.getTriggerDate();
 
-    /**
-     * Cancel all local notifications.
-     */
-    public void cancelAll () {
-        List<Notification> notifications = getAll();
+            if (date == null)
+                continue;
 
-        for (Notification notification : notifications) {
-            notification.cancel();
-        }
+            Intent intent = new Intent(context, receiver)
+                    .setAction(request.getIdentifier())
+                    .putExtra(Request.EXTRA, request.getOccurrence())
+                    .putExtra(Options.EXTRA, options.toString());
 
-        getNotMgr().cancelAll();
-    }
+            if (!date.after(new Date()) && trigger(intent, receiver))
+                continue;
 
-    /**
-     * All local notifications IDs.
-     */
-    public List<Integer> getIds() {
-        Set<String> keys = getPrefs().getAll().keySet();
-        ArrayList<Integer> ids = new ArrayList<Integer>();
+            PendingIntent pi = PendingIntent.getBroadcast(
+                     context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
 
-        for (String key : keys) {
             try {
-                ids.add(Integer.parseInt(key));
-            } catch (NumberFormatException e) {
-                e.printStackTrace();
-            }
-        }
-
-        return ids;
-    }
-
-    /**
-     * All local notification IDs for given type.
-     *
-     * @param type
-     *      The notification life cycle type
-     */
-    public List<Integer> getIdsByType(Notification.Type type) {
-        List<Notification> notifications = getAll();
-        ArrayList<Integer> ids = new ArrayList<Integer>();
-
-        for (Notification notification : notifications) {
-            if (notification.getType() == type) {
-                ids.add(notification.getId());
-            }
-        }
-
-        return ids;
-    }
-
-    /**
-     * List of local notifications with matching ID.
-     *
-     * @param ids
-     *      Set of notification IDs
-     */
-    public List<Notification> getByIds(List<Integer> ids) {
-        ArrayList<Notification> notifications = new ArrayList<Notification>();
-
-        for (int id : ids) {
-            Notification notification = get(id);
-
-            if (notification != null) {
-                notifications.add(notification);
-            }
-        }
-
-        return notifications;
-    }
-
-    /**
-     * List of all local notification.
-     */
-    public List<Notification> getAll() {
-        return getByIds(getIds());
-    }
-
-    /**
-     * List of local notifications from given type.
-     *
-     * @param type
-     *      The notification life cycle type
-     */
-    public List<Notification> getByType(Notification.Type type) {
-        List<Notification> notifications = getAll();
-        ArrayList<Notification> list = new ArrayList<Notification>();
-
-        if (type == Notification.Type.ALL)
-            return notifications;
-
-        for (Notification notification : notifications) {
-            if (notification.getType() == type) {
-                list.add(notification);
-            }
-        }
-
-        return list;
-    }
-
-    /**
-     * List of local notifications with matching ID from given type.
-     *
-     * @param type
-     *      The notification life cycle type
-     * @param ids
-     *      Set of notification IDs
-     */
-    @SuppressWarnings("UnusedDeclaration")
-    public List<Notification> getBy(Notification.Type type, List<Integer> ids) {
-        ArrayList<Notification> notifications = new ArrayList<Notification>();
-
-        for (int id : ids) {
-            Notification notification = get(id);
-
-            if (notification != null && notification.isScheduled()) {
-                notifications.add(notification);
-            }
-        }
-
-        return notifications;
-    }
-
-    /**
-     * If a notification with an ID exists.
-     *
-     * @param id
-     *      Notification ID
-     */
-    public boolean exist (int id) {
-        return get(id) != null;
-    }
-
-    /**
-     * If a notification with an ID and type exists.
-     *
-     * @param id
-     *      Notification ID
-     * @param type
-     *      Notification type
-     */
-    public boolean exist (int id, Notification.Type type) {
-        Notification notification = get(id);
-
-        return notification != null && notification.getType() == type;
-    }
-
-    /**
-     * List of properties from all local notifications.
-     */
-    public List<JSONObject> getOptions() {
-        return getOptionsById(getIds());
-    }
-
-    /**
-     * List of properties from local notifications with matching ID.
-     *
-     * @param ids
-     *      Set of notification IDs
-     */
-    public List<JSONObject> getOptionsById(List<Integer> ids) {
-        ArrayList<JSONObject> options = new ArrayList<JSONObject>();
-
-        for (int id : ids) {
-            Notification notification = get(id);
-
-            if (notification != null) {
-                options.add(notification.getOptions().getDict());
+                mgr.set(AlarmManager.RTC_WAKEUP, date.getTime(), pi);
+            } catch (Exception ignore) {
+                // on some Samsung devices there is a known bug where a 500 alarms limit can crash the app
+                // http://developer.samsung.com/forum/board/thread/view.do?boardName=General&messageId=280286&listLines=15&startId=zzzzz%7E&searchSubId=0000000001
             }
-        }
-
-        return options;
-    }
-
-    /**
-     * List of properties from all local notifications from given type.
-     *
-     * @param type
-     *      The notification life cycle type
-     */
-    public List<JSONObject> getOptionsByType(Notification.Type type) {
-        ArrayList<JSONObject> options = new ArrayList<JSONObject>();
-        List<Notification> notifications = getByType(type);
-
-        for (Notification notification : notifications) {
-            options.add(notification.getOptions().getDict());
-        }
-
-        return options;
-    }
-
-    /**
-     * List of properties from local notifications with matching ID from
-     * given type.
-     *
-     * @param type
-     *      The notification life cycle type
-     * @param ids
-     *      Set of notification IDs
-     */
-    public List<JSONObject> getOptionsBy(Notification.Type type,
-                                         List<Integer> ids) {
-
-        if (type == Notification.Type.ALL)
-            return getOptionsById(ids);
-
-        ArrayList<JSONObject> options = new ArrayList<JSONObject>();
-        List<Notification> notifications = getByIds(ids);
 
-        for (Notification notification : notifications) {
-            if (notification.getType() == type) {
-                options.add(notification.getOptions().getDict());
-            }
-        }
+        } while (request.moveNext());
 
-        return options;
+        return new Notification(context, options);
     }
 
     /**
-     * Get existent local notification.
+     * Trigger local notification specified by options.
      *
-     * @param id
-     *      Notification ID
+     * @param intent The intent to broadcast.
+     * @param cls    The broadcast class.
      */
-    public Notification get(int id) {
-        Map<String, ?> alarms = getPrefs().getAll();
-        String notId          = Integer.toString(id);
-        JSONObject options;
-
-        if (!alarms.containsKey(notId))
-            return null;
-
+    private boolean trigger (Intent intent, Class<?> cls) {
+        BroadcastReceiver receiver;
 
         try {
-            String json = alarms.get(notId).toString();
-            options = new JSONObject(json);
-        } catch (JSONException e) {
-            e.printStackTrace();
-            return null;
+            receiver = (BroadcastReceiver) cls.newInstance();
+        } catch (InstantiationException e) {
+            return false;
+        } catch (IllegalAccessException e) {
+            return false;
         }
 
-        Builder builder = new Builder(context, options);
-
-        return builder.build();
+        receiver.onReceive(context, intent);
+        return true;
     }
 
-    /**
-     * Merge two JSON objects.
-     *
-     * @param obj1
-     *      JSON object
-     * @param obj2
-     *      JSON object with new options
-     */
-    private JSONObject mergeJSONObjects (JSONObject obj1, JSONObject obj2) {
-        Iterator it = obj2.keys();
-
-        while (it.hasNext()) {
-            try {
-                String key = (String)it.next();
-
-                obj1.put(key, obj2.opt(key));
-            } catch (JSONException e) {
-                e.printStackTrace();
-            }
-        }
-
-        return obj1;
-    }
+    // /**
+    //  * Clear local notification specified by ID.
+    //  *
+    //  * @param id
+    //  *      The notification ID
+    //  * @param updates
+    //  *      JSON object with notification options
+    //  * @param receiver
+    //  *      Receiver to handle the trigger event
+    //  */
+    // public Notification update (int id, JSONObject updates, Class<?> receiver) {
+    //     Notification notification = get(id);
+
+    //     if (notification == null)
+    //         return null;
+
+    //     notification.cancel();
+
+    //     JSONObject options = mergeJSONObjects(
+    //             notification.getOptions().getDict(), updates);
+
+    //     try {
+    //         options.put("updated", true);
+    //     } catch (JSONException ignore) {}
+
+    //     return schedule(options, receiver);
+    // }
+
+    // /**
+    //  * Clear local notification specified by ID.
+    //  *
+    //  * @param id
+    //  *      The notification ID
+    //  */
+    // public Notification clear (int id) {
+    //     Notification notification = get(id);
+
+    //     if (notification != null) {
+    //         notification.clear();
+    //     }
+
+    //     return notification;
+    // }
+
+    // /**
+    //  * Clear local notification specified by ID.
+    //  *
+    //  * @param id
+    //  *      The notification ID
+    //  */
+    // public Notification cancel (int id) {
+    //     Notification notification = get(id);
+
+    //     if (notification != null) {
+    //         notification.cancel();
+    //     }
+
+    //     return notification;
+    // }
+
+    // /**
+    //  * Clear all local notifications.
+    //  */
+    // public void clearAll () {
+    //     List<Notification> notifications = getAll();
+
+    //     for (Notification notification : notifications) {
+    //         notification.clear();
+    //     }
+
+    //     getNotMgr().cancelAll();
+    // }
+
+    // /**
+    //  * Cancel all local notifications.
+    //  */
+    // public void cancelAll () {
+    //     List<Notification> notifications = getAll();
+
+    //     for (Notification notification : notifications) {
+    //         notification.cancel();
+    //     }
+
+    //     getNotMgr().cancelAll();
+    // }
+
+    // /**
+    //  * All local notifications IDs.
+    //  */
+    // public List<Integer> getIds() {
+    //     Set<String> keys = getPrefs().getAll().keySet();
+    //     ArrayList<Integer> ids = new ArrayList<Integer>();
+
+    //     for (String key : keys) {
+    //         try {
+    //             ids.add(Integer.parseInt(key));
+    //         } catch (NumberFormatException e) {
+    //             e.printStackTrace();
+    //         }
+    //     }
+
+    //     return ids;
+    // }
+
+    // /**
+    //  * All local notification IDs for given type.
+    //  *
+    //  * @param type
+    //  *      The notification life cycle type
+    //  */
+    // public List<Integer> getIdsByType(Notification.Type type) {
+    //     List<Notification> notifications = getAll();
+    //     ArrayList<Integer> ids = new ArrayList<Integer>();
+
+    //     for (Notification notification : notifications) {
+    //         if (notification.getType() == type) {
+    //             ids.add(notification.getId());
+    //         }
+    //     }
+
+    //     return ids;
+    // }
+
+    // /**
+    //  * List of local notifications with matching ID.
+    //  *
+    //  * @param ids
+    //  *      Set of notification IDs
+    //  */
+    // public List<Notification> getByIds(List<Integer> ids) {
+    //     ArrayList<Notification> notifications = new ArrayList<Notification>();
+
+    //     for (int id : ids) {
+    //         Notification notification = get(id);
+
+    //         if (notification != null) {
+    //             notifications.add(notification);
+    //         }
+    //     }
+
+    //     return notifications;
+    // }
+
+    // /**
+    //  * List of all local notification.
+    //  */
+    // public List<Notification> getAll() {
+    //     return getByIds(getIds());
+    // }
+
+    // /**
+    //  * List of local notifications from given type.
+    //  *
+    //  * @param type
+    //  *      The notification life cycle type
+    //  */
+    // public List<Notification> getByType(Notification.Type type) {
+    //     List<Notification> notifications = getAll();
+    //     ArrayList<Notification> list = new ArrayList<Notification>();
+
+    //     if (type == Notification.Type.ALL)
+    //         return notifications;
+
+    //     for (Notification notification : notifications) {
+    //         if (notification.getType() == type) {
+    //             list.add(notification);
+    //         }
+    //     }
+
+    //     return list;
+    // }
+
+    // /**
+    //  * List of local notifications with matching ID from given type.
+    //  *
+    //  * @param type
+    //  *      The notification life cycle type
+    //  * @param ids
+    //  *      Set of notification IDs
+    //  */
+    // @SuppressWarnings("UnusedDeclaration")
+    // public List<Notification> getBy(Notification.Type type, List<Integer> ids) {
+    //     ArrayList<Notification> notifications = new ArrayList<Notification>();
+
+    //     for (int id : ids) {
+    //         Notification notification = get(id);
+
+    //         if (notification != null && notification.isScheduled()) {
+    //             notifications.add(notification);
+    //         }
+    //     }
+
+    //     return notifications;
+    // }
+
+    // /**
+    //  * If a notification with an ID exists.
+    //  *
+    //  * @param id
+    //  *      Notification ID
+    //  */
+    // public boolean exist (int id) {
+    //     return get(id) != null;
+    // }
+
+    // /**
+    //  * If a notification with an ID and type exists.
+    //  *
+    //  * @param id
+    //  *      Notification ID
+    //  * @param type
+    //  *      Notification type
+    //  */
+    // public boolean exist (int id, Notification.Type type) {
+    //     Notification notification = get(id);
+
+    //     return notification != null && notification.getType() == type;
+    // }
+
+    // /**
+    //  * List of properties from all local notifications.
+    //  */
+    // public List<JSONObject> getOptions() {
+    //     return getOptionsById(getIds());
+    // }
+
+    // /**
+    //  * List of properties from local notifications with matching ID.
+    //  *
+    //  * @param ids
+    //  *      Set of notification IDs
+    //  */
+    // public List<JSONObject> getOptionsById(List<Integer> ids) {
+    //     ArrayList<JSONObject> options = new ArrayList<JSONObject>();
+
+    //     for (int id : ids) {
+    //         Notification notification = get(id);
+
+
+    //         if (notification != null) {
+    //             options.add(notification.getOptions().getDict());
+    //         }
+    //     }
+
+    //     return options;
+    // }
+
+    // /**
+    //  * List of properties from all local notifications from given type.
+    //  *
+    //  * @param type
+    //  *      The notification life cycle type
+    //  */
+    // public List<JSONObject> getOptionsByType(Notification.Type type) {
+    //     ArrayList<JSONObject> options = new ArrayList<JSONObject>();
+    //     List<Notification> notifications = getByType(type);
+
+    //     for (Notification notification : notifications) {
+    //         options.add(notification.getOptions().getDict());
+    //     }
+
+    //     return options;
+    // }
+
+    // /**
+    //  * List of properties from local notifications with matching ID from
+    //  * given type.
+    //  *
+    //  * @param type
+    //  *      The notification life cycle type
+    //  * @param ids
+    //  *      Set of notification IDs
+    //  */
+    // public List<JSONObject> getOptionsBy(Notification.Type type,
+    //                                      List<Integer> ids) {
+
+    //     if (type == Notification.Type.ALL)
+    //         return getOptionsById(ids);
+
+    //     ArrayList<JSONObject> options = new ArrayList<JSONObject>();
+    //     List<Notification> notifications = getByIds(ids);
+
+    //     for (Notification notification : notifications) {
+    //         if (notification.getType() == type) {
+    //             options.add(notification.getOptions().getDict());
+    //         }
+    //     }
+
+    //     return options;
+    // }
+
+    // /**
+    //  * Get existent local notification.
+    //  *
+    //  * @param id
+    //  *      Notification ID
+    //  */
+    // public Notification get(int id) {
+    //     Map<String, ?> alarms = getPrefs().getAll();
+    //     String notId          = Integer.toString(id);
+    //     JSONObject options;
+
+    //     if (!alarms.containsKey(notId))
+    //         return null;
+
+
+    //     try {
+    //         String json = alarms.get(notId).toString();
+    //         options = new JSONObject(json);
+    //     } catch (JSONException e) {
+    //         e.printStackTrace();
+    //         return null;
+    //     }
+
+    //     Builder builder = new Builder(context, options);
+
+    //     return builder.build();
+    // }
+
+    // /**
+    //  * Merge two JSON objects.
+    //  *
+    //  * @param obj1
+    //  *      JSON object
+    //  * @param obj2
+    //  *      JSON object with new options
+    //  */
+    // private JSONObject mergeJSONObjects (JSONObject obj1, JSONObject obj2) {
+    //     Iterator it = obj2.keys();
+
+    //     while (it.hasNext()) {
+    //         try {
+    //             String key = (String)it.next();
+
+    //             obj1.put(key, obj2.opt(key));
+    //         } catch (JSONException e) {
+    //             e.printStackTrace();
+    //         }
+    //     }
+
+    //     return obj1;
+    // }
+
+    // /**
+    //  * Shared private preferences for the application.
+    //  */
+    // private SharedPreferences getPrefs () {
+    //     return context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE);
+    // }
 
     /**
-     * Shared private preferences for the application.
+     * Alarm manager for the application.
      */
-    private SharedPreferences getPrefs () {
-        return context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE);
+    private AlarmManager getAlarmMgr () {
+        return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
     }
 
     /**
      * Notification manager for the application.
      */
-    private NotificationManager getNotMgr () {
-        return (NotificationManager) context
-                .getSystemService(Context.NOTIFICATION_SERVICE);
+    private NotificationManagerCompat getNotMgr () {
+        return NotificationManagerCompat.from(context);
     }
 
 }

+ 181 - 196
src/android/notification/Notification.java

@@ -23,6 +23,7 @@ package de.appplant.cordova.plugin.notification;
 
 
 import android.app.AlarmManager;
+import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -36,6 +37,8 @@ import org.json.JSONObject;
 
 import java.util.Date;
 
+import de.appplant.cordova.plugin.notification.receiver.TriggerReceiver;
+
 /**
  * Wrapper class around OS notification class. Handles basic operations
  * like show, delete, cancel for a single local notification instance.
@@ -47,9 +50,6 @@ public class Notification {
         ALL, SCHEDULED, TRIGGERED
     }
 
-    // Default receiver to handle the trigger event
-    private static Class<?> defaultReceiver = TriggerReceiver.class;
-
     // Key for private preferences
     static final String PREF_KEY = "LocalNotification";
 
@@ -62,27 +62,29 @@ public class Notification {
     // Builder with full configuration
     private final NotificationCompat.Builder builder;
 
-    // Receiver to handle the trigger event
-    private Class<?> receiver = defaultReceiver;
-
     /**
      * Constructor
      *
-     * @param context
-     *      Application context
-     * @param options
-     *      Parsed notification options
-     * @param builder
-     *      Pre-configured notification builder
+     * @param context Application context.
+     * @param options Parsed notification options.
+     * @param builder Pre-configured notification builder.
      */
-    protected Notification (Context context, Options options,
-                    NotificationCompat.Builder builder, Class<?> receiver) {
-
-        this.context = context;
-        this.options = options;
-        this.builder = builder;
+    Notification (Context context, Options options, NotificationCompat.Builder builder) {
+        this.context  = context;
+        this.options  = options;
+        this.builder  = builder;
+    }
 
-        this.receiver = receiver != null ? receiver : defaultReceiver;
+    /**
+     * Constructor
+     *
+     * @param context Application context.
+     * @param options Parsed notification options.
+     */
+    public Notification(Context context, Options options) {
+        this.context  = context;
+        this.options  = options;
+        this.builder  = null;
     }
 
     /**
@@ -106,113 +108,113 @@ public class Notification {
         return options.getId();
     }
 
-    /**
-     * If it's a repeating notification.
-     */
-    public boolean isRepeating () {
-        return getOptions().getRepeatInterval() > 0;
-    }
-
-    /**
-     * If the notification was in the past.
-     */
-    public boolean wasInThePast () {
-        return new Date().after(options.getTriggerDate());
-    }
-
-    /**
-     * If the notification is scheduled.
-     */
-    public boolean isScheduled () {
-        return isRepeating() || !wasInThePast();
-    }
-
-    /**
-     * If the notification is triggered.
-     */
-    public boolean isTriggered () {
-        return wasInThePast();
-    }
-
-    /**
-     * If the notification is an update.
-     *
-     * @param keepFlag
-     *      Set to false to remove the flag from the option map
-     */
-    protected boolean isUpdate (boolean keepFlag) {
-        boolean updated = options.getDict().optBoolean("updated", false);
-
-        if (!keepFlag) {
-            options.getDict().remove("updated");
-        }
-
-        return updated;
-    }
-
-    /**
-     * Notification type can be one of pending or scheduled.
-     */
-    public Type getType () {
-        return isScheduled() ? Type.SCHEDULED : Type.TRIGGERED;
-    }
+    // /**
+    //  * If it's a repeating notification.
+    //  */
+    // public boolean isRepeating () {
+    //     return getOptions().getRepeatInterval() > 0;
+    // }
+
+    // /**
+    //  * If the notification was in the past.
+    //  */
+    // public boolean wasInThePast () {
+    //     return new Date().after(options.getTriggerDate());
+    // }
+
+    // /**
+    //  * If the notification is scheduled.
+    //  */
+    // public boolean isScheduled () {
+    //     return isRepeating() || !wasInThePast();
+    // }
+
+    // /**
+    //  * If the notification is triggered.
+    //  */
+    // public boolean isTriggered () {
+    //     return wasInThePast();
+    // }
+
+    // /**
+    //  * If the notification is an update.
+    //  *
+    //  * @param keepFlag
+    //  *      Set to false to remove the flag from the option map
+    //  */
+    // protected boolean isUpdate (boolean keepFlag) {
+    //     boolean updated = options.getDict().optBoolean("updated", false);
+
+    //     if (!keepFlag) {
+    //         options.getDict().remove("updated");
+    //     }
+
+    //     return updated;
+    // }
+
+    // /**
+    //  * Notification type can be one of pending or scheduled.
+    //  */
+    // public Type getType () {
+    //     return isScheduled() ? Type.SCHEDULED : Type.TRIGGERED;
+    // }
 
     /**
      * Schedule the local notification.
      */
     public void schedule() {
-        long triggerTime = options.getTriggerTime();
+        // long triggerTime = options.getTriggerTime();
 
-        persist();
+        // persist();
 
-        // Intent gets called when the Notification gets fired
-        Intent intent = new Intent(context, receiver)
-                .setAction(options.getIdStr())
-                .putExtra(Options.EXTRA, options.toString());
+        // // Intent gets called when the Notification gets fired
+        // Intent intent = new Intent(context, receiver)
+        //         .setAction(options.getIdStr())
+        //         .putExtra(Options.EXTRA, options.toString());
 
-        PendingIntent pi = PendingIntent.getBroadcast(
-                context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+        // PendingIntent pi = PendingIntent.getBroadcast(
+        //         context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
 
-        if (isRepeating()) {
-            getAlarmMgr().setRepeating(AlarmManager.RTC_WAKEUP,
-                    triggerTime, options.getRepeatInterval(), pi);
-        } else {
-            getAlarmMgr().set(AlarmManager.RTC_WAKEUP, triggerTime, pi);
-        }
+        // if (isRepeating()) {
+        //     getAlarmMgr().setRepeating(AlarmManager.RTC_WAKEUP,
+        //             triggerTime, options.getRepeatInterval(), pi);
+        // } else {
+        //     getAlarmMgr().set(AlarmManager.RTC_WAKEUP, triggerTime, pi);
+        // }
     }
 
-    /**
-     * Clear the local notification without canceling repeating alarms.
-     */
-    public void clear () {
+    // /**
+    //  * Clear the local notification without canceling repeating alarms.
+    //  */
+    // public void clear () {
 
-        if (!isRepeating() && wasInThePast())
-            unpersist();
+    //     if (!isRepeating() && wasInThePast())
+    //         unpersist();
 
-        if (!isRepeating())
-            getNotMgr().cancel(getId());
-    }
+    //     if (!isRepeating())
+    //         getNotMgr().cancel(getId());
+    // }
 
-    /**
-     * Cancel the local notification.
-     *
-     * Create an intent that looks similar, to the one that was registered
-     * using schedule. 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.
-     */
-    public void cancel() {
-        Intent intent = new Intent(context, receiver)
-                .setAction(options.getIdStr());
+    // /**
+    //  * Cancel the local notification.
+    //  *
+    //  * Create an intent that looks similar, to the one that was registered
+    //  * using schedule. 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.
+    //  */
+    // public void cancel() {
+    //     Intent intent = new Intent(context, receiver)
+    //             .setAction(options.getIdStr());
 
-        PendingIntent pi = PendingIntent.
-                getBroadcast(context, 0, intent, 0);
+    //     PendingIntent pi = PendingIntent.
+    //             getBroadcast(context, 0, intent, 0);
 
-        getAlarmMgr().cancel(pi);
-        getNotMgr().cancel(options.getId());
+    //     getAlarmMgr().cancel(pi);
+    //     getNotMgr().cancel(options.getId());
 
-        unpersist();
-    }
+    //     unpersist();
+    // }
 
     /**
      * Present the local notification to user.
@@ -225,87 +227,80 @@ public class Notification {
     /**
      * Show as local notification when in background.
      */
-    @SuppressWarnings("deprecation")
     private void showNotification () {
-        int id = getOptions().getId();
-
-        if (Build.VERSION.SDK_INT <= 15) {
-            // Notification for HoneyComb to ICS
-            getNotMgr().notify(id, builder.getNotification());
-        } else {
-            // Notification for Jellybean and above
-            getNotMgr().notify(id, builder.build());
-        }
-    }
-
-    /**
-     * Count of triggers since schedule.
-     */
-    public int getTriggerCountSinceSchedule() {
-        long now = System.currentTimeMillis();
-        long triggerTime = options.getTriggerTime();
-
-        if (!wasInThePast())
-            return 0;
-
-        if (!isRepeating())
-            return 1;
-
-        return (int) ((now - triggerTime) / options.getRepeatInterval());
-    }
-
-    /**
-     * Encode options to JSON.
-     */
-    public String toString() {
-        JSONObject dict = options.getDict();
-        JSONObject json = new JSONObject();
-
-        try {
-            json = new JSONObject(dict.toString());
-        } catch (JSONException e) {
-            e.printStackTrace();
-        }
-
-        json.remove("firstAt");
-        json.remove("updated");
-        json.remove("soundUri");
-        json.remove("iconUri");
-
-        return json.toString();
-    }
-
-    /**
-     * Persist the information of this notification to the Android Shared
-     * Preferences. This will allow the application to restore the notification
-     * upon device reboot, app restart, retrieve notifications, aso.
-     */
-    private void persist () {
-        SharedPreferences.Editor editor = getPrefs().edit();
-
-        editor.putString(options.getIdStr(), options.toString());
-
-        if (Build.VERSION.SDK_INT < 9) {
-            editor.commit();
-        } else {
-            editor.apply();
+        if (builder != null) {
+            getNotMgr().notify(getId(), builder.build());
         }
     }
 
-    /**
-     * Remove the notification from the Android shared Preferences.
-     */
-    private void unpersist () {
-        SharedPreferences.Editor editor = getPrefs().edit();
-
-        editor.remove(options.getIdStr());
-
-        if (Build.VERSION.SDK_INT < 9) {
-            editor.commit();
-        } else {
-            editor.apply();
-        }
-    }
+    // /**
+    //  * Count of triggers since schedule.
+    //  */
+    // public int getTriggerCountSinceSchedule() {
+    //     long now = System.currentTimeMillis();
+    //     long triggerTime = options.getTriggerTime();
+
+    //     if (!wasInThePast())
+    //         return 0;
+
+    //     if (!isRepeating())
+    //         return 1;
+
+    //     return (int) ((now - triggerTime) / options.getRepeatInterval());
+    // }
+
+    // /**
+    //  * Encode options to JSON.
+    //  */
+    // public String toString() {
+    //     JSONObject dict = options.getDict();
+    //     JSONObject json = new JSONObject();
+
+    //     try {
+    //         json = new JSONObject(dict.toString());
+    //     } catch (JSONException e) {
+    //         e.printStackTrace();
+    //     }
+
+    //     json.remove("firstAt");
+    //     json.remove("updated");
+    //     json.remove("soundUri");
+    //     json.remove("iconUri");
+
+    //     return json.toString();
+    // }
+
+    // /**
+    //  * Persist the information of this notification to the Android Shared
+    //  * Preferences. This will allow the application to restore the notification
+    //  * upon device reboot, app restart, retrieve notifications, aso.
+    //  */
+    // private void persist () {
+    //     SharedPreferences.Editor editor = getPrefs().edit();
+
+    //     editor.putString(options.getIdStr(), options.toString());
+
+    //     if (Build.VERSION.SDK_INT < 9) {
+    //         editor.commit();
+    //     } else {
+    //         editor.apply();
+    //     }
+    // }
+
+    // /**
+    //  * Remove the notification from the Android shared Preferences.
+    //  */
+    // private void unpersist () {
+    //     SharedPreferences.Editor editor = getPrefs().edit();
+
+    //     editor.remove(options.getIdStr());
+
+    //     if (Build.VERSION.SDK_INT < 9) {
+    //         editor.commit();
+    //     } else {
+    //         editor.apply();
+    //     }
+    // }
 
     /**
      * Shared private preferences for the application.
@@ -329,14 +324,4 @@ public class Notification {
         return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
     }
 
-    /**
-     * Set default receiver to handle the trigger event.
-     *
-     * @param receiver
-     *      broadcast receiver
-     */
-    public static void setDefaultTriggerReceiver (Class<?> receiver) {
-        defaultReceiver = receiver;
-    }
-
 }

+ 234 - 154
src/android/notification/Options.java

@@ -21,16 +21,23 @@
 
 package de.appplant.cordova.plugin.notification;
 
-import android.app.AlarmManager;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.support.v4.app.NotificationCompat;
 
-import org.json.JSONException;
+import org.json.JSONArray;
 import org.json.JSONObject;
 
-import java.util.Date;
+import de.appplant.cordova.plugin.notification.util.AssetUtil;
+
+import static android.support.v4.app.NotificationCompat.DEFAULT_LIGHTS;
+import static android.support.v4.app.NotificationCompat.DEFAULT_SOUND;
+import static android.support.v4.app.NotificationCompat.DEFAULT_VIBRATE;
+import static android.support.v4.app.NotificationCompat.PRIORITY_MAX;
+import static android.support.v4.app.NotificationCompat.PRIORITY_MIN;
+import static android.support.v4.app.NotificationCompat.VISIBILITY_PUBLIC;
+import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
 
 /**
  * Wrapper around the JSON object passed through JS which contains all
@@ -40,119 +47,73 @@ import java.util.Date;
 public class Options {
 
     // Key name for bundled extras
-    static final String EXTRA = "NOTIFICATION_OPTIONS";
+    public static final String EXTRA = "NOTIFICATION_OPTIONS";
 
-    // The original JSON object
-    private JSONObject options = new JSONObject();
+    // Default icon path
+    private static final String DEFAULT_ICON = "res://icon";
 
-    // Repeat interval
-    private long interval = 0;
+    // The original JSON object
+    private final JSONObject options;
 
-    // Application context
+    // The application context
     private final Context context;
 
     // Asset util instance
     private final AssetUtil assets;
 
-
     /**
-     * Constructor
+     * When creating without a context, various methods might not work well.
      *
-     * @param context
-     *      Application context
+     * @param options The options dict map.
      */
-    public Options(Context context){
-    	this.context = context;
-        this.assets  = AssetUtil.getInstance(context);
+    public Options(JSONObject options) {
+        this.options = options;
+        this.context = null;
+        this.assets  = null;
     }
 
     /**
-     * Parse given JSON properties.
+     * Constructor
      *
-     * @param options
-     *      JSON properties
+     * @param context The application context.
+     * @param options The options dict map.
      */
-    public Options parse (JSONObject options) {
+    public Options(Context context, JSONObject options) {
+        this.context = context;
         this.options = options;
-
-        parseInterval();
-        parseAssets();
-
-        return this;
+        this.assets  = AssetUtil.getInstance(context);
     }
 
     /**
-     * Parse repeat interval.
+     * Application context.
      */
-    private void parseInterval() {
-        String every = options.optString("every").toLowerCase();
-
-        if (every.isEmpty()) {
-            interval = 0;
-        } else
-        if (every.equals("second")) {
-            interval = 1000;
-        } else
-        if (every.equals("minute")) {
-            interval = AlarmManager.INTERVAL_FIFTEEN_MINUTES / 15;
-        } else
-        if (every.equals("hour")) {
-            interval = AlarmManager.INTERVAL_HOUR;
-        } else
-        if (every.equals("day")) {
-            interval = AlarmManager.INTERVAL_DAY;
-        } else
-        if (every.equals("week")) {
-            interval = AlarmManager.INTERVAL_DAY * 7;
-        } else
-        if (every.equals("month")) {
-            interval = AlarmManager.INTERVAL_DAY * 31;
-        } else
-        if (every.equals("quarter")) {
-            interval = AlarmManager.INTERVAL_HOUR * 2190;
-        } else
-        if (every.equals("year")) {
-            interval = AlarmManager.INTERVAL_DAY * 365;
-        } else {
-            try {
-                interval = Integer.parseInt(every) * 60000;
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-        }
+    public Context getContext () {
+        return context;
     }
 
     /**
-     * Parse asset URIs.
+     * Wrapped JSON object.
      */
-    private void parseAssets() {
-
-        if (options.has("iconUri") && !options.optBoolean("updated"))
-            return;
-
-        Uri iconUri  = assets.parse(options.optString("icon", "res://icon"));
-        Uri soundUri = assets.parseSound(options.optString("sound", null));
-
-        try {
-            options.put("iconUri", iconUri.toString());
-            options.put("soundUri", soundUri.toString());
-        } catch (JSONException e) {
-            e.printStackTrace();
-        }
+    JSONObject getDict () {
+        return options;
     }
 
     /**
-     * Application context.
+     * Gets the ID for the local notification.
+     *
+     * @return 0 if the user did not specify.
      */
-    public Context getContext () {
-        return context;
+    public Integer getId() {
+        return options.optInt("id", 0);
     }
 
     /**
-     * Wrapped JSON object.
+     * The identifier for the local notification.
+     *
+     * @return The notification ID as the string
      */
-    JSONObject getDict () {
-        return options;
+    public String getIdentifier() {
+        return getId().toString();
     }
 
     /**
@@ -162,62 +123,27 @@ public class Options {
         return options.optString("text", "");
     }
 
-    /**
-     * Repeat interval (day, week, month, year, aso.)
-     */
-    public long getRepeatInterval() {
-        return interval;
-    }
-
     /**
      * Badge number for the local notification.
      */
-    public int getBadgeNumber() {
+    int getBadgeNumber() {
         return options.optInt("badge", 0);
     }
 
     /**
      * ongoing flag for local notifications.
      */
-    public Boolean isOngoing() {
-        return options.optBoolean("ongoing", false);
+    Boolean isSticky() {
+        return options.optBoolean("sticky", false);
     }
 
     /**
      * autoClear flag for local notifications.
      */
-    public Boolean isAutoClear() {
+    Boolean isAutoClear() {
         return options.optBoolean("autoClear", false);
     }
 
-    /**
-     * ID for the local notification as a number.
-     */
-    public Integer getId() {
-        return options.optInt("id", 0);
-    }
-
-    /**
-     * ID for the local notification as a string.
-     */
-    public String getIdStr() {
-        return getId().toString();
-    }
-
-    /**
-     * Trigger date.
-     */
-    public Date getTriggerDate() {
-        return new Date(getTriggerTime());
-    }
-
-    /**
-     * Trigger date in milliseconds.
-     */
-    public long getTriggerTime() {
-        return options.optLong("at", 0) * 1000;
-    }
-
     /**
      * Title for the local notification.
      */
@@ -233,65 +159,99 @@ public class Options {
     }
 
     /**
-     * @return
-     *      The notification color for LED
+     * The notification color for LED.
      */
-    public int getLedColor() {
-        String hex = options.optString("led", null);
+    int getLedColor() {
+        Object cfg = options.opt("led");
+        String hex = null;
+
+        if (cfg instanceof String) {
+            hex = options.optString("led");
+        } else
+        if (cfg instanceof JSONArray) {
+            hex = options.optJSONArray("led").optString(0);
+        }
 
-        if (hex == null) {
+        if (hex == null)
             return 0;
+
+        try {
+            hex      = stripHex(hex);
+            int aRGB = Integer.parseInt(hex, 16);
+
+            return aRGB + 0xFF000000;
+        } catch (NumberFormatException e) {
+            e.printStackTrace();
         }
 
-        int aRGB = Integer.parseInt(hex, 16);
+        return 0;
+    }
+
+    /**
+     * The notification color for LED.
+     */
+    int getLedOn() {
+        Object cfg = options.opt("led");
+
+        if (cfg instanceof JSONArray)
+            return options.optJSONArray("led").optInt(1);
 
-        return aRGB + 0xFF000000;
+        return 1000;
     }
 
     /**
-     * @return
-     *      The notification background color for the small icon
-     *      Returns null, if no color is given.
+     * The notification color for LED.
+     */
+    int getLedOff() {
+        Object cfg = options.opt("led");
+
+        if (cfg instanceof JSONArray)
+            return options.optJSONArray("led").optInt(2);
+
+        return 1000;
+    }
+
+    /**
+     * The notification background color for the small icon.
+     *
+     * @return null, if no color is given.
      */
     public int getColor() {
         String hex = options.optString("color", null);
 
-        if (hex == null) {
+        if (hex == null)
             return NotificationCompat.COLOR_DEFAULT;
-        }
 
-        int aRGB = Integer.parseInt(hex, 16);
+        try {
+            hex      = stripHex(hex);
+            int aRGB = Integer.parseInt(hex, 16);
+
+            return aRGB + 0xFF000000;
+        } catch (NumberFormatException e) {
+            e.printStackTrace();
+        }
 
-        return aRGB + 0xFF000000;
+        return NotificationCompat.COLOR_DEFAULT;
     }
 
     /**
      * Sound file path for the local notification.
      */
-    public Uri getSoundUri() {
-        Uri uri = null;
-
-        try{
-            uri = Uri.parse(options.optString("soundUri"));
-        } catch (Exception e){
-            e.printStackTrace();
-        }
-
-        return uri;
+    public Uri getSound() {
+        return assets.parse(options.optString("sound"));
     }
 
     /**
      * Icon bitmap for the local notification.
      */
-    public Bitmap getIconBitmap() {
-        Bitmap bmp;
+    Bitmap getLargeIcon() {
+        Uri uri    = assets.parse(options.optString("icon", DEFAULT_ICON));
+        Bitmap bmp = null;
 
         try {
-            Uri uri = Uri.parse(options.optString("iconUri"));
             bmp = assets.getIconFromUri(uri);
         } catch (Exception e){
             e.printStackTrace();
-            bmp = assets.getIconFromDrawable("icon");
         }
 
         return bmp;
@@ -301,14 +261,18 @@ public class Options {
      * Icon resource ID for the local notification.
      */
     public int getIcon () {
-        String icon = options.optString("icon", "");
+        String icon = options.optString("icon", DEFAULT_ICON);
 
-        int resId = assets.getResIdForDrawable(icon);
+        int resId = assets.getResId(icon);
 
         if (resId == 0) {
             resId = getSmallIcon();
         }
 
+        if (resId == 0) {
+            resId = assets.getResId(DEFAULT_ICON);
+        }
+
         if (resId == 0) {
             resId = android.R.drawable.ic_popup_reminder;
         }
@@ -319,10 +283,115 @@ public class Options {
     /**
      * Small icon resource ID for the local notification.
      */
-    public int getSmallIcon () {
+    int getSmallIcon() {
         String icon = options.optString("smallIcon", "");
+        return assets.getResId(icon);
+    }
+
+    /**
+     * If the phone should vibrate.
+     */
+    private boolean isWithVibration() {
+        return options.optBoolean("vibrate", true);
+    }
+
+    /**
+     * If the phone should play no sound.
+     */
+    private boolean isWithoutSound() {
+        Object value = options.opt("sound");
+        return value == null || value.equals(false);
+    }
+
+    /**
+     * If the phone should play the default sound.
+     */
+    private boolean isWithDefaultSound() {
+        Object value = options.opt("sound");
+        return value != null && value.equals(true);
+    }
+
+    /**
+     * If the phone should show no LED light.
+     */
+    private boolean isWithoutLights() {
+        Object value = options.opt("led");
+        return value == null || value.equals(false);
+    }
 
-        return assets.getResIdForDrawable(icon);
+    /**
+     * If the phone should show the default LED lights.
+     */
+    private boolean isWithDefaultLights() {
+        Object value = options.opt("led");
+        return value != null && value.equals(true);
+    }
+
+    /**
+     * Set the default notification options that will be used.
+     * The value should be one or more of the following fields combined with
+     * bitwise-or: DEFAULT_SOUND, DEFAULT_VIBRATE, DEFAULT_LIGHTS.
+     */
+    int getDefaults() {
+        int defaults = options.optInt("defaults", 0);
+
+        if (isWithVibration()) {
+            defaults |= DEFAULT_VIBRATE;
+        } else {
+            defaults &= DEFAULT_VIBRATE;
+        }
+
+        if (isWithDefaultSound()) {
+            defaults |= DEFAULT_SOUND;
+        } else
+        if (isWithoutSound()) {
+            defaults &= DEFAULT_SOUND;
+        }
+
+        if (isWithDefaultLights()) {
+            defaults |= DEFAULT_LIGHTS;
+        } else
+        if (isWithoutLights()) {
+            defaults &= DEFAULT_LIGHTS;
+        }
+
+        return defaults;
+    }
+
+    /**
+     * Gets the visibility for the notification.
+     *
+     * @return VISIBILITY_PUBLIC or VISIBILITY_SECRET
+     */
+    int getVisibility() {
+        if (options.optBoolean("lockscreen", true)) {
+            return VISIBILITY_PUBLIC;
+        } else {
+            return VISIBILITY_SECRET;
+        }
+    }
+
+    /**
+     * Gets the notifications priority.
+     */
+    int getPriority() {
+        int prio = options.optInt("priority");
+
+        return Math.min(Math.max(prio, PRIORITY_MIN), PRIORITY_MAX);
+    }
+
+    /**
+     * If the notification shall show the when date.
+     */
+    boolean getShowWhen() {
+        return options.optBoolean("showWhen", true);
+    }
+
+    /**
+     * Gets the raw trigger spec as provided by the user.
+     */
+    public JSONObject getTrigger() {
+        return options.optJSONObject("trigger");
     }
 
     /**
@@ -332,4 +401,15 @@ public class Options {
         return options.toString();
     }
 
+    /**
+     * Strips the hex code #FF00FF => FF00FF
+     *
+     * @param hex The hex code to strip.
+     *
+     * @return The stripped hex code without a leading #
+     */
+    private String stripHex(String hex) {
+        return (hex.charAt(0) == '#') ? hex.substring(1) : hex;
+    }
+
 }

+ 224 - 0
src/android/notification/Request.java

@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2014-2015 by appPlant UG. All rights reserved.
+ *
+ * @APPPLANT_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apache License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://opensource.org/licenses/Apache-2.0/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPPLANT_LICENSE_HEADER_END@
+ */
+
+package de.appplant.cordova.plugin.notification;
+
+import org.json.JSONObject;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import de.appplant.cordova.plugin.notification.trigger.MatchTrigger;
+import de.appplant.cordova.plugin.notification.trigger.DateTrigger;
+import de.appplant.cordova.plugin.notification.trigger.IntervalTrigger;
+
+import static de.appplant.cordova.plugin.notification.trigger.IntervalTrigger.Unit;
+
+public class Request {
+
+    // Key name for bundled extras
+    static final String EXTRA = "NOTIFICATION_OCCURRENCE_EXTRA";
+
+    // The options spec
+    private final Options options;
+
+    // The right trigger for the options
+    private final DateTrigger trigger;
+
+    // How often the trigger shall occur
+    private final int count;
+
+    // The trigger spec
+    private final JSONObject spec;
+
+    // The current trigger date
+    private Date triggerDate;
+
+    /**
+     * Constructor
+     *
+     * @param options The options spec.
+     */
+    public Request(Options options) {
+        this.options     = options;
+        this.spec        = options.getTrigger();
+        this.count       = spec.optInt("count", 1);
+        this.trigger     = buildTrigger();
+        this.triggerDate = trigger.getNextTriggerDate(getBaseDate());
+    }
+
+    /**
+     * Gets the options spec.
+     */
+    public Options getOptions() {
+        return options;
+    }
+
+    /**
+     * The identifier for the request.
+     *
+     * @return The notification ID as the string
+     */
+    public String getIdentifier() {
+        return options.getId().toString() + "-" + getOccurrence();
+    }
+
+    /**
+     * The value of the internal occurrence counter.
+     */
+    int getOccurrence() {
+        return trigger.getOccurrence();
+    }
+
+    /**
+     * If there's one more trigger date to calculate.
+     */
+    private boolean hasNext() {
+        return triggerDate != null && getOccurrence() < count;
+    }
+
+    /**
+     * Moves the internal occurrence counter by one.
+     */
+    boolean moveNext() {
+        if (hasNext()) {
+            triggerDate = getNextTriggerDate();
+        } else {
+            triggerDate = null;
+        }
+
+        return this.triggerDate != null;
+    }
+
+    /**
+     * Gets the current trigger date.
+     *
+     * @return null if there's no trigger date.
+     */
+    Date getTriggerDate() {
+        Calendar now = Calendar.getInstance();
+
+        if (triggerDate == null)
+            return null;
+
+        if ((now.getTimeInMillis() - triggerDate.getTime()) > 60000) {
+            return null;
+        }
+
+        return triggerDate;
+    }
+
+    /**
+     * Gets the next trigger date based on the current trigger date.
+     */
+    private Date getNextTriggerDate() {
+        return trigger.getNextTriggerDate(triggerDate);
+    }
+
+    /**
+     * Build the trigger specified in options.
+     */
+    private DateTrigger buildTrigger() {
+        Object every = spec.opt("every");
+
+        if (every instanceof JSONObject) {
+            return new MatchTrigger(getDateMatchingComponents());
+        }
+
+        Unit unit = getUnit();
+        int ticks = getTicks();
+
+        return new IntervalTrigger(ticks, unit);
+    }
+
+    /**
+     * Gets the unit value.
+     */
+    private Unit getUnit() {
+        Object every = spec.opt("every");
+        String unit  = "SECOND";
+
+        if (spec.has("unit")) {
+            unit = spec.optString("unit", "second");
+        } else
+        if (every instanceof String) {
+            unit = spec.optString("every", "second");
+        }
+
+        return Unit.valueOf(unit.toUpperCase());
+    }
+
+    /**
+     * Gets the tick value.
+     */
+    private int getTicks() {
+        Object every = spec.opt("every");
+        int ticks    = 0;
+
+        if (spec.has("at")) {
+            ticks = 0;
+        } else
+        if (spec.has("in")) {
+            ticks = spec.optInt("in", 0);
+        } else
+        if (every instanceof String) {
+            ticks = 1;
+        } else
+        if (!(every instanceof JSONObject)) {
+            ticks = spec.optInt("every", 0);
+        }
+
+        return ticks;
+    }
+
+    /**
+     * Gets an array of all date parts to construct a datetime instance.
+     *
+     * @return [min, hour, day, month, year]
+     */
+    private List<Integer> getDateMatchingComponents() {
+        JSONObject every = spec.optJSONObject("every");
+
+        return Arrays.asList(
+                (Integer) every.opt("minute"),
+                (Integer) every.opt("hour"),
+                (Integer) every.opt("day"),
+                (Integer) every.opt("month"),
+                (Integer) every.opt("year")
+        );
+    }
+
+    /**
+     * Gets the base date from where to calculate the next trigger date.
+     */
+    private Date getBaseDate() {
+        if (spec.has("at")) {
+            return new Date(spec.optLong("at", 0) * 1000);
+        } else {
+            return new Date();
+        }
+    }
+
+}

+ 10 - 19
src/android/notification/AbstractClickActivity.java → src/android/notification/activity/AbstractClickActivity.java

@@ -19,7 +19,7 @@
  * limitations under the License.
  */
 
-package de.appplant.cordova.plugin.notification;
+package de.appplant.cordova.plugin.notification.activity;
 
 import android.app.Activity;
 import android.content.Context;
@@ -29,6 +29,9 @@ import android.os.Bundle;
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import de.appplant.cordova.plugin.notification.Notification;
+import de.appplant.cordova.plugin.notification.Options;
+
 /**
  * Abstract content receiver activity for local notifications. Creates the
  * local notification and calls the event functions for further proceeding.
@@ -38,8 +41,7 @@ abstract public class AbstractClickActivity extends Activity {
     /**
      * Called when local notification was clicked to launch the main intent.
      *
-     * @param state
-     *      Saved instance state
+     * @param state Saved instance state
      */
     @Override
     public void onCreate (Bundle state) {
@@ -50,14 +52,12 @@ abstract public class AbstractClickActivity extends Activity {
         Context context = getApplicationContext();
 
         try {
-            String data = bundle.getString(Options.EXTRA);
-            JSONObject options = new JSONObject(data);
-
-            Builder builder =
-                    new Builder(context, options);
+            String data     = bundle.getString(Options.EXTRA);
+            JSONObject dict = new JSONObject(data);
+            Options options = new Options(context, dict);
 
             Notification notification =
-                    buildNotification(builder);
+                    new Notification(context, options);
 
             onClick(notification);
         } catch (JSONException e) {
@@ -78,19 +78,10 @@ abstract public class AbstractClickActivity extends Activity {
     /**
      * Called when local notification was clicked by the user.
      *
-     * @param notification
-     *      Wrapper around the local notification
+     * @param notification Wrapper around the local notification
      */
     abstract public void onClick (Notification notification);
 
-    /**
-     * Build notification specified by options.
-     *
-     * @param builder
-     *      Notification builder
-     */
-    abstract public Notification buildNotification (Builder builder);
-
     /**
      * Launch main intent from package.
      */

+ 9 - 18
src/android/notification/ClickActivity.java → src/android/notification/activity/ClickActivity.java

@@ -19,7 +19,9 @@
  * limitations under the License.
  */
 
-package de.appplant.cordova.plugin.notification;
+package de.appplant.cordova.plugin.notification.activity;
+
+import de.appplant.cordova.plugin.notification.Notification;
 
 /**
  * The receiver activity is triggered when a notification is clicked by a user.
@@ -32,28 +34,17 @@ public class ClickActivity extends AbstractClickActivity {
      * Called when local notification was clicked by the user. Will
      * move the app to foreground.
      *
-     * @param notification
-     *      Wrapper around the local notification
+     * @param notification Wrapper around the local notification
      */
     @Override
     public void onClick(Notification notification) {
         launchApp();
 
-        if (notification.isRepeating()) {
-            notification.clear();
-        } else {
-            notification.cancel();
-        }
-    }
-
-    /**
-     * Build notification specified by options.
-     *
-     * @param builder
-     *      Notification builder
-     */
-    public Notification buildNotification (Builder builder) {
-        return builder.build();
+        // if (notification.isRepeating()) {
+        //     notification.clear();
+        // } else {
+        //     notification.cancel();
+        // }
     }
 
 }

+ 15 - 16
src/android/notification/AbstractClearReceiver.java → src/android/notification/receiver/AbstractClearReceiver.java

@@ -19,7 +19,7 @@
  * limitations under the License.
  */
 
-package de.appplant.cordova.plugin.notification;
+package de.appplant.cordova.plugin.notification.receiver;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -29,6 +29,9 @@ import android.os.Bundle;
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import de.appplant.cordova.plugin.notification.Notification;
+import de.appplant.cordova.plugin.notification.Options;
+
 /**
  * Abstract delete receiver for local notifications. Creates the local
  * notification and calls the event functions for further proceeding.
@@ -38,35 +41,31 @@ abstract public class AbstractClearReceiver extends BroadcastReceiver {
     /**
      * Called when the notification was cleared from the notification center.
      *
-     * @param context
-     *      Application context
-     * @param intent
-     *      Received intent with content data
+     * @param context Application context
+     * @param intent  Received intent with content data
      */
     @Override
     public void onReceive(Context context, Intent intent) {
         Bundle bundle  = intent.getExtras();
-        JSONObject options;
+        String data    = bundle.getString(Options.EXTRA);
 
         try {
-            String data = bundle.getString(Options.EXTRA);
-            options = new JSONObject(data);
+            JSONObject dict = new JSONObject(data);
+            Options options = new Options(context, dict);
+
+            Notification notification =
+                    new Notification(context, options);
+
+            onClear(notification);
         } catch (JSONException e) {
             e.printStackTrace();
-            return;
         }
-
-        Notification notification =
-                new Builder(context, options).build();
-
-        onClear(notification);
     }
 
     /**
      * Called when a local notification was cleared from outside of the app.
      *
-     * @param notification
-     *      Wrapper around the local notification
+     * @param notification Wrapper around the local notification
      */
     abstract public void onClear (Notification notification);
 

+ 0 - 0
src/android/notification/AbstractRestoreReceiver.java → src/android/notification/receiver/AbstractRestoreReceiver.java


+ 12 - 47
src/android/notification/AbstractTriggerReceiver.java → src/android/notification/receiver/AbstractTriggerReceiver.java

@@ -19,7 +19,7 @@
  * limitations under the License.
  */
 
-package de.appplant.cordova.plugin.notification;
+package de.appplant.cordova.plugin.notification.receiver;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -29,7 +29,9 @@ import android.os.Bundle;
 import org.json.JSONException;
 import org.json.JSONObject;
 
-import java.util.Calendar;
+import de.appplant.cordova.plugin.notification.Builder;
+import de.appplant.cordova.plugin.notification.Notification;
+import de.appplant.cordova.plugin.notification.Options;
 
 /**
  * Abstract broadcast receiver for local notifications. Creates the
@@ -40,10 +42,8 @@ abstract public class AbstractTriggerReceiver extends BroadcastReceiver {
     /**
      * Called when an alarm was triggered.
      *
-     * @param context
-     *      Application context
-     * @param intent
-     *      Received intent with content data
+     * @param context Application context
+     * @param intent  Received intent with content data
      */
     @Override
     public void onReceive(Context context, Intent intent) {
@@ -54,21 +54,15 @@ abstract public class AbstractTriggerReceiver extends BroadcastReceiver {
             String data = bundle.getString(Options.EXTRA);
             JSONObject dict = new JSONObject(data);
 
-            options = new Options(context).parse(dict);
+            options = new Options(context, dict);
         } catch (JSONException e) {
             e.printStackTrace();
             return;
         }
 
-        if (options == null)
-            return;
-
-        if (isFirstAlarmInFuture(options))
-            return;
-
-        Builder builder = new Builder(options);
+        Builder builder           = new Builder(options);
         Notification notification = buildNotification(builder);
-        boolean updated = notification.isUpdate(false);
+        boolean updated           = false;// notification.isUpdate(false);
 
         onTrigger(notification, updated);
     }
@@ -76,45 +70,16 @@ abstract public class AbstractTriggerReceiver extends BroadcastReceiver {
     /**
      * Called when a local notification was triggered.
      *
-     * @param notification
-     *      Wrapper around the local notification
-     * @param updated
-     *      If an update has triggered or the original
+     * @param notification  Wrapper around the local notification
+     * @param updated       If an update has triggered or the original
      */
     abstract public void onTrigger (Notification notification, boolean updated);
 
     /**
      * Build notification specified by options.
      *
-     * @param builder
-     *      Notification builder
+     * @param builder Notification builder
      */
     abstract public Notification buildNotification (Builder builder);
 
-    /*
-     * 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'.
-     */
-    private Boolean isFirstAlarmInFuture (Options options) {
-        Notification notification = new Builder(options).build();
-
-        if (!notification.isRepeating())
-            return false;
-
-        Calendar now    = Calendar.getInstance();
-        Calendar alarm  = Calendar.getInstance();
-
-        alarm.setTime(notification.getOptions().getTriggerDate());
-
-        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);
-
-        return (currentHour != alarmHour && currentMin != alarmMin);
-    }
-
 }

+ 5 - 4
src/android/notification/ClearReceiver.java → src/android/notification/receiver/ClearReceiver.java

@@ -19,7 +19,9 @@
  * limitations under the License.
  */
 
-package de.appplant.cordova.plugin.notification;
+package de.appplant.cordova.plugin.notification.receiver;
+
+import de.appplant.cordova.plugin.notification.Notification;
 
 /**
  * The clear intent receiver is triggered when the user clears a
@@ -31,12 +33,11 @@ public class ClearReceiver extends AbstractClearReceiver {
     /**
      * Called when a local notification was cleared from outside of the app.
      *
-     * @param notification
-     *      Wrapper around the local notification
+     * @param notification Wrapper around the local notification
      */
     @Override
     public void onClear (Notification notification) {
-        notification.clear();
+        // notification.clear();
     }
 
 }

+ 7 - 7
src/android/notification/TriggerReceiver.java → src/android/notification/receiver/TriggerReceiver.java

@@ -19,7 +19,10 @@
  * limitations under the License.
  */
 
-package de.appplant.cordova.plugin.notification;
+package de.appplant.cordova.plugin.notification.receiver;
+
+import de.appplant.cordova.plugin.notification.Builder;
+import de.appplant.cordova.plugin.notification.Notification;
 
 /**
  * The alarm receiver is triggered when a scheduled alarm is fired. This class
@@ -33,10 +36,8 @@ public class TriggerReceiver extends AbstractTriggerReceiver {
      * Called when a local notification was triggered. Does present the local
      * notification and re-schedule the alarm if necessary.
      *
-     * @param notification
-     *      Wrapper around the local notification
-     * @param updated
-     *      If an update has triggered or the original
+     * @param notification Wrapper around the local notification
+     * @param updated      If an update has triggered or the original
      */
     @Override
     public void onTrigger (Notification notification, boolean updated) {
@@ -46,8 +47,7 @@ public class TriggerReceiver extends AbstractTriggerReceiver {
     /**
      * Build notification specified by options.
      *
-     * @param builder
-     *      Notification builder
+     * @param builder Notification builder
      */
     @Override
     public Notification buildNotification (Builder builder) {

+ 70 - 0
src/android/notification/trigger/DateTrigger.java

@@ -0,0 +1,70 @@
+/*
+ * Apache 2.0 License
+ *
+ * Copyright (c) Sebastian Katzer 2017
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apache License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://opensource.org/licenses/Apache-2.0/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ */
+
+package de.appplant.cordova.plugin.notification.trigger;
+
+import java.util.Calendar;
+import java.util.Date;
+
+abstract public class DateTrigger {
+
+    // Default unit is SECOND
+    public enum Unit { SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, QUARTER, YEAR }
+
+    // Internal counter
+    private int occurrence = 1;
+
+    /**
+     * Gets the next trigger date.
+     *
+     * @param base The date from where to calculate the trigger date.
+     *
+     * @return null if there's none next trigger date.
+     */
+    abstract public Date getNextTriggerDate(Date base);
+
+    /**
+     * The value of the occurrence.
+     */
+    public int getOccurrence() {
+        return occurrence;
+    }
+
+    /**
+     * Increase the occurrence by 1.
+     */
+    void incOccurrence() {
+        occurrence += 1;
+    }
+
+    /**
+     * Gets a calendar instance pointing to the specified date.
+     *
+     * @param date The date to point.
+     */
+    Calendar getCal (Date date) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+
+        return cal;
+    }
+
+}

+ 105 - 0
src/android/notification/trigger/IntervalTrigger.java

@@ -0,0 +1,105 @@
+/*
+ * Apache 2.0 License
+ *
+ * Copyright (c) Sebastian Katzer 2017
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apache License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://opensource.org/licenses/Apache-2.0/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ */
+
+package de.appplant.cordova.plugin.notification.trigger;
+
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * Trigger class for interval based notification. Trigger by a fixed interval
+ * from now.
+ */
+public class IntervalTrigger extends DateTrigger {
+
+    // The number of ticks per interval
+    private final int ticks;
+
+    // The unit of the ticks
+    final Unit unit;
+
+    /**
+     * Interval trigger based from now.
+     *
+     * @param ticks The number of ticks per interval.
+     * @param unit  The unit of the ticks.
+     */
+    public IntervalTrigger(int ticks, Unit unit) {
+        this.ticks = ticks;
+        this.unit  = unit;
+    }
+
+    /**
+     * Gets the next trigger date.
+     *
+     * @param base The date from where to calculate the trigger date.
+     *
+     * @return null if there's none next trigger date.
+     */
+    @Override
+    public Date getNextTriggerDate(Date base) {
+        Calendar cal = getCal(base);
+
+        addInterval(cal);
+        incOccurrence();
+
+        return cal.getTime();
+    }
+
+    /**
+     * Adds the amount of ticks to the calendar.
+     *
+     * @param cal The calendar to manipulate.
+     *
+     * @return The calendar instance.
+     */
+    Calendar addInterval(Calendar cal) {
+        switch (unit) {
+            case SECOND:
+                cal.add(Calendar.SECOND, ticks);
+                break;
+            case MINUTE:
+                cal.add(Calendar.MINUTE, ticks);
+                break;
+            case HOUR:
+                cal.add(Calendar.HOUR_OF_DAY, ticks);
+                break;
+            case DAY:
+                cal.add(Calendar.DAY_OF_YEAR, ticks);
+                break;
+            case WEEK:
+                cal.add(Calendar.WEEK_OF_YEAR, ticks);
+                break;
+            case MONTH:
+                cal.add(Calendar.MONTH, ticks);
+                break;
+            case QUARTER:
+                cal.add(Calendar.MONTH, ticks * 3);
+                break;
+            case YEAR:
+                cal.add(Calendar.YEAR, ticks);
+                break;
+        }
+
+        return cal;
+    }
+
+}

+ 208 - 0
src/android/notification/trigger/MatchTrigger.java

@@ -0,0 +1,208 @@
+/*
+ * Apache 2.0 License
+ *
+ * Copyright (c) Sebastian Katzer 2017
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apache License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://opensource.org/licenses/Apache-2.0/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ */
+
+package de.appplant.cordova.plugin.notification.trigger;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import static de.appplant.cordova.plugin.notification.trigger.DateTrigger.Unit.DAY;
+import static de.appplant.cordova.plugin.notification.trigger.DateTrigger.Unit.HOUR;
+import static de.appplant.cordova.plugin.notification.trigger.DateTrigger.Unit.MINUTE;
+import static de.appplant.cordova.plugin.notification.trigger.DateTrigger.Unit.MONTH;
+import static de.appplant.cordova.plugin.notification.trigger.DateTrigger.Unit.YEAR;
+
+/**
+ * Trigger for date matching components.
+ */
+public class MatchTrigger extends IntervalTrigger {
+
+    // Used to determine the interval
+    private static Unit[] INTERVALS = { null, MINUTE, HOUR, DAY, MONTH, YEAR };
+
+    // The date matching components
+    private final List<Integer> matchers;
+
+    /**
+     * Date matching trigger from now.
+     *
+     * @param matchers Describes the date matching parts.
+     *                 { day: 15, month: ... }
+     */
+    public MatchTrigger(List<Integer> matchers) {
+        super(1, INTERVALS[1 + matchers.indexOf(null)]);
+        this.matchers = matchers;
+    }
+
+    /**
+     * Gets the date from where to start calculating the initial trigger date.
+     */
+    private Calendar getBaseTriggerDate(Date date) {
+        Calendar cal = getCal(date);
+
+        cal.set(Calendar.SECOND, 0);
+
+        if (matchers.get(0) != null) {
+            cal.set(Calendar.MINUTE, matchers.get(0));
+        } else {
+            cal.set(Calendar.MINUTE, 0);
+        }
+
+        if (matchers.get(1) != null) {
+            cal.set(Calendar.HOUR_OF_DAY, matchers.get(1));
+        } else {
+            cal.set(Calendar.HOUR_OF_DAY, 0);
+        }
+
+        if (matchers.get(2) != null) {
+            cal.set(Calendar.DAY_OF_MONTH, matchers.get(2));
+        }
+
+        if (matchers.get(3) != null) {
+            cal.set(Calendar.MONTH, matchers.get(3));
+        }
+
+        if (matchers.get(4) != null) {
+            cal.set(Calendar.YEAR, matchers.get(4));
+        }
+
+        return cal;
+    }
+
+    /**
+     * Gets the first trigger date.
+     *
+     * @param base The date from where to calculate the trigger date.
+     *
+     * @return null if there's none trigger date.
+     */
+    private Date getTriggerDate (Date base) {
+        Calendar date = getBaseTriggerDate(base);
+        Calendar now  = getCal(base);
+
+        if (date.compareTo(now) >= 0)
+            return date.getTime();
+
+        if (unit == null || date.get(Calendar.YEAR) < now.get(Calendar.YEAR))
+            return null;
+
+        if (date.get(Calendar.MONTH) < now.get(Calendar.MONTH)) {
+            switch (unit) {
+                case MINUTE:
+                case HOUR:
+                case DAY:
+                    if (matchers.get(4) == null) {
+                        return addToDate(date, now, Calendar.YEAR, 1);
+                    } else break;
+                case YEAR:
+                    return addToDate(date, now, Calendar.YEAR, 1);
+            }
+        } else
+        if (date.get(Calendar.DAY_OF_YEAR) < now.get(Calendar.DAY_OF_YEAR)) {
+            switch (unit) {
+                case MINUTE:
+                case HOUR:
+                    if (matchers.get(3) == null) {
+                        return addToDate(date, now, Calendar.MONTH, 1);
+                    } else
+                    if (matchers.get(4) == null) {
+                        return addToDate(date, now, Calendar.YEAR, 1);
+                    }
+                    else break;
+                case MONTH:
+                    return addToDate(date, now, Calendar.MONTH, 1);
+                case YEAR:
+                    return addToDate(date, now, Calendar.YEAR, 1);
+            }
+        } else
+        if (date.get(Calendar.HOUR_OF_DAY) < now.get(Calendar.HOUR_OF_DAY)) {
+            switch (unit) {
+                case MINUTE:
+                    if (matchers.get(2) == null) {
+                        return addToDate(date, now, Calendar.DAY_OF_YEAR, 1);
+                    } else
+                    if (matchers.get(3) == null) {
+                        return addToDate(date, now, Calendar.MONTH, 1);
+                    }
+                    else break;
+                case HOUR:
+                    return addToDate(date, now, Calendar.HOUR_OF_DAY, 0);
+                case DAY:
+                    return addToDate(date, now, Calendar.DAY_OF_YEAR, 1);
+                case MONTH:
+                    return addToDate(date, now, Calendar.MONTH, 1);
+                case YEAR:
+                    return addToDate(date, now, Calendar.YEAR, 1);
+            }
+        } else
+        if (date.get(Calendar.MINUTE) < now.get(Calendar.MINUTE)) {
+            switch (unit) {
+                case MINUTE:
+                    return addToDate(date, now, Calendar.MINUTE, 1);
+                case HOUR:
+                    return addToDate(date, now, Calendar.HOUR_OF_DAY, 1);
+                case DAY:
+                    return addToDate(date, now, Calendar.DAY_OF_YEAR, 1);
+                case MONTH:
+                    return addToDate(date, now, Calendar.MONTH, 1);
+                case YEAR:
+                    return addToDate(date, now, Calendar.YEAR, 1);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Gets the next trigger date.
+     *
+     * @param base The date from where to calculate the trigger date.
+     *
+     * @return null if there's none next trigger date.
+     */
+    @Override
+    public Date getNextTriggerDate (Date base) {
+        Date date = base;
+
+        if (getOccurrence() > 1) {
+            Calendar cal = getCal(base);
+            addInterval(cal);
+            date = cal.getTime();
+        }
+
+        incOccurrence();
+
+        return getTriggerDate(date);
+    }
+
+    /**
+     * Sets the field value of now to date and adds by count.
+     *
+     * @return The new date.
+     */
+    private Date addToDate(Calendar date, Calendar now, int field, int count) {
+        date.set(field, now.get(field));
+        date.add(field, count);
+        return date.getTime();
+    }
+
+}

+ 34 - 78
src/android/notification/AssetUtil.java → src/android/notification/util/AssetUtil.java

@@ -19,7 +19,7 @@
  * limitations under the License.
  */
 
-package de.appplant.cordova.plugin.notification;
+package de.appplant.cordova.plugin.notification.util;
 
 import android.content.Context;
 import android.content.res.AssetManager;
@@ -42,20 +42,19 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.UUID;
 
+import static android.media.RingtoneManager.TYPE_NOTIFICATION;
+
 /**
  * Util class to map unified asset URIs to native URIs. URIs like file:///
  * map to absolute paths while file:// point relatively to the www folder
  * within the asset resources. And res:// means a resource from the native
  * res folder. Remote assets are accessible via http:// for example.
  */
-class AssetUtil {
+public class AssetUtil {
 
     // Name of the storage folder
     private static final String STORAGE_FOLDER = "/localnotification";
 
-    // Placeholder URI for default sound
-    private static final String DEFAULT_SOUND = "res://platform_default";
-
     // Ref to the context passed through the constructor to access the
     // resources and app directory.
     private final Context context;
@@ -63,8 +62,7 @@ class AssetUtil {
     /**
      * Constructor
      *
-     * @param context
-     *      Application context
+     * @param context Application context
      */
     private AssetUtil(Context context) {
         this.context = context;
@@ -73,41 +71,22 @@ class AssetUtil {
     /**
      * Static method to retrieve class instance.
      *
-     * @param context
-     *      Application context
+     * @param context Application context
      */
-    static AssetUtil getInstance(Context context) {
+    public static AssetUtil getInstance(Context context) {
         return new AssetUtil(context);
     }
 
-    /**
-     * Parse path path to native URI.
-     *
-     * @param path
-     *      Path to path file
-     */
-    Uri parseSound (String path) {
-
-        if (path == null || path.isEmpty())
-            return Uri.EMPTY;
-
-        if (path.equalsIgnoreCase(DEFAULT_SOUND)) {
-            return RingtoneManager.getDefaultUri(RingtoneManager
-                    .TYPE_NOTIFICATION);
-        }
-
-        return parse(path);
-    }
-
     /**
      * The URI for a path.
      *
      * @param path
      *      The given path
      */
-    Uri parse (String path) {
-
-        if (path.startsWith("res:")) {
+    public Uri parse (String path) {
+        if (path == null || path.isEmpty()) {
+            return Uri.EMPTY;
+        } else if (path.startsWith("res:")) {
             return getUriForResourcePath(path);
         } else if (path.startsWith("file:///")) {
             return getUriFromPath(path);
@@ -191,7 +170,7 @@ class AssetUtil {
      */
     private Uri getUriForResourcePath(String path) {
         String resPath = path.replaceFirst("res://", "");
-        int resId      = getResIdForDrawable(resPath);
+        int resId      = getResId(resPath);
         File file      = getTmpFile();
 
         if (resId == 0) {
@@ -296,14 +275,15 @@ class AssetUtil {
     /**
      * Resource ID for drawable.
      *
-     * @param resPath
-     *      Resource path as string
+     * @param resPath Resource path as string.
+     *
+     * @return The resource ID or 0 if not found.
      */
-    int getResIdForDrawable(String resPath) {
-        int resId = getResIdForDrawable(getPkgName(), resPath);
+    public int getResId(String resPath) {
+        int resId = getResId(context.getResources(), resPath);
 
         if (resId == 0) {
-            resId = getResIdForDrawable("android", resPath);
+            resId = getResId(Resources.getSystem(), resPath);
         }
 
         return resId;
@@ -312,56 +292,32 @@ class AssetUtil {
     /**
      * Resource ID for drawable.
      *
-     * @param clsName
-     *      Relative package or global android name space
-     * @param resPath
-     *      Resource path as string
-     */
-    int getResIdForDrawable(String clsName, String resPath) {
-        String drawable = getBaseName(resPath);
-        int resId = 0;
-
-        try {
-            Class<?> cls  = Class.forName(clsName + ".R$drawable");
-
-            resId = (Integer) cls.getDeclaredField(drawable).get(Integer.class);
-        } catch (Exception ignore) {}
-
-        return resId;
-    }
-
-    /**
-     * Convert drawable resource to bitmap.
+     * @param res     The resources where to look for.
+     * @param resPath The name of the resource.
      *
-     * @param drawable
-     *      Drawable resource name
+     * @return The resource ID or 0 if not found.
      */
-    Bitmap getIconFromDrawable (String drawable) {
-        Resources res = context.getResources();
-        int iconId;
-
-        iconId = getResIdForDrawable(getPkgName(), drawable);
+    private int getResId(Resources res, String resPath) {
+        String pkgName = getPkgName(res);
+        String resName = getBaseName(resPath);
+        int resId;
 
-        if (iconId == 0) {
-            iconId = getResIdForDrawable("android", drawable);
-        }
+        resId = res.getIdentifier(resName, "mipmap", pkgName);
 
-        if (iconId == 0) {
-            iconId = android.R.drawable.screen_background_dark_transparent;
+        if (resId == 0) {
+            resId = res.getIdentifier(resName, "drawable", pkgName);
         }
 
-        return BitmapFactory.decodeResource(res, iconId);
+        return resId;
     }
 
     /**
      * Convert URI to Bitmap.
      *
-     * @param uri
-     *      Internal image URI
+     * @param uri Internal image URI
      */
-    Bitmap getIconFromUri (Uri uri) throws IOException {
+    public Bitmap getIconFromUri(Uri uri) throws IOException {
         InputStream input = context.getContentResolver().openInputStream(uri);
-
         return BitmapFactory.decodeStream(input);
     }
 
@@ -422,10 +378,10 @@ class AssetUtil {
     }
 
     /**
-     * Package name specified by context.
+     * Package name specified by the resource bundle.
      */
-    private String getPkgName () {
-        return context.getPackageName();
+    private String getPkgName (Resources res) {
+        return res == Resources.getSystem() ? "android" : context.getPackageName();
     }
 
 }

+ 4 - 3
src/ios/APPNotificationOptions.m

@@ -158,12 +158,13 @@
     NSString* path = [dict objectForKey:@"sound"];
     NSString* file;
 
+    if ([path isKindOfClass:NSNumber.class]) {
+        return [path boolValue] ? [UNNotificationSound defaultSound] : NULL;
+    }
+
     if (!path.length)
         return NULL;
 
-    if ([path isEqualToString:@"res://platform_default"])
-        return [UNNotificationSound defaultSound];
-
     if ([path hasPrefix:@"file:/"]) {
         file = [self soundNameForAsset:path];
     } else

+ 29 - 18
www/local-notification-util.js

@@ -51,12 +51,17 @@ exports.applyPlatformSpecificOptions = function () {
 
     switch (device.platform) {
     case 'Android':
-        defaults.icon        = 'res://ic_popup_reminder';
-        defaults.smallIcon   = undefined;
-        defaults.ongoing     = false;
-        defaults.autoClear   = true;
-        defaults.led         = undefined;
-        defaults.color       = undefined;
+        defaults.icon       = 'res://icon';
+        defaults.smallIcon  = undefined;
+        defaults.sticky     = false;
+        defaults.autoClear  = true;
+        defaults.led        = true;
+        defaults.color      = undefined;
+        defaults.vibrate    = false;
+        defaults.lockscreen = true;
+        defaults.showWhen   = true;
+        defaults.priority   = 0;
+        defaults.defaults   = 0;
         break;
     }
 };
@@ -111,14 +116,17 @@ exports.mergeWithDefaults = function (options) {
  * @return [ Object ] The converted property list
  */
 exports.convertProperties = function (options) {
-
-    if (options.id) {
-        if (isNaN(options.id)) {
-            options.id = this.getDefaults().id;
-            console.warn('Id is not a number: ' + options.id);
+    var parseToInt = function (prop, options) {
+        if (isNaN(options[prop])) {
+            console.warn(prop + ' is not a number: ' + options[prop]);
+            return this.getDefaults()[prop];
         } else {
-            options.id = Number(options.id);
+            return Number(options[prop]);
         }
+    };
+
+    if (options.id) {
+        options.id = parseToInt('id', options);
     }
 
     if (options.title) {
@@ -130,12 +138,15 @@ exports.convertProperties = function (options) {
     }
 
     if (options.badge) {
-        if (isNaN(options.badge)) {
-            options.badge = this.getDefaults().badge;
-            console.warn('Badge number is not a number: ' + options.id);
-        } else {
-            options.badge = Number(options.badge);
-        }
+        options.badge = parseToInt('badge', options);
+    }
+
+    if (options.priority) {
+        options.priority = parseToInt('priority', options);
+    }
+
+    if (options.defaults) {
+        options.defaults = parseToInt('defaults', options);
     }
 
     if (typeof options.data == 'object') {