Ver código fonte

Initial start for Android support

Sebastián Katzer 12 anos atrás
pai
commit
0c3f71e13a

+ 26 - 9
plugin.xml

@@ -2,12 +2,12 @@
 
 <plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
         id="de.appplant.cordova.plugin.local-notifications"
-        version="0.2.0">
+        version="0.4.0.dev">
 
     <name>LocalNotifications</name>
 
     <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>
 
     <author>Sebastián Katzer (github.com/katzer)</author>
@@ -16,22 +16,39 @@
         <engine name="cordova" version=">=3.0.0" />
     </engines>
 
+    <js-module src="www/local-notification.js" name="LocalNotification">
+        <clobbers target="plugin.notification.local" />
+    </js-module>
+
     <!-- ios -->
     <platform name="ios">
 
         <config-file target="config.xml" parent="/*">
-		<feature name="LocalNotification">
-			<param name="ios-package" value="APPLocalNotification"/>
-		</feature>
+    		<feature name="LocalNotification">
+    			<param name="ios-package" value="APPLocalNotification"/>
+    		</feature>
         </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" />
 	<source-file src="src/ios/APPLocalNotification.m" />
 
     </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>

+ 87 - 0
src/android/AlaramReceiver.java

@@ -0,0 +1,87 @@
+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);
+	}
+}

+ 109 - 0
src/android/AlarmHelper.java

@@ -0,0 +1,109 @@
+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;
+    }
+}

+ 111 - 0
src/android/AlarmOptions.java

@@ -0,0 +1,111 @@
+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;
+    }
+
+}

+ 63 - 0
src/android/AlarmRestoreOnBoot.java

@@ -0,0 +1,63 @@
+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");
+	}
+    }
+}

+ 186 - 0
src/android/LocalNotification.java

@@ -0,0 +1,186 @@
+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();
+    }
+}