Sebastián Katzer vor 10 Jahren
Ursprung
Commit
53fe0a5e02
38 geänderte Dateien mit 3521 neuen und 3289 gelöschten Zeilen
  1. 103 53
      plugin.xml
  2. 48 0
      src/android/ClearReceiver.java
  3. 66 0
      src/android/ClickActivity.java
  4. 0 80
      src/android/DeleteIntentReceiver.java
  5. 382 376
      src/android/LocalNotification.java
  6. 0 128
      src/android/Receiver.java
  7. 0 78
      src/android/ReceiverActivity.java
  8. 0 72
      src/android/Restore.java
  9. 63 0
      src/android/RestoreReceiver.java
  10. 69 0
      src/android/TriggerReceiver.java
  11. 75 0
      src/android/notification/AbstractClearReceiver.java
  12. 103 0
      src/android/notification/AbstractClickActivity.java
  13. 83 0
      src/android/notification/AbstractRestoreReceiver.java
  14. 122 0
      src/android/notification/AbstractTriggerReceiver.java
  15. 0 310
      src/android/notification/Asset.java
  16. 438 0
      src/android/notification/AssetUtil.java
  17. 194 0
      src/android/notification/Builder.java
  18. 44 0
      src/android/notification/ClearReceiver.java
  19. 55 0
      src/android/notification/ClickActivity.java
  20. 353 343
      src/android/notification/Manager.java
  21. 368 0
      src/android/notification/Notification.java
  22. 0 114
      src/android/notification/NotificationBuilder.java
  23. 0 382
      src/android/notification/NotificationWrapper.java
  24. 176 212
      src/android/notification/Options.java
  25. 64 0
      src/android/notification/TriggerReceiver.java
  26. 33 27
      src/ios/APPLocalNotification.h
  27. 185 193
      src/ios/APPLocalNotification.m
  28. 20 20
      src/ios/APPLocalNotificationOptions.h
  29. 48 71
      src/ios/APPLocalNotificationOptions.m
  30. 20 18
      src/ios/AppDelegate+APPLocalNotification.h
  31. 20 18
      src/ios/AppDelegate+APPLocalNotification.m
  32. 43 38
      src/ios/UIApplication+APPLocalNotification.h
  33. 91 101
      src/ios/UIApplication+APPLocalNotification.m
  34. 32 20
      src/ios/UILocalNotification+APPLocalNotification.h
  35. 45 22
      src/ios/UILocalNotification+APPLocalNotification.m
  36. 0 237
      src/wp8/LocalNotification.cs
  37. 0 113
      src/wp8/Options.cs
  38. 178 263
      www/local-notification.js

+ 103 - 53
plugin.xml

@@ -3,7 +3,7 @@
 <plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
         xmlns:android="http://schemas.android.com/apk/res/android"
         id="de.appplant.cordova.plugin.local-notification"
-        version="0.8.0dev">
+        version="0.8.0rc">
 
     <name>LocalNotification</name>
 
@@ -14,12 +14,16 @@
 
     <author>Sebastián Katzer</author>
 
+    <!-- cordova -->
     <engines>
         <engine name="cordova" version=">=3.0.0" />
     </engines>
 
-    <dependency id="org.apache.cordova.device" url="https://github.com/apache/cordova-plugin-device" />
+    <dependency
+        id="org.apache.cordova.device"
+        url="https://github.com/apache/cordova-plugin-device" />
 
+    <!-- js -->
     <js-module src="www/local-notification.js" name="LocalNotification">
         <clobbers target="cordova.plugins.notification.local" />
         <clobbers target="plugin.notification.local" />
@@ -34,15 +38,15 @@
             </feature>
         </config-file>
 
+        <header-file src="src/ios/AppDelegate+APPLocalNotification.h" />
+        <source-file src="src/ios/AppDelegate+APPLocalNotification.m" />
+
         <header-file src="src/ios/APPLocalNotification.h" />
         <source-file src="src/ios/APPLocalNotification.m" />
 
         <header-file src="src/ios/APPLocalNotificationOptions.h" />
         <source-file src="src/ios/APPLocalNotificationOptions.m" />
 
-        <header-file src="src/ios/AppDelegate+APPLocalNotification.h" />
-        <source-file src="src/ios/AppDelegate+APPLocalNotification.m" />
-
         <header-file src="src/ios/UIApplication+APPLocalNotification.h" />
         <source-file src="src/ios/UIApplication+APPLocalNotification.m" />
 
@@ -59,38 +63,39 @@
         </config-file>
 
         <config-file target="AndroidManifest.xml" parent="/manifest/application">
-            <!--
-             * The alarm receiver is triggered when a scheduled alarm is fired. This class
-             * reads the information in the intent and displays this information in the
-             * Android notification bar. The notification uses the default notification
-             * sound and it vibrates the phone.
-            -->
-            <receiver android:name="de.appplant.cordova.plugin.localnotification.Receiver" android:exported="false" />
-
-			<!--
-             * The delete intent receiver is triggered when the user clears a notification
-			 * manually. It unpersists the cleared notification from the shared preferences.
-            -->
-            <receiver android:name="de.appplant.cordova.plugin.localnotification.DeleteIntentReceiver" android:exported="false"/>
-
-            <!--
-             * This class is triggered upon reboot of the device. It needs to re-register
-             * the alarms with the AlarmManager since these alarms are lost in case of
-             * reboot.
-             -->
-            <receiver android:name="de.appplant.cordova.plugin.localnotification.Restore" >
+
+            <receiver
+                android:name="de.appplant.cordova.plugin.localnotification.TriggerReceiver"
+                android:exported="false" />
+
+            <receiver
+                android:name="de.appplant.cordova.plugin.localnotification.ClearReceiver"
+                android:exported="false" />
+
+            <activity
+                android:name="de.appplant.cordova.plugin.localnotification.ClickActivity"
+                android:launchMode="singleInstance"
+                android:theme="@android:style/Theme.NoDisplay" />
+
+            <receiver
+                android:name="de.appplant.cordova.plugin.notification.TriggerReceiver"
+                android:exported="false" />
+
+            <receiver
+                android:name="de.appplant.cordova.plugin.notification.ClearReceiver"
+                android:exported="false" />
+
+            <receiver android:name="de.appplant.cordova.plugin.localnotification.RestoreReceiver" >
                 <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" />
 
-            <!--
-             * The receiver activity is triggered when a notification is clicked by a user.
-             * The activity calls the background callback and brings the launch inten
-             * up to foreground.
-            -->
-            <activity android:name="de.appplant.cordova.plugin.localnotification.ReceiverActivity" android:launchMode="singleInstance" android:theme="@android:style/Theme.NoDisplay" />
         </config-file>
 
         <config-file target="AndroidManifest.xml" parent="/manifest">
@@ -99,29 +104,74 @@
 
         <lib-file src="libs/android/android-support-v4.jar" />
 
-        <source-file src="src/android/LocalNotification.java" 		target-dir="src/de/appplant/cordova/plugin/localnotification" />
-        <source-file src="src/android/Receiver.java"          		target-dir="src/de/appplant/cordova/plugin/localnotification" />
-        <source-file src="src/android/Restore.java"           		target-dir="src/de/appplant/cordova/plugin/localnotification" />
-        <source-file src="src/android/ReceiverActivity.java"  		target-dir="src/de/appplant/cordova/plugin/localnotification" />
-		<source-file src="src/android/DeleteIntentReceiver.java"  	target-dir="src/de/appplant/cordova/plugin/localnotification" />
-
-		<source-file src="src/android/notification/Options.java"           	target-dir="src/de/appplant/cordova/plugin/notification" />
-		<source-file src="src/android/notification/Asset.java"           	target-dir="src/de/appplant/cordova/plugin/notification" />
-		<source-file src="src/android/notification/Manager.java"           	target-dir="src/de/appplant/cordova/plugin/notification" />
-		<source-file src="src/android/notification/NotificationBuilder.java" target-dir="src/de/appplant/cordova/plugin/notification" />
-		<source-file src="src/android/notification/NotificationWrapper.java" target-dir="src/de/appplant/cordova/plugin/notification" />
-    </platform>
+        <source-file
+            src="src/android/LocalNotification.java"
+            target-dir="src/de/appplant/cordova/plugin/localnotification" />
 
-    <!-- wp8 -->
-<!--     <platform name="wp8">
-        <config-file target="config.xml" parent="/*">
-            <feature name="LocalNotification">
-                <param name="wp-package" value="LocalNotification"/>
-            </feature>
-        </config-file>
+        <source-file
+            src="src/android/TriggerReceiver.java"
+            target-dir="src/de/appplant/cordova/plugin/localnotification" />
 
-        <source-file src="src/wp8/LocalNotification.cs" />
-        <source-file src="src/wp8/Options.cs" />
-    </platform> -->
+        <source-file
+            src="src/android/ClickActivity.java"
+            target-dir="src/de/appplant/cordova/plugin/localnotification" />
+
+        <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/AbstractClearReceiver.java"
+            target-dir="src/de/appplant/cordova/plugin/notification" />
+
+        <source-file
+            src="src/android/notification/AbstractClickActivity.java"
+            target-dir="src/de/appplant/cordova/plugin/notification" />
+
+        <source-file
+            src="src/android/notification/AbstractRestoreReceiver.java"
+            target-dir="src/de/appplant/cordova/plugin/notification" />
+
+        <source-file
+            src="src/android/notification/AbstractTriggerReceiver.java"
+            target-dir="src/de/appplant/cordova/plugin/notification" />
+
+        <source-file
+            src="src/android/notification/AssetUtil.java"
+            target-dir="src/de/appplant/cordova/plugin/notification" />
+
+        <source-file
+            src="src/android/notification/Builder.java"
+            target-dir="src/de/appplant/cordova/plugin/notification" />
+
+        <source-file
+            src="src/android/notification/ClearReceiver.java"
+            target-dir="src/de/appplant/cordova/plugin/notification" />
+
+        <source-file
+            src="src/android/notification/ClickActivity.java"
+            target-dir="src/de/appplant/cordova/plugin/notification" />
+
+        <source-file
+            src="src/android/notification/Manager.java"
+            target-dir="src/de/appplant/cordova/plugin/notification" />
+
+        <source-file
+            src="src/android/notification/Notification.java"
+            target-dir="src/de/appplant/cordova/plugin/notification" />
+
+        <source-file
+            src="src/android/notification/Options.java"
+            target-dir="src/de/appplant/cordova/plugin/notification" />
+
+        <source-file
+            src="src/android/notification/TriggerReceiver.java"
+            target-dir="src/de/appplant/cordova/plugin/notification" />
+
+    </platform>
 
 </plugin>

+ 48 - 0
src/android/ClearReceiver.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2013-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.localnotification;
+
+import de.appplant.cordova.plugin.notification.Notification;
+
+
+/**
+ * The clear intent receiver is triggered when the user clears a
+ * notification manually. It un-persists the cleared notification from the
+ * shared preferences.
+ */
+public class ClearReceiver extends de.appplant.cordova.plugin.notification.ClearReceiver {
+
+    /**
+     * Called when a local notification was cleared from outside of the app.
+     *
+     * @param notification
+     *      Wrapper around the local notification
+     */
+    @Override
+    public void onClear (Notification notification) {
+        super.onClear(notification);
+        LocalNotification.fireEvent("clear", notification);
+    }
+
+}

+ 66 - 0
src/android/ClickActivity.java

@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2013-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.localnotification;
+
+import de.appplant.cordova.plugin.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 {
+
+    /**
+     * Called when local notification was clicked by the user.
+     *
+     * @param notification
+     *      Wrapper around the local notification
+     */
+    @Override
+    public void onClick(Notification notification) {
+        LocalNotification.fireEvent("click", notification);
+
+        if (!notification.isRepeating()) {
+            LocalNotification.fireEvent("cancel", notification);
+        }
+
+        super.onClick(notification);
+    }
+
+    /**
+     * Build notification specified by options.
+     *
+     * @param builder
+     *      Notification builder
+     */
+    @Override
+    public Notification buildNotification (Builder builder) {
+        return builder
+                .setTriggerReceiver(TriggerReceiver.class)
+                .build();
+    }
+
+}

+ 0 - 80
src/android/DeleteIntentReceiver.java

@@ -1,80 +0,0 @@
-/*
-    Copyright 2013-2014 appPlant UG
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-
-package de.appplant.cordova.plugin.localnotification;
-
-import java.util.Date;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-
-import de.appplant.cordova.plugin.notification.Options;
-
-public class DeleteIntentReceiver extends BroadcastReceiver {
-
-    public static final String OPTIONS = "LOCAL_NOTIFICATION_OPTIONS";
-	
-    /**
-     * Is called when a Notification is cleared manualy by the User
-     */
-	@Override
-	public void onReceive(Context context, Intent intent) {
-        Options options = null;
-        Bundle bundle   = intent.getExtras();
-        JSONObject args;
-
-        try {
-            args    = new JSONObject(bundle.getString(OPTIONS));
-            options = new Options(context).parse(args);
-        } catch (JSONException e) {
-            return;
-        }
-
-        // The context may got lost if the app was not running before
-        LocalNotification.setContext(context);
-        
-        Date now = new Date();
-		if ((options.getInterval()!=0)){
-			options.setInitDate();
-			LocalNotification.persist(options.getId(), options.getJSONObject());
-		}
-		else if((new Date(options.getDate()).before(now))){
-			LocalNotification.unpersist(options.getId());
-		}
-		
-		fireClearEvent(options);
-	}
-	
-    /**
-     * Fires onclear event.
-     */
-    private void fireClearEvent (Options options) {
-    	JSONArray data = new JSONArray().put(options.getJSONObject());
-        LocalNotification.fireEvent("clear", options.getId(), options.getJSON(),data);
-    }
-
-}

+ 382 - 376
src/android/LocalNotification.java

@@ -1,30 +1,32 @@
 /*
-    Copyright 2013-2014 appPlant UG
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
+ * Copyright (c) 2013-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.localnotification;
 
 import java.util.ArrayList;
+import java.util.List;
 
 import org.apache.cordova.CallbackContext;
-import org.apache.cordova.CordovaInterface;
 import org.apache.cordova.CordovaPlugin;
 import org.apache.cordova.CordovaWebView;
 import org.apache.cordova.PluginResult;
@@ -32,474 +34,478 @@ import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
-import android.app.Activity;
-import android.app.AlarmManager;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
 import android.os.Build;
-import android.annotation.TargetApi;
 
 import de.appplant.cordova.plugin.notification.*;
 
 /**
- * This plugin utilizes the Android AlarmManager in combination with StatusBar
+ * This plugin utilizes the Android AlarmManager in combination with local
  * notifications. When a local notification is scheduled the alarm manager takes
  * care of firing the event. When the event is processed, a notification is put
- * in the Android status bar.
+ * in the Android notification center and status bar.
  */
 public class LocalNotification extends CordovaPlugin {
 
-    protected final static String PLUGIN_NAME = "LocalNotification";
-    static protected final String STORAGE_FOLDER = "/localnotification";
     private   static CordovaWebView webView = null;
     private   static Boolean deviceready = false;
-    protected static Context context = null;
     protected static Boolean isInBackground = true;
     private   static ArrayList<String> eventQueue = new ArrayList<String>();
-    static Activity activity;
-    Asset asset;
-    Manager manager;
-    NotificationWrapper nWrapper;
-    
-    @Override
-    public void initialize (CordovaInterface cordova, CordovaWebView webView) {
-        super.initialize(cordova, webView);
 
+    /**
+     * Called after plugin construction and fields have been initialized.
+     */
+    @Override
+    protected void pluginInitialize() {
         LocalNotification.webView = super.webView;
-        LocalNotification.context = super.cordova.getActivity().getApplicationContext();
-        LocalNotification.activity = super.cordova.getActivity();
-        this.asset = new Asset(activity,STORAGE_FOLDER);
-        this.manager = new Manager(context, PLUGIN_NAME);
-        this.nWrapper = new NotificationWrapper(context,Receiver.class,PLUGIN_NAME,Receiver.OPTIONS);
     }
+
+    /**
+     * Called when the system is about to start resuming a previous activity.
+     *
+     * @param multitasking
+     *      Flag indicating if multitasking is turned on for app
+     */
     @Override
-    public boolean execute (String action, final JSONArray args, final CallbackContext command) throws JSONException {
-    	
-        if (action.equalsIgnoreCase("add")) {
-            cordova.getThreadPool().execute( new Runnable() {
-                public void run() { 
-                	add(args);
+    public void onPause(boolean multitasking) {
+        super.onPause(multitasking);
+        isInBackground = true;
+    }
+
+    /**
+     * Called when the activity will start interacting with the user.
+     *
+     * @param multitasking
+     *      Flag indicating if multitasking is turned on for app
+     */
+    @Override
+    public void onResume(boolean multitasking) {
+        super.onResume(multitasking);
+        isInBackground = false;
+    }
+
+    /**
+     * Executes the request.
+     *
+     * This method is called from the WebView thread. To do a non-trivial
+     * amount of work, use:
+     *      cordova.getThreadPool().execute(runnable);
+     *
+     * To run on the UI thread, use:
+     *     cordova.getActivity().runOnUiThread(runnable);
+     *
+     * @param action
+     *      The action to execute.
+     * @param args
+     *      The exec() arguments in JSON form.
+     * @param command
+     *      The callback context used when calling back into JavaScript.
+     * @return
+     *      Whether the action was valid.
+     */
+    @Override
+    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")) {
+                    schedule(args);
                     command.success();
                 }
-            });
-        }
-        
-        if (action.equalsIgnoreCase("update")) {
-        	cordova.getThreadPool().execute( new Runnable() {
-                public void run() {
-                	update(args);
-                	command.success();
+                else if (action.equals("update")) {
+                    update(args);
+                    command.success();
                 }
-            });
-        }
-        
-        if (action.equalsIgnoreCase("cancel")) {
-            cordova.getThreadPool().execute( new Runnable() {
-                public void run() {
-                	cancel(args);
+                else if (action.equals("cancel")) {
+                    cancel(args);
                     command.success();
-                    
                 }
-            });
-        }
-
-        if (action.equalsIgnoreCase("cancelAll")) {
-            cordova.getThreadPool().execute( new Runnable() {
-                public void run() {
-                	cancelAll(args);
+                else if (action.equals("cancelAll")) {
+                    cancelAll();
                     command.success();
                 }
-            });
-        }
-        
-        if (action.equalsIgnoreCase("clear")) {
-        	cordova.getThreadPool().execute( new Runnable() {
-                public void run() {
-                	clear(args);
+                else if (action.equals("clear")) {
+                    clear(args);
                     command.success();
                 }
-            });
-        }
-        
-        if (action.equalsIgnoreCase("clearAll")) {
-        	cordova.getThreadPool().execute( new Runnable() {
-                public void run() {
-                	clearAll(args);
+                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("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.equalsIgnoreCase("isScheduled")) {
-            String id = args.optString(0);
-        	boolean isScheduled = manager.isScheduled(id);        
-            PluginResult result        = new PluginResult(PluginResult.Status.OK, (isScheduled));
-            command.sendPluginResult(result);
-        }
-        
-        if (action.equalsIgnoreCase("isTriggered")) {
-            String id = args.optString(0);
-            boolean isTriggered        = manager.isTriggered(id);
-            PluginResult result = new PluginResult(PluginResult.Status.OK, isTriggered);
-            command.sendPluginResult(result);
-        }
-        
-        if (action.equalsIgnoreCase("exist")) {
-            String id = args.optString(0);
-            boolean exist        = manager.exist(id);
-            PluginResult result = new PluginResult(PluginResult.Status.OK, exist);
-            command.sendPluginResult(result);
-        }
+        return true;
+    }
 
-        if (action.equalsIgnoreCase("getScheduledIds")) {
-        	JSONArray scheduledIds     = manager.getScheduledIds();
-            command.success(scheduledIds);
-        }
+    /**
+     * 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);
 
-        if (action.equalsIgnoreCase("getTriggeredIds")) {
-            JSONArray triggeredIds     = manager.getTriggeredIds();
-            command.success(triggeredIds);
-        }
-        
-        if (action.equalsIgnoreCase("getAllIds")) {
-            JSONArray allIds     = manager.getAllIds();
-            command.success(allIds);
-        }
-        
-        if (action.equalsIgnoreCase("getAll")) {
-        	JSONArray ids;
-        	JSONArray all;
-        	try{
-        		ids = args.getJSONArray(0);
-        		all = manager.getAll(ids);
-        	} catch (JSONException jse){
-        		all = manager.getAll();
-        	}
-        	command.success(all);
-        }
-        
-        if (action.equalsIgnoreCase("getTriggered")) {
-        	JSONArray ids;
-        	JSONArray triggered;
-        	try{
-        		ids = args.getJSONArray(0);
-        		triggered = manager.getTriggered(ids);
-        	} catch (JSONException jse){
-        		triggered = manager.getTriggered();
-        	}
-        	command.success(triggered);
-        }
-        
-        if (action.equalsIgnoreCase("getScheduled")) {
-        	JSONArray ids;
-        	JSONArray scheduled;
-        	try{
-        		ids = args.getJSONArray(0);
-        		scheduled = manager.getScheduled(ids);
-        	} catch (JSONException jse){
-        		scheduled = manager.getScheduled();
-        	}
-        	command.success(scheduled);
-        }
+            getNotificationMgr().schedule(options, TriggerReceiver.class);
+    	}
+    }
 
-        if (action.equalsIgnoreCase("deviceready")) {
-            cordova.getThreadPool().execute( new Runnable() {
-                public void run() {
-                    deviceready();
-                }
-            });
-        }
+    /**
+     * 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);
 
-        if (action.equalsIgnoreCase("pause")) {
-            isInBackground = true;
-        }
+            Notification notification =
+                    getNotificationMgr().update(id, update, TriggerReceiver.class);
 
-        if (action.equalsIgnoreCase("resume")) {
-            isInBackground = false;
+            fireEvent("update", notification);
         }
+    }
 
-        return true;
+    /**
+     * 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);
+
+            fireEvent("cancel", notification);
+        }
     }
-    
-    
-    //------------------------------------------------exec-Functions-----------------------------------------------
+
     /**
-     * Schedule notifications contained in the args-Array
-     * @param args
+     * Cancel all scheduled notifications.
      */
-    private void add(JSONArray args){
-    	JSONArray notifications = args;
-    	JSONObject arguments;
-    	for(int i=0;i<notifications.length();i++){
-    		arguments = notifications.optJSONObject(i);
-    		arguments = asset.parseURIs(arguments);
-    		Options options      = new Options(context).parse(arguments);
-    		options.setInitDate();
-        	nWrapper.schedule(options);
-        	JSONArray fireData= new JSONArray().put(options.getJSONObject());
-        	fireEvent("add", options.getId(),options.getJSON(), fireData);
-    	}     	
+    private void cancelAll() {
+        getNotificationMgr().cancelAll();
+        fireEvent("cancelall");
     }
-    
+
     /**
-     * Update existing notifications
-     * @param args
+     * Clear multiple local notifications without canceling them.
+     *
+     * @param ids
+     *      Set of local notification IDs
      */
-    private void update(JSONArray args){
-    	JSONArray updates = args;
-    	JSONObject updateContent;
-    	for(int i=0;i<updates.length();i++){
-    		updateContent = args.optJSONObject(i);
-    	
-    		nWrapper.update(updateContent);
-    	}
+    private void clear(JSONArray ids){
+        for (int i = 0; i < ids.length(); i++) {
+            int id = ids.optInt(i, 0);
+
+            Notification notification =
+                    getNotificationMgr().clear(id);
+
+            fireEvent("clear", notification);
+        }
     }
-    
+
     /**
-     * Cancel scheduled Notifications
-     * @param args
+     * Clear all triggered notifications without canceling them.
      */
-    private void cancel(JSONArray args){
-    	JSONArray ids = args;
-    	String id;
-    	for(int i=0;i<ids.length();i++){
-    		id = args.optString(i);
-    		nWrapper.cancel(id);
-        	JSONArray managerId = new JSONArray().put(id);
-        	JSONArray data = manager.getAll(managerId);
-            fireEvent("cancel", id, "",data);
-    	}
+    private void clearAll() {
+    	getNotificationMgr().clearAll();
+        fireEvent("clearall");
     }
-    
+
     /**
-     * Cancel all scheduled notifications
-     * @param args
+     * If a notification with an ID is present.
+     *
+     * @param id
+     *      Notification ID
+     * @param command
+     *      The callback context used when calling back into JavaScript.
      */
-    public void cancelAll(JSONArray args){
-    	JSONArray options = manager.getAll();
-        nWrapper.cancelAll();
-    	String id;
-    	JSONObject arguments;
-        for(int i=0;i<options.length();i++){
-        	arguments= (JSONObject) options.opt(i);
-          	JSONArray data = new JSONArray().put(arguments);
-          	id = arguments.optString("id");
-          	fireEvent("cancel", id, "",data);
-        }
+    private void isPresent (int id, CallbackContext command) {
+        boolean exist = getNotificationMgr().exist(id);
+
+        PluginResult result = new PluginResult(
+                PluginResult.Status.OK, exist);
+
+        command.sendPluginResult(result);
     }
-    
+
     /**
-     * Clear triggered notifications without cancel repeating.
-     * @param args
+     * If a notification with an ID is scheduled.
+     *
+     * @param id
+     *      Notification ID
+     * @param command
+     *      The callback context used when calling back into JavaScript.
      */
-    public void clear(JSONArray args){
-    	JSONArray ids = args;
-    	String id;
-    	for(int i=0;i<ids.length();i++){
-    		id = args.optString(i);
-    		nWrapper.clear(id);
-        	JSONArray managerId = new JSONArray().put(id);
-        	JSONArray data = manager.getAll(managerId);
-            fireEvent("clear", id, "",data);
-    	}
+    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);
     }
-    
+
     /**
-     * Clear all triggered notifications without cancel repeating.
-     * @param args
+     * If a notification with an ID is triggered.
+     *
+     * @param id
+     *      Notification ID
+     * @param command
+     *      The callback context used when calling back into JavaScript.
      */
-    public void clearAll(JSONArray args){
-    	JSONArray options = manager.getAll();
-    	nWrapper.clearAll();
-    	String id;
-    	JSONObject arguments;
-        for(int i=0;i<options.length();i++){
-        	arguments= (JSONObject) options.opt(i);
-          	JSONArray data = new JSONArray().put(arguments);
-          	id = arguments.optString("id");
-          	fireEvent("clear", id, "",data);
-        }
+    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);
     }
-    
 
-    
-    
-    
-    //-------------------------------------------------------------------------------------------------------------
     /**
-     * Calls all pending callbacks after the deviceready event has been fired.
+     * Set of IDs from all existent notifications.
+     *
+     * @param command
+     *      The callback context used when calling back into JavaScript.
      */
-    private static void deviceready () {
-        deviceready = true;
+    private void getAllIds (CallbackContext command) {
+        List<Integer> ids = getNotificationMgr().getIds();
 
-        for (String js : eventQueue) {
-            sendJavascript(js);
-        }
+        command.success(new JSONArray(ids));
+    }
 
-        eventQueue.clear();
+    /**
+     * 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));
     }
 
     /**
-     * Persist the information of this alarm to the Android Shared Preferences.
-     * This will allow the application to restore the alarm upon device reboot.
-     * Also this is used by the cancelAll method.
+     * Set of IDs from all triggered notifications.
      *
-     * @param alarmId
-     *            The Id of the notification that must be persisted.
-     * @param args
-     *            The assumption is that parse has been called already.
+     * @param command
+     *      The callback context used when calling back into JavaScript.
      */
-    public static void persist (String alarmId, JSONObject args) {
-        Editor editor = getSharedPreferences().edit();
-
-        if (alarmId != null) {
-            editor.putString(alarmId, args.toString());
-            if (Build.VERSION.SDK_INT<9) {
-                editor.commit();
-            } else {
-                editor.apply();
-            }
-        }
+    private void getTriggeredIds (CallbackContext command) {
+        List<Integer> ids = getNotificationMgr().getIdsByType(
+                Notification.Type.TRIGGERED);
+
+        command.success(new JSONArray(ids));
     }
 
     /**
-     * Remove a specific alarm from the Android shared Preferences.
+     * Set of options from local notification.
      *
-     * @param alarmId
-     *            The Id of the notification that must be removed.
+     * @param ids
+     *      Set of local notification IDs
+     * @param command
+     *      The callback context used when calling back into JavaScript.
      */
-    public static void unpersist (String alarmId) {
-        Editor editor = getSharedPreferences().edit();
-
-        if (alarmId != null) {
-            editor.remove(alarmId);
-            if (Build.VERSION.SDK_INT<9) {
-                editor.commit();
-            } else {
-                editor.apply();
-            }
+    private void getAll (JSONArray ids, CallbackContext command) {
+        List<JSONObject> options;
+
+        if (ids.length() == 0) {
+            options = getNotificationMgr().getOptions();
+        } else {
+            options = getNotificationMgr().getOptionsById(toList(ids));
         }
+
+        command.success(new JSONArray(options));
     }
 
     /**
-     * Clear all alarms from the Android shared Preferences.
+     * Set of options from scheduled notifications.
+     *
+     * @param ids
+     *      Set of local notification IDs
+     * @param command
+     *      The callback context used when calling back into JavaScript.
      */
-    public static void unpersistAll () {
-        Editor editor = getSharedPreferences().edit();
+    private void getScheduled (JSONArray ids, CallbackContext command) {
+        List<JSONObject> options;
 
-        editor.clear();
-        if (Build.VERSION.SDK_INT<9) {
-            editor.commit();
+        if (ids.length() == 0) {
+            options = getNotificationMgr().getOptionsByType(Notification.Type.SCHEDULED);
         } else {
-            editor.apply();
+            options = getNotificationMgr().getOptionsBy(
+                    Notification.Type.SCHEDULED, toList(ids));
         }
+
+        command.success(new JSONArray(options));
     }
 
-    // 
     /**
-     * Fires the given event (Only one Callback also for multiple notifications in one action).
+     * Set of options from triggered notifications.
      *
-     * @param {String} event The Name of the event
-     * @param ids    The IDs of the notifications as JSONArray
-     * @param json  The notifications JSONObjects in a JSONArray
+     * @param ids
+     *      Set of local notification IDs
+     * @param command
+     *      The callback context used when calling back into JavaScript.
      */
-    public static void fireEvent (String event, JSONArray ids, JSONArray json) {
-        String state  = getApplicationState();
-        String params = ids + ",\"" + state + "\"," + json;
-        String js     = "setTimeout('plugin.notification.local.on" + event + "(" + params + ")',0)";
-
-        // webview may available, but callbacks needs to be executed
-        // after deviceready
-        if (deviceready == false) {
-            eventQueue.add(js);
+    private void getTriggered (JSONArray ids, CallbackContext command) {
+        List<JSONObject> options;
+
+        if (ids.length() == 0) {
+            options = getNotificationMgr().getOptionsByType(Notification.Type.TRIGGERED);
         } else {
+            options = getNotificationMgr().getOptionsBy(
+                    Notification.Type.TRIGGERED, toList(ids));
+        }
+
+        command.success(new JSONArray(options));
+    }
+
+    /**
+     * Call all pending callbacks after the deviceready event has been fired.
+     */
+    private static synchronized void deviceready () {
+        isInBackground = false;
+        deviceready = true;
+
+        for (String js : eventQueue) {
             sendJavascript(js);
         }
+
+        eventQueue.clear();
     }
 
-    //
     /**
-     * Fires the given event. (Standard-method)
+     * Fire given event on JS side. Does inform all event listeners.
      *
-     * @param {String} event The Name of the event
-     * @param {String} id The ID of the notification
-     * @param {String} json A custom (JSON) string
-     * @param data	The notifications as JSONObject
+     * @param event
+     *      The event name
      */
-    public static void fireEvent (String event, String id, String json, JSONArray data) {
-    	String state = getApplicationState();
-    	String params = "\"" + id + "\",\"" + state + "\"," + JSONObject.quote(json)+","+ data;
-    	String js = "setTimeout('plugin.notification.local.on" + event + "(" + params + ")',0)";
-    
-    	// webview may available, but callbacks needs to be executed
-    	// after deviceready
-    	if (deviceready == false) {
-    		eventQueue.add(js);
-    	} else {
-    		sendJavascript(js);
-    	}
+    private void fireEvent (String event) {
+        fireEvent(event, null);
     }
-    
-    
-    
+
     /**
-     * Retrieves the application state
+     * Fire given event on JS side. Does inform all event listeners.
      *
-     * @return {String}
-     *      Either "background" or "foreground"
+     * @param event
+     *      The event name
+     * @param notification
+     *      Optional local notification to pass the id and properties.
      */
-    protected static String getApplicationState () {
-        return isInBackground ? "background" : "foreground";
+    static void fireEvent (String event, Notification notification) {
+    	String state = getApplicationState();
+        String params = "\"" + state + "\"";
+
+        if (notification != null) {
+            params = notification.toString() + "," + params;
+        }
+
+    	String js = "cordova.plugins.notification.local.fireEvent(" +
+                "\"" + event + "\"," + params + ")";
+
+        sendJavascript(js);
     }
 
     /**
-     * Set the application context if not already set.
+     * Use this instead of deprecated sendJavascript
+     *
+     * @param js
+     *       JS code snippet as string
      */
-    protected static void setContext (Context context) {
-        if (LocalNotification.context == null) {
-            LocalNotification.context = context;
+    private static void sendJavascript(final String js) {
+
+        if (!deviceready) {
+            eventQueue.add(js);
+            return;
         }
+
+        webView.post(new Runnable(){
+            public void run(){
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+                    webView.evaluateJavascript(js, null);
+                } else {
+                    webView.loadUrl("javascript:" + js);
+                }
+            }
+        });
     }
 
     /**
-     * The Local storage for the application.
+     * Convert JSON array of integers to List.
+     *
+     * @param ary
+     *      Array of integers
      */
-    protected static SharedPreferences getSharedPreferences () {
-        return context.getSharedPreferences(PLUGIN_NAME, Context.MODE_PRIVATE);
+    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));
+        }
+
+        return list;
     }
 
     /**
-     * The alarm manager for the application.
+     * Current application state.
+     *
+     * @return
+     *      "background" or "foreground"
      */
-    protected static AlarmManager getAlarmManager () {
-        return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+    static String getApplicationState () {
+        return isInBackground ? "background" : "foreground";
     }
 
     /**
-     * The notification manager for the application.
+     * Notification manager instance.
      */
-    protected static NotificationManager getNotificationManager () {
-        return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+    private Manager getNotificationMgr() {
+        return Manager.getInstance(cordova.getActivity());
     }
-    
-    
-   /**
-    * Use this instead of deprecated sendJavascript
-    */
-   @TargetApi(Build.VERSION_CODES.KITKAT)
-   private static void sendJavascript(final String js){
-	   webView.post(new Runnable(){
-		   public void run(){
-			   if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.KITKAT){
-				   webView.evaluateJavascript(js, null);
-			   } else {
-				   webView.loadUrl("javascript:" + js);
-			   }
-		   }
-	   });
-   }
-    
-  
+
 }

+ 0 - 128
src/android/Receiver.java

@@ -1,128 +0,0 @@
-/*
-    Copyright 2013-2014 appPlant UG
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-
-package de.appplant.cordova.plugin.localnotification;
-
-import java.util.Calendar;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-
-import android.support.v4.app.NotificationCompat.Builder;
-
-import de.appplant.cordova.plugin.notification.*;
-
-/**
- * The alarm receiver is triggered when a scheduled alarm is fired. This class
- * reads the information in the intent and displays this information in the
- * Android notification bar. The notification uses the default notification
- * sound and it vibrates the phone.
- */
-public class Receiver extends BroadcastReceiver {
-
-    public static final String OPTIONS = "LOCAL_NOTIFICATION_OPTIONS";
-
-    private Options options;
-
-    @Override
-    public void onReceive (Context context, Intent intent) {
-    	NotificationWrapper nWrapper = new NotificationWrapper(context,
-    			Receiver.class,LocalNotification.PLUGIN_NAME,OPTIONS);
-        Options options = null;
-        Bundle bundle   = intent.getExtras();
-        JSONObject args;
-
-        try {
-            args    = new JSONObject(bundle.getString(OPTIONS));
-            options = new Options(context).parse(args);
-        } catch (JSONException e) {
-            return;
-        }
-        this.options = options;
-        
-    	NotificationBuilder builder = new NotificationBuilder(options,context,OPTIONS,
-    			DeleteIntentReceiver.class,ReceiverActivity.class);
-
-        // The context may got lost if the app was not running before
-        LocalNotification.setContext(context);
-
-        fireTriggerEvent();
-
-        if (options.getInterval() == 0) {
-        } else if (isFirstAlarmInFuture()) {
-            return;
-        } else {
-        	JSONArray data = new JSONArray().put(options.getJSONObject());
-        	LocalNotification.fireEvent("updateCall", options.getId(), options.getJSON(),data);
-            nWrapper.schedule(options.moveDate());
-        }
-        if (!LocalNotification.isInBackground && options.getForegroundMode()){
-        	if (options.getInterval() == 0) {
-        		LocalNotification.unpersist(options.getId());
-        	}
-        	nWrapper.showNotificationToast(options);
-        	fireTriggerEvent();
-        } else {
-        	Builder notification = builder.buildNotification();
-
-        	nWrapper.showNotification(notification, options);
-        }
-    }
-
-    /*
-     * 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 () {
-        if (options.getInterval() > 0) {
-            Calendar now    = Calendar.getInstance();
-            Calendar alarm  = options.getCalendar();
-
-            int alarmHour   = alarm.get(Calendar.HOUR_OF_DAY);
-            int alarmMin    = alarm.get(Calendar.MINUTE);
-            int currentHour = now.get(Calendar.HOUR_OF_DAY);
-            int currentMin  = now.get(Calendar.MINUTE);
-
-            if (currentHour != alarmHour && currentMin != alarmMin) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Fires ontrigger event.
-     */
-    private void fireTriggerEvent () {
-    	JSONArray data = new JSONArray().put(options.getJSONObject());
-        LocalNotification.fireEvent("trigger", options.getId(), options.getJSON(),data);
-    }
-}

+ 0 - 78
src/android/ReceiverActivity.java

@@ -1,78 +0,0 @@
-/*
-    Copyright 2013-2014 appPlant UG
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-
-package de.appplant.cordova.plugin.localnotification;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONArray;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-
-import de.appplant.cordova.plugin.notification.*;
-
-public class ReceiverActivity extends Activity {
-
-    /** Called when the activity is first created. */
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        Intent intent = this.getIntent();
-        Bundle bundle = intent.getExtras();
-
-        try {
-            JSONObject args = new JSONObject(bundle.getString(Receiver.OPTIONS));
-            Options options = new Options(getApplicationContext()).parse(args);
-
-            launchMainIntent();
-            fireClickEvent(options);
-        } catch (JSONException e) {}
-    }
-
-    /**
-     * Launch main intent for package.
-     */
-    private void launchMainIntent () {
-        Context context     = getApplicationContext();
-        String packageName  = context.getPackageName();
-        Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
-
-        launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
-
-        context.startActivity(launchIntent);
-    }
-
-    /**
-     * Fires the onclick event.
-     */
-    private void fireClickEvent (Options options) {
-    	JSONArray data = new JSONArray().put(options.getJSONObject());
-        LocalNotification.fireEvent("click", options.getId(), options.getJSON(),data);
-
-        if (options.getAutoCancel()) {
-            LocalNotification.fireEvent("cancel", options.getId(), options.getJSON(),data);
-        }
-    }
-}

+ 0 - 72
src/android/Restore.java

@@ -1,72 +0,0 @@
-/*
-    Copyright 2013-2014 appPlant UG
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-
-package de.appplant.cordova.plugin.localnotification;
-
-import java.util.Set;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-
-import de.appplant.cordova.plugin.notification.*;
-
-/**
- * This class is triggered upon reboot of the device. It needs to re-register
- * the alarms with the AlarmManager since these alarms are lost in case of
- * reboot.
- */
-public class Restore extends BroadcastReceiver {
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        // The application context needs to be set as first
-        LocalNotification.setContext(context);
-        //Create NotificationWrapper
-    	NotificationWrapper nWrapper = new NotificationWrapper(context,
-    			Receiver.class,LocalNotification.PLUGIN_NAME,Receiver.OPTIONS);
-
-        // Obtain alarm details form Shared Preferences
-        SharedPreferences alarms = LocalNotification.getSharedPreferences();
-        Set<String> alarmIds     = alarms.getAll().keySet();
-
-        /*
-         * For each alarm, parse its alarm options and register is again with
-         * the Alarm Manager
-         */
-        for (String alarmId : alarmIds) {
-            try {
-                JSONArray args  = new JSONArray(alarms.getString(alarmId, ""));
-                Options options = new Options(context).parse(args.getJSONObject(0));
-
-                /*
-                 * If the trigger date was in the past, the notification will be displayed immediately.
-                 */
-                nWrapper.schedule(options);
-                
-            } catch (JSONException e) {}
-        }
-    }
-}

+ 63 - 0
src/android/RestoreReceiver.java

@@ -0,0 +1,63 @@
+/*
+ * 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.localnotification;
+
+import de.appplant.cordova.plugin.notification.AbstractRestoreReceiver;
+import de.appplant.cordova.plugin.notification.Builder;
+import de.appplant.cordova.plugin.notification.Notification;
+
+/**
+ * This class is triggered upon reboot of the device. It needs to re-register
+ * the alarms with the AlarmManager since these alarms are lost in case of
+ * reboot.
+ */
+public class RestoreReceiver extends AbstractRestoreReceiver {
+
+    /**
+     * Called when a local notification need to be restored.
+     *
+     * @param notification
+     *      Wrapper around the local notification
+     */
+    @Override
+    public void onRestore (Notification notification) {
+        notification.schedule();
+    }
+
+    /**
+     * Build notification specified by options.
+     *
+     * @param builder
+     *      Notification builder
+     */
+    @Override
+    public Notification buildNotification (Builder builder) {
+        return builder
+                .setTriggerReceiver(TriggerReceiver.class)
+                .setClearReceiver(ClearReceiver.class)
+                .setClickActivity(ClickActivity.class)
+                .build();
+    }
+
+}

+ 69 - 0
src/android/TriggerReceiver.java

@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2013-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.localnotification;
+
+import de.appplant.cordova.plugin.notification.*;
+
+/**
+ * The alarm receiver is triggered when a scheduled alarm is fired. This class
+ * reads the information in the intent and displays this information in the
+ * Android notification bar. The notification uses the default notification
+ * sound and it vibrates the phone.
+ */
+public class TriggerReceiver extends de.appplant.cordova.plugin.notification.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
+     */
+    @Override
+    public void onTrigger (Notification notification, boolean updated) {
+        super.onTrigger(notification, updated);
+
+        if (!updated) {
+            LocalNotification.fireEvent("trigger", notification);
+        }
+    }
+
+    /**
+     * Build notification specified by options.
+     *
+     * @param builder
+     *      Notification builder
+     */
+    @Override
+    public Notification buildNotification (Builder builder) {
+        return builder
+                .setTriggerReceiver(TriggerReceiver.class)
+                .setClickActivity(ClickActivity.class)
+                .setClearReceiver(ClearReceiver.class)
+                .build();
+    }
+
+}

+ 75 - 0
src/android/notification/AbstractClearReceiver.java

@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2013-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 android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Abstract delete receiver for local notifications. Creates the local
+ * notification and calls the event functions for further proceeding.
+ */
+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
+     */
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Bundle bundle  = intent.getExtras();
+        JSONObject options;
+
+        try {
+            String data = bundle.getString(Options.EXTRA);
+            options = new JSONObject(data);
+        } 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
+     */
+    abstract public void onClear (Notification notification);
+
+}

+ 103 - 0
src/android/notification/AbstractClickActivity.java

@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2013-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 android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Abstract content receiver activity for local notifications. Creates the
+ * local notification and calls the event functions for further proceeding.
+ */
+abstract public class AbstractClickActivity extends Activity {
+
+    /**
+     * Called when local notification was clicked to launch the main intent.
+     *
+     * @param state
+     *      Saved instance state
+     */
+    @Override
+    public void onCreate (Bundle state) {
+        super.onCreate(state);
+
+        Intent intent   = getIntent();
+        Bundle bundle   = intent.getExtras();
+        Context context = getApplicationContext();
+
+        try {
+            String data = bundle.getString(Options.EXTRA);
+            JSONObject options = new JSONObject(data);
+
+            Builder builder =
+                    new Builder(context, options);
+
+            Notification notification =
+                    buildNotification(builder);
+
+            onClick(notification);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Called when local notification was clicked by the user.
+     *
+     * @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.
+     */
+    public void launchApp() {
+        Context context = getApplicationContext();
+        String pkgName  = context.getPackageName();
+
+        Intent intent = context
+                .getPackageManager()
+                .getLaunchIntentForPackage(pkgName);
+
+        intent.addFlags(
+                Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+
+        context.startActivity(intent);
+    }
+
+}

+ 83 - 0
src/android/notification/AbstractRestoreReceiver.java

@@ -0,0 +1,83 @@
+/*
+ * 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 android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import org.json.JSONObject;
+
+import java.util.List;
+
+/**
+ * This class is triggered upon reboot of the device. It needs to re-register
+ * the alarms with the AlarmManager since these alarms are lost in case of
+ * reboot.
+ */
+abstract public class AbstractRestoreReceiver extends BroadcastReceiver {
+
+    /**
+     * Called on device reboot.
+     *
+     * @param context
+     *      Application context
+     * @param intent
+     *      Received intent with content data
+     */
+    @Override
+    public void onReceive (Context context, Intent intent) {
+        Manager notificationMgr =
+                Manager.getInstance(context);
+
+        List<JSONObject> options =
+                notificationMgr.getOptions();
+
+        for (JSONObject data : options) {
+            Builder builder = new Builder(context, data);
+
+            Notification notification =
+                    buildNotification(builder);
+
+            onRestore(notification);
+        }
+    }
+
+    /**
+     * Called when a local notification need to be restored.
+     *
+     * @param notification
+     *      Wrapper around the local notification
+     */
+    abstract public void onRestore (Notification notification);
+
+    /**
+     * Build notification specified by options.
+     *
+     * @param builder
+     *      Notification builder
+     */
+    abstract public Notification buildNotification (Builder builder);
+
+}

+ 122 - 0
src/android/notification/AbstractTriggerReceiver.java

@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2013-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 android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Calendar;
+
+/**
+ * Abstract broadcast receiver for local notifications. Creates the
+ * notification options and calls the event functions for further proceeding.
+ */
+abstract public class AbstractTriggerReceiver extends BroadcastReceiver {
+
+    /**
+     * Called when an alarm was triggered.
+     *
+     * @param context
+     *      Application context
+     * @param intent
+     *      Received intent with content data
+     */
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Bundle bundle  = intent.getExtras();
+        Options options;
+
+        try {
+            String data = bundle.getString(Options.EXTRA);
+            JSONObject dict = new JSONObject(data);
+
+            options = new Options(context).parse(dict);
+        } catch (JSONException e) {
+            e.printStackTrace();
+            return;
+        }
+
+        if (options == null)
+            return;
+
+        if (isFirstAlarmInFuture(options))
+            return;
+
+        Builder builder = new Builder(options);
+        Notification notification = buildNotification(builder);
+        boolean updated = notification.isUpdate();
+
+        onTrigger(notification, updated);
+    }
+
+    /**
+     * Called when a local notification was triggered.
+     *
+     * @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
+     */
+    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);
+    }
+
+}

+ 0 - 310
src/android/notification/Asset.java

@@ -1,310 +0,0 @@
-/*
-    Copyright 2013-2014 appPlant UG
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-
-package de.appplant.cordova.plugin.notification;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.app.Activity;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.StrictMode;
-import android.util.Log;
-
-
-public class Asset {
-	private Activity activity;
-	protected final String STORAGE_FOLDER;
-	
-	/**
-	 * Constructor of Asset Class. Takes Applications Activity and a foldername for temporary saving.
-	 *
-	 *@param activity Applications Activity
-	 *@param storageFolder foldername for temporary saving.
-	 */
-	public Asset(Activity activity,String storageFolder){
-		this.activity = activity;
-		this.STORAGE_FOLDER = storageFolder;
-	}
-	
-	/**
-	 * Parse given PathStrings to Uris
-	 * @param notification Notifications JSONObject
-	 * @return	new Notification JSONObject with additional iconUri and soundUri
-	 */
-    public JSONObject parseURIs(JSONObject notification){
-    	//sound
-        String sound = notification.optString("sound", null);	
-        Uri soundUri = null;
-        if (sound != null) {
-            try {
-                int soundId = (Integer) RingtoneManager.class.getDeclaredField(sound).get(Integer.class);
-
-                soundUri = RingtoneManager.getDefaultUri(soundId);
-            } catch (Exception e) {
-            	soundUri = getURIfromPath(sound);
-            }
-        }
-        if (soundUri!= null&&soundUri!=Uri.EMPTY){
-        	try{
-        		notification.put("soundUri", soundUri.toString());
-        	} catch (JSONException jse){
-        		jse.printStackTrace();
-        	}
-        }
-    	//image
-        String icon = notification.optString("icon", "icon");
-        Uri iconUri = null;
-        iconUri = getURIfromPath(icon);
-        if (iconUri != Uri.EMPTY&&iconUri != null){
-        	try{
-        		notification.put("iconUri", iconUri.toString());
-        	} catch (JSONException jse){
-        		jse.printStackTrace();
-        	}
-        }
-        return notification;
-    }
-	
-	/**
-	 * The URI for a path.
-	 * 
-	 * @param path The given path
-	 * 
-	 * @return The URI pointing to the given path
-	 */
-    public Uri getURIfromPath(String path){
-		if (path.startsWith("res:")) {
-			return getUriForResourcePath(path);
-		} else if (path.startsWith("file:///")) {
-			return getUriForAbsolutePath(path);
-		} else if (path.startsWith("file://")) {
-			return getUriForAssetPath(path);
-		} else if (path.startsWith("http")){
-			return getUriForHTTP(path);
-		}
-		return Uri.EMPTY;
-	}
-    
-
-    
-	/**
-	 * The URI for a file.
-	 * 
-	 * @param path
-	 *            The given absolute path
-	 * 
-	 * @return The URI pointing to the given path
-	 */
-	private Uri getUriForAbsolutePath(String path) {
-		String absPath = path.replaceFirst("file://", "");
-		File file = new File(absPath);
-		if (!file.exists()) {
-			Log.e("Asset", "File not found: " + file.getAbsolutePath());
-			return Uri.EMPTY;
-		}
-		return Uri.fromFile(file);
-	}
-
-	/**
-	 * The URI for an asset.
-	 * 
-	 * @param path
-	 *            The given asset path
-	 * 
-	 * @return The URI pointing to the given path
-	 */
-	private Uri getUriForAssetPath(String path) {
-		String resPath = path.replaceFirst("file:/", "www");
-		String fileName = resPath.substring(resPath.lastIndexOf('/') + 1);
-		File dir = 		activity.getExternalCacheDir();
-		if (dir == null) {
-			Log.e("Asset", "Missing external cache dir");
-			return Uri.EMPTY;
-		}
-		String storage = dir.toString() + STORAGE_FOLDER;
-		File file = new File(storage, fileName);
-		new File(storage).mkdir();
-		try {
-			AssetManager assets = activity.getAssets();
-			FileOutputStream outStream = new FileOutputStream(file);
-			InputStream inputStream = assets.open(resPath);
-			copyFile(inputStream, outStream);
-			outStream.flush();
-			outStream.close();
-			return Uri.fromFile(file);
-		} catch (Exception e) {
-			Log.e("Asset", "File not found: assets/" + resPath);
-			e.printStackTrace();
-		}
-		return Uri.EMPTY;
-	}
-
-	/**
-	 * The URI for a resource.
-	 * 
-	 * @param path
-	 *            The given relative path
-	 * 
-	 * @return The URI pointing to the given path
-	 */
-	private Uri getUriForResourcePath(String path) {
-		String resPath = path.replaceFirst("res://", "");
-		String fileName = resPath.substring(resPath.lastIndexOf('/') + 1);
-		String resName = fileName.substring(0, fileName.lastIndexOf('.'));
-		String extension = resPath.substring(resPath.lastIndexOf('.'));
-		File dir = activity.getExternalCacheDir();
-		if (dir == null) {
-			Log.e("Asset", "Missing external cache dir");
-			return Uri.EMPTY;
-		}
-		String storage = dir.toString() + STORAGE_FOLDER;
-		int resId = getResId(resPath);
-		File file = new File(storage, resName + extension);
-		if (resId == 0) {
-			Log.e("Asset", "File not found: " + resPath);
-			return Uri.EMPTY;
-		}
-		new File(storage).mkdir();
-		try {
-			Resources res = activity.getResources();
-			FileOutputStream outStream = new FileOutputStream(file);
-			InputStream inputStream = res.openRawResource(resId);
-			copyFile(inputStream, outStream);
-			outStream.flush();
-			outStream.close();
-			return Uri.fromFile(file);
-		} catch (Exception e) {
-			e.printStackTrace();
-		}
-		return Uri.EMPTY;
-	}
-	
-	/**
-	 *Get Uri for HTTP Content
-	 * @param path HTTP adress
-	 * @return Uri of the downloaded file
-	 */
-	private Uri getUriForHTTP(String path) {
-        try {
-			URL url = new URL(path);
-			String fileName = path.substring(path.lastIndexOf('/') + 1);
-			String resName = fileName.substring(0, fileName.lastIndexOf('.'));
-			String extension = path.substring(path.lastIndexOf('.'));
-			File dir = activity.getExternalCacheDir();
-			if (dir == null) {
-				Log.e("Asset", "Missing external cache dir");
-				return Uri.EMPTY;
-			}
-			String storage = dir.toString() + STORAGE_FOLDER;
-			File file = new File(storage, resName + extension);
-			new File(storage).mkdir();
-			
-			HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-
-			StrictMode.ThreadPolicy policy =
-			        new StrictMode.ThreadPolicy.Builder().permitAll().build();
-
-			StrictMode.setThreadPolicy(policy);
-
-			connection.setDoInput(true);
-			connection.connect();
-
-			InputStream input = connection.getInputStream();
-			FileOutputStream outStream = new FileOutputStream(file);		
-			copyFile(input, outStream);
-			outStream.flush();
-			outStream.close();
-			
-			return Uri.fromFile(file);
-		} catch (MalformedURLException e) {
-			Log.e("Asset", "Incorrect URL");			
-			e.printStackTrace();
-		} catch (FileNotFoundException e) {
-			Log.e("Asset", "Failed to create new File from HTTP Content");
-			e.printStackTrace();
-		} catch (IOException e) {
-			Log.e("Asset", "No Input can be created from http Stream");
-			e.printStackTrace();
-		}
-        return Uri.EMPTY;
-	}
-
-	/**
-	 * Writes an InputStream to an OutputStream
-	 * 
-	 * @param in
-	 *            The input stream
-	 * @param out
-	 *            The output stream
-	 */
-	private void copyFile(InputStream in, OutputStream out) throws IOException {
-		byte[] buffer = new byte[1024];
-		int read;
-		while ((read = in.read(buffer)) != -1) {
-			out.write(buffer, 0, read);
-		}
-	}
-
-	/**
-	 * @return The resource ID for the given resource.
-	 */
-	private int getResId(String resPath) {
-		Resources res = activity.getResources();
-		int resId;
-		String pkgName = getPackageName();
-		String dirName = "drawable";
-		String fileName = resPath;
-		if (resPath.contains("/")) {
-			dirName = resPath.substring(0, resPath.lastIndexOf('/'));
-			fileName = resPath.substring(resPath.lastIndexOf('/') + 1);
-		}
-		String resName = fileName.substring(0, fileName.lastIndexOf('.'));
-		resId = res.getIdentifier(resName, dirName, pkgName);
-		if (resId == 0) {
-			resId = res.getIdentifier(resName, "drawable", pkgName);
-		}
-		return resId;
-	}
-	
-	/**
-	 * The name for the package.
-	 * 
-	 * @return The package name
-	 */
-	private String getPackageName() {
-		return activity.getPackageName();
-	}
-
-}

+ 438 - 0
src/android/notification/AssetUtil.java

@@ -0,0 +1,438 @@
+/*
+ * Copyright (c) 2013-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 java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.StrictMode;
+import android.util.Log;
+
+/**
+ * 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 {
+
+    // 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;
+	
+	/**
+	 * Constructor
+	 *
+	 * @param context
+     *      Application context
+	 */
+	private AssetUtil(Context context) {
+		this.context = context;
+	}
+
+    /**
+     * Static method to retrieve class instance.
+     *
+     * @param context
+     *      Application context
+     */
+    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:")) {
+			return getUriForResourcePath(path);
+		} else if (path.startsWith("file:///")) {
+			return getUriFromPath(path);
+		} else if (path.startsWith("file://")) {
+			return getUriFromAsset(path);
+		} else if (path.startsWith("http")){
+			return getUriFromRemote(path);
+		}
+
+		return Uri.EMPTY;
+	}
+    
+
+    
+	/**
+	 * URI for a file.
+	 * 
+	 * @param path
+	 *      Absolute path like file:///...
+	 * 
+	 * @return
+     *      URI pointing to the given path
+	 */
+	private Uri getUriFromPath(String path) {
+		String absPath = path.replaceFirst("file://", "");
+		File file = new File(absPath);
+
+		if (!file.exists()) {
+			Log.e("Asset", "File not found: " + file.getAbsolutePath());
+			return Uri.EMPTY;
+		}
+
+		return Uri.fromFile(file);
+	}
+
+	/**
+	 * URI for an asset.
+	 * 
+	 * @param path
+	 *      Asset path like file://...
+	 * 
+	 * @return
+     *      URI pointing to the given path
+	 */
+    private Uri getUriFromAsset(String path) {
+		File dir = context.getExternalCacheDir();
+
+		if (dir == null) {
+			Log.e("Asset", "Missing external cache dir");
+			return Uri.EMPTY;
+		}
+
+        String resPath  = path.replaceFirst("file:/", "www");
+        String fileName = resPath.substring(resPath.lastIndexOf('/') + 1);
+		String storage  = dir.toString() + STORAGE_FOLDER;
+		File file       = new File(storage, fileName);
+
+        //noinspection ResultOfMethodCallIgnored
+        new File(storage).mkdir();
+
+		try {
+			AssetManager assets = context.getAssets();
+			FileOutputStream outStream = new FileOutputStream(file);
+			InputStream inputStream = assets.open(resPath);
+
+			copyFile(inputStream, outStream);
+
+			outStream.flush();
+			outStream.close();
+
+			return Uri.fromFile(file);
+
+		} catch (Exception e) {
+			Log.e("Asset", "File not found: assets/" + resPath);
+			e.printStackTrace();
+		}
+
+		return Uri.EMPTY;
+	}
+
+	/**
+	 * The URI for a resource.
+	 * 
+	 * @param path
+	 *            The given relative path
+	 * 
+	 * @return
+     *      URI pointing to the given path
+	 */
+	private Uri getUriForResourcePath(String path) {
+		File dir = context.getExternalCacheDir();
+
+		if (dir == null) {
+			Log.e("Asset", "Missing external cache dir");
+			return Uri.EMPTY;
+		}
+
+        String resPath = path.replaceFirst("res://", "");
+
+		int resId = getResIdForDrawable(resPath);
+
+		if (resId == 0) {
+			Log.e("Asset", "File not found: " + resPath);
+			return Uri.EMPTY;
+		}
+
+        String resName = extractResourceName(resPath);
+        String extName = extractResourceExtension(resPath);
+        String storage = dir.toString() + STORAGE_FOLDER;
+        File file      = new File(storage, resName + extName);
+
+        //noinspection ResultOfMethodCallIgnored
+        new File(storage).mkdir();
+
+		try {
+			Resources res = context.getResources();
+			FileOutputStream outStream = new FileOutputStream(file);
+			InputStream inputStream = res.openRawResource(resId);
+			copyFile(inputStream, outStream);
+
+			outStream.flush();
+			outStream.close();
+
+			return Uri.fromFile(file);
+
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+
+        return Uri.EMPTY;
+	}
+	
+	/**
+	 * Uri from remote located content.
+     *
+	 * @param path
+     *      Remote address
+     *
+	 * @return
+     *      Uri of the downloaded file
+	 */
+	private Uri getUriFromRemote(String path) {
+        File dir = context.getExternalCacheDir();
+
+        if (dir == null) {
+            Log.e("Asset", "Missing external cache dir");
+            return Uri.EMPTY;
+        }
+
+        String resName  = extractResourceName(path);
+        String extName  = extractResourceExtension(path);
+        String storage  = dir.toString() + STORAGE_FOLDER;
+        File file       = new File(storage, resName + extName);
+
+        //noinspection ResultOfMethodCallIgnored
+        new File(storage).mkdir();
+
+        try {
+            URL url = new URL(path);
+			HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+
+			StrictMode.ThreadPolicy policy =
+			        new StrictMode.ThreadPolicy.Builder().permitAll().build();
+
+			StrictMode.setThreadPolicy(policy);
+
+            connection.setRequestProperty("Connection", "close");
+            connection.setConnectTimeout(5000);
+			connection.connect();
+
+			InputStream input = connection.getInputStream();
+			FileOutputStream outStream = new FileOutputStream(file);
+
+			copyFile(input, outStream);
+
+			outStream.flush();
+			outStream.close();
+			
+			return Uri.fromFile(file);
+
+		} catch (MalformedURLException e) {
+			Log.e("Asset", "Incorrect URL");			
+			e.printStackTrace();
+		} catch (FileNotFoundException e) {
+			Log.e("Asset", "Failed to create new File from HTTP Content");
+			e.printStackTrace();
+		} catch (IOException e) {
+			Log.e("Asset", "No Input can be created from http Stream");
+			e.printStackTrace();
+		}
+
+        return Uri.EMPTY;
+	}
+
+	/**
+	 * Copy content from input stream into output stream.
+	 * 
+	 * @param in
+	 *      The input stream
+	 * @param out
+	 *      The output stream
+	 */
+	private void copyFile(InputStream in, OutputStream out) throws IOException {
+		byte[] buffer = new byte[1024];
+		int read;
+
+		while ((read = in.read(buffer)) != -1) {
+			out.write(buffer, 0, read);
+		}
+	}
+
+    /**
+     * Resource ID for drawable.
+     *
+     * @param resPath
+     *      Resource path as string
+     */
+    int getResIdForDrawable(String resPath) {
+        int resId = getResIdForDrawable(getPkgName(), resPath);
+
+        if (resId == 0) {
+            resId = getResIdForDrawable("android", resPath);
+        }
+
+        return resId;
+    }
+
+    /**
+     * 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 = extractResourceName(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 drawable
+     *      Drawable resource name
+     */
+    Bitmap getIconFromDrawable (String drawable) {
+        Resources res = context.getResources();
+        int iconId;
+
+        iconId = getResIdForDrawable(getPkgName(), drawable);
+
+        if (iconId == 0) {
+            iconId = getResIdForDrawable("android", drawable);
+        }
+
+        if (iconId == 0) {
+            iconId = android.R.drawable.ic_menu_info_details;
+        }
+
+        return BitmapFactory.decodeResource(res, iconId);
+    }
+
+    /**
+     * Convert URI to Bitmap.
+     *
+     * @param uri
+     *      Internal image URI
+     */
+    Bitmap getIconFromUri (Uri uri) throws IOException {
+        InputStream input = context.getContentResolver().openInputStream(uri);
+
+        return BitmapFactory.decodeStream(input);
+    }
+
+    /**
+     * Extract name of drawable resource from path.
+     *
+     * @param resPath
+     *      Resource path as string
+     */
+    private String extractResourceName (String resPath) {
+        String drawable = resPath;
+
+        if (drawable.contains("/")) {
+            drawable = drawable.substring(drawable.lastIndexOf('/') + 1);
+        }
+
+        if (resPath.contains(".")) {
+            drawable = drawable.substring(0, drawable.lastIndexOf('.'));
+        }
+
+        return drawable;
+    }
+
+    /**
+     * Extract extension of drawable resource from path.
+     *
+     * @param resPath
+     *      Resource path as string
+     */
+    private String extractResourceExtension (String resPath) {
+        String extName = "png";
+
+        if (resPath.contains(".")) {
+            extName = resPath.substring(resPath.lastIndexOf('.'));
+        }
+
+        return extName;
+    }
+
+    /**
+     * Package name specified by context.
+     */
+    private String getPkgName () {
+        return context.getPackageName();
+    }
+
+}

+ 194 - 0
src/android/notification/Builder.java

@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2013-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 java.util.Random;
+
+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;
+
+/**
+ * Builder class for local notifications. Build fully configured local
+ * notification specified by JSON object passed from JS side.
+ */
+public class Builder {
+
+    // Application context passed by constructor
+    private final Context context;
+
+    // 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;
+
+    // Activity to handle the click event
+    private Class<?> clickActivity = ClickActivity.class;
+
+    /**
+     * 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
+     */
+    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
+     */
+    public Builder setClearReceiver(Class<?> receiver) {
+        this.clearReceiver = receiver;
+        return this;
+    }
+
+    /**
+     * Set click activity.
+     *
+     * @param activity
+     *      Activity
+     */
+    public Builder setClickActivity(Class<?> activity) {
+        this.clickActivity = activity;
+        return this;
+    }
+
+    /**
+     * Creates the notification with all its options passed through JS.
+     */
+    public Notification build() {
+        Uri sound = options.getSoundUri();
+        NotificationCompat.BigTextStyle style;
+        NotificationCompat.Builder builder;
+
+        style = new NotificationCompat.BigTextStyle()
+                .bigText(options.getText());
+
+        builder = new NotificationCompat.Builder(context)
+                .setDefaults(0)
+                .setContentTitle(options.getTitle())
+                .setContentText(options.getText())
+                .setNumber(options.getBadgeNumber())
+                .setTicker(options.getText())
+                .setSmallIcon(options.getSmallIcon())
+                .setLargeIcon(options.getIconBitmap())
+                .setAutoCancel(true)
+                .setOngoing(options.isOngoing())
+                .setStyle(style)
+                .setLights(options.getLedColor(), 500, 500);
+
+        if (sound != null) {
+            builder.setSound(sound);
+        }
+
+        applyDeleteReceiver(builder);
+        applyContentReceiver(builder);
+
+        return new Notification(context, options, builder, triggerReceiver);
+    }
+
+    /**
+     * Set intent to handle the delete event. Will clean up some persisted
+     * preferences.
+     *
+     * @param builder
+     *      Local notification builder instance
+     */
+    private void applyDeleteReceiver(NotificationCompat.Builder builder) {
+
+        if (clearReceiver == null)
+            return;
+
+        Intent deleteIntent = new Intent(context, clearReceiver)
+                .setAction(options.getId())
+                .putExtra(Options.EXTRA, options.toString());
+
+        PendingIntent dpi = PendingIntent.getBroadcast(
+                context, 0, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+
+        builder.setDeleteIntent(dpi);
+    }
+
+    /**
+     * Set intent to handle the click event. Will bring the app to
+     * foreground.
+     *
+     * @param builder
+     *      Local notification builder instance
+     */
+    private void applyContentReceiver(NotificationCompat.Builder builder) {
+
+        if (clickActivity == null)
+            return;
+
+        Intent intent = new Intent(context, clickActivity)
+                .putExtra(Options.EXTRA, options.toString())
+                .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+
+        int requestCode = new Random().nextInt();
+
+        PendingIntent contentIntent = PendingIntent.getActivity(
+                context, requestCode, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+
+        builder.setContentIntent(contentIntent);
+    }
+
+}

+ 44 - 0
src/android/notification/ClearReceiver.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2013-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;
+
+/**
+ * The clear intent receiver is triggered when the user clears a
+ * notification manually. It un-persists the cleared notification from the
+ * shared preferences.
+ */
+public class ClearReceiver extends AbstractClearReceiver {
+
+    /**
+     * Called when a local notification was cleared from outside of the app.
+     *
+     * @param notification
+     *      Wrapper around the local notification
+     */
+    @Override
+    public void onClear (Notification notification) {
+        notification.clear();
+    }
+
+}

+ 55 - 0
src/android/notification/ClickActivity.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2013-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;
+
+/**
+ * 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 AbstractClickActivity {
+
+    /**
+     * Called when local notification was clicked by the user. Will
+     * move the app to foreground.
+     *
+     * @param notification
+     *      Wrapper around the local notification
+     */
+    @Override
+    public void onClick(Notification notification) {
+        launchApp();
+    }
+
+    /**
+     * Build notification specified by options.
+     *
+     * @param builder
+     *      Notification builder
+     */
+    public Notification buildNotification (Builder builder) {
+        return builder.build();
+    }
+
+}

+ 353 - 343
src/android/notification/Manager.java

@@ -1,440 +1,450 @@
+/*
+ * Copyright (c) 2013-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 java.util.Date;
-import java.util.Map;
-import java.util.Set;
+import static de.appplant.cordova.plugin.notification.Notification.PREF_KEY;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.SharedPreferences;
 
-import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.os.Build;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
-public class Manager {	
-    //---------------Global Parameter------------------------------------------------------------
+/**
+ * Central way to access all or single local notifications set by specific
+ * state like triggered or scheduled. Offers shortcut ways to schedule,
+ * cancel or clear local notifications.
+ */
+public class Manager {
+
+    // Context passed through constructor and used for notification builder.
 	private Context context;
-	private final String PLUGIN_NAME;
-	
-    //---------------Constructor-----------------------------------------------------------------
-	/**
-	 * Constructor of NotificationWrapper-Class
-	 */
-	public Manager(Context context,String PluginName){
+
+    /**
+     * Constructor
+     *
+     * @param context
+     *      Application context
+     */
+	private Manager(Context context){
 		this.context = context;
-		this.PLUGIN_NAME = PluginName;
 	}
-	
-    //---------------Public Functions------------------------------------------------------------
-	/**
-     * Checks if a notification with an ID is scheduled.
+
+    /**
+     * Static method to retrieve class instance.
      *
-     * @param id
-     *          The notification ID to be check.
-     * @return true if the notification is scheduled
+     * @param context
+     *      Application context
      */
-    public boolean isScheduled (String id) {
-        SharedPreferences settings = getSharedPreferences();
-        Map<String, ?> alarms      = settings.getAll();
-        boolean isScheduled        = alarms.containsKey(id);
-        boolean isNotTriggered	   = false;
-        
-        if (isScheduled) {
-        	JSONObject arguments;
-			try {
-				arguments = new JSONObject(alarms.get(id).toString());
-	        	Options options      = new Options(context).parse(arguments);
-	        	Date fireDate        = new Date(options.getDate());
-	        	isNotTriggered = new Date().before(fireDate);
-			} catch (JSONException e) {
-				isNotTriggered = false;
-				e.printStackTrace();
-			}
-        	
-        }
-        
-        return (isScheduled && isNotTriggered);
+    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;
     }
-    
+
     /**
-     * Checks if a notification with an ID was triggered.
+     * Clear local notification specified by ID.
      *
      * @param id
-     *          The notification ID to be check.
-     * @return true if the notification is triggered
+     *      The notification ID
+     * @param updates
+     *      JSON object with notification options
+     * @param receiver
+     *      Receiver to handle the trigger event
      */
-    public boolean isTriggered (String id) {
-        SharedPreferences settings = getSharedPreferences();
-        Map<String, ?> alarms      = settings.getAll();
-        boolean isScheduled        = alarms.containsKey(id);
-        boolean isTriggered        = isScheduled;
-
-        if (isScheduled) {
-        	JSONObject arguments;
-			try {
-				arguments = new JSONObject(alarms.get(id).toString());
-	        	Options options      = new Options(context).parse(arguments);
-	        	Date fireDate        = new Date(options.getInitialDate());
-	        	isTriggered = new Date().after(fireDate);
-			} catch (JSONException e) {
-				isTriggered = false;
-				e.printStackTrace();
-			}
-        	
-        }
-        return isTriggered;
+    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.putOpt("updatedAt", new Date().getTime());
+        } catch (JSONException ignore) {}
+
+        return schedule(options, receiver);
     }
+
     /**
-     * Checks whether a notification with an ID exist.
+     * Clear local notification specified by ID.
      *
      * @param id
-     *          The notification ID to check.
-     * @return true if the notification exist
+     *      The notification ID
      */
-    public boolean exist(String id){
-    	boolean exist;
-        SharedPreferences settings = getSharedPreferences();
-        Map<String, ?> alarms      = settings.getAll();
-        exist = alarms.containsKey(id);
-    	return exist;
+    public Notification clear (int id) {
+        Notification notification = get(id);
+
+        if (notification != null) {
+            notification.clear();
+        }
+
+        return notification;
     }
 
     /**
-     * Retrieves a list with all currently pending notification Ids.
+     * Clear local notification specified by ID.
      *
-     * @return JSONArray with all Id-Strings
+     * @param id
+     *      The notification ID
      */
-    public JSONArray getScheduledIds () {
-        SharedPreferences settings = getSharedPreferences();
-        Map<String, ?> alarms      = settings.getAll();
-        Set<String> alarmIds       = alarms.keySet();
-        JSONArray scheduledIds     = new JSONArray();
-        
-        for (String id : alarmIds) {
-        	boolean isScheduled;
-        	JSONObject arguments;
- 			try {
- 				arguments = new JSONObject(alarms.get(id).toString());
- 	        	Options options      = new Options(context).parse(arguments);
- 	        	Date fireDate        = new Date(options.getDate());
- 	        	isScheduled = new Date().before(fireDate);
- 			} catch (JSONException e) {
- 				isScheduled = false;
- 				e.printStackTrace();
- 			}
- 			if (isScheduled){
- 				scheduledIds.put(id);
- 			}
+    public Notification cancel (int id) {
+        Notification notification = get(id);
+
+        if (notification != null) {
+            notification.cancel();
         }
 
-        
-        
-        return scheduledIds;
+        return notification;
     }
 
+    /**
+     * Clear all local notifications.
+     */
+    public void clearAll () {
+        List<Notification> notifications = getAll();
+
+        for (Notification notification : notifications) {
+            notification.clear();
+        }
 
+        getNotMgr().cancelAll();
+    }
 
     /**
-     * Retrieves a list with all currently triggered notification Ids.
-     *
-     * @return JSONArray with all Id-Strings
+     * Cancel all local notifications.
      */
-    public JSONArray getTriggeredIds () {
-        SharedPreferences settings = getSharedPreferences();
-        Map<String, ?> alarms      = settings.getAll();
-        Set<String> alarmIds       = alarms.keySet();
-        JSONArray triggeredIds     = new JSONArray();
-        Date now                   = new Date();
-
-        for (String id : alarmIds) {
-        	boolean isTriggered;
-        	JSONObject arguments;
-        	try{
-        		arguments = new JSONObject(alarms.get(id).toString());
-        		Options options      = new Options(context).parse(arguments);
-        		Date fireDate        = new Date(options.getInitialDate());
-        		isTriggered  = now.after(fireDate);
-            } catch(ClassCastException cce) {
-            	cce.printStackTrace();
-            	isTriggered = false;
-            } catch(JSONException jse) {
-        		jse.printStackTrace();
-            	isTriggered = false;
-            }
+    public void cancelAll () {
+        List<Notification> notifications = getAll();
 
-            if (isTriggered == true) {
-                triggeredIds.put(id);
-            }
+        for (Notification notification : notifications) {
+            notification.cancel();
         }
 
-        return triggeredIds;
+        getNotMgr().cancelAll();
     }
-    
+
     /**
-     * Retrieves a list with all currently triggered or scheduled notification-Ids.
-     * @return JSONArray with all Id-Strings
+     * All local notifications IDs.
      */
-    public JSONArray getAllIds (){
-        JSONArray allIds     = new JSONArray();
-        SharedPreferences settings = getSharedPreferences();
-        Map<String, ?> alarms      = settings.getAll();
-        Set<String> alarmIds       = alarms.keySet();
-        for (String id : alarmIds) {
-        	allIds.put(id);
+    public List<Integer> getIds() {
+        Set<String> keys = getPrefs().getAll().keySet();
+        ArrayList<Integer> ids = new ArrayList<Integer>();
+
+        for (String key : keys) {
+            ids.add(Integer.parseInt(key));
         }
-        return allIds;
+
+        return ids;
     }
-    
+
     /**
-     * Retrieves a list with all currently pending notification JSONObject.
+     * All local notification IDs for given type.
      *
-     * @return JSONArray with all notification-JSONObjects
+     * @param type
+     *      The notification life cycle type
      */
-    public JSONArray getAll(){
-        SharedPreferences settings = getSharedPreferences();
-        Map<String, ?> alarms      = settings.getAll();
-        Set<String> alarmIds       = alarms.keySet();
-        JSONArray all     = new JSONArray();
-
-        for (String id : alarmIds) {
-        	JSONObject arguments;
-        	try{
-        		arguments = new JSONObject(alarms.get(id).toString());
-        		all.put(arguments);
-        	} catch(JSONException jse) {
-        		jse.printStackTrace();
+    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 all;
+
+        return ids;
     }
+
     /**
-     * Retrieves a list with all currently scheduled notification-JSONObjects.
+     * List of local notifications with matching ID.
      *
-     * @return JSONArray with all notification-JSONObjects
+     * @param ids
+     *      Set of notification IDs
      */
-    public JSONArray getScheduled(){
-        SharedPreferences settings = getSharedPreferences();
-        Map<String, ?> alarms      = settings.getAll();
-        Set<String> alarmIds       = alarms.keySet();
-        JSONArray scheduled     = new JSONArray();
-        
-        for (String id : alarmIds) {
-        	boolean isScheduled;
-        	JSONObject arguments = null;
- 			try {
- 				arguments = new JSONObject(alarms.get(id).toString());
- 	        	Options options      = new Options(context).parse(arguments);
- 	        	Date fireDate        = new Date(options.getDate());
- 	        	isScheduled = new Date().before(fireDate);
- 			} catch (JSONException e) {
- 				isScheduled = false;
- 				e.printStackTrace();
- 			}
- 			if (isScheduled){
- 				scheduled.put(arguments);
- 			}
+    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 scheduled;
-    	
+        return notifications;
+    }
+
+    /**
+     * List of all local notification.
+     */
+    public List<Notification> getAll() {
+        return getByIds(getIds());
     }
-    
+
     /**
-     * Retrieves a list with all currently triggered notification-JSONObjects.
+     * List of local notifications from given type.
      *
-     * @return JSONArray with all notification-JSONObjects
+     * @param type
+     *      The notification life cycle type
      */
-    public JSONArray getTriggered(){
-        SharedPreferences settings = getSharedPreferences();
-        Map<String, ?> alarms      = settings.getAll();
-        Set<String> alarmIds       = alarms.keySet();
-        JSONArray triggered     = new JSONArray();
-        Date now                   = new Date();
-
-        for (String id : alarmIds) {
-        	boolean isTriggered;
-        	JSONObject arguments = null;
-        	try{
-        		arguments = new JSONObject(alarms.get(id).toString());
-        		Options options      = new Options(context).parse(arguments);
-        		Date fireDate        = new Date(options.getInitialDate());
-        		isTriggered  = now.after(fireDate);
-            } catch(ClassCastException cce) {
-            	cce.printStackTrace();
-            	isTriggered = false;
-            } catch(JSONException jse) {
-        		jse.printStackTrace();
-            	isTriggered = false;
-            }
+    public List<Notification> getByType(Notification.Type type) {
+        List<Notification> notifications = getAll();
+        ArrayList<Notification> list = new ArrayList<Notification>();
 
-            if (isTriggered == true) {
-                triggered.put(arguments);
+        for (Notification notification : notifications) {
+            if (notification.getType() == type) {
+                list.add(notification);
             }
         }
 
-        return triggered;
+        return list;
     }
-    
-    
+
     /**
-     * Retrieves a list with all currently pending notification JSONObjects that matches with the given String-Array
+     * List of local notifications with matching ID from given type.
      *
-     * @return JSONArray with notification-JSONObjects 
+     * @param type
+     *      The notification life cycle type
+     * @param ids
+     *      Set of notification IDs
      */
-    public JSONArray getAll(JSONArray ids){
-        SharedPreferences settings = getSharedPreferences();
-        Map<String, ?> alarms      = settings.getAll();
-        Set<String> alarmIds       = alarms.keySet();
-        JSONArray all     = new JSONArray();
-        for (String id : alarmIds) {
-        	for(int i=0;i<ids.length();i++){
-        		if(ids.optString(i).equals(id)){
-		        	JSONObject arguments;
-		        	try{
-		        		arguments = new JSONObject(alarms.get(id).toString());
-		        		all.put(arguments);
-		        	} catch(JSONException jse) {
-		        		jse.printStackTrace();
-		            }
-        		}
-        	}
+    @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 all;
+
+        return notifications;
     }
+
     /**
-     * Retrieves a list with all currently scheduled notification-JSONObjects.
+     * If a notification with an ID exists.
      *
-     * @return JSONArray with all notification-JSONObjects
+     * @param id
+     *      Notification ID
      */
-    public JSONArray getScheduled(JSONArray ids){
-        SharedPreferences settings = getSharedPreferences();
-        Map<String, ?> alarms      = settings.getAll();
-        Set<String> alarmIds       = alarms.keySet();
-        JSONArray scheduled     = new JSONArray();
-        boolean isScheduled;
-        
-        for (String id : alarmIds) {
-        	for(int i=0;i<ids.length();i++){
-        		if(ids.optString(i).equals(id)){
-		        	JSONObject arguments = null;
-		 			try {
-		 				arguments = new JSONObject(alarms.get(id).toString());
-		 	        	Options options      = new Options(context).parse(arguments);
-		 	        	Date fireDate        = new Date(options.getDate());
-		 	        	isScheduled = new Date().before(fireDate);
-		 			} catch (JSONException e) {
-		 				isScheduled = false;
-		 				e.printStackTrace();
-		 			}
-		 			if (isScheduled){
-		 				scheduled.put(arguments);
-		 			}
-        		}
-        	}
-        }
+    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 scheduled;
-    	
+        return notification != null && notification.getType() == type;
     }
-    
+
     /**
-     * Retrieves a list with all currently triggered notification-JSONObjects.
+     * List of properties from all local notifications.
+     */
+    public List<JSONObject> getOptions() {
+        return getOptionsById(getIds());
+    }
+
+    /**
+     * List of properties from local notifications with matching ID.
      *
-     * @return JSONArray with all notification-JSONObjects
+     * @param ids
+     *      Set of notification IDs
      */
-    public JSONArray getTriggered(JSONArray ids){
-        SharedPreferences settings = getSharedPreferences();
-        Map<String, ?> alarms      = settings.getAll();
-        Set<String> alarmIds       = alarms.keySet();
-        JSONArray triggered     = new JSONArray();
-        Date now                   = new Date();
-
-        for (String id : alarmIds) {
-        	for(int i=0;i<ids.length();i++){
-        		if(ids.optString(i).equals(id)){
-		        	boolean isTriggered;
-		        	JSONObject arguments = null;
-		        	try{
-		        		arguments = new JSONObject(alarms.get(id).toString());
-		        		Options options      = new Options(context).parse(arguments);
-		        		Date fireDate        = new Date(options.getInitialDate());
-		        		isTriggered  = now.after(fireDate);
-		            } catch(ClassCastException cce) {
-		            	cce.printStackTrace();
-		            	isTriggered = false;
-		            } catch(JSONException jse) {
-		        		jse.printStackTrace();
-		            	isTriggered = false;
-		            }
-		
-		            if (isTriggered == true) {
-		                triggered.put(arguments);
-		            }
-        		}
-        	}
+    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 triggered;
+        return options;
     }
-    
-    
-    
-    //---------------Manage Shared Preferences---------------------------------------------------
-    
+
     /**
-     * The Local storage for the application.
+     * List of properties from all local notifications from given type.
+     *
+     * @param type
+     *      The notification life cycle type
      */
-    private SharedPreferences getSharedPreferences () {
-        return context.getSharedPreferences(PLUGIN_NAME, Context.MODE_PRIVATE);
+    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;
     }
-    
+
     /**
-     * Persist the information of this alarm to the Android Shared Preferences.
-     * This will allow the application to restore the alarm upon device reboot.
-     * Also this is used by the cancelAll method.
+     * List of properties from local notifications with matching ID from
+     * given type.
      *
-     * @param alarmId
-     *            The Id of the notification that must be persisted.
-     * @param args
-     *            The assumption is that parse has been called already.
+     * @param type
+     *      The notification life cycle type
+     * @param ids
+     *      Set of notification IDs
      */
-    public void persist (String alarmId, JSONObject args) {
-        Editor editor = getSharedPreferences().edit();
-
-        if (alarmId != null) {
-            editor.putString(alarmId, args.toString());
-            if (Build.VERSION.SDK_INT<9) {
-                editor.commit();
-            } else {
-                editor.apply();
+    public List<JSONObject> getOptionsBy(Notification.Type type,
+                                         List<Integer> 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;
     }
-    
+
     /**
-     * Remove a specific alarm from the Android shared Preferences.
+     * Get existent local notification.
      *
-     * @param alarmId
-     *            The Id of the notification that must be removed.
+     * @param id
+     *      Notification ID
      */
-    public void unpersist (String alarmId) {
-        Editor editor = getSharedPreferences().edit();
-
-        if (alarmId != null) {
-            editor.remove(alarmId);
-            if (Build.VERSION.SDK_INT<9) {
-                editor.commit();
-            } else {
-                editor.apply();
+    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);
+    }
+
+    /**
+     * Notification manager for the application.
+     */
+    private NotificationManager getNotMgr () {
+        return (NotificationManager) context
+                .getSystemService(Context.NOTIFICATION_SERVICE);
+    }
 
 }

+ 368 - 0
src/android/notification/Notification.java

@@ -0,0 +1,368 @@
+/*
+ * Copyright (c) 2013-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 android.app.AlarmManager;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.support.v4.app.NotificationCompat;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Date;
+
+/**
+ * Wrapper class around OS notification class. Handles basic operations
+ * like show, delete, cancel for a single local notification instance.
+ */
+public class Notification {
+
+    // Used to differ notifications by their life cycle state
+    public static enum Type {
+        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";
+
+    // Application context passed by constructor
+    private final Context context;
+
+    // Notification options passed by JS
+    private final Options options;
+
+    // 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
+     */
+    protected Notification (Context context, Options options,
+                    NotificationCompat.Builder builder, Class<?> receiver) {
+
+        this.context = context;
+        this.options = options;
+        this.builder = builder;
+
+        this.receiver = receiver != null ? receiver : defaultReceiver;
+    }
+
+    /**
+     * Get application context.
+     */
+    public Context getContext () {
+        return context;
+    }
+
+    /**
+     * Get notification options.
+     */
+    public Options getOptions () {
+        return options;
+    }
+
+    /**
+     * Get notification ID.
+     */
+    public int getId () {
+        return options.getIdAsInt();
+    }
+
+    /**
+     * 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.
+     */
+    protected boolean isUpdate () {
+
+        if (!options.getDict().has("updatedAt"))
+            return false;
+
+        long now = new Date().getTime();
+
+        long updatedAt = options.getDict().optLong("updatedAt", now);
+
+        return (now - updatedAt) < 1000;
+    }
+
+    /**
+     * Notification type can be one of pending or scheduled.
+     */
+    public Type getType () {
+        return isTriggered() ? Type.TRIGGERED : Type.SCHEDULED;
+    }
+
+    /**
+     * Schedule the local notification.
+     */
+    public void schedule() {
+        long triggerTime = getNextTriggerTime();
+
+        persist();
+
+        // Intent gets called when the Notification gets fired
+        Intent intent = new Intent(context, receiver)
+                .setAction(options.getId())
+                .putExtra(Options.EXTRA, options.toString());
+
+        PendingIntent pi = PendingIntent.getBroadcast(
+                context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+
+        getAlarmMgr().set(AlarmManager.RTC_WAKEUP, triggerTime, pi);
+    }
+
+    /**
+     * Re-schedule the local notification if repeating.
+     */
+    void reschedule () {
+        if (isRepeating()) {
+            schedule();
+        }
+    }
+
+    /**
+     * Clear the local notification without canceling repeating alarms.
+     *
+     */
+    public void clear () {
+        if (!isRepeating() && wasInThePast()) {
+            unpersist();
+        } else {
+            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.getId());
+
+        PendingIntent pi = PendingIntent.
+                getBroadcast(context, 0, intent, 0);
+
+        getAlarmMgr().cancel(pi);
+        getNotMgr().cancel(options.getIdAsInt());
+
+        unpersist();
+    }
+
+    /**
+     * Present the local notification to user.
+     */
+    public void show () {
+        // TODO Show dialog when in foreground
+        showNotification();
+    }
+
+    /**
+     * Show as local notification when in background.
+     */
+    @SuppressWarnings("deprecation")
+    private void showNotification () {
+        int id = getOptions().getIdAsInt();
+
+        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());
+        }
+    }
+
+    /**
+     * Show as modal dialog when in foreground.
+     */
+    private void showDialog () {
+        // TODO
+    }
+
+    /**
+     * Next trigger time.
+     */
+    public long getNextTriggerTime() {
+        long triggerTime = options.getTriggerTime();
+
+        if (!isRepeating() || !isTriggered())
+            return triggerTime;
+
+        long interval    = options.getRepeatInterval();
+        int triggerCount = getTriggerCountSinceSchedule();
+
+        return triggerTime + (triggerCount + 1) * interval;
+    }
+
+    /**
+     * Count of triggers since schedule.
+     */
+    public int getTriggerCountSinceSchedule() {
+        long now = System.currentTimeMillis();
+        long initTriggerTime = options.getTriggerTime();
+
+        if (!wasInThePast())
+            return 0;
+
+        if (!isRepeating())
+            return 1;
+
+        return (int) ((now - initTriggerTime) / 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("updatedAt");
+        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.getId(), 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.getId());
+
+        if (Build.VERSION.SDK_INT < 9) {
+            editor.commit();
+        } else {
+            editor.apply();
+        }
+    }
+
+    /**
+     * Shared private preferences for the application.
+     */
+    private SharedPreferences getPrefs () {
+        return context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Notification manager for the application.
+     */
+    private NotificationManager getNotMgr () {
+        return (NotificationManager) context
+                .getSystemService(Context.NOTIFICATION_SERVICE);
+    }
+
+    /**
+     * Alarm manager for the application.
+     */
+    private AlarmManager getAlarmMgr () {
+        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;
+    }
+
+}

+ 0 - 114
src/android/notification/NotificationBuilder.java

@@ -1,114 +0,0 @@
-/*
-    Copyright 2013-2014 appPlant UG
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-package de.appplant.cordova.plugin.notification;
-
-import java.util.Random;
-
-import android.annotation.SuppressLint;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Build;
-import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.NotificationCompat.Builder;
-
-public class NotificationBuilder {
-	private Options options;
-	private Context context;
-	private Builder notification;
-	private final String OPTIONS;
-	private Class<?> deleteIntentReceiver;
-	private Class<?> receiverActivity;
-	
-	/**
-	 * Constructor of NotificationBuilder
-	 * @param options
-	 * @param context
-	 * @param OPTIONS
-	 * @param deleteIntentReceiver
-	 * @param receiverActivity
-	 */
-	public NotificationBuilder(Options options,Context context, String OPTIONS,	
-			Class<?> deleteIntentReceiver, Class<?> receiverActivity){
-		this.options = options;
-		this.context = context;
-		this.OPTIONS = OPTIONS;
-		this.deleteIntentReceiver = deleteIntentReceiver;
-		this.receiverActivity = receiverActivity;
-		}
-	
-    /**
-     * Creates the notification.
-     */
-    @SuppressLint("NewApi")
-    public Builder buildNotification () {
-        Uri sound = options.getSound();
-        
-        //DeleteIntent is called when the user clears a notification manually
-        Intent deleteIntent = new Intent(context, deleteIntentReceiver)
-        	.setAction("" + options.getId())
-        	.putExtra(OPTIONS, options.getJSONObject().toString());
-        PendingIntent dpi = PendingIntent.getBroadcast(context, 0, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT);
-        
-        notification = new NotificationCompat.Builder(context)
-            .setDefaults(0) // Do not inherit any defaults
-            .setContentTitle(options.getTitle())
-            .setContentText(options.getMessage())
-            .setNumber(options.getBadge())
-            .setTicker(options.getMessage())
-            .setSmallIcon(options.getSmallIcon())
-            .setLargeIcon(options.getIcon())
-            .setAutoCancel(options.getAutoCancel())
-            .setOngoing(options.getOngoing())
-            .setLights(options.getColor(), 500, 500)
-            .setDeleteIntent(dpi);
-
-        if (sound != null) {
-            notification.setSound(sound);
-        }
-
-        if (Build.VERSION.SDK_INT > 16) {
-            notification.setStyle(new NotificationCompat.BigTextStyle()
-                .bigText(options.getMessage()));
-        }
-
-        setClickEvent(notification);
-        
-        return notification;
-    }
-
-    /**
-     * Adds an onclick handler to the notification
-     */
-    private Builder setClickEvent (Builder notification) {
-        Intent intent = new Intent(context, receiverActivity)
-            .putExtra(OPTIONS, options.getJSONObject().toString())
-            .setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
-
-        int requestCode = new Random().nextInt();
-
-        PendingIntent contentIntent = PendingIntent.getActivity(context, requestCode, intent, PendingIntent.FLAG_CANCEL_CURRENT);
-
-        return notification.setContentIntent(contentIntent);
-    }
-	
-}

+ 0 - 382
src/android/notification/NotificationWrapper.java

@@ -1,382 +0,0 @@
-/*
-    Copyright 2013-2014 appPlant UG
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-
-package de.appplant.cordova.plugin.notification;
-
-import java.util.Date;
-import java.util.Map;
-import java.util.Set;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.app.AlarmManager;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.os.Build;
-import android.support.v4.app.NotificationCompat.Builder;
-import android.util.Log;
-import android.widget.Toast;
-
-/**
- * Wrapper class to schedule, cancel, clear, and update notifications.
- *
- */
-public class NotificationWrapper {
-    //---------------Global Parameter------------------------------------------------------------
-	private Context context;
-	private Class<?> receiver;
-	private final String PLUGIN_NAME;
-	private final String OPTIONS;
-	
-    //---------------Constructor-----------------------------------------------------------------
-	/**
-	 * Constructor of NotificationWrapper-Class
-	 */
-	public NotificationWrapper(Context context, Class<?> receiver,String PluginName, String OPTIONS){
-		this.context = context;
-		this.receiver = receiver;
-		this.PLUGIN_NAME = PluginName;
-		this.OPTIONS = OPTIONS;
-	}
-	
-	
-	//---------------public functions------------------------------------------------------------
-	/**
-	 * Schedule new notification
-	 */
-	public void schedule(Options options){
-        long triggerTime = options.getDate();
-
-        persist(options.getId(), options.getJSONObject());
-        
-        //Intent is called when the Notification gets fired
-        Intent intent = new Intent(context, receiver)
-            .setAction("" + options.getId())
-            .putExtra(OPTIONS, options.getJSONObject().toString());
-
-        AlarmManager am  = getAlarmManager();
-        PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
-
-        am.set(AlarmManager.RTC_WAKEUP, triggerTime, pi);
-	}
-
-	/**
-	 * Cancel existing notification
-	 */
-	public void cancel(String notificationId){
-		/*
-         * Create an intent that looks similar, to the one that was registered
-         * using add. Making sure the notification id in the action is the same.
-         * Now we can search for such an intent using the 'getService' method
-         * and cancel it.
-         */
-        Intent intent = new Intent(context, receiver)
-            .setAction("" + notificationId);
-
-        PendingIntent pi       = PendingIntent.getBroadcast(context, 0, intent, 0);
-        AlarmManager am        = getAlarmManager();
-        NotificationManager nc = getNotificationManager();
-
-        am.cancel(pi);
-
-        try {
-            nc.cancel(Integer.parseInt(notificationId));
-        } catch (Exception e) {}
-        unpersist(notificationId);
-	}
-	
-    /**
-     * Cancel all notifications that were created by this plugin.
-     *
-     * Android can only unregister a specific alarm. There is no such thing
-     * as cancelAll. Therefore we rely on the Shared Preferences which holds
-     * all our alarms to loop through these alarms and unregister them one
-     * by one.
-     */
-    public void cancelAll() {
-        SharedPreferences settings = getSharedPreferences();
-        NotificationManager nc     = getNotificationManager();
-        Map<String, ?> alarms      = settings.getAll();
-        Set<String> alarmIds       = alarms.keySet();
-
-        for (String alarmId : alarmIds) {
-            cancel(alarmId);
-        }
-
-        nc.cancelAll();
-    }
-	
-    /** 
-     * Update an existing notification 
-     * 
-     * @param updates JSONObject with update-content
-     */
-	public void update(JSONObject updates){
-		String id = updates.optString("id", "0");
-    	
-    	// update shared preferences
-    	SharedPreferences settings = getSharedPreferences();
-    	Map<String, ?> alarms      = settings.getAll();
-    	JSONObject arguments;
-		try {
-			arguments = new JSONObject(alarms.get(id).toString());
-		} catch (JSONException e) {
-			Log.e("NotificationWrapper", "Update failed. No Notification available for the given id: " + id );
-			return;
-		}
-		arguments = updateArguments(arguments, updates);
-		    	
-    	// cancel existing alarm
-        Intent intent = new Intent(context, receiver)
-        	.setAction("" + id);
-        PendingIntent pi       = PendingIntent.getBroadcast(context, 0, intent, 0);
-        AlarmManager am        = getAlarmManager();
-        am.cancel(pi);
-        
-        //add new alarm
-        Options options      = new Options(context).parse(arguments);
-        schedule(options);		
-	}
-	
-    /**
-     * Clear a specific notification without canceling repeating alarms
-     * 
-     * @param notificationID
-     *            The original ID of the notification that was used when it was
-     *            registered using add()
-     */
-    public void clear (String notificationId){
-    	SharedPreferences settings = getSharedPreferences();
-    	Map<String, ?> alarms      = settings.getAll();
-        NotificationManager nc = getNotificationManager();
-
-        try {
-            nc.cancel(Integer.parseInt(notificationId));
-        } catch (Exception e) {}
-        
-        JSONObject arguments;
-		try {
-			arguments = new JSONObject(alarms.get(notificationId).toString());
-			Options options      = new Options(context).parse(arguments);
-			Date now = new Date();
-			if ((options.getInterval()!=0)){
-				persist(notificationId, setInitDate(arguments));
-			}
-			else if((new Date(options.getDate()).before(now))){
-				unpersist(notificationId);
-			}
-		} catch (JSONException e) {
-			unpersist(notificationId);
-			e.printStackTrace();
-			return;
-		}
-    }
-    
-    /**
-     * Clear all notifications without canceling repeating alarms
-     */
-    public void clearAll (){
-        SharedPreferences settings = getSharedPreferences();
-        NotificationManager nc     = getNotificationManager();
-        Map<String, ?> alarms      = settings.getAll();
-        Set<String> alarmIds       = alarms.keySet();
-
-        for (String alarmId : alarmIds) {
-            clear(alarmId);
-        }
-
-        nc.cancelAll();
-    }
-    
-    /**
-     * Shows the notification
-     */
-    @SuppressWarnings("deprecation")
-    public void showNotification (Builder notification, Options options) {
-        NotificationManager mgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
-        int id                  = 0;
-
-        try {
-            id = Integer.parseInt(options.getId());
-        } catch (Exception e) {}
-
-        if (Build.VERSION.SDK_INT<16) {
-            // build notification for HoneyComb to ICS
-            mgr.notify(id, notification.getNotification());
-        } else if (Build.VERSION.SDK_INT>15) {
-            // Notification for Jellybean and above
-            mgr.notify(id, notification.build());
-        }
-    }
-    
-    /**
-     * Show a notification as a Toast when App is runing in foreground
-     * @param title Title of the notification
-     * @param notification Notification to show
-     */
-    public void showNotificationToast(Options options){
-    	String title = options.getTitle();
-    	String message = options.getMessage();
-       	int duration = Toast.LENGTH_LONG;
-       	if(title.equals("")){
-       		title = "Notification";
-       	}
-       	String text = title + " \n " + message;
-       	
-    	Toast notificationToast = Toast.makeText(context, text, duration);
-    	notificationToast.show();
-   }
-	
-    //---------------Manage Shared Preferences---------------------------------------------------
-    
-    /**
-     * The Local storage for the application.
-     */
-    private SharedPreferences getSharedPreferences () {
-        return context.getSharedPreferences(PLUGIN_NAME, Context.MODE_PRIVATE);
-    }
-    
-    /**
-     * Persist the information of this alarm to the Android Shared Preferences.
-     * This will allow the application to restore the alarm upon device reboot.
-     * Also this is used by the cancelAll method.
-     *
-     * @param alarmId
-     *            The Id of the notification that must be persisted.
-     * @param args
-     *            The assumption is that parse has been called already.
-     */
-    public void persist (String alarmId, JSONObject args) {
-        Editor editor = getSharedPreferences().edit();
-
-        if (alarmId != null) {
-            editor.putString(alarmId, args.toString());
-            if (Build.VERSION.SDK_INT<9) {
-                editor.commit();
-            } else {
-                editor.apply();
-            }
-        }
-    }
-    
-    /**
-     * Remove a specific alarm from the Android shared Preferences.
-     *
-     * @param alarmId
-     *            The Id of the notification that must be removed.
-     */
-    public void unpersist (String alarmId) {
-        Editor editor = getSharedPreferences().edit();
-
-        if (alarmId != null) {
-            editor.remove(alarmId);
-            if (Build.VERSION.SDK_INT<9) {
-                editor.commit();
-            } else {
-                editor.apply();
-            }
-        }
-    }
-    
-    //---------------private functions-----------------------------------------------------------
-    
-    /**
-     * The alarm manager for the application.
-     */
-    private AlarmManager getAlarmManager () {
-        return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-    }
-    
-    /**
-     * The notification manager for the application.
-     */
-    private NotificationManager getNotificationManager () {
-        return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
-    }
-    
-    /**
-     * Update the Arguments Input with content from updates-input
-     * 
-     * @param arguments The notifications optionArray
-     * @param updates	The content you like to change 
-     * 
-     * @return The updated value
-     */
-    private JSONObject updateArguments(JSONObject arguments,JSONObject updates){
-    	try	{
-    		if(!updates.isNull("message")){
-    			arguments.put("message", updates.get("message"));
-    		}
-    		if(!updates.isNull("title")){
-    			arguments.put("title", updates.get("title"));
-    		}
-    		if(!updates.isNull("badge")){
-    			arguments.put("badge", updates.get("badge"));
-    		}
-    		if(!updates.isNull("sound")){
-    			arguments.put("sound", updates.get("sound"));
-    		}
-    		if(!updates.isNull("icon")){
-    			arguments.put("icon", updates.get("icon"));
-    		}
-    		if(!updates.isNull("date")){
-    			arguments.put("date", updates.get("date"));
-    		}
-    		if(!updates.isNull("repeat")){
-    			arguments.put("repeat", updates.get("repeat"));
-    		}
-    		if(!updates.isNull("json")){
-    			arguments.put("json", updates.get("json"));
-    		}
-    		if(!updates.isNull("autoCancel")){
-    			arguments.put("autoCancel", updates.get("autoCancel"));
-    		}
-    		if(!updates.isNull("ongoing")){
-    			arguments.put("ongoing", updates.get("ongoing"));
-    		}
-    	} catch (JSONException jse){
-    		jse.printStackTrace();
-    	}
-    	
-    	return arguments;
-    }
-    
-    /**
-     * Function to set the value of "initialDate" in the JSONArray
-     * @param args The given JSONArray
-     * @return A new JSONArray with the parameter "initialDate" set.
-     */
-    private JSONObject setInitDate(JSONObject arguments){
-    	long initialDate = arguments.optLong("date", 0) * 1000;
-    	try {
-    		arguments.put("initialDate", initialDate);
-		} catch (JSONException e) {
-			e.printStackTrace();
-		}
-    	return arguments;
-    }
-
-}

+ 176 - 212
src/android/notification/Options.java

@@ -1,29 +1,28 @@
 /*
-    Copyright 2013-2014 appPlant UG
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
+ * Copyright (c) 2013-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 java.io.IOException;
-import java.io.InputStream;
-import java.util.Calendar;
 import java.util.Date;
 
 import org.json.JSONException;
@@ -31,303 +30,268 @@ import org.json.JSONObject;
 
 import android.app.AlarmManager;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.net.Uri;
 
 /**
- * Class that helps to store the options that can be specified per alarm.
+ * Wrapper around the JSON object passed through JS which contains all
+ * possible option values. Class provides simple readers and more advanced
+ * methods to convert independent values into platform specific values.
  */
 public class Options {
+
+    // Key name for bundled extras
+    static final String EXTRA = "NOTIFICATION_OPTIONS";
+
+    // The original JSON object
     private JSONObject options = new JSONObject();
-    private String packageName = null;
-    private long interval      = 0;
-    private Context context;
 
+    // Repeat interval
+    private long interval = 0;
 
+    // Application context
+    private final Context context;
 
+    // Asset util instance
+    private final AssetUtil assets;
+
+
+    /**
+     * Constructor
+     *
+     * @param context
+     *      Application context
+     */
     public Options(Context context){
-    	this.context= context;
-    	this.packageName = context.getPackageName();
+    	this.context = context;
+        this.assets  = AssetUtil.getInstance(context);
     }
-    
-    
+
     /**
-     * Parses the given properties
+     * Parse given JSON properties.
+     *
+     * @param options
+     *      JSON properties
      */
     public Options parse (JSONObject options) {
-        String repeat = options.optString("repeat");
-
         this.options = options;
 
-        if (repeat.equalsIgnoreCase("secondly")) {
+        parseInterval();
+        parseAssets();
+
+        return this;
+    }
+
+    /**
+     * Parse repeat interval.
+     */
+    private void parseInterval() {
+        String every = options.optString("every").toLowerCase();
+
+        if (every.isEmpty()) {
+            interval = 0;
+        } else
+        if (every.equals("second")) {
             interval = 1000;
-        } if (repeat.equalsIgnoreCase("minutely")) {
+        } else
+        if (every.equals("minute")) {
             interval = AlarmManager.INTERVAL_FIFTEEN_MINUTES / 15;
-        } if (repeat.equalsIgnoreCase("hourly")) {
+        } else
+        if (every.equals("hour")) {
             interval = AlarmManager.INTERVAL_HOUR;
-        } if (repeat.equalsIgnoreCase("daily")) {
+        } else
+        if (every.equals("day")) {
             interval = AlarmManager.INTERVAL_DAY;
-        } else if (repeat.equalsIgnoreCase("weekly")) {
-            interval = AlarmManager.INTERVAL_DAY*7;
-        } else if (repeat.equalsIgnoreCase("monthly")) {
-            interval = AlarmManager.INTERVAL_DAY*31; // 31 days
-        } else if (repeat.equalsIgnoreCase("yearly")) {
-            interval = AlarmManager.INTERVAL_DAY*365;
+        } else
+        if (every.equals("week")) {
+            interval = AlarmManager.INTERVAL_DAY * 7;
+        } else
+        if (every.equals("month")) {
+            interval = AlarmManager.INTERVAL_DAY * 31;
+        } else
+        if (every.equals("year")) {
+            interval = AlarmManager.INTERVAL_DAY * 365;
         } else {
             try {
-                interval = Integer.parseInt(repeat) * 60000;
-            } catch (Exception e) {};
+                interval = Integer.parseInt(every) * 60000;
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
         }
-
-        return this;
     }
 
     /**
-     * Set new time according to interval
+     * Parse asset URIs.
      */
-    public Options moveDate () {
-        try {
-            options.put("date", (getDate() + interval) / 1000);
-        } catch (JSONException e) {}
+    private void parseAssets() {
 
-        return this;
-    }
-    
-    /**
-     * Returns options as JSON object
-     */
-    public JSONObject getJSONObject() {
-        return options;
-    }
+        if (options.has("iconUri"))
+            return;
 
-    /**
-     * Returns time in milliseconds when notification is scheduled to fire
-     */
-    public long getDate() {
-        return options.optLong("date", 0) * 1000;
-    }
-    
-    /**
-     * Returns time in milliseconds when the notification was scheduled first
-     */
-    public long getInitialDate() {
-    	return options.optLong("initialDate", 0);
+        Uri iconUri = assets.parse(options.optString("icon", "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();
+        }
     }
 
     /**
-     * Returns time as calender
+     * Application context.
      */
-    public Calendar getCalendar () {
-        Calendar calendar = Calendar.getInstance();
-
-        calendar.setTime(new Date(getDate()));
-
-        return calendar;
+    public Context getContext () {
+        return context;
     }
 
     /**
-     * Returns the notification's message
+     * Wrapped JSON object.
      */
-    public String getMessage () {
-        return options.optString("message", "");
+    JSONObject getDict () {
+        return options;
     }
 
     /**
-     * Returns the notification's title
+     * Text for the local notification.
      */
-    public String getTitle () {
-        return options.optString("title", "");
+    public String getText() {
+        return options.optString("text", "");
     }
 
     /**
-     * Returns the path of the notification's sound file
+     * Repeat interval (day, week, month, year, aso.)
      */
-    public Uri getSound () {
-        Uri soundUri = null;
-        try{
-        	soundUri = Uri.parse(options.optString("soundUri"));
-        	return soundUri;
-        } catch (Exception e){
-        	e.printStackTrace();
-        }
-        return null;
+    public long getRepeatInterval() {
+        return interval;
     }
 
     /**
-     * Returns the icon's ID
+     * Badge number for the local notification.
      */
-    public Bitmap getIcon () {
-        String icon = options.optString("icon", "icon");
-        Bitmap bmp = null;
-        Uri iconUri = null;
-        try{
-        	iconUri = Uri.parse(options.optString("iconUri"));
-            bmp = getIconFromUri(iconUri);
-        } catch (Exception e){
-        	bmp = getIconFromRes(icon);
-        }
-        return bmp;
+    public int getBadgeNumber() {
+        return options.optInt("badge", 0);
     }
 
     /**
-     * Returns the small icon's ID
+     * Android only ongoing flag for local notifications.
      */
-    public int getSmallIcon () {
-        int resId       = 0;
-        String iconName = options.optString("smallIcon", "");
-
-        resId = getIconValue(packageName, iconName);
-
-        if (resId == 0) {
-            resId = getIconValue("android", iconName);
-        }
-
-        if (resId == 0) {
-            resId = getIconValue(packageName, "icon");
-        }
-
-        return options.optInt("smallIcon", resId);
+    public Boolean isOngoing() {
+        return options.optBoolean("ongoing", false);
     }
 
     /**
-     * Returns notification repetition interval (daily, weekly, monthly, yearly)
+     * Trigger date in milliseconds.
      */
-    public long getInterval () {
-        return interval;
+    public long getTriggerTime() {
+        return options.optLong("at", 0) * 1000;
     }
 
     /**
-     * Returns notification badge number
+     * Trigger date.
      */
-    public int getBadge () {
-        return options.optInt("badge", 0);
+    public Date getTriggerDate() {
+        return new Date(getTriggerTime());
     }
 
     /**
-     * Returns PluginResults' callback ID
+     * ID for the local notification.
      */
-    public String getId () {
+    public String getId() {
         return options.optString("id", "0");
     }
 
     /**
-     * Returns whether notification is cancelled automatically when clicked.
+     * ID for the local notification.
      */
-    public Boolean getAutoCancel () {
-        return options.optBoolean("autoCancel", false);
+    public int getIdAsInt() {
+        try {
+            return Integer.parseInt(getId());
+        } catch (Exception ignore) {
+            return 0;
+        }
     }
 
     /**
-     * Returns whether the notification is ongoing (uncancellable). Android only.
+     * Title for the local notification.
      */
-    public Boolean getOngoing () {
-        return options.optBoolean("ongoing", false);
-    }
+    public String getTitle() {
+        String title = options.optString("title", "");
 
-    /**
-     * Returns additional data as string
-     */
-    public String getJSON () {
-        return options.optString("json", "");
+        if (title.isEmpty()) {
+            title = context.getApplicationInfo().loadLabel(
+                    context.getPackageManager()).toString();
+        }
+
+        return title;
     }
 
     /**
      * @return
      *      The notification color for LED
      */
-   public int getColor () {
-        String hexColor = options.optString("led", "000000");
-        int aRGB        = Integer.parseInt(hexColor,16);
+    public int getLedColor() {
+        String hex = options.optString("led", "000000");
+        int aRGB   = Integer.parseInt(hex,16);
 
         aRGB += 0xFF000000;
 
         return aRGB;
     }
-   
-	/**
-	 * Shows the behavior of notifications when the application is in foreground 
-	 * 
-	 */
-	public boolean getForegroundMode(){
-		return options.optBoolean("foregroundMode",false);	
-	}
 
     /**
-     * Returns numerical icon Value
-     *
-     * @param {String} className
-     * @param {String} iconName
+     * Sound file path for the local notification.
      */
-    private int getIconValue (String className, String iconName) {
-        int icon = 0;
+    public Uri getSoundUri() {
+        Uri uri = null;
 
-        try {
-            Class<?> klass  = Class.forName(className + ".R$drawable");
-
-            icon = (Integer) klass.getDeclaredField(iconName).get(Integer.class);
-        } catch (Exception e) {}
+        try{
+            uri = Uri.parse(options.optString("soundUri"));
+        } catch (Exception e){
+            e.printStackTrace();
+        }
 
-        return icon;
+        return uri;
     }
 
     /**
-     * Converts an resource to Bitmap.
-     *
-     * @param icon
-     *      The resource name
-     * @return
-     *      The corresponding bitmap
+     * Icon bitmap for the local notification.
      */
-    private Bitmap getIconFromRes (String icon) {
-        Resources res = context.getResources();
-        int iconId = 0;
-
-        iconId = getIconValue(packageName, icon);
-
-        if (iconId == 0) {
-            iconId = getIconValue("android", icon);
-        }
+    public Bitmap getIconBitmap() {
+        String icon = options.optString("icon", "icon");
+        Bitmap bmp;
 
-        if (iconId == 0) {
-            iconId = android.R.drawable.ic_menu_info_details;
+        try{
+            Uri uri = Uri.parse(options.optString("iconUri"));
+            bmp = assets.getIconFromUri(uri);
+        } catch (Exception e){
+            bmp = assets.getIconFromDrawable(icon);
         }
 
-        Bitmap bmp = BitmapFactory.decodeResource(res, iconId);
-
         return bmp;
     }
 
-
-
     /**
-     * Converts an Image URI to Bitmap.
-     *
-     * @param src
-     *      The internal image URI
-     * @return
-     *      The corresponding bitmap
+     * Small icon resource ID for the local notification.
      */
-    private Bitmap getIconFromUri (Uri uri) throws IOException {
-        Bitmap bmp = null;
-          
-        InputStream input = context.getContentResolver().openInputStream(uri);
-        bmp = BitmapFactory.decodeStream(input);
+    public int getSmallIcon () {
+        String icon = options.optString("smallIcon", "");
 
-        return bmp;
+        int resId = assets.getResIdForDrawable(icon);
+
+        if (resId == 0) {
+            resId = android.R.drawable.screen_background_dark;
+        }
+
+        return resId;
     }
-    
+
     /**
-     * Function to set the value of "initialDate" in the JSONArray
+     * JSON object as string.
      */
-    public void setInitDate(){
-    	long initialDate = options.optLong("date", 0) * 1000;
-    	try {
-    		options.put("initialDate", initialDate);
-		} catch (JSONException e) {
-			e.printStackTrace();
-		}
+    public String toString() {
+        return options.toString();
     }
-	
+
 }

+ 64 - 0
src/android/notification/TriggerReceiver.java

@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2013-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;
+
+/**
+ * The alarm receiver is triggered when a scheduled alarm is fired. This class
+ * reads the information in the intent and displays this information in the
+ * Android notification bar. The notification uses the default notification
+ * sound and it vibrates the phone.
+ */
+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
+     */
+    @Override
+    public void onTrigger (Notification notification, boolean updated) {
+
+        if (notification.isRepeating()) {
+            notification.reschedule();
+        }
+
+        notification.show();
+    }
+
+    /**
+     * Build notification specified by options.
+     *
+     * @param builder
+     *      Notification builder
+     */
+    @Override
+    public Notification buildNotification (Builder builder) {
+        return builder.build();
+    }
+
+}

+ 33 - 27
src/ios/APPLocalNotification.h

@@ -1,22 +1,24 @@
 /*
- Copyright 2013-2014 appPlant UG
-
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements.  See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership.  The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License.  You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied.  See the License for the
- specific language governing permissions and limitations
- under the License.
+ * Copyright (c) 2013-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@
  */
 
 #import <Foundation/Foundation.h>
@@ -32,20 +34,24 @@
 // Register permission to show notifications
 - (void) registerPermission:(CDVInvokedUrlCommand*)command;
 
-// Schedule a new notification
-- (void) add:(CDVInvokedUrlCommand*)command;
-// Update a notification
+// Schedule set of notifications
+- (void) schedule:(CDVInvokedUrlCommand*)command;
+// Update set of notifications
 - (void) update:(CDVInvokedUrlCommand*)command;
-// Cancel a given notification
+// Cancel set of notifications
 - (void) cancel:(CDVInvokedUrlCommand*)command;
-// Cancel all currently scheduled notifications
+// Cancel all notifications
 - (void) cancelAll:(CDVInvokedUrlCommand*)command;
+// Clear set of notifications
+- (void) clear:(CDVInvokedUrlCommand*)command;
+// Clear all notifications
+- (void) clearAll:(CDVInvokedUrlCommand*)command;
 
-// If a notification with an ID exists
-- (void) exist:(CDVInvokedUrlCommand*)command;
-// If a notification with an ID was scheduled
+// If a notification with an ID is present
+- (void) isPresent:(CDVInvokedUrlCommand*)command;
+// If a notification with an ID is scheduled
 - (void) isScheduled:(CDVInvokedUrlCommand*)command;
-// If a notification with an ID was triggered
+// If a notification with an ID is triggered
 - (void) isTriggered:(CDVInvokedUrlCommand*)command;
 
 // List all ids from all local notifications

+ 185 - 193
src/ios/APPLocalNotification.m

@@ -1,22 +1,24 @@
 /*
- Copyright 2013-2014 appPlant UG
-
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements.  See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership.  The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License.  You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied.  See the License for the
- specific language governing permissions and limitations
- under the License.
+ * Copyright (c) 2013-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@
  */
 
 #import "APPLocalNotification.h"
@@ -67,7 +69,7 @@
  * @param properties
  *      A dict of properties for each notification
  */
-- (void) add:(CDVInvokedUrlCommand*)command
+- (void) schedule:(CDVInvokedUrlCommand*)command
 {
     NSArray* notifications = command.arguments;
 
@@ -79,7 +81,7 @@
                             initWithOptions:options];
 
             [self scheduleLocalNotification:[notification copy]];
-            [self fireEvent:@"add" localNotification:notification];
+            [self fireEvent:@"schedule" notification:notification];
 
             if (notifications.count > 1) {
                 [NSThread sleepForTimeInterval:0.01];
@@ -105,8 +107,7 @@
             NSString* id = [options objectForKey:@"id"];
             UILocalNotification* notification;
 
-            notification = [[UIApplication sharedApplication]
-                            scheduledLocalNotificationWithId:id];
+            notification = [self.app localNotificationWithId:id];
 
             if (!notification)
                 continue;
@@ -114,6 +115,8 @@
             [self updateLocalNotification:[notification copy]
                               withOptions:options];
 
+            [self fireEvent:@"update" notification:notification];
+
             if (notifications.count > 1) {
                 [NSThread sleepForTimeInterval:0.01];
             }
@@ -135,14 +138,13 @@
         for (NSString* id in command.arguments) {
             UILocalNotification* notification;
 
-            notification = [[UIApplication sharedApplication]
-                            scheduledLocalNotificationWithId:id];
+            notification = [self.app localNotificationWithId:id];
 
             if (!notification)
                 continue;
 
-            [self cancelLocalNotification:notification];
-            [self fireEvent:@"cancel" localNotification:notification];
+            [self.app cancelLocalNotification:notification];
+            [self fireEvent:@"cancel" notification:notification];
         }
 
         [self execCallback:command];
@@ -150,7 +152,7 @@
 }
 
 /**
- * Cancel all currently scheduled notifications.
+ * Cancel all local notifications.
  */
 - (void) cancelAll:(CDVInvokedUrlCommand*)command
 {
@@ -162,33 +164,53 @@
 }
 
 /**
- * If a notification by ID exists.
+ * Clear a set of notifications.
  *
- * @param id
- *      The ID of the notification
+ * @param ids
+ *      The IDs of the notifications
  */
-- (void) exist:(CDVInvokedUrlCommand *)command
+- (void) clear:(CDVInvokedUrlCommand*)command
 {
     [self.commandDelegate runInBackground:^{
-        NSString* id = [[command arguments]
-                        objectAtIndex:0];
+        for (NSString* id in command.arguments) {
+            UILocalNotification* notification;
 
-        CDVPluginResult* result;
-        UILocalNotification* notification;
+            notification = [self.app localNotificationWithId:id];
 
-        notification = [[UIApplication sharedApplication]
-                        localNotificationWithId:id];
+            if (!notification)
+                continue;
 
-        bool exists = notification != NULL;
+            [self.app clearLocalNotification:notification];
+            [self fireEvent:@"clear" notification:notification];
+        }
 
-        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
-                                     messageAsBool:exists];
+        [self execCallback:command];
+    }];
+}
 
-        [self.commandDelegate sendPluginResult:result
-                                    callbackId:command.callbackId];
+/**
+ * Clear all local notifications.
+ */
+- (void) clearAll:(CDVInvokedUrlCommand*)command
+{
+    [self.commandDelegate runInBackground:^{
+        [self clearAllLocalNotifications];
+        [self fireEvent:@"clearall"];
+        [self execCallback:command];
     }];
 }
 
+/**
+ * If a notification by ID is present.
+ *
+ * @param id
+ *      The ID of the notification
+ */
+- (void) isPresent:(CDVInvokedUrlCommand *)command
+{
+    [self isPresent:command type:NotifcationTypeAll];
+}
+
 /**
  * If a notification by ID is scheduled.
  *
@@ -197,48 +219,43 @@
  */
 - (void) isScheduled:(CDVInvokedUrlCommand*)command
 {
-    [self.commandDelegate runInBackground:^{
-        NSString* id = [[command arguments]
-                        objectAtIndex:0];
-
-        CDVPluginResult* result;
-        UILocalNotification* notification;
-
-        notification = [[UIApplication sharedApplication]
-                        scheduledLocalNotificationWithId:id];
-
-        bool isScheduled = notification != NULL;
-
-        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
-                                     messageAsBool:isScheduled];
-
-        [self.commandDelegate sendPluginResult:result
-                                    callbackId:command.callbackId];
-    }];
+    [self isPresent:command type:NotifcationTypeScheduled];
 }
 
 /**
- * Check if a notification with an ID was triggered.
+ * Check if a notification with an ID is triggered.
  *
  * @param id
  *      The ID of the notification
  */
 - (void) isTriggered:(CDVInvokedUrlCommand*)command
+{
+    [self isPresent:command type:NotifcationTypeTriggered];
+}
+
+/**
+ * Check if a notification with an ID exists.
+ *
+ * @param type
+ *      The notification life cycle type
+ */
+- (void) isPresent:(CDVInvokedUrlCommand*)command
+              type:(APPLocalNotificationType)type;
 {
     [self.commandDelegate runInBackground:^{
-        NSString* id = [[command arguments]
-                        objectAtIndex:0];
+        NSString* id = [command argumentAtIndex:0];
+        BOOL exist;
 
         CDVPluginResult* result;
-        UILocalNotification* notification;
 
-        notification = [[UIApplication sharedApplication]
-                        triggeredLocalNotificationWithId:id];
-
-        bool isTriggered = notification != NULL;
+        if (type == NotifcationTypeAll) {
+            exist = [self.app localNotificationExist:id];
+        } else {
+            exist = [self.app localNotificationExist:id type:type];
+        }
 
         result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
-                                     messageAsBool:isTriggered];
+                                     messageAsBool:exist];
 
         [self.commandDelegate sendPluginResult:result
                                     callbackId:command.callbackId];
@@ -250,19 +267,7 @@
  */
 - (void) getAllIds:(CDVInvokedUrlCommand*)command
 {
-    [self.commandDelegate runInBackground:^{
-        CDVPluginResult* result;
-        NSArray* notIds;
-
-        notIds = [[UIApplication sharedApplication]
-                  localNotificationIds];
-
-        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
-                                    messageAsArray:notIds];
-
-        [self.commandDelegate sendPluginResult:result
-                                    callbackId:command.callbackId];
-    }];
+    [self getIds:command byType:NotifcationTypeAll];
 }
 
 /**
@@ -270,19 +275,7 @@
  */
 - (void) getScheduledIds:(CDVInvokedUrlCommand*)command
 {
-    [self.commandDelegate runInBackground:^{
-        CDVPluginResult* result;
-        NSArray* scheduledIds;
-
-        scheduledIds = [[UIApplication sharedApplication]
-                        scheduledLocalNotificationIds];
-
-        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
-                                    messageAsArray:scheduledIds];
-
-        [self.commandDelegate sendPluginResult:result
-                                    callbackId:command.callbackId];
-    }];
+    [self getIds:command byType:NotifcationTypeScheduled];
 }
 
 /**
@@ -290,50 +283,49 @@
  */
 - (void) getTriggeredIds:(CDVInvokedUrlCommand*)command
 {
-    [self.commandDelegate runInBackground:^{
-        CDVPluginResult* result;
-        NSArray* triggeredIds;
-
-        triggeredIds = [[UIApplication sharedApplication]
-                        triggeredLocalNotificationIds];
-
-        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
-                                    messageAsArray:triggeredIds];
-
-        [self.commandDelegate sendPluginResult:result
-                                    callbackId:command.callbackId];
-    }];
+    [self getIds:command byType:NotifcationTypeTriggered];
 }
 
 /**
- * Property list for given local notifications.
+ * List of ids for given local notifications.
  *
+ * @param type
+ *      Notification life cycle type
  * @param ids
  *      The IDs of the notifications
  */
-- (void) getAll:(CDVInvokedUrlCommand*)command
+- (void) getIds:(CDVInvokedUrlCommand*)command
+             byType:(APPLocalNotificationType)type;
 {
     [self.commandDelegate runInBackground:^{
-        NSArray* ids = command.arguments;
-        NSArray* notifications;
         CDVPluginResult* result;
+        NSArray* ids;
 
-        if (ids.count == 0) {
-            notifications = [[UIApplication sharedApplication]
-                             localNotificationOptions];
+        if (type == NotifcationTypeAll) {
+            ids = [self.app localNotificationIds];
         } else {
-            notifications = [[UIApplication sharedApplication]
-                             localNotificationOptions:ids];
+            ids = [self.app localNotificationIdsByType:type];
         }
 
         result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
-                                    messageAsArray:notifications];
+                                    messageAsArray:ids];
 
         [self.commandDelegate sendPluginResult:result
                                     callbackId:command.callbackId];
     }];
 }
 
+/**
+ * Property list for given local notifications.
+ *
+ * @param ids
+ *      The IDs of the notifications
+ */
+- (void) getAll:(CDVInvokedUrlCommand*)command
+{
+    [self getOptions:command byType:NotifcationTypeAll];
+}
+
 /**
  * Property list for given scheduled notifications.
  *
@@ -342,25 +334,7 @@
  */
 - (void) getScheduled:(CDVInvokedUrlCommand*)command
 {
-    [self.commandDelegate runInBackground:^{
-        NSArray* ids = command.arguments;
-        NSArray* notifications;
-        CDVPluginResult* result;
-
-        if (ids.count == 0) {
-            notifications = [[UIApplication sharedApplication]
-                             scheduledLocalNotificationOptions];
-        } else {
-            notifications = [[UIApplication sharedApplication]
-                             scheduledLocalNotificationOptions:ids];
-        }
-
-        result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
-                                    messageAsArray:notifications];
-
-        [self.commandDelegate sendPluginResult:result
-                                    callbackId:command.callbackId];
-    }];
+    [self getOptions:command byType:NotifcationTypeScheduled];
 }
 
 /**
@@ -370,18 +344,38 @@
  *      The IDs of the notifications
  */
 - (void) getTriggered:(CDVInvokedUrlCommand *)command
+{
+    [self getOptions:command byType:NotifcationTypeTriggered];
+}
+
+/**
+ * Property list for given triggered notifications.
+ *
+ * @param type
+ *      Notification life cycle type
+ * @param ids
+ *      The IDs of the notifications
+ */
+- (void) getOptions:(CDVInvokedUrlCommand*)command
+             byType:(APPLocalNotificationType)type;
 {
     [self.commandDelegate runInBackground:^{
         NSArray* ids = command.arguments;
         NSArray* notifications;
         CDVPluginResult* result;
 
-        if (ids.count == 0) {
-            notifications = [[UIApplication sharedApplication]
-                             triggeredLocalNotificationOptions];
-        } else {
-            notifications = [[UIApplication sharedApplication]
-                             triggeredLocalNotificationOptions:ids];
+        if (type == NotifcationTypeAll && ids.count == 0) {
+            notifications = [self.app localNotificationOptions];
+        }
+        else if (type == NotifcationTypeAll) {
+            notifications = [self.app localNotificationOptionsById:ids];
+        }
+        else if (ids.count == 0) {
+            notifications = [self.app localNotificationOptionsByType:type];
+        }
+        else {
+            notifications = [self.app localNotificationOptionsByType:type
+                                                               andId:ids];
         }
 
         result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
@@ -402,8 +396,7 @@
         CDVPluginResult* result;
         BOOL hasPermission;
 
-        hasPermission = [[UIApplication sharedApplication]
-                         hasPermissionToScheduleLocalNotifications];
+        hasPermission = [self.app hasPermissionToScheduleLocalNotifications];
 
         result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK
                                      messageAsBool:hasPermission];
@@ -423,8 +416,7 @@
     _command = command;
 
     [self.commandDelegate runInBackground:^{
-        [[UIApplication sharedApplication]
-         registerPermissionToScheduleLocalNotifications];
+        [self.app registerPermissionToScheduleLocalNotifications];
     }];
 #else
     [self hasPermission:command];
@@ -440,9 +432,7 @@
 - (void) scheduleLocalNotification:(UILocalNotification*)notification
 {
     [self cancelForerunnerLocalNotification:notification];
-
-    [[UIApplication sharedApplication]
-     scheduleLocalNotification:notification];
+    [self.app scheduleLocalNotification:notification];
 }
 
 /**
@@ -460,31 +450,24 @@
                     initWithOptions:options];
 
     [self scheduleLocalNotification:notification];
-
 }
 
 /**
- * Cancel the local notification.
+ * Cancel all local notifications.
  */
-- (void) cancelLocalNotification:(UILocalNotification*)notification
+- (void) cancelAllLocalNotifications
 {
-    [[UIApplication sharedApplication]
-     cancelLocalNotification:notification];
-
-    [UIApplication sharedApplication]
-    .applicationIconBadgeNumber -= 1;
+    [self.app cancelAllLocalNotifications];
+    [self.app setApplicationIconBadgeNumber:0];
 }
 
 /**
- * Cancel all currently scheduled notifications.
+ * Clear all local notifications.
  */
-- (void) cancelAllLocalNotifications
+- (void) clearAllLocalNotifications
 {
-    [[UIApplication sharedApplication]
-     cancelAllLocalNotifications];
-
-    [[UIApplication sharedApplication]
-     setApplicationIconBadgeNumber:0];
+    [self.app clearAllLocalNotifications];
+    [self.app setApplicationIconBadgeNumber:0];
 }
 
 /**
@@ -495,13 +478,12 @@
     NSString* id = notification.options.id;
     UILocalNotification* forerunner;
 
-    forerunner = [[UIApplication sharedApplication]
-                  scheduledLocalNotificationWithId:id];
+    forerunner = [self.app localNotificationWithId:id];
 
     if (!forerunner)
         return;
 
-    [self cancelLocalNotification:forerunner];
+    [self.app cancelLocalNotification:forerunner];
 }
 
 /**
@@ -512,16 +494,16 @@
 {
     NSArray* notifications;
 
-    notifications = [[UIApplication sharedApplication]
-                     scheduledLocalNotifications];
+    notifications = [self.app scheduledLocalNotifications];
 
     for (UILocalNotification* notification in notifications)
     {
         if (notification && [notification isRepeating]
             && notification.timeIntervalSinceFireDate > seconds)
         {
-            [self cancelLocalNotification:notification];
-            [self fireEvent:@"cancel" localNotification:notification];
+            [self.app cancelLocalNotification:notification];
+
+            [self fireEvent:@"cancel" notification:notification];
         }
     }
 }
@@ -540,21 +522,29 @@
     if ([notification wasUpdated])
         return;
 
-    BOOL autoCancel = notification.options.autoCancel;
-    NSTimeInterval timeInterval = [notification timeIntervalSinceFireDate];
+    NSTimeInterval timeInterval = [notification timeIntervalSinceLastTrigger];
 
     NSString* event = (timeInterval <= 1 && deviceready) ? @"trigger" : @"click";
 
-    if ([event isEqualToString:@"click"]) {
-        [UIApplication sharedApplication]
-        .applicationIconBadgeNumber -= 1;
+    [self fireEvent:event notification:notification];
+
+    if ([event isEqualToString:@"click"] && ![notification isRepeating])
+    {
+        [self.app cancelLocalNotification:notification];
+
+        [self fireEvent:@"cancel" notification:notification];
+        return;
     }
 
-    [self fireEvent:event localNotification:notification];
+    if ([event isEqualToString:@"click"])
+        return;
+
+    timeInterval = [notification timeIntervalSinceFireDate];
 
-    if (autoCancel && [event isEqualToString:@"click"]) {
-        [self cancelLocalNotification:notification];
-        [self fireEvent:@"cancel" localNotification:notification];
+    if (timeInterval > 1 && [notification isRepeating])
+    {
+        [self fireEvent:@"fireUpdateEvent" notification:notification];
+        return;
     }
 }
 
@@ -639,8 +629,7 @@
  */
 - (NSString*) applicationState
 {
-    UIApplicationState state = [[UIApplication sharedApplication]
-                                applicationState];
+    UIApplicationState state = [self.app applicationState];
 
     bool isActive = state == UIApplicationStateActive;
 
@@ -659,38 +648,41 @@
                                 callbackId:command.callbackId];
 }
 
+/**
+ * Short hand for shared application instance.
+ */
+- (UIApplication*) app
+{
+    return [UIApplication sharedApplication];
+}
+
 /**
  * Fire general event.
  */
 - (void) fireEvent:(NSString*)event
 {
-    [self fireEvent:event localNotification:NULL];
+    [self fireEvent:event notification:NULL];
 }
 
 /**
  * Fire event for local notification.
  */
-- (void) fireEvent:(NSString*)event localNotification:(UILocalNotification*)notification
+- (void) fireEvent:(NSString*)event notification:(UILocalNotification*)notification
 {
     NSString* js;
     NSString* params = [NSString stringWithFormat:
                         @"\"%@\"", self.applicationState];
 
     if (notification) {
-        NSString* id = notification.options.id;
-        NSString* json = notification.options.json;
         NSString* args = [notification encodeToJSON];
 
-        json = [json stringByReplacingOccurrencesOfString:@"'"
-                                               withString:@"\\\\\\'"];
-
         params = [NSString stringWithFormat:
-                  @"\"%@\",\"%@\",\\'%@\\',JSON.parse(\\'%@\\')",
-                  id, self.applicationState, json, args];
+                  @"%@,'%@'",
+                  args, self.applicationState];
     }
 
     js = [NSString stringWithFormat:
-          @"setTimeout('cordova.plugins.notification.local.on%@(%@)',0)",
+          @"cordova.plugins.notification.local.fireEvent('%@', %@)",
           event, params];
 
     if (deviceready) {

+ 20 - 20
src/ios/APPLocalNotificationOptions.h

@@ -1,22 +1,24 @@
 /*
- Copyright 2013-2014 appPlant UG
-
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements.  See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership.  The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License.  You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied.  See the License for the
- specific language governing permissions and limitations
- under the License.
+ * Copyright (c) 2013-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@
  */
 
 @interface APPLocalNotificationOptions : NSObject
@@ -24,8 +26,6 @@
 - (id) initWithDict:(NSDictionary*)dict;
 
 @property (readonly, getter=id) NSString* id;
-@property (readonly, getter=autoCancel) BOOL autoCancel;
-@property (readonly, getter=json) NSString* json;
 @property (readonly, getter=badgeNumber) NSInteger badgeNumber;
 @property (readonly, getter=alertBody) NSString* alertBody;
 @property (readonly, getter=soundName) NSString* soundName;

+ 48 - 71
src/ios/APPLocalNotificationOptions.m

@@ -1,26 +1,30 @@
 /*
- Copyright 2013-2014 appPlant UG
-
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements.  See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership.  The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License.  You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied.  See the License for the
- specific language governing permissions and limitations
- under the License.
+ * Copyright (c) 2013-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@
  */
 
 #import "APPLocalNotificationOptions.h"
-#import <Cordova/CDVAvailability.h>
+
+// Default sound ressource path
+NSString* const DEFAULT_SOUND = @"res://platform_default";
 
 @interface APPLocalNotificationOptions ()
 
@@ -71,29 +75,9 @@
 /**
  * The notification's message.
  */
-- (NSString*) message
-{
-    return [dict objectForKey:@"message"];
-}
-
-/**
- * The notification's auto cancel flag.
- */
-- (BOOL) autoCancel
+- (NSString*) text
 {
-    if (IsAtLeastiOSVersion(@"8.0")){
-        return ![self isRepeating];
-    } else {
-        return [[dict objectForKey:@"autoCancel"] boolValue];
-    }
-}
-
-/**
- * The notification's JSON data.
- */
-- (NSString*) json
-{
-    return [dict objectForKey:@"json"];
+    return [dict objectForKey:@"text"];
 }
 
 /**
@@ -101,13 +85,7 @@
  */
 - (NSInteger) badgeNumber
 {
-    NSInteger number = [[dict objectForKey:@"badge"] intValue];
-
-    if (number == -1) {
-        number = 1 + [UIApplication sharedApplication].applicationIconBadgeNumber;
-    }
-
-    return number;
+    return [[dict objectForKey:@"badge"] intValue];
 }
 
 #pragma mark -
@@ -119,7 +97,7 @@
 - (NSString*) alertBody
 {
     NSString* title = [self title];
-    NSString* msg = [self message];
+    NSString* msg = [self text];
 
     NSString* alertBody = msg;
 
@@ -139,16 +117,19 @@
 {
     NSString* path = [dict objectForKey:@"sound"];
 
+    if ([self stringIsNullOrEmpty:path])
+        return NULL;
+
+    if ([path isEqualToString:DEFAULT_SOUND])
+        return UILocalNotificationDefaultSoundName;
+
     if ([path hasPrefix:@"file:/"])
-    {
         return [self soundNameForAsset:path];
-    }
-    else if ([path hasPrefix:@"res:"])
-    {
+
+    if ([path hasPrefix:@"res:"])
         return [self soundNameForResource:path];
-    }
 
-    return UILocalNotificationDefaultSoundName;
+    return NULL;
 }
 
 /**
@@ -156,7 +137,7 @@
  */
 - (NSDate*) fireDate
 {
-    double timestamp = [[dict objectForKey:@"date"]
+    double timestamp = [[dict objectForKey:@"at"]
                         doubleValue];
 
     return [NSDate dateWithTimeIntervalSince1970:timestamp];
@@ -167,34 +148,30 @@
  */
 - (NSCalendarUnit) repeatInterval
 {
-    NSString* interval = [dict objectForKey:@"repeat"];
+    NSString* interval = [dict objectForKey:@"every"];
 
-    if ([interval isEqualToString:@"secondly"])
-    {
+    if ([self stringIsNullOrEmpty:interval]) {
+        return NSCalendarUnitEra;
+    }
+    else if ([interval isEqualToString:@"second"]) {
         return NSCalendarUnitSecond;
     }
-    else if ([interval isEqualToString:@"minutely"])
-    {
+    else if ([interval isEqualToString:@"minute"]) {
         return NSCalendarUnitMinute;
     }
-    else if ([interval isEqualToString:@"hourly"])
-    {
+    else if ([interval isEqualToString:@"houre"]) {
         return NSCalendarUnitHour;
     }
-    else if ([interval isEqualToString:@"daily"])
-    {
+    else if ([interval isEqualToString:@"day"]) {
         return NSCalendarUnitDay;
     }
-    else if ([interval isEqualToString:@"weekly"])
-    {
+    else if ([interval isEqualToString:@"week"]) {
         return NSCalendarUnitWeekOfYear;
     }
-    else if ([interval isEqualToString:@"monthly"])
-    {
+    else if ([interval isEqualToString:@"month"]) {
         return NSCalendarUnitMonth;
     }
-    else if ([interval isEqualToString:@"yearly"])
-    {
+    else if ([interval isEqualToString:@"year"]) {
         return NSCalendarUnitYear;
     }
 

+ 20 - 18
src/ios/AppDelegate+APPLocalNotification.h

@@ -1,22 +1,24 @@
 /*
- Copyright 2013-2014 appPlant UG
-
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements.  See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership.  The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License.  You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied.  See the License for the
- specific language governing permissions and limitations
- under the License.
+ * Copyright (c) 2013-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@
  */
 
 #import "AppDelegate.h"

+ 20 - 18
src/ios/AppDelegate+APPLocalNotification.m

@@ -1,22 +1,24 @@
 /*
- Copyright 2013-2014 appPlant UG
-
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements.  See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership.  The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License.  You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied.  See the License for the
- specific language governing permissions and limitations
- under the License.
+ * Copyright (c) 2013-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@
  */
 
 #import "AppDelegate+APPLocalNotification.h"

+ 43 - 38
src/ios/UIApplication+APPLocalNotification.h

@@ -1,58 +1,63 @@
 /*
- Copyright 2013-2014 appPlant UG
-
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements.  See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership.  The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License.  You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied.  See the License for the
- specific language governing permissions and limitations
- under the License.
+ * Copyright (c) 2013-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@
  */
 
+#import "UILocalNotification+APPLocalNotification.h"
+
 @interface UIApplication (APPLocalNotification)
 
 @property (readonly, getter=localNotifications) NSArray* localNotifications;
-@property (readonly, getter=scheduledLocalNotifications2) NSArray* triggeredLocalNotifications2;
-@property (readonly, getter=triggeredLocalNotifications) NSArray* triggeredLocalNotifications;
-
 @property (readonly, getter=localNotificationIds) NSArray* localNotificationIds;
-@property (readonly, getter=triggeredLocalNotificationIds) NSArray* triggeredLocalNotificationIds;
-@property (readonly, getter=scheduledLocalNotificationIds) NSArray* scheduledLocalNotificationIds;
 
 // If the app has the permission to schedule local notifications
 - (BOOL) hasPermissionToScheduleLocalNotifications;
 // Ask for permission to schedule local notifications
 - (void) registerPermissionToScheduleLocalNotifications;
 
-// Get local notification by ID
+// List of all local notification IDs from given type
+- (NSArray*) localNotificationIdsByType:(APPLocalNotificationType)type;
+
+// If local notification with ID exists
+- (BOOL) localNotificationExist:(NSString*)id;
+// If local notification with ID and type exists
+- (BOOL) localNotificationExist:(NSString*)id type:(APPLocalNotificationType)type;
+
+// Local notification by ID
 - (UILocalNotification*) localNotificationWithId:(NSString*)id;
-// Get scheduled local notification by ID
-- (UILocalNotification*) scheduledLocalNotificationWithId:(NSString*)id;
-// Get triggered local notification by ID
-- (UILocalNotification*) triggeredLocalNotificationWithId:(NSString*)id;
+// Local notification by ID and type
+- (UILocalNotification*) localNotificationWithId:(NSString*)id andType:(APPLocalNotificationType)type;
 
 // Property list from all local notifications
 - (NSArray*) localNotificationOptions;
-// Property list from all scheduled notifications
-- (NSArray*) scheduledLocalNotificationOptions;
-// Property list from all triggered notifications
-- (NSArray*) triggeredLocalNotificationOptions;
-
 // Property list from given local notifications
-- (NSArray*) localNotificationOptions:(NSArray*)ids;
-// Property list from given scheduled notifications
-- (NSArray*) scheduledLocalNotificationOptions:(NSArray*)ids;
-// Property list from given triggered notifications
-- (NSArray*) triggeredLocalNotificationOptions:(NSArray*)ids;
+- (NSArray*) localNotificationOptionsById:(NSArray*)ids;
+// Property list from all local notifications with type constraint
+- (NSArray*) localNotificationOptionsByType:(APPLocalNotificationType)type;
+// Property list from given local notifications with type constraint
+- (NSArray*) localNotificationOptionsByType:(APPLocalNotificationType)type andId:(NSArray*)ids;
+
+// Clear single local notfications
+- (void) clearLocalNotification:(UILocalNotification*)notification;
+// Clear all local notfications
+- (void) clearAllLocalNotifications;
 
 @end

+ 91 - 101
src/ios/UIApplication+APPLocalNotification.m

@@ -1,22 +1,24 @@
 /*
- Copyright 2013-2014 appPlant UG
-
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements.  See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership.  The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License.  You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied.  See the License for the
- specific language governing permissions and limitations
- under the License.
+ * Copyright (c) 2013-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@
  */
 
 #import "UIApplication+APPLocalNotification.h"
@@ -101,7 +103,7 @@
 
     for (UILocalNotification* notification in scheduledNotifications)
     {
-        if (notification && [notification wasScheduled]) {
+        if (notification && [notification isScheduled]) {
             [notifications addObject:notification];
         }
     }
@@ -120,7 +122,7 @@
 
     for (UILocalNotification* notification in notifications)
     {
-        if ([notification wasTriggered]) {
+        if ([notification isTriggered]) {
             [triggeredNotifications addObject:notification];
         }
     }
@@ -129,8 +131,7 @@
 }
 
 /**
- * List of all triggered local notifications IDs which have been scheduled
- * and not yet removed from the notification center.
+ * List of all local notifications IDs.
  */
 - (NSArray*) localNotificationIds
 {
@@ -146,67 +147,58 @@
 }
 
 /**
- * List of all added local notifications IDs which have been scheduled
- * and not yet removed from the notification center.
+ * List of all local notifications IDs from given type.
+ *
+ * @param type
+ *      Notification life cycle type
  */
-- (NSArray*) triggeredLocalNotificationIds
+- (NSArray*) localNotificationIdsByType:(APPLocalNotificationType)type
 {
-    NSArray* notifications = self.triggeredLocalNotifications;
+    NSArray* notifications = self.localNotifications;
     NSMutableArray* ids = [[NSMutableArray alloc] init];
 
     for (UILocalNotification* notification in notifications)
     {
-        [ids addObject:notification.options.id];
+        if (notification.type == type) {
+            [ids addObject:notification.options.id];
+        }
     }
 
     return ids;
 }
 
-/**
- * List of all scheduled local notifications IDs.
+/*
+ * If local notification with ID exists.
+ *
+ * @param id
+ *      Notification ID
  */
-- (NSArray*) scheduledLocalNotificationIds
+- (BOOL) localNotificationExist:(NSString*)id
 {
-    NSArray* notifications = self.scheduledLocalNotifications2;
-    NSMutableArray* ids = [[NSMutableArray alloc] init];
-
-    for (UILocalNotification* notification in notifications)
-    {
-        [ids addObject:notification.options.id];
-    }
-
-    return ids;
+    return [self localNotificationWithId:id] != NULL;
 }
 
-/**
- * Get local notification by ID.
+/* If local notification with ID and type exists
  *
  * @param id
  *      Notification ID
+ * @param type
+ *      Notification life cycle type
  */
-- (UILocalNotification*) localNotificationWithId:(NSString*)id
+- (BOOL) localNotificationExist:(NSString*)id type:(APPLocalNotificationType)type
 {
-    NSArray* notifications = self.localNotifications;
-
-    for (UILocalNotification* notification in notifications)
-    {
-        if ([notification.options.id isEqualToString:id]) {
-            return notification;
-        }
-    }
-
-    return NULL;
+    return [self localNotificationWithId:id andType:type] != NULL;
 }
 
 /**
- * Get scheduled local notification by ID.
+ * Get local notification with ID.
  *
  * @param id
  *      Notification ID
  */
-- (UILocalNotification*) scheduledLocalNotificationWithId:(NSString*)id
+- (UILocalNotification*) localNotificationWithId:(NSString*)id
 {
-    NSArray* notifications = self.scheduledLocalNotifications2;
+    NSArray* notifications = self.localNotifications;
 
     for (UILocalNotification* notification in notifications)
     {
@@ -218,19 +210,20 @@
     return NULL;
 }
 
-/**
- * Get triggered local notification by ID.
+/*
+ * Get local notification with ID and type.
  *
  * @param id
  *      Notification ID
+ * @param type
+ *      Notification life cycle type
  */
-- (UILocalNotification*) triggeredLocalNotificationWithId:(NSString*)id
+- (UILocalNotification*) localNotificationWithId:(NSString*)id andType:(APPLocalNotificationType)type
 {
     UILocalNotification* notification = [self localNotificationWithId:id];
 
-    if (notification && [notification wasTriggered]) {
+    if (notification && notification.type == type)
         return notification;
-    }
 
     return NULL;
 }
@@ -252,32 +245,21 @@
 }
 
 /**
- * List of properties from all scheduled notifications.
- */
-- (NSArray*) scheduledLocalNotificationOptions
-{
-    NSArray* notifications = [self scheduledLocalNotifications2];
-    NSMutableArray* options = [[NSMutableArray alloc] init];
-
-    for (UILocalNotification* notification in notifications)
-    {
-        [options addObject:notification.userInfo];
-    }
-
-    return options;
-}
-
-/**
- * List of properties from all triggered notifications.
+ * List of properties from all local notifications from given type.
+ *
+ * @param type
+ *      Notification life cycle type
  */
-- (NSArray*) triggeredLocalNotificationOptions
+- (NSArray*) localNotificationOptionsByType:(APPLocalNotificationType)type
 {
-    NSArray* notifications = self.triggeredLocalNotifications;
+    NSArray* notifications = self.localNotifications;
     NSMutableArray* options = [[NSMutableArray alloc] init];
 
     for (UILocalNotification* notification in notifications)
     {
-        [options addObject:notification.userInfo];
+        if (notification.type == type) {
+            [options addObject:notification.userInfo];
+        }
     }
 
     return options;
@@ -289,7 +271,7 @@
  * @param ids
  *      Notification IDs
  */
-- (NSArray*) localNotificationOptions:(NSArray*)ids
+- (NSArray*) localNotificationOptionsById:(NSArray*)ids
 {
     UILocalNotification* notification;
     NSMutableArray* options = [[NSMutableArray alloc] init];
@@ -307,21 +289,23 @@
 }
 
 /**
- * List of properties from given scheduled notifications.
+ * List of properties from given local notifications.
  *
+ * @param type
+ *      Notification life cycle type
  * @param ids
  *      Notification IDs
  */
-- (NSArray*) scheduledLocalNotificationOptions:(NSArray*)ids
+- (NSArray*) localNotificationOptionsByType:(APPLocalNotificationType)type andId:(NSArray*)ids
 {
     UILocalNotification* notification;
     NSMutableArray* options = [[NSMutableArray alloc] init];
 
     for (NSString* id in ids)
     {
-        notification = [self scheduledLocalNotificationWithId:id];
+        notification = [self localNotificationWithId:id];
 
-        if (notification) {
+        if (notification && notification.type == type) {
             [options addObject:notification.userInfo];
         }
     }
@@ -329,27 +313,33 @@
     return options;
 }
 
-/**
- * List of properties from given triggered notifications.
- *
- * @param ids
- *      Notification IDs
+/*
+ * Clear all local notfications.
  */
-- (NSArray*) triggeredLocalNotificationOptions:(NSArray*)ids
+- (void) clearAllLocalNotifications
 {
-    UILocalNotification* notification;
-    NSMutableArray* options = [[NSMutableArray alloc] init];
-
-    for (NSString* id in ids)
-    {
-        notification = [self triggeredLocalNotificationWithId:id];
+    NSArray* notifications = self.triggeredLocalNotifications;
 
-        if (notification) {
-            [options addObject:notification.userInfo];
-        }
+    for (UILocalNotification* notification in notifications) {
+        [self clearLocalNotification:notification];
     }
+}
 
-    return options;
+/*
+ * Clear single local notfication.
+ *
+ * @param notification
+ *      The local notification object
+ */
+- (void) clearLocalNotification:(UILocalNotification*)notification
+{
+    [self cancelLocalNotification:notification];
+
+    if ([notification isRepeating]) {
+        notification.fireDate = notification.options.fireDate;
+
+        [self scheduleLocalNotification:notification];
+    };
 }
 
 @end

+ 32 - 20
src/ios/UILocalNotification+APPLocalNotification.h

@@ -1,44 +1,56 @@
 /*
- Copyright 2013-2014 appPlant UG
-
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements.  See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership.  The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License.  You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied.  See the License for the
- specific language governing permissions and limitations
- under the License.
+ * Copyright (c) 2013-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@
  */
 
 #import "APPLocalNotificationOptions.h"
 
+typedef NS_ENUM(NSUInteger, APPLocalNotificationType) {
+    NotifcationTypeAll = 0,
+    NotifcationTypeScheduled = 1,
+    NotifcationTypeTriggered = 2
+};
+
 @interface UILocalNotification (APPLocalNotification)
 
 // Initialize a new local notification
 - (id) initWithOptions:(NSDictionary*)dict;
 // The options provided by the plug-in
 - (APPLocalNotificationOptions*) options;
+// Timeinterval since last trigger date
+- (double) timeIntervalSinceLastTrigger;
 // Timeinterval since fire date
 - (double) timeIntervalSinceFireDate;
 // If the fire date was in the past
 - (BOOL) wasInThePast;
 // If the notification was already scheduled
-- (BOOL) wasScheduled;
+- (BOOL) isScheduled;
 // If the notification was already triggered
-- (BOOL) wasTriggered;
+- (BOOL) isTriggered;
 // If the notification was updated
 - (BOOL) wasUpdated;
 // If it's a repeating notification
 - (BOOL) isRepeating;
+// Notifciation type
+- (APPLocalNotificationType) type;
 // Encode the user info dict to JSON
 - (NSString*) encodeToJSON;
 

+ 45 - 22
src/ios/UILocalNotification+APPLocalNotification.m

@@ -1,22 +1,24 @@
 /*
- Copyright 2013-2014 appPlant UG
-
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements.  See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership.  The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License.  You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied.  See the License for the
- specific language governing permissions and limitations
- under the License.
+ * Copyright (c) 2013-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@
  */
 
 #import "UILocalNotification+APPLocalNotification.h"
@@ -25,6 +27,9 @@
 
 static char optionsKey;
 
+NSInteger const APPLocalNotificationTypeScheduled = 1;
+NSInteger const APPLocalNotificationTypeTriggered = 2;
+
 @implementation UILocalNotification (APPLocalNotification)
 
 #pragma mark -
@@ -131,10 +136,20 @@ static char optionsKey;
 - (double) timeIntervalSinceFireDate
 {
     NSDate* now      = [NSDate date];
-    NSDate* fireDate = self.options.fireDate;
+    NSDate* fireDate = self.fireDate;
 
     int timespan = [now timeIntervalSinceDate:fireDate];
 
+    return timespan;
+}
+
+/**
+ * Timeinterval since last trigger date.
+ */
+- (double) timeIntervalSinceLastTrigger
+{
+    int timespan = [self timeIntervalSinceFireDate];
+
     if ([self isRepeating]) {
         timespan = timespan % [self repeatIntervalInSeconds];
     }
@@ -173,11 +188,11 @@ static char optionsKey;
  */
 - (BOOL) wasInThePast
 {
-    return [self timeIntervalSinceFireDate] > 0;
+    return [self timeIntervalSinceLastTrigger] > 0;
 }
 
 // If the notification was already scheduled
-- (BOOL) wasScheduled
+- (BOOL) isScheduled
 {
     return [self isRepeating] || ![self wasInThePast];
 }
@@ -185,7 +200,7 @@ static char optionsKey;
 /**
  * If the notification was already triggered.
  */
-- (BOOL) wasTriggered
+- (BOOL) isTriggered
 {
     NSDate* now      = [NSDate date];
     NSDate* fireDate = self.fireDate;
@@ -219,4 +234,12 @@ static char optionsKey;
     return [self.options isRepeating];
 }
 
+/**
+ * Process state type of the local notification.
+ */
+- (APPLocalNotificationType) type
+{
+    return [self isTriggered] ? NotifcationTypeTriggered : NotifcationTypeScheduled;
+}
+
 @end

+ 0 - 237
src/wp8/LocalNotification.cs

@@ -1,237 +0,0 @@
-/*
-    Copyright 2013-2014 appPlant UG
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-
-using System;
-using System.Linq;
-
-using Microsoft.Phone.Shell;
-
-using WPCordovaClassLib.Cordova;
-using WPCordovaClassLib.Cordova.Commands;
-using WPCordovaClassLib.Cordova.JSON;
-
-using De.APPPlant.Cordova.Plugin.LocalNotification;
-
-namespace Cordova.Extension.Commands
-{
-    /// <summary>
-    /// Implementes access to application live tiles
-    /// http://msdn.microsoft.com/en-us/library/hh202948(v=VS.92).aspx
-    /// </summary>
-    public class LocalNotification : BaseCommand
-    {
-        /// <summary>
-        /// Informs if the device is ready and the deviceready event has been fired
-        /// </summary>
-        private bool DeviceReady = false;
-
-        /// <summary>
-        /// Informs either the app is running in background or foreground
-        /// </summary>
-        private bool RunsInBackground = false;
-
-        /// <summary>
-        /// Sets application live tile
-        /// </summary>
-        public void add (string jsonArgs)
-        {
-            string[] args   = JsonHelper.Deserialize<string[]>(jsonArgs);
-            Options options = JsonHelper.Deserialize<Options>(args[0]);
-            // Application Tile is always the first Tile, even if it is not pinned to Start.
-            ShellTile AppTile = ShellTile.ActiveTiles.First();
-
-            if (AppTile != null)
-            {
-                // Set the properties to update for the Application Tile
-                // Empty strings for the text values and URIs will result in the property being cleared.
-                FlipTileData TileData = CreateTileData(options);
-
-                AppTile.Update(TileData);
-
-                FireEvent("trigger", options.ID, options.JSON);
-                FireEvent("add", options.ID, options.JSON);
-            }
-
-            DispatchCommandResult();
-        }
-
-        /// <summary>
-        /// Clears the application live tile
-        /// </summary>
-        public void cancel (string jsonArgs)
-        {
-            string[] args         = JsonHelper.Deserialize<string[]>(jsonArgs);
-            string notificationID = args[0];
-
-            cancelAll(jsonArgs);
-
-            FireEvent("cancel", notificationID, "");
-            DispatchCommandResult();
-        }
-
-        /// <summary>
-        /// Clears the application live tile
-        /// </summary>
-        public void cancelAll (string jsonArgs)
-        {
-            // Application Tile is always the first Tile, even if it is not pinned to Start.
-            ShellTile AppTile = ShellTile.ActiveTiles.First();
-
-            if (AppTile != null)
-            {
-                // Set the properties to update for the Application Tile
-                // Empty strings for the text values and URIs will result in the property being cleared.
-                FlipTileData TileData = new FlipTileData
-                {
-                    Count                = 0,
-                    BackTitle            = "",
-                    BackContent          = "",
-                    WideBackContent      = "",
-                    SmallBackgroundImage = new Uri("appdata:Background.png"),
-                    BackgroundImage      = new Uri("appdata:Background.png"),
-                    WideBackgroundImage  = new Uri("/Assets/Tiles/FlipCycleTileLarge.png", UriKind.Relative),
-                };
-
-                // Update the Application Tile
-                AppTile.Update(TileData);
-            }
-
-            DispatchCommandResult();
-        }
-
-        /// <summary>
-        /// Checks wether a notification with an ID is scheduled
-        /// </summary>
-        public void isScheduled (string jsonArgs)
-        {
-            DispatchCommandResult();
-        }
-
-        /// <summary>
-        /// Retrieves a list with all currently pending notifications
-        /// </summary>
-        public void getScheduledIds (string jsonArgs)
-        {
-            DispatchCommandResult();
-        }
-
-        /// <summary>
-        /// Checks wether a notification with an ID was triggered
-        /// </summary>
-        public void isTriggered (string jsonArgs)
-        {
-            DispatchCommandResult();
-        }
-
-        /// <summary>
-        /// Retrieves a list with all currently triggered notifications
-        /// </summary>
-        public void getTriggeredIds (string jsonArgs)
-        {
-            DispatchCommandResult();
-        }
-
-        /// <summary>
-        /// Informs that the device is ready and the deviceready event has been fired
-        /// </summary>
-        public void deviceready (string jsonArgs)
-        {
-            DeviceReady = true;
-        }
-
-        /// <summary>
-        /// Creates tile data
-        /// </summary>
-        private FlipTileData CreateTileData (Options options)
-        {
-            FlipTileData tile = new FlipTileData();
-
-            // Badge sollte nur gelöscht werden, wenn expliziet eine `0` angegeben wurde
-            if (options.Badge != 0)
-            {
-                tile.Count = options.Badge;
-            }
-
-            tile.BackTitle       = options.Title;
-            tile.BackContent     = options.ShortMessage;
-            tile.WideBackContent = options.Message;
-
-            if (!String.IsNullOrEmpty(options.SmallImage))
-            {
-                tile.SmallBackgroundImage = new Uri(options.SmallImage, UriKind.RelativeOrAbsolute);
-            }
-
-            if (!String.IsNullOrEmpty(options.Image))
-            {
-                tile.BackgroundImage = new Uri(options.Image, UriKind.RelativeOrAbsolute);
-            }
-
-            if (!String.IsNullOrEmpty(options.WideImage))
-            {
-                tile.WideBackgroundImage = new Uri(options.WideImage, UriKind.RelativeOrAbsolute);
-            }
-
-            return tile;
-        }
-
-        /// <summary>
-        /// Fires the given event.
-        /// </summary>
-        private void FireEvent (string Event, string Id, string JSON = "")
-        {
-            string state = ApplicationState();
-            string args  = String.Format("\'{0}\',\'{1}\',\'{2}\'", Id, state, JSON);
-            string js    = String.Format("window.plugin.notification.local.on{0}({1})", Event, args);
-
-            PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, js);
-
-            pluginResult.KeepCallback = true;
-
-            DispatchCommandResult(pluginResult);
-        }
-
-        /// <summary>
-        /// Retrieves the application state
-        /// Either "background" or "foreground"
-        /// </summary>
-        private String ApplicationState ()
-        {
-            return RunsInBackground ? "background" : "foreground";
-        }
-
-        /// <summary>
-        /// Occurs when the application is being deactivated.
-        /// </summary>
-        public override void OnPause (object sender, DeactivatedEventArgs e)
-        {
-            RunsInBackground = true;
-        }
-
-        /// <summary>
-        /// Occurs when the application is being made active after previously being put
-        /// into a dormant state or tombstoned.
-        /// </summary>
-        public override void OnResume (object sender, Microsoft.Phone.Shell.ActivatedEventArgs e)
-        {
-            RunsInBackground = false;
-        }
-    }
-}

+ 0 - 113
src/wp8/Options.cs

@@ -1,113 +0,0 @@
-/*
-    Copyright 2013-2014 appPlant UG
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
-
-using System;
-using System.Linq;
-using System.Runtime.Serialization;
-
-namespace De.APPPlant.Cordova.Plugin.LocalNotification
-{
-    /// <summary>
-    /// Represents LiveTile options
-    /// </summary>
-    [DataContract]
-    class Options
-    {
-        /// <summary>
-        /// The Title that is displayed
-        /// </summary>
-        [DataMember(IsRequired = false, Name = "title")]
-        public string Title { get; set; }
-
-        /// <summary>
-        /// The message that is displayed
-        /// </summary>
-        [DataMember(IsRequired = false, Name = "message")]
-        public string Message { get; set; }
-
-        /// <summary>
-        /// Gekürzte Nachricht (alles ab dem Zeilenumbruch entfernt)
-        /// </summary>
-        public string ShortMessage
-        {
-            get
-            {
-                string[] separator = new string[] { "\r\n", "\n" };
-
-                return Message.Split(separator, StringSplitOptions.RemoveEmptyEntries).First();
-            }
-        }
-
-        /// <summary>
-        /// Displays number badge to notification
-        /// </summary>
-        [DataMember(IsRequired = false, Name = "badge")]
-        public int Badge { get; set; }
-
-        /// <summary>
-        /// Tile count
-        /// </summary>
-        [DataMember(IsRequired = false, Name = "Date")]
-        public int Date { get; set; }
-
-        /// <summary>
-        /// Has the options of daily', 'weekly',''monthly','yearly')
-        /// </summary>
-        [DataMember(IsRequired = false, Name = "repeat")]
-        public string Repeat { get; set; }
-
-        /// <summary>
-        /// Notification specific data
-        /// </summary>
-        [DataMember(IsRequired = false, Name = "json")]
-        public string JSON { get; set; }
-
-        /// <summary>
-        /// Message-ID
-        /// </summary>
-        [DataMember(IsRequired = false, Name = "id")]
-        public string ID { get; set; }
-
-        /// <summary>
-        /// Setting this flag will make it so the notification is automatically canceled when the user clicks it
-        /// </summary>
-        [DataMember(IsRequired = false, Name = "autoCancel")]
-        public bool AutoCancel { get; set; }
-
-        /// <summary>
-        /// The notification small background image to be displayed
-        /// </summary>
-        [DataMember(IsRequired = false, Name = "smallImage")]
-        public string SmallImage { get; set; }
-
-        /// <summary>
-        /// The notification background image to be displayed
-        /// </summary>
-        [DataMember(IsRequired = false, Name = "image")]
-        public string Image { get; set; }
-
-        /// <summary>
-        /// The notification wide background image to be displayed
-        /// </summary>
-        [DataMember(IsRequired = false, Name = "wideImage")]
-        public string WideImage { get; set; }
-    }
-}

+ 178 - 263
www/local-notification.js

@@ -1,76 +1,33 @@
 /*
-    Copyright 2013-2014 appPlant UG
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-     http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-*/
+ * Copyright (c) 2013-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@
+ */
 
 var exec    = require('cordova/exec'),
     channel = require('cordova/channel');
 
 
-// Called after 'deviceready' event
-channel.deviceready.subscribe( function () {
-    // Device is ready now, the listeners are registered
-    // and all queued events can be executed.
-    exec(null, null, 'LocalNotification', 'deviceready', []);
-});
-
-// Called before 'deviceready' event
-channel.onCordovaReady.subscribe( function () {
-    // The cordova device plugin is ready now
-    channel.onCordovaInfoReady.subscribe( function () {
-        if (device.platform == 'Android') {
-            channel.onPause.subscribe( function () {
-                // Necessary to set the state to `background`
-                exec(null, null, 'LocalNotification', 'pause', []);
-            });
-
-            channel.onResume.subscribe( function () {
-                // Necessary to set the state to `foreground`
-                exec(null, null, 'LocalNotification', 'resume', []);
-            });
-
-            // Necessary to set the state to `foreground`
-            exec(null, null, 'LocalNotification', 'resume', []);
-        }
-
-        // Merges the platform specific properties into the default properties
-        exports.applyPlatformSpecificOptions();
-    });
-});
-
-
-/**
- * @private
- *
- * Default values.
- */
-exports._defaults = {
-    message:    '',
-    title:      '',
-    autoCancel: false,
-    badge:      -1,
-    id:         '0',
-    json:       '',
-    repeat:     '',
-    date:       undefined
-};
-
+/*************
+ * INTERFACE *
+ *************/
 
 /**
  * Returns the default settings
@@ -90,14 +47,14 @@ exports.setDefaults = function (newDefaults) {
     var defaults = this.getDefaults();
 
     for (var key in defaults) {
-        if (newDefaults[key] !== undefined) {
+        if (newDefaults.hasOwnProperty(key)) {
             defaults[key] = newDefaults[key];
         }
     }
 };
 
 /**
- * Add a new entry to the registry
+ * Schedule a new local notification.
  *
  * @param {Object} opts
  *      The notification properties
@@ -106,7 +63,7 @@ exports.setDefaults = function (newDefaults) {
  * @param {Object?} scope
  *      The scope for the callback function
  */
-exports.add = function (opts, callback, scope) {
+exports.schedule = function (opts, callback, scope) {
     this.registerPermission(function(granted) {
 
         if (!granted)
@@ -121,7 +78,7 @@ exports.add = function (opts, callback, scope) {
             this.convertProperties(properties);
         }
 
-        this.exec('add', notifications, callback, scope);
+        this.exec('schedule', notifications, callback, scope);
     }, this);
 };
 
@@ -141,7 +98,7 @@ exports.update = function (opts, callback, scope) {
     for (var i = 0; i < notifications.length; i++) {
         var properties = notifications[i];
 
-        this.convertUpdateProperties(properties);
+        this.convertProperties(properties);
     }
 
     this.exec('update', notifications, callback, scope);
@@ -209,7 +166,7 @@ exports.cancelAll = function (callback, scope) {
 };
 
 /**
- * Check if a notification with an ID exists.
+ * Check if a notification with an ID is present.
  *
  * @param {String} id
  *      The ID of the notification
@@ -218,17 +175,10 @@ exports.cancelAll = function (callback, scope) {
  * @param {Object?} scope
  *      The scope for the callback function
  */
-exports.exist = function (id, callback, scope) {
+exports.isPresent = function (id, callback, scope) {
     var notId = (id || '0').toString();
 
-    this.exec('exist', notId, callback, scope);
-};
-
-/**
- * Alias for `exist`.
- */
-exports.exists = function () {
-    this.exist.apply(this, arguments);
+    this.exec('isPresent', notId, callback, scope);
 };
 
 /**
@@ -489,117 +439,83 @@ exports.promptForPermission = function (callback, scope) {
     exports.registerPermission.apply(this, arguments);
 };
 
-/**
- * Occurs when a notification was added.
- *
- * @param {String} id
- *      The ID of the notification
- * @param {String} state
- *      Either "foreground" or "background"
- * @param {String} json
- *      A custom (JSON) string
- * @param {Object} data
- *      The notification properties
- */
-exports.onadd = function (id, state, json, data) {};
 
-/**
- * Occurs when the notification is triggered.
- *
- * @param {String} id
- *      The ID of the notification
- * @param {String} state
- *      Either "foreground" or "background"
- * @param {String} json
- *      A custom (JSON) string
- * @param {Object} data
- *      The notification properties
- */
-exports.ontrigger = function (id, state, json, data) {};
+/**********
+ * EVENTS *
+ **********/
 
 /**
- * Fires after the notification was clicked.
+ * Register callback for given event.
  *
- * @param {String} id
- *      The ID of the notification
- * @param {String} state
- *      Either "foreground" or "background"
- * @param {String} json
- *      A custom (JSON) string
- * @param {Object} data
- *      The notification properties
+ * @param {String} event
+ *      The event's name
+ * @param {Function} callback
+ *      The function to be exec as callback
+ * @param {Object?} scope
+ *      The callback function's scope
  */
-exports.onclick = function (id, state, json, data) {};
+exports.on = function (event, callback, scope) {
 
-/**
- * Fires if the notification was canceled.
- *
- * @param {String} id
- *      The ID of the notification
- * @param {String} state
- *      Either "foreground" or "background"
- * @param {String} json
- *      A custom (JSON) string
- * @param {Object} data
- *      The notification properties
- */
-exports.oncancel = function (id, state, json, data) {};
+    if (!this._listener[event]) {
+        this._listener[event] = [];
+    }
 
-/**
- * Get fired when the notification was cleared.
- *
- * @param {String} id
- *      The ID of the notification
- * @param {String} state
- *      Either "foreground" or "background"
- * @param {String} json
- *      A custom (JSON) string
- * @param {Object} data
- *      The notification properties
- */
-exports.onclear = function (id, state, json, data) {};
+    var item = [callback, scope || window];
 
-/**
- * Get fired when a repeating notification should be updated.
- *
- * @param {String} id
- *      The ID of the notification
- * @param {String} state
- *      Either "foreground" or "background"
- * @param {String} json
- *      A custom (JSON) string
- * @param {Object} data
- *      The notification properties
- * @return {Object} JSONObject with updatevalues
- */
-exports.onupdate = function (id, state, json, data) {
-	return null;
+    this._listener[event].push(item);
 };
-	
+
 /**
- * Is called from the native part to receive the onupdate resultarray and send it back to native.
+ * Unregister callback for given event.
  *
- * @param {String} id
- *      The ID of the notification
- * @param {String} state
- *      Either "foreground" or "background"
- * @param {String} json
- *      A custom (JSON) string
- * @param {Object} data
- *      The notification properties
+ * @param {String} event
+ *      The event's name
+ * @param {Function} callback
+ *      The function to be exec as callback
  */
-exports.onupdateCall = function (id, state, json, data) {
-	var updates = exports.onupdate(id, state, json, data);
-	if (updates != null){
-		updates.id = id;
-		update(updates,null,null);
-	};
+exports.un = function (event, callback) {
+    var listener = this._listener[event];
+
+    if (!listener)
+        return;
+
+    for (var i = 0; i < listener.length; i++) {
+        var fn = listener[i][0];
+
+        if (fn == callback) {
+            listener.splice(i, 1);
+            break;
+        }
+    }
+};
+
+
+/***********
+ * MEMBERS *
+ ***********/
+
+// Default values
+exports._defaults = {
+    text:  '',
+    title: '',
+    sound: 'res://platform_default',
+    badge: 0,
+    id:    0,
+    data:  undefined,
+    every: undefined,
+    at:    undefined
 };
 
+// listener
+exports._listener = {};
+
+
+/***********
+ * PRIVATE *
+ ***********/
+
 /**
- * @private
- *
- * Merges custom properties with the default values.
+ * Merge custom properties with the default values.
  *
  * @param {Object} options
  *      Set of custom values
@@ -610,13 +526,21 @@ exports.onupdateCall = function (id, state, json, data) {
 exports.mergeWithDefaults = function (options) {
     var defaults = this.getDefaults();
 
-    options.date    = this.getValueFor(options, 'date', 'at', 'firstAt');
-    options.repeat  = this.getValueFor(options, 'repeat', 'every');
-    options.message = this.getValueFor(options, 'message', 'text');
+    options.at   = this.getValueFor(options, 'at', 'firstAt', 'date');
+    options.text = this.getValueFor(options, 'text', 'message');
+    options.data = this.getValueFor(options, 'data', 'json');
+
+    if (options.at === undefined || options.at === null) {
+        options.at = new Date();
+    }
 
     for (var key in defaults) {
         if (options[key] === null || options[key] === undefined) {
-            options[key] = defaults[key];
+            if (options.hasOwnProperty(key) && ['data','sound'].indexOf(key) > -1) {
+                options[key] = undefined;
+            } else {
+                options[key] = defaults[key];
+            }
         }
     }
 
@@ -630,8 +554,6 @@ exports.mergeWithDefaults = function (options) {
 };
 
 /**
- * @private
- *
  * Convert the passed values to their required type.
  *
  * @param {Object} options
@@ -642,79 +564,43 @@ exports.mergeWithDefaults = function (options) {
  */
 exports.convertProperties = function (options) {
 
-    options.id         = options.id.toString();
-    options.title      = options.title.toString();
-    options.message    = options.message.toString();
-    options.autoCancel = options.autoCancel === true;
-
-    if (isNaN(options.id)) {
-        options.id = this.getDefaults().id;
-    }
-
-    if (isNaN(options.badge)) {
-        options.badge = this.getDefaults().badge;
-    }
-
-    options.badge = Number(options.badge);
-
-    if (options.date === undefined || options.date === null) {
-        options.date = new Date();
-    }
-
-    if (typeof options.date == 'object') {
-        options.date = Math.round(options.date.getTime()/1000);
+    if (options.id) {
+        if (isNaN(options.id)) {
+            options.id = this.getDefaults().id;
+        } else {
+            options.id = options.id.toString();
+        }
     }
 
-    if (typeof options.json == 'object') {
-        options.json = JSON.stringify(options.json);
+    if (options.title) {
+        options.title = options.title.toString();
     }
 
-    return options;
-};
-
-/**
- * @private
- *
- * Convert the passed values to their required type only for update function.
- *
- * @param {Object} options
- *      Set of custom values
- *
- * @retrun {Object}
- *      The converted property list
- */
-exports.convertUpdateProperties = function (options) {
-
-    options.id         = options.id.toString();
-    options.title      = options.title.toString();
-    options.message    = options.message.toString();
-    options.autoCancel = options.autoCancel === true;
-
-    if (isNaN(options.id)) {
-        options.id = this.getDefaults().id;
+    if (options.text) {
+        options.text  = options.text.toString();
     }
 
-    if (isNaN(options.badge)) {
-        options.badge = this.getDefaults().badge;
+    if (options.badge) {
+        if (isNaN(options.badge)) {
+            options.badge = this.getDefaults().badge;
+        } else {
+            options.badge = Number(options.badge);
+        }
     }
 
-    options.badge = Number(options.badge);
-
-    if (typeof options.date == 'object') {
-        options.date = Math.round(options.date.getTime()/1000);
+    if (typeof options.at == 'object') {
+        options.at = Math.round(options.at.getTime()/1000);
     }
 
-    if (typeof options.json == 'object') {
-        options.json = JSON.stringify(options.json);
+    if (typeof options.data == 'object') {
+        options.data = JSON.stringify(options.data);
     }
 
     return options;
 };
 
 /**
- * @private
- *
- * Merges the platform specific properties into the default properties.
+ * Merge platform specific properties into the default ones.
  *
  * @return {Object}
  *      The default properties for the platform
@@ -724,26 +610,18 @@ exports.applyPlatformSpecificOptions = function () {
 
     switch (device.platform) {
     case 'Android':
-        defaults.icon       = 'icon';
-        defaults.smallIcon  = null;
-        defaults.ongoing    = false;
-        defaults.led        = 'FFFFFF'; /*RRGGBB*/
-        defaults.sound      = 'TYPE_NOTIFICATION'; break;
-    case 'iOS':
-        defaults.sound      = ''; break;
-    case 'WinCE': case 'Win32NT':
-        defaults.smallImage = null;
-        defaults.image      = null;
-        defaults.wideImage  = null;
+        defaults.icon      = 'res://ic_popup_reminder';
+        defaults.smallIcon = 'res://ic_popup_reminder';
+        defaults.ongoing   = false;
+        defaults.led       = 'FFFFFF';
+        break;
     }
 
     return defaults;
 };
 
 /**
- * @private
- *
- * Creates a callback, which will be executed within a specific scope.
+ * Create callback, which will be executed within a specific scope.
  *
  * @param {Function} callbackFn
  *      The callback function
@@ -763,8 +641,6 @@ exports.createCallbackFn = function (callbackFn, scope) {
 };
 
 /**
- * @private
- *
  * Convert the IDs to Strings.
  *
  * @param {String/Number[]} ids
@@ -782,13 +658,10 @@ exports.convertIds = function (ids) {
 };
 
 /**
- * @private
- *
- * Return the first found value for the given keys.
+ * First found value for the given keys.
  *
  * @param {Object} options
  *      Object with key-value properties
- *
  * @param {String[]} keys*
  *      Key list
  */
@@ -805,9 +678,30 @@ exports.getValueFor = function (options) {
 };
 
 /**
- * @private
+ * Fire event with given arguments.
  *
- * Executes the native counterpart.
+ * @param {String} event
+ *      The event's name
+ * @param {args*}
+ *      The callback's arguments
+ */
+exports.fireEvent = function (event) {
+    var args     = Array.apply(null, arguments).slice(1),
+        listener = this._listener[event];
+
+    if (!listener)
+        return;
+
+    for (var i = 0; i < listener.length; i++) {
+        var fn    = listener[i][0],
+            scope = listener[i][1];
+
+        fn.apply(scope, args);
+    }
+};
+
+/**
+ * Execute the native counterpart.
  *
  * @param {String} action
  *      The name of the action
@@ -830,3 +724,24 @@ exports.exec = function (action, args, callback, scope) {
 
     exec(fn, null, 'LocalNotification', action, params);
 };
+
+
+/*********
+ * HOOKS *
+ *********/
+
+// Called after 'deviceready' event
+channel.deviceready.subscribe(function () {
+    // Device is ready now, the listeners are registered
+    // and all queued events can be executed.
+    exec(null, null, 'LocalNotification', 'deviceready', []);
+});
+
+// Called before 'deviceready' event
+channel.onCordovaReady.subscribe(function () {
+    // Device plugin is ready now
+    channel.onCordovaInfoReady.subscribe(function () {
+        // Merge platform specifics into defaults
+        exports.applyPlatformSpecificOptions();
+    });
+});