summaryrefslogtreecommitdiff
path: root/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/de.appplant.cordova.plugin.local-notification/src/android/notification')
-rw-r--r--plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractClearReceiver.java75
-rw-r--r--plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractClickActivity.java103
-rw-r--r--plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractRestoreReceiver.java83
-rw-r--r--plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractTriggerReceiver.java122
-rw-r--r--plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AssetUtil.java436
-rw-r--r--plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Builder.java194
-rw-r--r--plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/ClearReceiver.java44
-rw-r--r--plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/ClickActivity.java55
-rw-r--r--plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Manager.java455
-rw-r--r--plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Notification.java350
-rw-r--r--plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Options.java303
-rw-r--r--plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/TriggerReceiver.java59
12 files changed, 2279 insertions, 0 deletions
diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractClearReceiver.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractClearReceiver.java
new file mode 100644
index 00000000..94d2a19b
--- /dev/null
+++ b/plugins/de.appplant.cordova.plugin.local-notification/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);
+
+}
diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractClickActivity.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractClickActivity.java
new file mode 100644
index 00000000..a02a9981
--- /dev/null
+++ b/plugins/de.appplant.cordova.plugin.local-notification/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);
+ }
+
+}
diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractRestoreReceiver.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractRestoreReceiver.java
new file mode 100644
index 00000000..8a1f3656
--- /dev/null
+++ b/plugins/de.appplant.cordova.plugin.local-notification/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);
+
+}
diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractTriggerReceiver.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AbstractTriggerReceiver.java
new file mode 100644
index 00000000..fc6759c5
--- /dev/null
+++ b/plugins/de.appplant.cordova.plugin.local-notification/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);
+ }
+
+}
diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AssetUtil.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AssetUtil.java
new file mode 100644
index 00000000..2da8a2c3
--- /dev/null
+++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/AssetUtil.java
@@ -0,0 +1,436 @@
+/*
+ * 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.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;
+
+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;
+
+/**
+ * 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();
+ }
+
+}
diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Builder.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Builder.java
new file mode 100644
index 00000000..a0be8b93
--- /dev/null
+++ b/plugins/de.appplant.cordova.plugin.local-notification/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 android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.support.v4.app.NotificationCompat;
+
+import org.json.JSONObject;
+
+import java.util.Random;
+
+/**
+ * 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(options.isAutoClear())
+ .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.getIdStr())
+ .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);
+ }
+
+}
diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/ClearReceiver.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/ClearReceiver.java
new file mode 100644
index 00000000..761b6c5c
--- /dev/null
+++ b/plugins/de.appplant.cordova.plugin.local-notification/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();
+ }
+
+}
diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/ClickActivity.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/ClickActivity.java
new file mode 100644
index 00000000..01af5c45
--- /dev/null
+++ b/plugins/de.appplant.cordova.plugin.local-notification/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();
+ }
+
+}
diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Manager.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Manager.java
new file mode 100644
index 00000000..03ea384f
--- /dev/null
+++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Manager.java
@@ -0,0 +1,455 @@
+/*
+ * 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.NotificationManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static de.appplant.cordova.plugin.notification.Notification.PREF_KEY;
+
+/**
+ * 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;
+
+ /**
+ * Constructor
+ *
+ * @param context
+ * Application context
+ */
+ private Manager(Context context){
+ this.context = context;
+ }
+
+ /**
+ * Static method to retrieve class instance.
+ *
+ * @param context
+ * Application context
+ */
+ public static Manager getInstance(Context context) {
+ return new Manager(context);
+ }
+
+ /**
+ * Schedule local notification specified by JSON object.
+ *
+ * @param options
+ * JSON object with set of options
+ * @param receiver
+ * Receiver to handle the trigger event
+ */
+ public Notification schedule (JSONObject options, Class<?> receiver) {
+ return schedule(new Options(context).parse(options), receiver);
+ }
+
+ /**
+ * Schedule local notification specified by options object.
+ *
+ * @param options
+ * Set of notification options
+ * @param receiver
+ * Receiver to handle the trigger event
+ */
+ public Notification schedule (Options options, Class<?> receiver) {
+ Notification notification = new Builder(options)
+ .setTriggerReceiver(receiver)
+ .build();
+
+ notification.schedule();
+
+ return notification;
+ }
+
+ /**
+ * Clear local notification specified by ID.
+ *
+ * @param id
+ * The notification ID
+ * @param updates
+ * JSON object with notification options
+ * @param receiver
+ * Receiver to handle the trigger event
+ */
+ public Notification update (int id, JSONObject updates, Class<?> receiver) {
+ Notification notification = get(id);
+
+ if (notification == null)
+ return null;
+
+ notification.cancel();
+
+ JSONObject options = mergeJSONObjects(
+ notification.getOptions().getDict(), updates);
+
+ try {
+ options.putOpt("updatedAt", new Date().getTime());
+ } catch (JSONException ignore) {}
+
+ return schedule(options, receiver);
+ }
+
+ /**
+ * Clear local notification specified by ID.
+ *
+ * @param id
+ * The notification ID
+ */
+ public Notification clear (int id) {
+ Notification notification = get(id);
+
+ if (notification != null) {
+ notification.clear();
+ }
+
+ return notification;
+ }
+
+ /**
+ * Clear local notification specified by ID.
+ *
+ * @param id
+ * The notification ID
+ */
+ public Notification cancel (int id) {
+ Notification notification = get(id);
+
+ if (notification != null) {
+ notification.cancel();
+ }
+
+ return notification;
+ }
+
+ /**
+ * Clear all local notifications.
+ */
+ public void clearAll () {
+ List<Notification> notifications = getAll();
+
+ for (Notification notification : notifications) {
+ notification.clear();
+ }
+
+ getNotMgr().cancelAll();
+ }
+
+ /**
+ * Cancel all local notifications.
+ */
+ public void cancelAll () {
+ List<Notification> notifications = getAll();
+
+ for (Notification notification : notifications) {
+ notification.cancel();
+ }
+
+ getNotMgr().cancelAll();
+ }
+
+ /**
+ * All local notifications IDs.
+ */
+ public List<Integer> getIds() {
+ Set<String> keys = getPrefs().getAll().keySet();
+ ArrayList<Integer> ids = new ArrayList<Integer>();
+
+ for (String key : keys) {
+ ids.add(Integer.parseInt(key));
+ }
+
+ return ids;
+ }
+
+ /**
+ * All local notification IDs for given type.
+ *
+ * @param type
+ * The notification life cycle type
+ */
+ public List<Integer> getIdsByType(Notification.Type type) {
+ List<Notification> notifications = getAll();
+ ArrayList<Integer> ids = new ArrayList<Integer>();
+
+ for (Notification notification : notifications) {
+ if (notification.getType() == type) {
+ ids.add(notification.getId());
+ }
+ }
+
+ return ids;
+ }
+
+ /**
+ * List of local notifications with matching ID.
+ *
+ * @param ids
+ * Set of notification IDs
+ */
+ public List<Notification> getByIds(List<Integer> ids) {
+ ArrayList<Notification> notifications = new ArrayList<Notification>();
+
+ for (int id : ids) {
+ Notification notification = get(id);
+
+ if (notification != null) {
+ notifications.add(notification);
+ }
+ }
+
+ return notifications;
+ }
+
+ /**
+ * List of all local notification.
+ */
+ public List<Notification> getAll() {
+ return getByIds(getIds());
+ }
+
+ /**
+ * List of local notifications from given type.
+ *
+ * @param type
+ * The notification life cycle type
+ */
+ public List<Notification> getByType(Notification.Type type) {
+ List<Notification> notifications = getAll();
+ ArrayList<Notification> list = new ArrayList<Notification>();
+
+ if (type == Notification.Type.ALL)
+ return notifications;
+
+ for (Notification notification : notifications) {
+ if (notification.getType() == type) {
+ list.add(notification);
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * List of local notifications with matching ID from given type.
+ *
+ * @param type
+ * The notification life cycle type
+ * @param ids
+ * Set of notification IDs
+ */
+ @SuppressWarnings("UnusedDeclaration")
+ public List<Notification> getBy(Notification.Type type, List<Integer> ids) {
+ ArrayList<Notification> notifications = new ArrayList<Notification>();
+
+ for (int id : ids) {
+ Notification notification = get(id);
+
+ if (notification != null && notification.isScheduled()) {
+ notifications.add(notification);
+ }
+ }
+
+ return notifications;
+ }
+
+ /**
+ * If a notification with an ID exists.
+ *
+ * @param id
+ * Notification ID
+ */
+ public boolean exist (int id) {
+ return get(id) != null;
+ }
+
+ /**
+ * If a notification with an ID and type exists.
+ *
+ * @param id
+ * Notification ID
+ * @param type
+ * Notification type
+ */
+ public boolean exist (int id, Notification.Type type) {
+ Notification notification = get(id);
+
+ return notification != null && notification.getType() == type;
+ }
+
+ /**
+ * List of properties from all local notifications.
+ */
+ public List<JSONObject> getOptions() {
+ return getOptionsById(getIds());
+ }
+
+ /**
+ * List of properties from local notifications with matching ID.
+ *
+ * @param ids
+ * Set of notification IDs
+ */
+ public List<JSONObject> getOptionsById(List<Integer> ids) {
+ ArrayList<JSONObject> options = new ArrayList<JSONObject>();
+
+ for (int id : ids) {
+ Notification notification = get(id);
+
+ if (notification != null) {
+ options.add(notification.getOptions().getDict());
+ }
+ }
+
+ return options;
+ }
+
+ /**
+ * List of properties from all local notifications from given type.
+ *
+ * @param type
+ * The notification life cycle type
+ */
+ public List<JSONObject> getOptionsByType(Notification.Type type) {
+ ArrayList<JSONObject> options = new ArrayList<JSONObject>();
+ List<Notification> notifications = getByType(type);
+
+ for (Notification notification : notifications) {
+ options.add(notification.getOptions().getDict());
+ }
+
+ return options;
+ }
+
+ /**
+ * List of properties from local notifications with matching ID from
+ * given type.
+ *
+ * @param type
+ * The notification life cycle type
+ * @param ids
+ * Set of notification IDs
+ */
+ public List<JSONObject> getOptionsBy(Notification.Type type,
+ List<Integer> ids) {
+
+ if (type == Notification.Type.ALL)
+ return getOptionsById(ids);
+
+ ArrayList<JSONObject> options = new ArrayList<JSONObject>();
+ List<Notification> notifications = getByIds(ids);
+
+ for (Notification notification : notifications) {
+ if (notification.getType() == type) {
+ options.add(notification.getOptions().getDict());
+ }
+ }
+
+ return options;
+ }
+
+ /**
+ * Get existent local notification.
+ *
+ * @param id
+ * Notification ID
+ */
+ public Notification get(int id) {
+ Map<String, ?> alarms = getPrefs().getAll();
+ String notId = Integer.toString(id);
+ JSONObject options;
+
+ if (!alarms.containsKey(notId))
+ return null;
+
+
+ try {
+ String json = alarms.get(notId).toString();
+ options = new JSONObject(json);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ Builder builder = new Builder(context, options);
+
+ return builder.build();
+ }
+
+ /**
+ * Merge two JSON objects.
+ *
+ * @param obj1
+ * JSON object
+ * @param obj2
+ * JSON object with new options
+ */
+ private JSONObject mergeJSONObjects (JSONObject obj1, JSONObject obj2) {
+ Iterator it = obj2.keys();
+
+ while (it.hasNext()) {
+ try {
+ String key = (String)it.next();
+
+ obj1.put(key, obj2.opt(key));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return obj1;
+ }
+
+ /**
+ * Shared private preferences for the application.
+ */
+ private SharedPreferences getPrefs () {
+ return context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE);
+ }
+
+ /**
+ * Notification manager for the application.
+ */
+ private NotificationManager getNotMgr () {
+ return (NotificationManager) context
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+}
diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Notification.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Notification.java
new file mode 100644
index 00000000..5dba9d54
--- /dev/null
+++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Notification.java
@@ -0,0 +1,350 @@
+/*
+ * 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 enum Type {
+ ALL, SCHEDULED, TRIGGERED
+ }
+
+ // Default receiver to handle the trigger event
+ private static Class<?> defaultReceiver = TriggerReceiver.class;
+
+ // Key for private preferences
+ static final String PREF_KEY = "LocalNotification";
+
+ // 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.getId();
+ }
+
+ /**
+ * If it's a repeating notification.
+ */
+ public boolean isRepeating () {
+ return getOptions().getRepeatInterval() > 0;
+ }
+
+ /**
+ * If the notification was in the past.
+ */
+ public boolean wasInThePast () {
+ return new Date().after(options.getTriggerDate());
+ }
+
+ /**
+ * If the notification is scheduled.
+ */
+ public boolean isScheduled () {
+ return isRepeating() || !wasInThePast();
+ }
+
+ /**
+ * If the notification is triggered.
+ */
+ public boolean isTriggered () {
+ return wasInThePast();
+ }
+
+ /**
+ * If the notification is an update.
+ */
+ 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 = options.getTriggerTime();
+
+ persist();
+
+ // Intent gets called when the Notification gets fired
+ Intent intent = new Intent(context, receiver)
+ .setAction(options.getIdStr())
+ .putExtra(Options.EXTRA, options.toString());
+
+ PendingIntent pi = PendingIntent.getBroadcast(
+ context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+
+ if (isRepeating()) {
+ getAlarmMgr().setRepeating(AlarmManager.RTC_WAKEUP,
+ triggerTime, options.getRepeatInterval(), pi);
+ } else {
+ getAlarmMgr().set(AlarmManager.RTC_WAKEUP, triggerTime, pi);
+ }
+ }
+
+ /**
+ * Clear the local notification without canceling repeating alarms.
+ *
+ */
+ public void clear () {
+ 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.getIdStr());
+
+ PendingIntent pi = PendingIntent.
+ getBroadcast(context, 0, intent, 0);
+
+ getAlarmMgr().cancel(pi);
+ getNotMgr().cancel(options.getId());
+
+ 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().getId();
+
+ if (Build.VERSION.SDK_INT <= 15) {
+ // Notification for HoneyComb to ICS
+ getNotMgr().notify(id, builder.getNotification());
+ } else {
+ // Notification for Jellybean and above
+ getNotMgr().notify(id, builder.build());
+ }
+ }
+
+ /**
+ * Show as modal dialog when in foreground.
+ */
+ private void showDialog () {
+ // TODO
+ }
+
+ /**
+ * Count of triggers since schedule.
+ */
+ public int getTriggerCountSinceSchedule() {
+ long now = System.currentTimeMillis();
+ long triggerTime = options.getTriggerTime();
+
+ if (!wasInThePast())
+ return 0;
+
+ if (!isRepeating())
+ return 1;
+
+ return (int) ((now - triggerTime) / options.getRepeatInterval());
+ }
+
+ /**
+ * Encode options to JSON.
+ */
+ public String toString() {
+ JSONObject dict = options.getDict();
+ JSONObject json = new JSONObject();
+
+ try {
+ json = new JSONObject(dict.toString());
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ json.remove("firstAt");
+ json.remove("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.getIdStr(), options.toString());
+
+ if (Build.VERSION.SDK_INT < 9) {
+ editor.commit();
+ } else {
+ editor.apply();
+ }
+ }
+
+ /**
+ * Remove the notification from the Android shared Preferences.
+ */
+ private void unpersist () {
+ SharedPreferences.Editor editor = getPrefs().edit();
+
+ editor.remove(options.getIdStr());
+
+ if (Build.VERSION.SDK_INT < 9) {
+ editor.commit();
+ } else {
+ editor.apply();
+ }
+ }
+
+ /**
+ * Shared private preferences for the application.
+ */
+ 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;
+ }
+
+}
diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Options.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Options.java
new file mode 100644
index 00000000..198a52f4
--- /dev/null
+++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/Options.java
@@ -0,0 +1,303 @@
+/*
+ * 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.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Date;
+
+/**
+ * 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();
+
+ // 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.assets = AssetUtil.getInstance(context);
+ }
+
+ /**
+ * Parse given JSON properties.
+ *
+ * @param options
+ * JSON properties
+ */
+ public Options parse (JSONObject options) {
+ this.options = options;
+
+ 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;
+ } else
+ if (every.equals("minute")) {
+ interval = AlarmManager.INTERVAL_FIFTEEN_MINUTES / 15;
+ } else
+ if (every.equals("hour")) {
+ interval = AlarmManager.INTERVAL_HOUR;
+ } else
+ if (every.equals("day")) {
+ interval = AlarmManager.INTERVAL_DAY;
+ } else
+ if (every.equals("week")) {
+ interval = AlarmManager.INTERVAL_DAY * 7;
+ } else
+ if (every.equals("month")) {
+ interval = AlarmManager.INTERVAL_DAY * 31;
+ } else
+ if (every.equals("year")) {
+ interval = AlarmManager.INTERVAL_DAY * 365;
+ } else {
+ try {
+ interval = Integer.parseInt(every) * 60000;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Parse asset URIs.
+ */
+ private void parseAssets() {
+
+ if (options.has("iconUri"))
+ return;
+
+ 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();
+ }
+ }
+
+ /**
+ * Application context.
+ */
+ public Context getContext () {
+ return context;
+ }
+
+ /**
+ * Wrapped JSON object.
+ */
+ JSONObject getDict () {
+ return options;
+ }
+
+ /**
+ * Text for the local notification.
+ */
+ public String getText() {
+ return options.optString("text", "");
+ }
+
+ /**
+ * Repeat interval (day, week, month, year, aso.)
+ */
+ public long getRepeatInterval() {
+ return interval;
+ }
+
+ /**
+ * Badge number for the local notification.
+ */
+ public int getBadgeNumber() {
+ return options.optInt("badge", 0);
+ }
+
+ /**
+ * ongoing flag for local notifications.
+ */
+ public Boolean isOngoing() {
+ return options.optBoolean("ongoing", false);
+ }
+
+ /**
+ * autoClear flag for local notifications.
+ */
+ public Boolean isAutoClear() {
+ return options.optBoolean("autoClear", false);
+ }
+
+ /**
+ * ID for the local notification as a number.
+ */
+ public Integer getId() {
+ return options.optInt("id", 0);
+ }
+
+ /**
+ * ID for the local notification as a string.
+ */
+ public String getIdStr() {
+ return getId().toString();
+ }
+
+ /**
+ * Trigger date.
+ */
+ public Date getTriggerDate() {
+ return new Date(getTriggerTime());
+ }
+
+ /**
+ * Trigger date in milliseconds.
+ */
+ public long getTriggerTime() {
+ return Math.max(
+ System.currentTimeMillis(),
+ options.optLong("at", 0) * 1000
+ );
+ }
+
+ /**
+ * Title for the local notification.
+ */
+ public String getTitle() {
+ String title = options.optString("title", "");
+
+ if (title.isEmpty()) {
+ title = context.getApplicationInfo().loadLabel(
+ context.getPackageManager()).toString();
+ }
+
+ return title;
+ }
+
+ /**
+ * @return
+ * The notification color for LED
+ */
+ public int getLedColor() {
+ String hex = options.optString("led", "000000");
+ int aRGB = Integer.parseInt(hex,16);
+
+ aRGB += 0xFF000000;
+
+ return aRGB;
+ }
+
+ /**
+ * Sound file path for the local notification.
+ */
+ public Uri getSoundUri() {
+ Uri uri = null;
+
+ try{
+ uri = Uri.parse(options.optString("soundUri"));
+ } catch (Exception e){
+ e.printStackTrace();
+ }
+
+ return uri;
+ }
+
+ /**
+ * Icon bitmap for the local notification.
+ */
+ public Bitmap getIconBitmap() {
+ String icon = options.optString("icon", "icon");
+ Bitmap bmp;
+
+ try{
+ Uri uri = Uri.parse(options.optString("iconUri"));
+ bmp = assets.getIconFromUri(uri);
+ } catch (Exception e){
+ bmp = assets.getIconFromDrawable(icon);
+ }
+
+ return bmp;
+ }
+
+ /**
+ * Small icon resource ID for the local notification.
+ */
+ public int getSmallIcon () {
+ String icon = options.optString("smallIcon", "");
+
+ int resId = assets.getResIdForDrawable(icon);
+
+ if (resId == 0) {
+ resId = android.R.drawable.screen_background_dark;
+ }
+
+ return resId;
+ }
+
+ /**
+ * JSON object as string.
+ */
+ public String toString() {
+ return options.toString();
+ }
+
+}
diff --git a/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/TriggerReceiver.java b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/TriggerReceiver.java
new file mode 100644
index 00000000..9427e31e
--- /dev/null
+++ b/plugins/de.appplant.cordova.plugin.local-notification/src/android/notification/TriggerReceiver.java
@@ -0,0 +1,59 @@
+/*
+ * 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) {
+ notification.show();
+ }
+
+ /**
+ * Build notification specified by options.
+ *
+ * @param builder
+ * Notification builder
+ */
+ @Override
+ public Notification buildNotification (Builder builder) {
+ return builder.build();
+ }
+
+}