/*
* 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 android.app.Activity;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import de.appplant.cordova.plugin.notification.Manager;
import de.appplant.cordova.plugin.notification.Notification;
/**
* 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 notification center and status bar.
*/
public class LocalNotification extends CordovaPlugin {
// Reference to the web view for static access
private static CordovaWebView webView = null;
// Indicates if the device is ready (to receive events)
private static Boolean deviceready = false;
// To inform the user about the state of the app in callbacks
protected static Boolean isInBackground = true;
// Queues all events before deviceready
private static ArrayList<String> eventQueue = new ArrayList<String>();
/**
* Called after plugin construction and fields have been initialized.
* Prefer to use pluginInitialize instead since there is no value in
* having parameters on the initialize() function.
*
* pluginInitialize is not available for cordova 3.0-3.5 !
*/
@Override
public void initialize (CordovaInterface cordova, CordovaWebView webView) {
LocalNotification.webView = super.webView;
}
/**
* 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 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;
deviceready();
}
/**
* The final call you receive before your activity is destroyed.
*/
@Override
public void onDestroy() {
deviceready = false;
isInBackground = true;
}
/**
* 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();
}
else if (action.equals("update")) {
update(args);
command.success();
}
else if (action.equals("cancel")) {
cancel(args);
command.success();
}
else if (action.equals("cancelAll")) {
cancelAll();
command.success();
}
else if (action.equals("clear")) {
clear(args);
command.success();
}
else if (action.equals("clearAll")) {
clearAll();
command.success();
}
else if (action.equals("isPresent")) {
isPresent(args.optInt(0), command);
}
else if (action.equals("isScheduled")) {
isScheduled(args.optInt(0), command);
}
else if (action.equals("isTriggered")) {
isTriggered(args.optInt(0), command);
}
else if (action.equals("getAllIds")) {
getAllIds(command);
}
else if (action.equals("getScheduledIds")) {
getScheduledIds(command);
}
else if (action.equals("getTriggeredIds")) {
getTriggeredIds(command);
}
else if (action.equals("getSingle")) {
getSingle(args, command);
}
else if (action.equals("getSingleScheduled")) {
getSingleScheduled(args, command);
}
else if (action.equals("getSingleTriggered")) {
getSingleTriggered(args, command);
}
else if (action.equals("getAll")) {
getAll(args, command);
}
else if (action.equals("getScheduled")) {
getScheduled(args, command);
}
else if (action.equals("getTriggered")) {
getTriggered(args, command);
}
else if (action.equals("deviceready")) {
deviceready();
}
}
});
return true;
}
/**
* 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);
Notification notification =
getNotificationMgr().schedule(options, TriggerReceiver.class);
fireEvent("schedule", notification);
}
}
/**
* Update multiple local notifications.
*
* @param updates
* Notification properties including their IDs
*/
private void update (JSONArray updates) {
for (int i = 0; i < updates.length(); i++) {
JSONObject update = updates.optJSONObject(i);
int id = update.optInt("id", 0);
Notification notification =
getNotificationMgr().update(id, update, TriggerReceiver.class);
if (notification == null)
continue;
fireEvent("update", notification);
}
}
/**
* Cancel multiple local notifications.
*
* @param ids
* Set of local notification IDs
*/
private void cancel (JSONArray ids) {
for (int i = 0; i < ids.length(); i++) {
int id = ids.optInt(i, 0);
Notification notification =
getNotificationMgr().cancel(id);
if (notification == null)
continue;
fireEvent("cancel", notification);
}
}
/**
* Cancel all scheduled notifications.
*/
private void cancelAll() {
getNotificationMgr().cancelAll();
fireEvent("cancelall");
}
/**
* Clear multiple local notifications without canceling them.
*
* @param ids
* Set of local notification IDs
*/
private void clear(JSONArray ids){
for (int i = 0; i < ids.length(); i++) {
int id = ids.optInt(i, 0);
Notification notification =
getNotificationMgr().clear(id);
if (notification == null)
continue;
fireEvent("clear", notification);
}
}
/**
* Clear all triggered notifications without canceling them.
*/
private void clearAll() {
getNotificationMgr().clearAll();
fireEvent("clearall");
}
/**
* If a notification with an ID is present.
*
* @param id
* Notification ID
* @param command
* The callback context used when calling back into JavaScript.
*/
private void isPresent (int id, CallbackContext command) {
boolean exist = getNotificationMgr().exist(id);
PluginResult result = new PluginResult(
PluginResult.Status.OK, exist);
command.sendPluginResult(result);
}
/**
* If a notification with an ID is scheduled.
*
* @param id
* Notification ID
* @param command
* The callback context used when calling back into JavaScript.
*/
private void isScheduled (int id, CallbackContext command) {
boolean exist = getNotificationMgr().exist(
id, Notification.Type.SCHEDULED);
PluginResult result = new PluginResult(
PluginResult.Status.OK, exist);
command.sendPluginResult(result);
}
/**
* If a notification with an ID is triggered.
*
* @param id
* Notification ID
* @param command
* The callback context used when calling back into JavaScript.
*/
private void isTriggered (int id, CallbackContext command) {
boolean exist = getNotificationMgr().exist(
id, Notification.Type.TRIGGERED);
PluginResult result = new PluginResult(
PluginResult.Status.OK, exist);
command.sendPluginResult(result);
}
/**
* Set of IDs from all existent notifications.
*
* @param command
* The callback context used when calling back into JavaScript.
*/
private void getAllIds (CallbackContext command) {
List<Integer> ids = getNotificationMgr().getIds();
command.success(new JSONArray(ids));
}
/**
* Set of IDs from all scheduled notifications.
*
* @param command
* The callback context used when calling back into JavaScript.
*/
private void getScheduledIds (CallbackContext command) {
List<Integer> ids = getNotificationMgr().getIdsByType(
Notification.Type.SCHEDULED);
command.success(new JSONArray(ids));
}
/**
* Set of IDs from all triggered notifications.
*
* @param command
* The callback context used when calling back into JavaScript.
*/
private void getTriggeredIds (CallbackContext command) {
List<Integer> ids = getNotificationMgr().getIdsByType(
Notification.Type.TRIGGERED);
command.success(new JSONArray(ids));
}
/**
* Options from local notification.
*
* @param ids
* Set of local notification IDs
* @param command
* The callback context used when calling back into JavaScript.
*/
private void getSingle (JSONArray ids, CallbackContext command) {
getOptions(ids.optString(0), Notification.Type.ALL, command);
}
/**
* Options from scheduled notification.
*
* @param ids
* Set of local notification IDs
* @param command
* The callback context used when calling back into JavaScript.
*/
private void getSingleScheduled (JSONArray ids, CallbackContext command) {
getOptions(ids.optString(0), Notification.Type.SCHEDULED, command);
}
/**
* Options from triggered notification.
*
* @param ids
* Set of local notification IDs
* @param command
* The callback context used when calling back into JavaScript.
*/
private void getSingleTriggered (JSONArray ids, CallbackContext command) {
getOptions(ids.optString(0), Notification.Type.TRIGGERED, command);
}
/**
* Set of options from local notification.
*
* @param ids
* Set of local notification IDs
* @param command
* The callback context used when calling back into JavaScript.
*/
private void getAll (JSONArray ids, CallbackContext command) {
getOptions(ids, Notification.Type.ALL, command);
}
/**
* Set of options from scheduled notifications.
*
* @param ids
* Set of local notification IDs
* @param command
* The callback context used when calling back into JavaScript.
*/
private void getScheduled (JSONArray ids, CallbackContext command) {
getOptions(ids, Notification.Type.SCHEDULED, command);
}
/**
* Set of options from triggered notifications.
*
* @param ids
* Set of local notification IDs
* @param command
* The callback context used when calling back into JavaScript.
*/
private void getTriggered (JSONArray ids, CallbackContext command) {
getOptions(ids, Notification.Type.TRIGGERED, command);
}
/**
* Options from local notification.
*
* @param id
* Set of local notification IDs
* @param type
* The local notification life cycle type
* @param command
* The callback context used when calling back into JavaScript.
*/
private void getOptions (String id, Notification.Type type,
CallbackContext command) {
JSONArray ids = new JSONArray().put(id);
PluginResult result;
List<JSONObject> options =
getNotificationMgr().getOptionsBy(type, toList(ids));
if (options.isEmpty()) {
// Status.NO_RESULT led to no callback invocation :(
// Status.OK led to no NPE and crash
result = new PluginResult(PluginResult.Status.NO_RESULT);
} else {
result = new PluginResult(PluginResult.Status.OK, options.get(0));
}
command.sendPluginResult(result);
}
/**
* Set of options from local notifications.
*
* @param ids
* Set of local notification IDs
* @param type
* The local notification life cycle type
* @param command
* The callback context used when calling back into JavaScript.
*/
private void getOptions (JSONArray ids, Notification.Type type,
CallbackContext command) {
List<JSONObject> options;
if (ids.length() == 0) {
options = getNotificationMgr().getOptionsByType(type);
} else {
options = getNotificationMgr().getOptionsBy(type, toList(ids));
}
command.success(new JSONArray(options));
}
/**
* Call all pending callbacks after the deviceready event has been fired.
*/
private static synchronized void deviceready () {
isInBackground = false;
deviceready = true;
for (String js : eventQueue) {
sendJavascript(js);
}
eventQueue.clear();
}
/**
* Fire given event on JS side. Does inform all event listeners.
*
* @param event
* The event name
*/
private void fireEvent (String event) {
fireEvent(event, null);
}
/**
* Fire given event on JS side. Does inform all event listeners.
*
* @param event
* The event name
* @param notification
* Optional local notification to pass the id and properties.
*/
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.core.fireEvent(" +
"\"" + event + "\"," + params + ")";
sendJavascript(js);
}
/**
* Use this instead of deprecated sendJavascript
*
* @param js
* JS code snippet as string
*/
private static synchronized void sendJavascript(final String js) {
if (!deviceready) {
eventQueue.add(js);
return;
}
Runnable jsLoader = new Runnable() {
public void run() {
webView.loadUrl("javascript:" + js);
}
};
try {
Method post = webView.getClass().getMethod("post",Runnable.class);
post.invoke(webView,jsLoader);
} catch(Exception e) {
((Activity)(webView.getContext())).runOnUiThread(jsLoader);
}
}
/**
* Convert JSON array of integers to List.
*
* @param ary
* Array of integers
*/
private List<Integer> toList (JSONArray ary) {
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < ary.length(); i++) {
list.add(ary.optInt(i));
}
return list;
}
/**
* Current application state.
*
* @return
* "background" or "foreground"
*/
static String getApplicationState () {
return isInBackground ? "background" : "foreground";
}
/**
* Notification manager instance.
*/
private Manager getNotificationMgr() {
return Manager.getInstance(cordova.getActivity());
}
}