Commit 6618cb2d by Sebastián Katzer

Update example

parent 4b537731
......@@ -11,12 +11,13 @@
</activity>
<service android:name="de.appplant.cordova.plugin.background.ForegroundService" />
<receiver android:name="de.appplant.cordova.plugin.localnotification.Receiver" />
<receiver android:name="de.appplant.cordova.plugin.localnotification.DeleteIntentReceiver" />
<receiver android:name="de.appplant.cordova.plugin.localnotification.Restore">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<activity android:launchMode="singleInstance" android:name="de.appplant.cordova.plugin.localnotification.ReceiverActivity" />
<activity android:launchMode="singleInstance" android:name="de.appplant.cordova.plugin.localnotification.ReceiverActivity" android:theme="@android:style/Theme.NoDisplay" />
</application>
<uses-sdk android:minSdkVersion="10" android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
......
......@@ -92,9 +92,11 @@
<a href="#" class="button" onclick="hasPermission()">Has permission?<br/><span class="hint">notification.local.hasPermission()</span></a>
<a href="#" class="button" onclick="registerPermission()">Register permission<br/><span class="hint">notification.local.registerPermission()</span></a>
<a href="#" class="button" onclick="schedule()">Schedule now<br/><span class="hint">notification.local.add()</span></a>
<a href="#" class="button" onclick="scheduleDelayed()">Schedule in 5 sec<br/><span class="hint">notification.local.add()</span></a>
<a href="#" class="button" onclick="scheduleMultiple()">Schedule multiple<br/><span class="hint">notification.local.add()</span></a>
<a href="#" class="button" onclick="scheduleDelayed()">Schedule in 5 sec<br/><span class="hint">notification.local.add([])</span></a>
<a href="#" class="button" onclick="scheduleMinutely()">Schedule every min<br/><span class="hint">notification.local.add()</span></a>
<a href="#" class="button" onclick="cancel()">Cancel<br/><span class="hint">notification.local.cancel()</span></a>
<a href="#" class="button" onclick="cancelMultiple()">Cancel multiple<br/><span class="hint">notification.local.cancel([])</span></a>
<a href="#" class="button" onclick="cancelAll()">Cancel all<br/><span class="hint">notification.local.cancelAll()</span></a>
<a href="#" class="button" onclick="getScheduledIds()">Scheduled IDs<br/><span class="hint">notification.local.getScheduledIds()</span></a>
<a href="#" class="button" onclick="isScheduled()">Is scheduled?<br/><span class="hint">notification.local.isScheduled()</span></a>
......@@ -107,7 +109,7 @@
<script type="text/javascript" src="js/index.js"></script>
<script type="text/javascript">
var counter = 0, id = 12;
var counter = 1, id = 12;
var callback = function () {
alert('finished or canceled');
......@@ -129,10 +131,26 @@
plugin.notification.local.add({
id: id,
message: 'Test Message ' + (++counter),
json: { test: 123 }
json: { test: id }
});
};
scheduleMultiple = function () {
plugin.notification.local.add([{
id: id,
message: 'Test Message ' + (++counter),
json: { test: id }
},{
id: id+1,
message: 'Test Message ' + (++counter),
json: { test: id+1 }
},{
id: id+2,
message: 'Test Message ' + (++counter),
json: { test: id+2 }
}]);
};
scheduleDelayed = function () {
var now = new Date().getTime(),
_5_sec_from_now = new Date(now + 5*1000);
......@@ -160,6 +178,11 @@
plugin.notification.local.cancel(id,callback);
};
cancelMultiple = function () {
counter = 0;
plugin.notification.local.cancel([id, id+1],callback);
};
cancelAll = function () {
counter = 0;
plugin.notification.local.cancelAll(callback);
......@@ -198,7 +221,7 @@
<!-- callbacks -->
<script type="text/javascript">
document.addEventListener('deviceready', function () {
document.addEventListener('sdeviceready', function () {
plugin.notification.local.onadd = function (id, state, json) {
alert('on add\n' + Array.apply(null, arguments).join("\n"));
};
......
/*
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.JSONException;
import org.json.JSONObject;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class DeleteIntentReceiver extends BroadcastReceiver {
public static final String OPTIONS = "LOCAL_NOTIFICATION_OPTIONS";
@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)){
LocalNotification.persist(options.getId(), setInitDate(args));
}
else if((new Date(options.getDate()).before(now))){
LocalNotification.unpersist(options.getId());
}
fireClearEvent(options);
}
private JSONObject setInitDate(JSONObject arguments){
long initialDate = arguments.optLong("date", 0) * 1000;
try {
arguments.put("initialDate", initialDate);
} catch (JSONException e) {
e.printStackTrace();
}
return arguments;
}
/**
* Fires onclear event.
*/
private void fireClearEvent (Options options) {
LocalNotification.fireEvent("clear", options.getId(), options.getJSON());
}
}
......@@ -35,6 +35,7 @@ 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.app.PendingIntent;
......@@ -43,6 +44,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Build;
import android.widget.Toast;
/**
* This plugin utilizes the Android AlarmManager in combination with StatusBar
......@@ -59,6 +61,7 @@ public class LocalNotification extends CordovaPlugin {
protected static Context context = null;
protected static Boolean isInBackground = true;
private static ArrayList<String> eventQueue = new ArrayList<String>();
static Activity activity;
@Override
public void initialize (CordovaInterface cordova, CordovaWebView webView) {
......@@ -66,6 +69,7 @@ public class LocalNotification extends CordovaPlugin {
LocalNotification.webView = super.webView;
LocalNotification.context = super.cordova.getActivity().getApplicationContext();
LocalNotification.activity = super.cordova.getActivity();
}
@Override
......@@ -73,14 +77,70 @@ public class LocalNotification extends CordovaPlugin {
if (action.equalsIgnoreCase("add")) {
cordova.getThreadPool().execute( new Runnable() {
public void run() {
JSONObject arguments = setInitDate(args).optJSONObject(0);
JSONObject arguments = setInitDate(args.optJSONObject(0));
Options options = new Options(context).parse(arguments);
add(options, true);
command.success();
}
});
}
if (action.equalsIgnoreCase("addMultiple")) {
cordova.getThreadPool().execute( new Runnable() {
public void run() {
JSONArray notifications = args.optJSONArray(0);
for (int i =0; i<notifications.length();i++){
JSONObject arguments = setInitDate(notifications.optJSONObject(i));
Options options = new Options(context).parse(arguments);
add(options, true);
}
command.success();
}
});
}
if (action.equalsIgnoreCase("update")) {
cordova.getThreadPool().execute( new Runnable() {
public void run() {
JSONObject updates = args.optJSONObject(0);
update(updates);
command.success();
}
});
}
if (action.equalsIgnoreCase("clear")) {
cordova.getThreadPool().execute( new Runnable() {
public void run() {
String id = args.optString(0);
clear(id);
command.success();
}
});
}
if (action.equalsIgnoreCase("clearMultiple")) {
cordova.getThreadPool().execute( new Runnable() {
public void run() {
JSONArray ids = args.optJSONArray(0);
for (int i =0; i<ids.length();i++){
clear(ids.optString(i));
}
command.success();
}
});
}
if (action.equalsIgnoreCase("clearAll")) {
cordova.getThreadPool().execute( new Runnable() {
public void run() {
clearAll();
command.success();
}
});
}
if (action.equalsIgnoreCase("cancel")) {
cordova.getThreadPool().execute( new Runnable() {
......@@ -93,6 +153,18 @@ public class LocalNotification extends CordovaPlugin {
}
});
}
if (action.equalsIgnoreCase("cancelMultiple")) {
cordova.getThreadPool().execute( new Runnable() {
public void run() {
JSONArray ids = args.optJSONArray(0);
for (int i =0; i<ids.length();i++){
cancel(ids.optString(i));
}
command.success();
}
});
}
if (action.equalsIgnoreCase("cancelAll")) {
cordova.getThreadPool().execute( new Runnable() {
......@@ -169,6 +241,7 @@ public class LocalNotification extends CordovaPlugin {
persist(options.getId(), options.getJSONObject());
//Intent is called when the Notification gets fired
Intent intent = new Intent(context, Receiver.class)
.setAction("" + options.getId())
.putExtra(Receiver.OPTIONS, options.getJSONObject().toString());
......@@ -183,6 +256,91 @@ public class LocalNotification extends CordovaPlugin {
am.set(AlarmManager.RTC_WAKEUP, triggerTime, pi);
}
/**
* Update an existing notification
*
* @param updates JSONObject with update-content
*/
public static 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) {
e.printStackTrace();
return;
}
arguments = updateArguments(arguments, updates);
// cancel existing alarm
Intent intent = new Intent(context, Receiver.class)
.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);
add(options,false);
}
/**
* 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 static 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) {
e.printStackTrace();
return;
}
fireEvent("clear", notificationId, "");
}
/**
* Clear all notifications without canceling repeating alarms
*/
public static 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();
}
/**
* Cancel a specific notification that was previously registered.
*
......@@ -490,15 +648,50 @@ public class LocalNotification extends CordovaPlugin {
* @param args The given JSONArray
* @return A new JSONArray with the parameter "initialDate" set.
*/
private static JSONArray setInitDate(JSONArray args){
long initialDate = args.optJSONObject(0).optLong("date", 0) * 1000;
private static JSONObject setInitDate(JSONObject arguments){
long initialDate = arguments.optLong("date", 0) * 1000;
try {
args.optJSONObject(0).put("initialDate", initialDate);
arguments.put("initialDate", initialDate);
} catch (JSONException e) {
e.printStackTrace();
}
return args;
return arguments;
}
private static 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"));
}
} catch (JSONException jse){
jse.printStackTrace();
}
return arguments;
}
public static void showNotification(String title,String notification){
int duration = Toast.LENGTH_LONG;
if(title.equals("")){
title = "Notification";
}
String text = title + " \n " + notification;
Toast notificationToast = Toast.makeText(context, text, duration);
notificationToast.show();
}
}
\ No newline at end of file
}
......@@ -21,8 +21,11 @@
package de.appplant.cordova.plugin.localnotification;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Calendar;
......@@ -42,12 +45,13 @@ import android.media.RingtoneManager;
import android.net.Uri;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
import android.util.Log;
/**
* Class that helps to store the options that can be specified per alarm.
*/
public class Options {
static protected final String STORAGE_FOLDER = "/localnotification";
private JSONObject options = new JSONObject();
private String packageName = null;
private long interval = 0;
......@@ -101,7 +105,7 @@ public class Options {
return this;
}
/**
* Returns options as JSON object
*/
......@@ -160,7 +164,7 @@ public class Options {
return RingtoneManager.getDefaultUri(soundId);
} catch (Exception e) {
return Uri.parse(sound);
return getURIfromPath(sound);
}
}
......@@ -176,7 +180,7 @@ public class Options {
if (icon.startsWith("http")) {
bmp = getIconFromURL(icon);
} else if (icon.startsWith("file://")) {
} else if (icon.startsWith("file://") || (icon.startsWith("res"))) {
bmp = getIconFromURI(icon);
}
......@@ -352,13 +356,11 @@ public class Options {
* The corresponding bitmap
*/
private Bitmap getIconFromURI (String src) {
AssetManager assets = LocalNotification.context.getAssets();
Bitmap bmp = null;
Uri uri = getURIfromPath(src);
try {
String path = src.replace("file:/", "www");
InputStream input = assets.open(path);
try {
InputStream input = LocalNotification.activity.getContentResolver().openInputStream(uri);
bmp = BitmapFactory.decodeStream(input);
} catch (IOException e) {
e.printStackTrace();
......@@ -366,4 +368,166 @@ public class Options {
return bmp;
}
}
\ No newline at end of file
/**
* The URI for a path.
*
* @param path The given path
*
* @return The URI pointing to the given path
*/
private 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);
}
return Uri.parse(path);
}
/**
* 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("LocalNotifocation", "File not found: " + file.getAbsolutePath());
}
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 = LocalNotification.activity.getExternalCacheDir();
if (dir == null) {
Log.e("LocalNotifocation", "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 = LocalNotification.activity.getAssets();
FileOutputStream outStream = new FileOutputStream(file);
InputStream inputStream = assets.open(resPath);
copyFile(inputStream, outStream);
outStream.flush();
outStream.close();
} catch (Exception e) {
Log.e("LocalNotifocation", "File not found: assets/" + resPath);
e.printStackTrace();
}
return Uri.fromFile(file);
}
/**
* 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 = LocalNotification.activity.getExternalCacheDir();
if (dir == null) {
Log.e("LocalNotifocation", "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("LocalNotifocation", "File not found: " + resPath);
}
new File(storage).mkdir();
try {
Resources res = LocalNotification.activity.getResources();
FileOutputStream outStream = new FileOutputStream(file);
InputStream inputStream = res.openRawResource(resId);
copyFile(inputStream, outStream);
outStream.flush();
outStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return Uri.fromFile(file);
}
/**
* 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 = LocalNotification.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 LocalNotification.activity.getPackageName();
}
/**
* Shows the behavior of notifications when the application is in foreground
*
*
*/
public boolean getForegroundMode(){
return options.optBoolean("foregroundMode",false);
}
}
......@@ -79,10 +79,17 @@ public class Receiver extends BroadcastReceiver {
} else {
LocalNotification.add(options.moveDate(), false);
}
if (!LocalNotification.isInBackground && options.getForegroundMode()){
if (options.getInterval() == 0) {
LocalNotification.unpersist(options.getId());
}
LocalNotification.showNotification(options.getTitle(), options.getMessage());
fireTriggerEvent();
} else {
Builder notification = buildNotification();
Builder notification = buildNotification();
showNotification(notification);
showNotification(notification);
}
}
/*
......@@ -116,7 +123,13 @@ public class Receiver extends BroadcastReceiver {
@SuppressLint("NewApi")
private Builder buildNotification () {
Uri sound = options.getSound();
//DeleteIntent is called when the user clears a notification manually
Intent deleteIntent = new Intent(context, DeleteIntentReceiver.class)
.setAction("" + options.getId())
.putExtra(Receiver.OPTIONS, options.getJSONObject().toString());
PendingIntent dpi = PendingIntent.getBroadcast(context, 0, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT);
Builder notification = new NotificationCompat.Builder(context)
.setDefaults(0) // Do not inherit any defaults
.setContentTitle(options.getTitle())
......@@ -127,7 +140,8 @@ public class Receiver extends BroadcastReceiver {
.setLargeIcon(options.getIcon())
.setAutoCancel(options.getAutoCancel())
.setOngoing(options.getOngoing())
.setLights(options.getColor(), 500, 500);
.setLights(options.getColor(), 500, 500)
.setDeleteIntent(dpi);
if (sound != null) {
notification.setSound(sound);
......
......@@ -2,22 +2,4 @@
<Bucket
type = "1"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "NotificationExample/Plugins/de.appplant.cordova.plugin.local-notification/UILocalNotification+APPLocalNotification.m"
timestampString = "441842238.113728"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "61"
endingLineNumber = "61"
landmarkName = "-__init"
landmarkType = "5">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>
......@@ -62,47 +62,50 @@
}
/**
* Schedule a new local notification.
* Schedule a set of notifications.
*
* @param properties
* A dict of properties
* A dict of properties for each notification
*/
- (void) add:(CDVInvokedUrlCommand*)command
{
[self.commandDelegate runInBackground:^{
NSDictionary* options = [[command arguments]
objectAtIndex:0];
for (NSDictionary* options in command.arguments) {
UILocalNotification* notification;
UILocalNotification* notification;
notification = [[UILocalNotification alloc]
initWithOptions:options];
notification = [[UILocalNotification alloc]
initWithOptions:options];
[self scheduleLocalNotification:notification];
[self fireEvent:@"add" localNotification:notification];
}
[self scheduleLocalNotification:notification];
[self fireEvent:@"add" localNotification:notification];
[self execCallback:command];
}];
}
/**
* Cancels a given local notification.
* Cancel a set of notifications.
*
* @param id
* The ID of the local notification
* @param ids
* The IDs of the notifications
*/
- (void) cancel:(CDVInvokedUrlCommand*)command
{
[self.commandDelegate runInBackground:^{
NSString* id = [[command arguments]
objectAtIndex:0];
for (NSString* id in command.arguments) {
UILocalNotification* notification;
UILocalNotification* notification;
notification = [[UIApplication sharedApplication]
scheduledLocalNotificationWithId:id];
notification = [[UIApplication sharedApplication]
scheduledLocalNotificationWithId:id];
if (!notification)
continue;
[self cancelLocalNotification:notification];
[self fireEvent:@"cancel" localNotification:notification];
}
[self cancelLocalNotification:notification];
[self fireEvent:@"cancel" localNotification:notification];
[self execCallback:command];
}];
}
......@@ -242,9 +245,9 @@
- (void) registerPermission:(CDVInvokedUrlCommand*)command
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
_command = command;
[self.commandDelegate runInBackground:^{
[[UIApplication sharedApplication]
registerPermissionToScheduleLocalNotifications];
......@@ -282,10 +285,10 @@
{
if (!notification)
return;
[[UIApplication sharedApplication]
cancelLocalNotification:notification];
[UIApplication sharedApplication]
.applicationIconBadgeNumber -= 1;
}
......@@ -328,13 +331,9 @@
[self cancelLocalNotification:forerunner];
}
/**
* Cancels all local notification with are older then
* a specific amount of seconds
*
* @param {float} seconds
* The time interval in seconds
*/
- (void) cancelAllNotificationsWhichAreOlderThen:(float)seconds
{
......@@ -419,9 +418,7 @@
#pragma mark Life Cycle
/**
* Registers obervers for the following events after plugin was initialized.
* didReceiveLocalNotification:
* didFinishLaunchingWithOptions:
* Registers obervers after plugin was initialized.
*/
- (void) pluginInitialize
{
......@@ -439,7 +436,7 @@
selector:@selector(didFinishLaunchingWithOptions:)
name:UIApplicationDidFinishLaunchingNotification
object:nil];
[center addObserver:self
selector:@selector(didRegisterUserNotificationSettings:)
name:UIApplicationRegisterUserNotificationSettings
......
......@@ -102,11 +102,11 @@
- (NSInteger) badgeNumber
{
NSInteger number = [[dict objectForKey:@"badge"] intValue];
if (number == -1) {
number = 1 + [UIApplication sharedApplication].applicationIconBadgeNumber;
}
return number;
}
......
......@@ -37,7 +37,7 @@ NSString* const UIApplicationRegisterUserNotificationSettings = @"UIApplicationR
{
NSNotificationCenter* center = [NSNotificationCenter
defaultCenter];
// re-post (broadcast)
[center postNotificationName:UIApplicationRegisterUserNotificationSettings
object:settings];
......
......@@ -103,16 +103,16 @@ static char optionsKey;
switch (self.repeatInterval) {
case NSCalendarUnitMinute:
return 60;
case NSCalendarUnitHour:
return 60000;
case NSCalendarUnitDay:
case NSCalendarUnitWeekOfYear:
case NSCalendarUnitMonth:
case NSCalendarUnitYear:
return 86400;
default:
return 1;
}
......@@ -125,13 +125,13 @@ static char optionsKey;
{
NSDate* now = [NSDate date];
NSDate* fireDate = self.options.fireDate;
int timespan = [now timeIntervalSinceDate:fireDate];
if (self.repeatInterval != NSCalendarUnitEra) {
timespan = timespan % [self repeatIntervalInSeconds];
}
return timespan;
}
......
......@@ -92,9 +92,11 @@
<a href="#" class="button" onclick="hasPermission()">Has permission?<br/><span class="hint">notification.local.hasPermission()</span></a>
<a href="#" class="button" onclick="registerPermission()">Register permission<br/><span class="hint">notification.local.registerPermission()</span></a>
<a href="#" class="button" onclick="schedule()">Schedule now<br/><span class="hint">notification.local.add()</span></a>
<a href="#" class="button" onclick="scheduleDelayed()">Schedule in 5 sec<br/><span class="hint">notification.local.add()</span></a>
<a href="#" class="button" onclick="scheduleMultiple()">Schedule multiple<br/><span class="hint">notification.local.add()</span></a>
<a href="#" class="button" onclick="scheduleDelayed()">Schedule in 5 sec<br/><span class="hint">notification.local.add([])</span></a>
<a href="#" class="button" onclick="scheduleMinutely()">Schedule every min<br/><span class="hint">notification.local.add()</span></a>
<a href="#" class="button" onclick="cancel()">Cancel<br/><span class="hint">notification.local.cancel()</span></a>
<a href="#" class="button" onclick="cancelMultiple()">Cancel multiple<br/><span class="hint">notification.local.cancel([])</span></a>
<a href="#" class="button" onclick="cancelAll()">Cancel all<br/><span class="hint">notification.local.cancelAll()</span></a>
<a href="#" class="button" onclick="getScheduledIds()">Scheduled IDs<br/><span class="hint">notification.local.getScheduledIds()</span></a>
<a href="#" class="button" onclick="isScheduled()">Is scheduled?<br/><span class="hint">notification.local.isScheduled()</span></a>
......@@ -107,7 +109,7 @@
<script type="text/javascript" src="js/index.js"></script>
<script type="text/javascript">
var counter = 0, id = 12;
var counter = 1, id = 12;
var callback = function () {
alert('finished or canceled');
......@@ -129,10 +131,26 @@
plugin.notification.local.add({
id: id,
message: 'Test Message ' + (++counter),
json: { test: 123 }
json: { test: id }
});
};
scheduleMultiple = function () {
plugin.notification.local.add([{
id: id,
message: 'Test Message ' + (++counter),
json: { test: id }
},{
id: id+1,
message: 'Test Message ' + (++counter),
json: { test: id+1 }
},{
id: id+2,
message: 'Test Message ' + (++counter),
json: { test: id+2 }
}]);
};
scheduleDelayed = function () {
var now = new Date().getTime(),
_5_sec_from_now = new Date(now + 5*1000);
......@@ -160,6 +178,11 @@
plugin.notification.local.cancel(id,callback);
};
cancelMultiple = function () {
counter = 0;
plugin.notification.local.cancel([id, id+1],callback);
};
cancelAll = function () {
counter = 0;
plugin.notification.local.cancelAll(callback);
......@@ -198,7 +221,7 @@
<!-- callbacks -->
<script type="text/javascript">
document.addEventListener('deviceready', function () {
document.addEventListener('sdeviceready', function () {
plugin.notification.local.onadd = function (id, state, json) {
alert('on add\n' + Array.apply(null, arguments).join("\n"));
};
......
......@@ -39,11 +39,15 @@
"count": 1
},
{
"xml": "<receiver android:name=\"de.appplant.cordova.plugin.localnotification.DeleteIntentReceiver\" />",
"count": 1
},
{
"xml": "<receiver android:name=\"de.appplant.cordova.plugin.localnotification.Restore\"><intent-filter><action android:name=\"android.intent.action.BOOT_COMPLETED\" /></intent-filter></receiver>",
"count": 1
},
{
"xml": "<activity android:launchMode=\"singleInstance\" android:name=\"de.appplant.cordova.plugin.localnotification.ReceiverActivity\" />",
"xml": "<activity android:launchMode=\"singleInstance\" android:name=\"de.appplant.cordova.plugin.localnotification.ReceiverActivity\" android:theme=\"@android:style/Theme.NoDisplay\" />",
"count": 1
}
],
......
......@@ -9,6 +9,14 @@
- [enhancement:] Scope parameter for `isScheduled` and `getScheduledIds`
- [enhancement:] Callbacks for `add`, `cancel` & `cancelAll`
- [enhancement:] `image:` accepts remote URLs and local URIs (Android)
- [enhancement:] Schedule multiple notifications at once
- [enhancement:] Cancel multiple notifications at once
- [enhancement:] Clear multiple notifications at once (Android)
- [enhancement:] `clear` & `clearAll` methods (Android)
- [enhancement:] `onclear` event (Android)
- [enhancement:] Modal dialogs when in foreground (Android)
- [enhancement:] Ability to change repeating notifications (Android)
- [enhancement:] `sound:` accepts local URIs for absolute (file:///), relative (file://) and resource path (res:). (Android)
#### Version 0.7.4 (22.03.2014)
- [bugfix:] Platform specific properties were ignored.
......
......@@ -65,6 +65,12 @@
* sound and it vibrates the phone.
-->
<receiver android:name="de.appplant.cordova.plugin.localnotification.Receiver" />
<!--
* 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" />
<!--
* This class is triggered upon reboot of the device. It needs to re-register
......@@ -76,13 +82,14 @@
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<!--
* 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" />
<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">
......@@ -96,6 +103,7 @@
<source-file src="src/android/Options.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" />
</platform>
<!-- wp8 -->
......
/*
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.JSONException;
import org.json.JSONObject;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class DeleteIntentReceiver extends BroadcastReceiver {
public static final String OPTIONS = "LOCAL_NOTIFICATION_OPTIONS";
@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)){
LocalNotification.persist(options.getId(), setInitDate(args));
}
else if((new Date(options.getDate()).before(now))){
LocalNotification.unpersist(options.getId());
}
fireClearEvent(options);
}
private JSONObject setInitDate(JSONObject arguments){
long initialDate = arguments.optLong("date", 0) * 1000;
try {
arguments.put("initialDate", initialDate);
} catch (JSONException e) {
e.printStackTrace();
}
return arguments;
}
/**
* Fires onclear event.
*/
private void fireClearEvent (Options options) {
LocalNotification.fireEvent("clear", options.getId(), options.getJSON());
}
}
......@@ -35,6 +35,7 @@ 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.app.PendingIntent;
......@@ -43,6 +44,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Build;
import android.widget.Toast;
/**
* This plugin utilizes the Android AlarmManager in combination with StatusBar
......@@ -59,6 +61,7 @@ public class LocalNotification extends CordovaPlugin {
protected static Context context = null;
protected static Boolean isInBackground = true;
private static ArrayList<String> eventQueue = new ArrayList<String>();
static Activity activity;
@Override
public void initialize (CordovaInterface cordova, CordovaWebView webView) {
......@@ -66,6 +69,7 @@ public class LocalNotification extends CordovaPlugin {
LocalNotification.webView = super.webView;
LocalNotification.context = super.cordova.getActivity().getApplicationContext();
LocalNotification.activity = super.cordova.getActivity();
}
@Override
......@@ -73,14 +77,70 @@ public class LocalNotification extends CordovaPlugin {
if (action.equalsIgnoreCase("add")) {
cordova.getThreadPool().execute( new Runnable() {
public void run() {
JSONObject arguments = setInitDate(args).optJSONObject(0);
JSONObject arguments = setInitDate(args.optJSONObject(0));
Options options = new Options(context).parse(arguments);
add(options, true);
command.success();
}
});
}
if (action.equalsIgnoreCase("addMultiple")) {
cordova.getThreadPool().execute( new Runnable() {
public void run() {
JSONArray notifications = args.optJSONArray(0);
for (int i =0; i<notifications.length();i++){
JSONObject arguments = setInitDate(notifications.optJSONObject(i));
Options options = new Options(context).parse(arguments);
add(options, true);
}
command.success();
}
});
}
if (action.equalsIgnoreCase("update")) {
cordova.getThreadPool().execute( new Runnable() {
public void run() {
JSONObject updates = args.optJSONObject(0);
update(updates);
command.success();
}
});
}
if (action.equalsIgnoreCase("clear")) {
cordova.getThreadPool().execute( new Runnable() {
public void run() {
String id = args.optString(0);
clear(id);
command.success();
}
});
}
if (action.equalsIgnoreCase("clearMultiple")) {
cordova.getThreadPool().execute( new Runnable() {
public void run() {
JSONArray ids = args.optJSONArray(0);
for (int i =0; i<ids.length();i++){
clear(ids.optString(i));
}
command.success();
}
});
}
if (action.equalsIgnoreCase("clearAll")) {
cordova.getThreadPool().execute( new Runnable() {
public void run() {
clearAll();
command.success();
}
});
}
if (action.equalsIgnoreCase("cancel")) {
cordova.getThreadPool().execute( new Runnable() {
......@@ -93,6 +153,18 @@ public class LocalNotification extends CordovaPlugin {
}
});
}
if (action.equalsIgnoreCase("cancelMultiple")) {
cordova.getThreadPool().execute( new Runnable() {
public void run() {
JSONArray ids = args.optJSONArray(0);
for (int i =0; i<ids.length();i++){
cancel(ids.optString(i));
}
command.success();
}
});
}
if (action.equalsIgnoreCase("cancelAll")) {
cordova.getThreadPool().execute( new Runnable() {
......@@ -169,6 +241,7 @@ public class LocalNotification extends CordovaPlugin {
persist(options.getId(), options.getJSONObject());
//Intent is called when the Notification gets fired
Intent intent = new Intent(context, Receiver.class)
.setAction("" + options.getId())
.putExtra(Receiver.OPTIONS, options.getJSONObject().toString());
......@@ -183,6 +256,91 @@ public class LocalNotification extends CordovaPlugin {
am.set(AlarmManager.RTC_WAKEUP, triggerTime, pi);
}
/**
* Update an existing notification
*
* @param updates JSONObject with update-content
*/
public static 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) {
e.printStackTrace();
return;
}
arguments = updateArguments(arguments, updates);
// cancel existing alarm
Intent intent = new Intent(context, Receiver.class)
.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);
add(options,false);
}
/**
* 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 static 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) {
e.printStackTrace();
return;
}
fireEvent("clear", notificationId, "");
}
/**
* Clear all notifications without canceling repeating alarms
*/
public static 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();
}
/**
* Cancel a specific notification that was previously registered.
*
......@@ -490,15 +648,50 @@ public class LocalNotification extends CordovaPlugin {
* @param args The given JSONArray
* @return A new JSONArray with the parameter "initialDate" set.
*/
private static JSONArray setInitDate(JSONArray args){
long initialDate = args.optJSONObject(0).optLong("date", 0) * 1000;
private static JSONObject setInitDate(JSONObject arguments){
long initialDate = arguments.optLong("date", 0) * 1000;
try {
args.optJSONObject(0).put("initialDate", initialDate);
arguments.put("initialDate", initialDate);
} catch (JSONException e) {
e.printStackTrace();
}
return args;
return arguments;
}
private static 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"));
}
} catch (JSONException jse){
jse.printStackTrace();
}
return arguments;
}
public static void showNotification(String title,String notification){
int duration = Toast.LENGTH_LONG;
if(title.equals("")){
title = "Notification";
}
String text = title + " \n " + notification;
Toast notificationToast = Toast.makeText(context, text, duration);
notificationToast.show();
}
}
\ No newline at end of file
}
......@@ -21,8 +21,11 @@
package de.appplant.cordova.plugin.localnotification;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Calendar;
......@@ -42,12 +45,13 @@ import android.media.RingtoneManager;
import android.net.Uri;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
import android.util.Log;
/**
* Class that helps to store the options that can be specified per alarm.
*/
public class Options {
static protected final String STORAGE_FOLDER = "/localnotification";
private JSONObject options = new JSONObject();
private String packageName = null;
private long interval = 0;
......@@ -101,7 +105,7 @@ public class Options {
return this;
}
/**
* Returns options as JSON object
*/
......@@ -160,7 +164,7 @@ public class Options {
return RingtoneManager.getDefaultUri(soundId);
} catch (Exception e) {
return Uri.parse(sound);
return getURIfromPath(sound);
}
}
......@@ -176,7 +180,7 @@ public class Options {
if (icon.startsWith("http")) {
bmp = getIconFromURL(icon);
} else if (icon.startsWith("file://")) {
} else if (icon.startsWith("file://") || (icon.startsWith("res"))) {
bmp = getIconFromURI(icon);
}
......@@ -352,13 +356,11 @@ public class Options {
* The corresponding bitmap
*/
private Bitmap getIconFromURI (String src) {
AssetManager assets = LocalNotification.context.getAssets();
Bitmap bmp = null;
Uri uri = getURIfromPath(src);
try {
String path = src.replace("file:/", "www");
InputStream input = assets.open(path);
try {
InputStream input = LocalNotification.activity.getContentResolver().openInputStream(uri);
bmp = BitmapFactory.decodeStream(input);
} catch (IOException e) {
e.printStackTrace();
......@@ -366,4 +368,166 @@ public class Options {
return bmp;
}
}
\ No newline at end of file
/**
* The URI for a path.
*
* @param path The given path
*
* @return The URI pointing to the given path
*/
private 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);
}
return Uri.parse(path);
}
/**
* 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("LocalNotifocation", "File not found: " + file.getAbsolutePath());
}
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 = LocalNotification.activity.getExternalCacheDir();
if (dir == null) {
Log.e("LocalNotifocation", "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 = LocalNotification.activity.getAssets();
FileOutputStream outStream = new FileOutputStream(file);
InputStream inputStream = assets.open(resPath);
copyFile(inputStream, outStream);
outStream.flush();
outStream.close();
} catch (Exception e) {
Log.e("LocalNotifocation", "File not found: assets/" + resPath);
e.printStackTrace();
}
return Uri.fromFile(file);
}
/**
* 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 = LocalNotification.activity.getExternalCacheDir();
if (dir == null) {
Log.e("LocalNotifocation", "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("LocalNotifocation", "File not found: " + resPath);
}
new File(storage).mkdir();
try {
Resources res = LocalNotification.activity.getResources();
FileOutputStream outStream = new FileOutputStream(file);
InputStream inputStream = res.openRawResource(resId);
copyFile(inputStream, outStream);
outStream.flush();
outStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return Uri.fromFile(file);
}
/**
* 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 = LocalNotification.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 LocalNotification.activity.getPackageName();
}
/**
* Shows the behavior of notifications when the application is in foreground
*
*
*/
public boolean getForegroundMode(){
return options.optBoolean("foregroundMode",false);
}
}
......@@ -79,10 +79,17 @@ public class Receiver extends BroadcastReceiver {
} else {
LocalNotification.add(options.moveDate(), false);
}
if (!LocalNotification.isInBackground && options.getForegroundMode()){
if (options.getInterval() == 0) {
LocalNotification.unpersist(options.getId());
}
LocalNotification.showNotification(options.getTitle(), options.getMessage());
fireTriggerEvent();
} else {
Builder notification = buildNotification();
Builder notification = buildNotification();
showNotification(notification);
showNotification(notification);
}
}
/*
......@@ -116,7 +123,13 @@ public class Receiver extends BroadcastReceiver {
@SuppressLint("NewApi")
private Builder buildNotification () {
Uri sound = options.getSound();
//DeleteIntent is called when the user clears a notification manually
Intent deleteIntent = new Intent(context, DeleteIntentReceiver.class)
.setAction("" + options.getId())
.putExtra(Receiver.OPTIONS, options.getJSONObject().toString());
PendingIntent dpi = PendingIntent.getBroadcast(context, 0, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT);
Builder notification = new NotificationCompat.Builder(context)
.setDefaults(0) // Do not inherit any defaults
.setContentTitle(options.getTitle())
......@@ -127,7 +140,8 @@ public class Receiver extends BroadcastReceiver {
.setLargeIcon(options.getIcon())
.setAutoCancel(options.getAutoCancel())
.setOngoing(options.getOngoing())
.setLights(options.getColor(), 500, 500);
.setLights(options.getColor(), 500, 500)
.setDeleteIntent(dpi);
if (sound != null) {
notification.setSound(sound);
......
......@@ -35,6 +35,8 @@
@property (readwrite, assign) BOOL deviceready;
// Event queue
@property (readonly, nonatomic, retain) NSMutableArray* eventQueue;
// Needed when calling `registerPermission`
@property (nonatomic, retain) CDVInvokedUrlCommand* command;
@end
......@@ -60,47 +62,50 @@
}
/**
* Schedule a new local notification.
* Schedule a set of notifications.
*
* @param properties
* A dict of properties
* A dict of properties for each notification
*/
- (void) add:(CDVInvokedUrlCommand*)command
{
[self.commandDelegate runInBackground:^{
NSDictionary* options = [[command arguments]
objectAtIndex:0];
for (NSDictionary* options in command.arguments) {
UILocalNotification* notification;
UILocalNotification* notification;
notification = [[UILocalNotification alloc]
initWithOptions:options];
notification = [[UILocalNotification alloc]
initWithOptions:options];
[self scheduleLocalNotification:notification];
[self fireEvent:@"add" localNotification:notification];
}
[self scheduleLocalNotification:notification];
[self fireEvent:@"add" localNotification:notification];
[self execCallback:command];
}];
}
/**
* Cancels a given local notification.
* Cancel a set of notifications.
*
* @param id
* The ID of the local notification
* @param ids
* The IDs of the notifications
*/
- (void) cancel:(CDVInvokedUrlCommand*)command
{
[self.commandDelegate runInBackground:^{
NSString* id = [[command arguments]
objectAtIndex:0];
for (NSString* id in command.arguments) {
UILocalNotification* notification;
UILocalNotification* notification;
notification = [[UIApplication sharedApplication]
scheduledLocalNotificationWithId:id];
notification = [[UIApplication sharedApplication]
scheduledLocalNotificationWithId:id];
if (!notification)
continue;
[self cancelLocalNotification:notification];
[self fireEvent:@"cancel" localNotification:notification];
}
[self cancelLocalNotification:notification];
[self fireEvent:@"cancel" localNotification:notification];
[self execCallback:command];
}];
}
......@@ -217,7 +222,7 @@
* Inform if the app has the permission to show
* badges and local notifications.
*/
- (void) hasPermission:(CDVInvokedUrlCommand *)command
- (void) hasPermission:(CDVInvokedUrlCommand*)command
{
[self.commandDelegate runInBackground:^{
CDVPluginResult* result;
......@@ -237,12 +242,19 @@
/**
* Ask for permission to show badges.
*/
- (void) registerPermission:(CDVInvokedUrlCommand *)command
- (void) registerPermission:(CDVInvokedUrlCommand*)command
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
_command = command;
[self.commandDelegate runInBackground:^{
[[UIApplication sharedApplication]
registerPermissionToScheduleLocalNotifications];
}];
#else
[self hasPermission:command];
#endif
}
#pragma mark -
......@@ -276,6 +288,9 @@
[[UIApplication sharedApplication]
cancelLocalNotification:notification];
[UIApplication sharedApplication]
.applicationIconBadgeNumber -= 1;
}
/**
......@@ -316,13 +331,9 @@
[self cancelLocalNotification:forerunner];
}
/**
* Cancels all local notification with are older then
* a specific amount of seconds
*
* @param {float} seconds
* The time interval in seconds
*/
- (void) cancelAllNotificationsWhichAreOlderThen:(float)seconds
{
......@@ -351,7 +362,6 @@
*/
- (void) didReceiveLocalNotification:(NSNotification*)localNotification
{
UIApplication* app = [UIApplication sharedApplication];
UILocalNotification* notification = [localNotification object];
BOOL autoCancel = notification.options.autoCancel;
......@@ -359,7 +369,10 @@
NSString* event = (timeInterval <= 1 && deviceready) ? @"trigger" : @"click";
app.applicationIconBadgeNumber -= 1;
if ([event isEqualToString:@"click"]) {
[UIApplication sharedApplication]
.applicationIconBadgeNumber -= 1;
}
[self fireEvent:event localNotification:notification];
......@@ -389,32 +402,45 @@
}
}
/**
* Called on otification settings registration is completed.
*/
- (void) didRegisterUserNotificationSettings:(UIUserNotificationSettings*)settings
{
if (_command)
{
[self hasPermission:_command];
_command = NULL;
}
}
#pragma mark -
#pragma mark Life Cycle
/**
* Registers obervers for the following events after plugin was initialized.
* didReceiveLocalNotification:
* didFinishLaunchingWithOptions:
* Registers obervers after plugin was initialized.
*/
- (void) pluginInitialize
{
NSNotificationCenter* notificationCenter;
notificationCenter = [NSNotificationCenter
defaultCenter];
NSNotificationCenter* center = [NSNotificationCenter
defaultCenter];
eventQueue = [[NSMutableArray alloc] init];
[notificationCenter addObserver:self
selector:@selector(didReceiveLocalNotification:)
name:CDVLocalNotification
object:nil];
[center addObserver:self
selector:@selector(didReceiveLocalNotification:)
name:CDVLocalNotification
object:nil];
[center addObserver:self
selector:@selector(didFinishLaunchingWithOptions:)
name:UIApplicationDidFinishLaunchingNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(didFinishLaunchingWithOptions:)
name:UIApplicationDidFinishLaunchingNotification
object:nil];
[center addObserver:self
selector:@selector(didRegisterUserNotificationSettings:)
name:UIApplicationRegisterUserNotificationSettings
object:nil];
}
/**
......
......@@ -82,7 +82,7 @@
- (BOOL) autoCancel
{
if (IsAtLeastiOSVersion(@"8.0")){
return YES;
return self.repeatInterval == NSCalendarUnitEra;
} else {
return [[dict objectForKey:@"autoCancel"] boolValue];
}
......@@ -101,7 +101,13 @@
*/
- (NSInteger) badgeNumber
{
return [[dict objectForKey:@"badge"] intValue];
NSInteger number = [[dict objectForKey:@"badge"] intValue];
if (number == -1) {
number = 1 + [UIApplication sharedApplication].applicationIconBadgeNumber;
}
return number;
}
#pragma mark -
......
......@@ -23,6 +23,8 @@
#import <Availability.h>
extern NSString* const UIApplicationRegisterUserNotificationSettings;
@interface AppDelegate (APPLocalNotification)
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
......
......@@ -23,13 +23,9 @@
#import <Availability.h>
@implementation AppDelegate (APPLocalNotification)
NSString* const UIApplicationRegisterUserNotificationSettings = @"UIApplicationRegisterUserNotificationSettings";
+ (void)load {
Methods original = class_getInstanceMethod(self, @selector(applicationDidFinishLaunching:));
Method custom = class_getInstanceMethod(self, @selector(customApplicationDidFinishLaunching:));
method_exchangeImplementations(original, custom);
}
@implementation AppDelegate (APPLocalNotification)
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
/**
......@@ -39,7 +35,12 @@
- (void) application:(UIApplication*)application
didRegisterUserNotificationSettings:(UIUserNotificationSettings*)settings
{
[[NSURLCache sharedURLCache] removeAllCachedResponses];
NSNotificationCenter* center = [NSNotificationCenter
defaultCenter];
// re-post (broadcast)
[center postNotificationName:UIApplicationRegisterUserNotificationSettings
object:settings];
}
#endif
......
......@@ -28,7 +28,7 @@
// The options provided by the plug-in
- (APPLocalNotificationOptions*) options;
// Timeinterval since fire date
- (NSTimeInterval) timeIntervalSinceFireDate;
- (double) timeIntervalSinceFireDate;
// If the fire date was in the past
- (BOOL) wasInThePast;
// If the notification was already triggered
......
......@@ -96,14 +96,43 @@ static char optionsKey;
}
/**
* The repeating interval in seconds.
*/
- (int) repeatIntervalInSeconds
{
switch (self.repeatInterval) {
case NSCalendarUnitMinute:
return 60;
case NSCalendarUnitHour:
return 60000;
case NSCalendarUnitDay:
case NSCalendarUnitWeekOfYear:
case NSCalendarUnitMonth:
case NSCalendarUnitYear:
return 86400;
default:
return 1;
}
}
/**
* Timeinterval since fire date.
*/
- (NSTimeInterval) timeIntervalSinceFireDate
- (double) timeIntervalSinceFireDate
{
NSDate* now = [NSDate date];
NSDate* fireDate = self.options.fireDate;
return [now timeIntervalSinceDate:fireDate];
int timespan = [now timeIntervalSinceDate:fireDate];
if (self.repeatInterval != NSCalendarUnitEra) {
timespan = timespan % [self repeatIntervalInSeconds];
}
return timespan;
}
/**
......
......@@ -92,9 +92,11 @@
<a href="#" class="button" onclick="hasPermission()">Has permission?<br/><span class="hint">notification.local.hasPermission()</span></a>
<a href="#" class="button" onclick="registerPermission()">Register permission<br/><span class="hint">notification.local.registerPermission()</span></a>
<a href="#" class="button" onclick="schedule()">Schedule now<br/><span class="hint">notification.local.add()</span></a>
<a href="#" class="button" onclick="scheduleDelayed()">Schedule in 5 sec<br/><span class="hint">notification.local.add()</span></a>
<a href="#" class="button" onclick="scheduleMultiple()">Schedule multiple<br/><span class="hint">notification.local.add()</span></a>
<a href="#" class="button" onclick="scheduleDelayed()">Schedule in 5 sec<br/><span class="hint">notification.local.add([])</span></a>
<a href="#" class="button" onclick="scheduleMinutely()">Schedule every min<br/><span class="hint">notification.local.add()</span></a>
<a href="#" class="button" onclick="cancel()">Cancel<br/><span class="hint">notification.local.cancel()</span></a>
<a href="#" class="button" onclick="cancelMultiple()">Cancel multiple<br/><span class="hint">notification.local.cancel([])</span></a>
<a href="#" class="button" onclick="cancelAll()">Cancel all<br/><span class="hint">notification.local.cancelAll()</span></a>
<a href="#" class="button" onclick="getScheduledIds()">Scheduled IDs<br/><span class="hint">notification.local.getScheduledIds()</span></a>
<a href="#" class="button" onclick="isScheduled()">Is scheduled?<br/><span class="hint">notification.local.isScheduled()</span></a>
......@@ -107,7 +109,7 @@
<script type="text/javascript" src="js/index.js"></script>
<script type="text/javascript">
var counter = 0, id = 12;
var counter = 1, id = 12;
var callback = function () {
alert('finished or canceled');
......@@ -129,10 +131,26 @@
plugin.notification.local.add({
id: id,
message: 'Test Message ' + (++counter),
json: { test: 123 }
json: { test: id }
});
};
scheduleMultiple = function () {
plugin.notification.local.add([{
id: id,
message: 'Test Message ' + (++counter),
json: { test: id }
},{
id: id+1,
message: 'Test Message ' + (++counter),
json: { test: id+1 }
},{
id: id+2,
message: 'Test Message ' + (++counter),
json: { test: id+2 }
}]);
};
scheduleDelayed = function () {
var now = new Date().getTime(),
_5_sec_from_now = new Date(now + 5*1000);
......@@ -160,6 +178,11 @@
plugin.notification.local.cancel(id,callback);
};
cancelMultiple = function () {
counter = 0;
plugin.notification.local.cancel([id, id+1],callback);
};
cancelAll = function () {
counter = 0;
plugin.notification.local.cancelAll(callback);
......@@ -198,7 +221,7 @@
<!-- callbacks -->
<script type="text/javascript">
document.addEventListener('deviceready', function () {
document.addEventListener('sdeviceready', function () {
plugin.notification.local.onadd = function (id, state, json) {
alert('on add\n' + Array.apply(null, arguments).join("\n"));
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment