Browse Source

Release v0.8rc

Sebastián Katzer 10 năm trước cách đây
mục cha
commit
53fe0a5e02
38 tập tin đã thay đổi với 3521 bổ sung3289 xóa
  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();
+    });
+});