Improvements to Android status bar notifications

Several things here:
- Add support for the W3C notifications spec, and rewrite the old API to
  call the new W3C one behind the scenes.
- Notifications are now automatically cleared when the user clicks.
- Clicking the notification brings the user to the same instance of the
  app as was already running (and not a new copy as by default in
  Android notifications).
- Support for multiple notifications from one app using tags, as
  specified in the W3C spec and supported by Android.
- Support for canceling a single notification by tag, as per W3C spec.

Callbacks to Javascript when the user clicks are specified but not yet
supported; I will add that assuming I can get Android to call some code
in the plugin when the notification is clicked.
This commit is contained in:
Braden Shepherdson
2012-08-28 15:19:40 -04:00
parent d1bdd99a50
commit 161428abb9
3 changed files with 200 additions and 115 deletions

View File

@@ -1,110 +1,120 @@
/*
*
* Copyright (C) 2011 Dmitry Savchenko <dg.freak@gmail.com>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
*
* Copyright (C) 2011 Dmitry Savchenko <dg.freak@gmail.com>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.phonegap.plugins.statusBarNotification;
import org.json.JSONArray;
import org.json.JSONException;
import android.app.NotificationManager;
import android.app.Notification;
import android.content.Context;
import android.util.Log;
import org.apache.cordova.DroidGap;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.apache.cordova.api.PluginResult.Status;
import org.json.JSONArray;
import org.json.JSONException;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.util.Log;
public class StatusBarNotification extends Plugin {
// Action to execute
public static final String NOTIFY = "notify";
public static final String CLEAR = "clear";
/**
* Executes the request and returns PluginResult
*
* @param action Action to execute
* @param data JSONArray of arguments to the plugin
* @param callbackId The callback id used when calling back into JavaScript
*
* @return A PluginRequest object with a status
* */
@Override
public PluginResult execute(String action, JSONArray data, String callbackId) {
PluginResult result = null;
if (NOTIFY.equals(action)) {
try {
// Action to execute
public static final String NOTIFY = "notify";
public static final String CLEAR = "clear";
String title = data.getString(0);
String body = data.getString(1);
Log.d("NotificationPlugin", "Notification: " + title + ", " + body);
showNotification(title, body);
result = new PluginResult(Status.OK);
} catch (JSONException jsonEx) {
Log.d("NotificationPlugin", "Got JSON Exception "
+ jsonEx.getMessage());
result = new PluginResult(Status.JSON_EXCEPTION);
}
} else if (CLEAR.equals(action)){
clearNotification();
} else {
result = new PluginResult(Status.INVALID_ACTION);
Log.d("NotificationPlugin", "Invalid action : "+action+" passed");
}
return result;
}
/**
* Displays status bar notification
*
* @param contentTitle Notification title
* @param contentText Notification text
* */
public void showNotification( CharSequence contentTitle, CharSequence contentText ) {
String ns = Context.NOTIFICATION_SERVICE;
mNotificationManager = (NotificationManager) cordova.getContext().getSystemService(ns);
context = ctx.getApplicationContext();
int currentapiVersion = android.os.Build.VERSION.SDK_INT;
if (currentapiVersion >= android.os.Build.VERSION_CODES.HONEYCOMB) {
Notification noti = new StatusNotificationBuilder().buildNotification(context, contentTitle, contentText);
mNotificationManager.notify(1, noti);
} else {
Notification noti = new StatusNotificationIntent().buildNotification(context, contentTitle, contentText);
mNotificationManager.notify(1, noti);
/**
* Executes the request and returns PluginResult
*
* @param action Action to execute
* @param data JSONArray of arguments to the plugin
* @param callbackId The callback id used when calling back into JavaScript
*
* @return A PluginRequest object with a status
* */
@Override
public PluginResult execute(String action, JSONArray data, String callbackId) {
PluginResult result = null;
if (NOTIFY.equals(action)) {
try {
String tag = data.getString(0);
String title = data.getString(1);
String body = data.getString(2);
Log.d("NotificationPlugin", "Notification: " + tag + ", " + title + ", " + body);
showNotification(tag, title, body);
result = new PluginResult(Status.OK);
} catch (JSONException jsonEx) {
Log.d("NotificationPlugin", "Got JSON Exception "
+ jsonEx.getMessage());
result = new PluginResult(Status.JSON_EXCEPTION);
}
} else if (CLEAR.equals(action)){
try {
String tag = data.getString(0);
Log.d("NotificationPlugin", "Notification cancel: " + tag);
clearNotification(tag);
} catch (JSONException jsonEx) {
Log.d("NotificationPlugin", "Got JSON Exception " + jsonEx.getMessage());
result = new PluginResult(Status.JSON_EXCEPTION);
}
} else {
result = new PluginResult(Status.INVALID_ACTION);
Log.d("NotificationPlugin", "Invalid action : "+action+" passed");
}
return result;
}
}
/**
* Removes the Notification from status bar
*/
public void clearNotification() {
mNotificationManager.cancelAll();
}
private NotificationManager mNotificationManager;
private Context context;
/**
* Displays status bar notification
*
* @param tag Notification tag.
* @param contentTitle Notification title
* @param contentText Notification text
* */
public void showNotification( CharSequence tag, CharSequence contentTitle, CharSequence contentText ) {
String ns = Context.NOTIFICATION_SERVICE;
context = cordova.getActivity().getApplicationContext();
mNotificationManager = (NotificationManager) context.getSystemService(ns);
Notification noti = StatusNotificationIntent.buildNotification(context, contentTitle, contentText);
mNotificationManager.notify(tag.hashCode(), noti);
}
/**
* Cancels a single notification by tag.
*
* @param tag Notification tag to cancel.
*/
public void clearNotification(String tag) {
mNotificationManager.cancel(tag.hashCode());
}
/**
* Removes all Notifications from the status bar.
*/
public void clearAllNotifications() {
mNotificationManager.cancelAll();
}
private NotificationManager mNotificationManager;
private Context context;
}

View File

@@ -1,22 +1,27 @@
// This class is used on all Androids below Honeycomb
package com.phonegap.plugins.statusBarNotification;
// import com.yourapp.R;
import com.google.cordova.statusbarnotificationtest.R;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.content.pm.PackageManager;
public class StatusNotificationIntent {
public Notification buildNotification( Context ctx, CharSequence contentTitle, CharSequence contentText ) {
int icon = R.drawable.notification;
long when = System.currentTimeMillis();
Notification noti = new Notification(icon, contentTitle, when);
Intent notificationIntent = new Intent(ctx, ctx.getClass());
PendingIntent contentIntent = PendingIntent.getActivity(ctx, 0, notificationIntent, 0);
noti.setLatestEventInfo(ctx, contentTitle, contentText, contentIntent);
return noti;
}
public static Notification buildNotification( Context context, CharSequence contentTitle, CharSequence contentText ) {
int icon = R.drawable.notification;
long when = System.currentTimeMillis();
Notification noti = new Notification(icon, contentTitle, when);
noti.flags |= Notification.FLAG_AUTO_CANCEL;
PackageManager pm = context.getPackageManager();
Intent notificationIntent = pm.getLaunchIntentForPackage(context.getPackageName());
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
noti.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
return noti;
}
}

View File

@@ -27,23 +27,93 @@
var cordovaRef = window.PhoneGap || window.Cordova || window.cordova; // old to new fallbacks
/** @deprecated Use the W3C standard window.Notification API instead. */
var NotificationMessenger = function() { }
/**
* @param title Title of the notification
* @param body Body of the notification
* @deprecated Use the W3C standard window.Notification API instead.
*/
NotificationMessenger.prototype.notify = function(title, body) {
return cordovaRef.exec(null, null, 'StatusBarNotification', 'notify', [title, body])
if (window.Notification) {
this.activeNotification = new window.Notification(title, {
body: body
});
}
}
/**
* Clears the Notificaiton Bar
* @deprecated Use the W3C standard window.Notification API instead.
*/
NotificationMessenger.prototype.clear = function() {
return cordovaRef.exec(null, null, 'StatusBarNotification', 'clear', []);
if (this.activeNotification) {
this.activeNotification.close();
this.activeNotification = undefined;
}
}
if (!window.plugins) window.plugins = {}
if (!window.plugins.statusBarNotification) window.plugins.statusBarNotification = new NotificationMessenger()
;
if (!window.plugins.statusBarNotification) window.plugins.statusBarNotification = new NotificationMessenger();
/*
* The W3C standard API, window.Notification. See http://www.w3.org/TR/notifications/
* This API should be used for new applications instead of the old plugin API above.
*/
if (typeof window.Notification == "undefined") {
/**
* Creates and shows a new notification.
* @param title
* @param options
*/
window.Notification = function(title, options) {
options = options || {};
this.tag = options.tag || "defaultTag";
// May be undefined.
this.onclick = options.onclick;
this.onerror = options.onerror;
this.onshow = options.onshow;
this.onclose = options.onclose;
var content = title + (options.body ? "\n" + options.body : "");
cordova.exec(function() {
if (this.onshow) {
this.onshow();
}
}, function(error) {
if (this.onerror) {
this.onerror(error);
}
}, "StatusBarNotification", "notify", [this.tag, title, content]);
};
// Permission is always granted on Android.
window.Notification.permission = "granted";
window.Notification.requestPermission = function(callback) {
callback('granted');
};
/**
* Cancels a notification that has already been created and shown to the user.
*/
window.Notification.prototype.close = function() {
cordova.exec(function() {
if (this.onclose) {
this.onclose();
}
}, function(error) {
if (this.onerror) {
this.onerror(error);
}
}, "StatusBarNotification", "clear", [this.tag]);
};
}
// vim: tabstop=4:softtabstop=4:shiftwidth=4:expandtab