Commit 0c3f71e1 by Sebastián Katzer

Initial start for Android support

parent fe9661a6
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
<plugin xmlns="http://www.phonegap.com/ns/plugins/1.0" <plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
id="de.appplant.cordova.plugin.local-notifications" id="de.appplant.cordova.plugin.local-notifications"
version="0.2.0"> version="0.4.0.dev">
<name>LocalNotifications</name> <name>LocalNotifications</name>
<description>A bunch of local-notification plugins for Cordova 3.x.x</description> <description>A bunch of local-notification plugins for Cordova 3.x.x</description>
<keywords>notification, localnotification, ios</keywords> <keywords>notification, local notification, alarm, ios, android</keywords>
<license>GPL v2 License</license> <license>GPL v2 License</license>
<author>Sebastián Katzer (github.com/katzer)</author> <author>Sebastián Katzer (github.com/katzer)</author>
...@@ -16,6 +16,10 @@ ...@@ -16,6 +16,10 @@
<engine name="cordova" version=">=3.0.0" /> <engine name="cordova" version=">=3.0.0" />
</engines> </engines>
<js-module src="www/local-notification.js" name="LocalNotification">
<clobbers target="plugin.notification.local" />
</js-module>
<!-- ios --> <!-- ios -->
<platform name="ios"> <platform name="ios">
...@@ -25,13 +29,26 @@ ...@@ -25,13 +29,26 @@
</feature> </feature>
</config-file> </config-file>
<js-module src="www/local-notification.js" name="LocalNotification">
<clobbers target="plugin.notification.local" />
</js-module>
<header-file src="src/ios/APPLocalNotification.h" /> <header-file src="src/ios/APPLocalNotification.h" />
<source-file src="src/ios/APPLocalNotification.m" /> <source-file src="src/ios/APPLocalNotification.m" />
</platform> </platform>
<!-- android -->
<platform name="android">
<config-file target="res/xml/config.xml" parent="/*">
<feature name="LocalNotification">
<param name="android-package" value="de.appplant.cordova.plugin.LocalNotification"/>
</feature>
</config-file>
<source-file src="src/android/AlarmReceiver.java" target-dir="src/de/appplant/cordova/plugin" />
<source-file src="src/android/AlarmHelper.java" target-dir="src/de/appplant/cordova/plugin" />
<source-file src="src/android/AlarmOptions.java" target-dir="src/de/appplant/cordova/plugin" />
<source-file src="src/android/AlarmRestoreOnBoot.java" target-dir="src/de/appplant/cordova/plugin" />
<source-file src="src/android/LocalNotification.java" target-dir="src/de/appplant/cordova/plugin" />
</platform>
</plugin> </plugin>
package de.appplant.cordova.plugin;
import java.util.Calendar;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
/**
* 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.
*
* @author dvtoever
*/
public class AlarmReceiver extends BroadcastReceiver {
public static final String TITLE = "ALARM_TITLE";
public static final String SUBTITLE = "ALARM_SUBTITLE";
public static final String TICKER_TEXT = "ALARM_TICKER";
public static final String NOTIFICATION_ID = "NOTIFICATION_ID";
/* Contains time in 24hour format 'HH:mm' e.g. '04:30' or '18:23' */
public static final String HOUR_OF_DAY = "HOUR_OF_DAY";
public static final String MINUTE = "MINUTES";
@Override
public void onReceive(Context context, Intent intent) {
Log.d("AlarmReceiver", "AlarmReceiver invoked!");
final Bundle bundle = intent.getExtras();
final Object systemService = context.getSystemService(Context.NOTIFICATION_SERVICE);
// Retrieve notification details from the intent
final String tickerText = bundle.getString(TICKER_TEXT);
final String notificationTitle = bundle.getString(TITLE);
final String notificationSubText = bundle.getString(SUBTITLE);
int notificationId = 0;
try {
notificationId = Integer.parseInt(bundle.getString(NOTIFICATION_ID));
} catch (Exception e) {
Log.d("AlarmReceiver", "Unable to process alarm with id: " + bundle.getString(NOTIFICATION_ID));
}
Calendar currentCal = Calendar.getInstance();
int alarmHour = bundle.getInt(HOUR_OF_DAY);
int alarmMin = bundle.getInt(MINUTE);
int currentHour = currentCal.get(Calendar.HOUR_OF_DAY);
int currentMin = currentCal.get(Calendar.MINUTE);
if (currentHour != alarmHour && currentMin != alarmMin) {
/*
* 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'.
*/
Log.d(LocalNotification.PLUGIN_NAME, "AlarmReceiver, ignoring alarm since it is due");
return;
}
// Construct the notification and notificationManager objects
final NotificationManager notificationMgr = (NotificationManager) systemService;
final Notification notification = new Notification(R.drawable.ic_launcher, tickerText,
System.currentTimeMillis());
final PendingIntent contentIntent = PendingIntent.getActivity(context, 0, new Intent(), 0);
notification.defaults |= Notification.DEFAULT_SOUND;
notification.vibrate = new long[] { 0, 100, 200, 300 };
notification.setLatestEventInfo(context, notificationTitle, notificationSubText, contentIntent);
/*
* If you want all reminders to stay in the notification bar, you should
* generate a random ID. If you want do replace an existing
* notification, make sure the ID below matches the ID that you store in
* the alarm intent.
*/
notificationMgr.notify(notificationId, notification);
}
}
\ No newline at end of file
package de.appplant.cordova.plugin;
import java.util.Calendar;
import java.util.Map;
import java.util.Set;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
/**
* Helper class for the LocalNotification plugin. This class is reused by the
* AlarmRestoreOnBoot.
*
* @see LocalNotification
* @see AlarmRestoreOnBoot
*
* @author dvtoever
*/
public class AlarmHelper {
private Context ctx;
public AlarmHelper(Context context) {
this.ctx = context;
}
/**
* @see LocalNotification#add(boolean, String, String, String, int,
* Calendar)
*/
public boolean addAlarm(boolean repeatDaily, String alarmTitle, String alarmSubTitle, String alarmTicker,
String notificationId, Calendar cal) {
final long triggerTime = cal.getTimeInMillis();
final Intent intent = new Intent(this.ctx, AlarmReceiver.class);
final int hour = cal.get(Calendar.HOUR_OF_DAY);
final int min = cal.get(Calendar.MINUTE);
intent.setAction("" + notificationId);
intent.putExtra(AlarmReceiver.TITLE, alarmTitle);
intent.putExtra(AlarmReceiver.SUBTITLE, alarmSubTitle);
intent.putExtra(AlarmReceiver.TICKER_TEXT, alarmTicker);
intent.putExtra(AlarmReceiver.NOTIFICATION_ID, notificationId);
intent.putExtra(AlarmReceiver.HOUR_OF_DAY, hour);
intent.putExtra(AlarmReceiver.MINUTE, min);
final PendingIntent sender = PendingIntent.getBroadcast(this.ctx, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
/* Get the AlarmManager service */
final AlarmManager am = getAlarmManager();
if (repeatDaily) {
am.setRepeating(AlarmManager.RTC_WAKEUP, triggerTime, AlarmManager.INTERVAL_DAY, sender);
} else {
am.set(AlarmManager.RTC_WAKEUP, triggerTime, sender);
}
return true;
}
/**
* @see LocalNotification#cancelNotification(int)
*/
public boolean cancelAlarm(String notificationId) {
/*
* Create an intent that looks similar, to the one that was registered
* using add. Making sure the notification id in the action is the same.
* Now we can search for such an intent using the 'getService' method
* and cancel it.
*/
final Intent intent = new Intent(this.ctx, AlarmReceiver.class);
intent.setAction("" + notificationId);
final PendingIntent pi = PendingIntent.getBroadcast(this.ctx, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
final AlarmManager am = getAlarmManager();
try {
am.cancel(pi);
} catch (Exception e) {
return false;
}
return true;
}
/**
* @see LocalNotification#cancelAllNotifications()
*/
public boolean cancelAll(SharedPreferences alarmSettings) {
final Map<String, ?> allAlarms = alarmSettings.getAll();
final Set<String> alarmIds = allAlarms.keySet();
for (String alarmId : alarmIds) {
Log.d(LocalNotification.PLUGIN_NAME, "Canceling notification with id: " + alarmId);
String alarmInt = alarmId;
cancelAlarm(alarmInt);
}
return true;
}
private AlarmManager getAlarmManager() {
final AlarmManager am = (AlarmManager) this.ctx.getSystemService(Context.ALARM_SERVICE);
return am;
}
}
\ No newline at end of file
package de.appplant.cordova.plugin;
import java.util.Calendar;
import org.json.JSONArray;
import org.json.JSONObject;
/**
* Class that helps to store the options that can be specified per alarm.
*
* @author dvtoever
*/
public class AlarmOptions {
/*
* Options that can be set when this plugin is invoked
*/
private Calendar cal = Calendar.getInstance();
private String alarmTitle = "";
private String alarmSubTitle = "";
private String alarmTicker = "";
private boolean repeatDaily = false;
private String notificationId = "";
/**
* Parse options passed from javascript part of this plugin.
*
* @param optionsArr
* JSON Array containing the list options.
*/
public void parseOptions(JSONArray optionsArr) {
final JSONObject options = optionsArr.optJSONObject(0);
if (options != null) {
// Parse string representing the date
String textDate = options.optString("date");
if (!"".equals(textDate)) {
String[] datePart = textDate.split("/");
int month = Integer.parseInt(datePart[0]);
int day = Integer.parseInt(datePart[1]);
int year = Integer.parseInt(datePart[2]);
int hour = Integer.parseInt(datePart[3]);
int min = Integer.parseInt(datePart[4]);
cal.set(year, month, day, hour, min);
}
String optString = options.optString("message");
if (!"".equals(optString)) {
String lines[] = optString.split("\\r?\\n");
alarmTitle = lines[0];
if (lines.length > 1)
alarmSubTitle = lines[1];
}
alarmTicker = options.optString("ticker");
repeatDaily = options.optBoolean("repeatDaily");
notificationId = options.optString("id");
}
}
public Calendar getCal() {
return cal;
}
public void setCal(Calendar cal) {
this.cal = cal;
}
public String getAlarmTitle() {
return alarmTitle;
}
public void setAlarmTitle(String alarmTitle) {
this.alarmTitle = alarmTitle;
}
public String getAlarmSubTitle() {
return alarmSubTitle;
}
public void setAlarmSubTitle(String alarmSubTitle) {
this.alarmSubTitle = alarmSubTitle;
}
public String getAlarmTicker() {
return alarmTicker;
}
public void setAlarmTicker(String alarmTicker) {
this.alarmTicker = alarmTicker;
}
public boolean isRepeatDaily() {
return repeatDaily;
}
public void setRepeatDaily(boolean repeatDaily) {
this.repeatDaily = repeatDaily;
}
public String getNotificationId() {
return notificationId;
}
public void setNotificationId(String notificationId) {
this.notificationId = notificationId;
}
}
\ No newline at end of file
package de.appplant.cordova.plugin;
import java.util.Calendar;
import java.util.Map;
import java.util.Set;
import org.json.JSONArray;
import org.json.JSONException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
/**
* 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.
*
* @author dvtoever
*/
public class AlarmRestoreOnBoot extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final String pluginName = LocalNotification.PLUGIN_NAME;
// Obtain alarm details form Shared Preferences
final SharedPreferences alarmSettings = context.getSharedPreferences(pluginName, Context.MODE_PRIVATE);
final Map<String, ?> allAlarms = alarmSettings.getAll();
final Set<String> alarmIds = allAlarms.keySet();
/*
* For each alarm, parse its alarm options and register is again with
* the Alarm Manager
*/
for (String alarmId : alarmIds) {
try {
final AlarmHelper alarm = new AlarmHelper(context);
final JSONArray alarmDetails = new JSONArray(alarmSettings.getString(alarmId, ""));
final AlarmOptions options = new AlarmOptions();
options.parseOptions(alarmDetails);
final boolean daily = options.isRepeatDaily();
final String title = options.getAlarmTitle();
final String subTitle = options.getAlarmSubTitle();
final String ticker = options.getAlarmTicker();
final String id = options.getNotificationId();
final Calendar cal = options.getCal();
alarm.addAlarm(daily, title, subTitle, ticker, id, cal);
} catch (JSONException e) {
Log.d(pluginName,
"AlarmRestoreOnBoot: Error while restoring alarm details after reboot: " + e.toString());
}
Log.d(pluginName, "AlarmRestoreOnBoot: Successfully restored alarms upon reboot");
}
}
}
\ No newline at end of file
package de.appplant.cordova.plugin;
import java.util.Calendar;
import org.json.JSONArray;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.util.Log;
import com.phonegap.api.Plugin;
import com.phonegap.api.PluginResult;
/**
* This plugin utilizes the Android AlarmManager in combination with StatusBar
* notifications. When a local notification is scheduled the alarm manager takes
* care of firing the event. When the event is processed, a notification is put
* in the Android status bar.
*
* @author Daniel van 't Oever
*/
public class LocalNotification extends Plugin {
public static final String PLUGIN_NAME = "LocalNotification";
/**
* Delegate object that does the actual alarm registration. Is reused by the
* AlarmRestoreOnBoot class.
*/
private AlarmHelper alarm = null;
@Override
public PluginResult execute(String action, JSONArray optionsArr, String callBackId) {
alarm = new AlarmHelper(this.ctx);
Log.d(PLUGIN_NAME, "Plugin execute called with action: " + action);
PluginResult result = null;
final AlarmOptions alarmOptions = new AlarmOptions();
alarmOptions.parseOptions(optionsArr);
/*
* Determine which action of the plugin needs to be invoked
*/
String alarmId = alarmOptions.getNotificationId();
if (action.equalsIgnoreCase("add")) {
final boolean daily = alarmOptions.isRepeatDaily();
final String title = alarmOptions.getAlarmTitle();
final String subTitle = alarmOptions.getAlarmSubTitle();
final String ticker = alarmOptions.getAlarmTicker();
persistAlarm(alarmId, optionsArr);
return this.add(daily, title, subTitle, ticker, alarmId, alarmOptions.getCal());
} else if (action.equalsIgnoreCase("cancel")) {
unpersistAlarm(alarmId);
return this.cancelNotification(alarmId);
} else if (action.equalsIgnoreCase("cancelall")) {
unpersistAlarmAll();
return this.cancelAllNotifications();
}
return result;
}
/**
* Set an alarm
*
* @param repeatDaily
* If true, the alarm interval will be set to one day.
* @param alarmTitle
* The title of the alarm as shown in the Android notification
* panel
* @param alarmSubTitle
* The subtitle of the alarm
* @param alarmId
* The unique ID of the notification
* @param cal
* A calendar object that represents the time at which the alarm
* should first be started
* @return A pluginresult.
*/
public PluginResult add(boolean repeatDaily, String alarmTitle, String alarmSubTitle, String alarmTicker,
String alarmId, Calendar cal) {
final long triggerTime = cal.getTimeInMillis();
final String recurring = repeatDaily ? "daily" : "onetime";
Log.d(PLUGIN_NAME, "Adding " + recurring + " notification: '" + alarmTitle + alarmSubTitle + "' with id: "
+ alarmId + " at timestamp: " + triggerTime);
boolean result = alarm.addAlarm(repeatDaily, alarmTitle, alarmSubTitle, alarmTicker, alarmId, cal);
if (result) {
return new PluginResult(PluginResult.Status.OK);
} else {
return new PluginResult(PluginResult.Status.ERROR);
}
}
/**
* Cancel a specific notification that was previously registered.
*
* @param notificationId
* The original ID of the notification that was used when it was
* registered using addNotification()
*/
public PluginResult cancelNotification(String notificationId) {
Log.d(PLUGIN_NAME, "cancelNotification: Canceling event with id: " + notificationId);
boolean result = alarm.cancelAlarm(notificationId);
if (result) {
return new PluginResult(PluginResult.Status.OK);
} else {
return new PluginResult(PluginResult.Status.ERROR);
}
}
/**
* Cancel all notifications that were created by this plugin.
*/
public PluginResult cancelAllNotifications() {
Log.d(PLUGIN_NAME, "cancelAllNotifications: cancelling all events for this application");
/*
* Android can only unregister a specific alarm. There is no such thing
* as cancelAll. Therefore we rely on the Shared Preferences which holds
* all our alarms to loop through these alarms and unregister them one
* by one.
*/
final SharedPreferences alarmSettings = this.ctx.getSharedPreferences(PLUGIN_NAME, Context.MODE_PRIVATE);
final boolean result = alarm.cancelAll(alarmSettings);
if (result) {
return new PluginResult(PluginResult.Status.OK);
} else {
return new PluginResult(PluginResult.Status.ERROR);
}
}
/**
* Persist the information of this alarm to the Android Shared Preferences.
* This will allow the application to restore the alarm upon device reboot.
* Also this is used by the cancelAllNotifications method.
*
* @see #cancelAllNotifications()
*
* @param optionsArr
* The assumption is that parseOptions has been called already.
*
* @return true when successfull, otherwise false
*/
private boolean persistAlarm(String alarmId, JSONArray optionsArr) {
final Editor alarmSettingsEditor = this.ctx.getSharedPreferences(PLUGIN_NAME, Context.MODE_PRIVATE).edit();
alarmSettingsEditor.putString(alarmId, optionsArr.toString());
return alarmSettingsEditor.commit();
}
/**
* Remove a specific alarm from the Android shared Preferences
*
* @param alarmId
* The Id of the notification that must be removed.
*
* @return true when successfull, otherwise false
*/
private boolean unpersistAlarm(String alarmId) {
final Editor alarmSettingsEditor = this.ctx.getSharedPreferences(PLUGIN_NAME, Context.MODE_PRIVATE).edit();
alarmSettingsEditor.remove(alarmId);
return alarmSettingsEditor.commit();
}
/**
* Clear all alarms from the Android shared Preferences
*
* @return true when successfull, otherwise false
*/
private boolean unpersistAlarmAll() {
final Editor alarmSettingsEditor = this.ctx.getSharedPreferences(PLUGIN_NAME, Context.MODE_PRIVATE).edit();
alarmSettingsEditor.clear();
return alarmSettingsEditor.commit();
}
}
\ No newline at end of file
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