diff options
Diffstat (limited to 'StoneIsland/plugins/phonegap-plugin-push/src')
11 files changed, 1772 insertions, 0 deletions
diff --git a/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/GCMIntentService.java b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/GCMIntentService.java new file mode 100644 index 00000000..24daa6a5 --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/GCMIntentService.java @@ -0,0 +1,603 @@ +package com.adobe.phonegap.push; + +import android.annotation.SuppressLint; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.NotificationCompat; +import android.text.Html; +import android.util.Log; + +import com.google.android.gms.gcm.GcmListenerService; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Random; + +@SuppressLint("NewApi") +public class GCMIntentService extends GcmListenerService implements PushConstants { + + private static final String LOG_TAG = "PushPlugin_GCMIntentService"; + private static HashMap<Integer, ArrayList<String>> messageMap = new HashMap<Integer, ArrayList<String>>(); + + public void setNotification(int notId, String message){ + ArrayList<String> messageList = messageMap.get(notId); + if(messageList == null) { + messageList = new ArrayList<String>(); + messageMap.put(notId, messageList); + } + + if(message.isEmpty()){ + messageList.clear(); + }else{ + messageList.add(message); + } + } + + @Override + public void onMessageReceived(String from, Bundle extras) { + Log.d(LOG_TAG, "onMessage - from: " + from); + + if (extras != null) { + + SharedPreferences prefs = getApplicationContext().getSharedPreferences(PushPlugin.COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE); + boolean forceShow = prefs.getBoolean(FORCE_SHOW, false); + + extras = normalizeExtras(extras); + + // if we are in the foreground and forceShow is `false` only send data + if (!forceShow && PushPlugin.isInForeground()) { + Log.d(LOG_TAG, "foreground"); + extras.putBoolean(FOREGROUND, true); + PushPlugin.sendExtras(extras); + } + // if we are in the foreground and forceShow is `true`, force show the notification if the data has at least a message or title + else if (forceShow && PushPlugin.isInForeground()) { + Log.d(LOG_TAG, "foreground force"); + extras.putBoolean(FOREGROUND, true); + + showNotificationIfPossible(getApplicationContext(), extras); + } + // if we are not in the foreground always send notification if the data has at least a message or title + else { + Log.d(LOG_TAG, "background"); + extras.putBoolean(FOREGROUND, false); + + showNotificationIfPossible(getApplicationContext(), extras); + } + } + } + + /* + * Change a values key in the extras bundle + */ + private void replaceKey(String oldKey, String newKey, Bundle extras, Bundle newExtras) { + Object value = extras.get(oldKey); + if ( value != null ) { + if (value instanceof String) { + newExtras.putString(newKey, (String) value); + } else if (value instanceof Boolean) { + newExtras.putBoolean(newKey, (Boolean) value); + } else if (value instanceof Number) { + newExtras.putDouble(newKey, ((Number) value).doubleValue()); + } else { + newExtras.putString(newKey, String.valueOf(value)); + } + } + } + + /* + * Replace alternate keys with our canonical value + */ + private String normalizeKey(String key) { + if (key.equals(BODY) || key.equals(ALERT) || key.equals(GCM_NOTIFICATION_BODY)) { + return MESSAGE; + } else if (key.equals(MSGCNT) || key.equals(BADGE)) { + return COUNT; + } else if (key.equals(SOUNDNAME)) { + return SOUND; + } else if (key.startsWith(GCM_NOTIFICATION)) { + return key.substring(GCM_NOTIFICATION.length()+1, key.length()); + } else if (key.startsWith(GCM_N)) { + return key.substring(GCM_N.length()+1, key.length()); + } else if (key.startsWith(UA_PREFIX)) { + key = key.substring(UA_PREFIX.length()+1, key.length()); + return key.toLowerCase(); + } else { + return key; + } + } + + /* + * Parse bundle into normalized keys. + */ + private Bundle normalizeExtras(Bundle extras) { + Log.d(LOG_TAG, "normalize extras"); + Iterator<String> it = extras.keySet().iterator(); + Bundle newExtras = new Bundle(); + + while (it.hasNext()) { + String key = it.next(); + + Log.d(LOG_TAG, "key = " + key); + + // If normalizeKeythe key is "data" or "message" and the value is a json object extract + // This is to support parse.com and other services. Issue #147 and pull #218 + if (key.equals(PARSE_COM_DATA) || key.equals(MESSAGE)) { + Object json = extras.get(key); + // Make sure data is json object stringified + if ( json instanceof String && ((String) json).startsWith("{") ) { + Log.d(LOG_TAG, "extracting nested message data from key = " + key); + try { + // If object contains message keys promote each value to the root of the bundle + JSONObject data = new JSONObject((String) json); + if ( data.has(ALERT) || data.has(MESSAGE) || data.has(BODY) || data.has(TITLE) ) { + Iterator<String> jsonIter = data.keys(); + while (jsonIter.hasNext()) { + String jsonKey = jsonIter.next(); + + Log.d(LOG_TAG, "key = data/" + jsonKey); + + String value = data.getString(jsonKey); + jsonKey = normalizeKey(jsonKey); + newExtras.putString(jsonKey, value); + } + } + } catch( JSONException e) { + Log.e(LOG_TAG, "normalizeExtras: JSON exception"); + } + } + } else if (key.equals(("notification"))) { + Bundle value = extras.getBundle(key); + Iterator<String> iterator = value.keySet().iterator(); + while (iterator.hasNext()) { + String notifkey = iterator.next(); + + Log.d(LOG_TAG, "notifkey = " + notifkey); + String newKey = normalizeKey(notifkey); + Log.d(LOG_TAG, "replace key " + notifkey + " with " + newKey); + + newExtras.putString(newKey, value.getString(notifkey)); + } + continue; + } + + String newKey = normalizeKey(key); + Log.d(LOG_TAG, "replace key " + key + " with " + newKey); + replaceKey(key, newKey, extras, newExtras); + + } // while + + return newExtras; + } + + private void showNotificationIfPossible (Context context, Bundle extras) { + + // Send a notification if there is a message or title, otherwise just send data + String message = extras.getString(MESSAGE); + String title = extras.getString(TITLE); + + Log.d(LOG_TAG, "message =[" + message + "]"); + Log.d(LOG_TAG, "title =[" + title + "]"); + + if ((message != null && message.length() != 0) || + (title != null && title.length() != 0)) { + + Log.d(LOG_TAG, "create notification"); + + createNotification(context, extras); + } else { + Log.d(LOG_TAG, "send notification event"); + PushPlugin.sendExtras(extras); + } + } + + public void createNotification(Context context, Bundle extras) { + NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + String appName = getAppName(this); + String packageName = context.getPackageName(); + Resources resources = context.getResources(); + + int notId = parseInt(NOT_ID, extras); + Intent notificationIntent = new Intent(this, PushHandlerActivity.class); + notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); + notificationIntent.putExtra(PUSH_BUNDLE, extras); + notificationIntent.putExtra(NOT_ID, notId); + + int requestCode = new Random().nextInt(); + PendingIntent contentIntent = PendingIntent.getActivity(this, requestCode, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); + + NotificationCompat.Builder mBuilder = + new NotificationCompat.Builder(context) + .setWhen(System.currentTimeMillis()) + .setContentTitle(extras.getString(TITLE)) + .setTicker(extras.getString(TITLE)) + .setContentIntent(contentIntent) + .setAutoCancel(true); + + SharedPreferences prefs = context.getSharedPreferences(PushPlugin.COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE); + String localIcon = prefs.getString(ICON, null); + String localIconColor = prefs.getString(ICON_COLOR, null); + boolean soundOption = prefs.getBoolean(SOUND, true); + boolean vibrateOption = prefs.getBoolean(VIBRATE, true); + Log.d(LOG_TAG, "stored icon=" + localIcon); + Log.d(LOG_TAG, "stored iconColor=" + localIconColor); + Log.d(LOG_TAG, "stored sound=" + soundOption); + Log.d(LOG_TAG, "stored vibrate=" + vibrateOption); + + /* + * Notification Vibration + */ + + setNotificationVibration(extras, vibrateOption, mBuilder); + + /* + * Notification Icon Color + * + * Sets the small-icon background color of the notification. + * To use, add the `iconColor` key to plugin android options + * + */ + setNotificationIconColor(extras.getString("color"), mBuilder, localIconColor); + + /* + * Notification Icon + * + * Sets the small-icon of the notification. + * + * - checks the plugin options for `icon` key + * - if none, uses the application icon + * + * The icon value must be a string that maps to a drawable resource. + * If no resource is found, falls + * + */ + setNotificationSmallIcon(context, extras, packageName, resources, mBuilder, localIcon); + + /* + * Notification Large-Icon + * + * Sets the large-icon of the notification + * + * - checks the gcm data for the `image` key + * - checks to see if remote image, loads it. + * - checks to see if assets image, Loads It. + * - checks to see if resource image, LOADS IT! + * - if none, we don't set the large icon + * + */ + setNotificationLargeIcon(extras, packageName, resources, mBuilder); + + /* + * Notification Sound + */ + if (soundOption) { + setNotificationSound(context, extras, mBuilder); + } + + /* + * LED Notification + */ + setNotificationLedColor(extras, mBuilder); + + /* + * Priority Notification + */ + setNotificationPriority(extras, mBuilder); + + /* + * Notification message + */ + setNotificationMessage(notId, extras, mBuilder); + + /* + * Notification count + */ + setNotificationCount(extras, mBuilder); + + /* + * Notification add actions + */ + createActions(extras, mBuilder, resources, packageName); + + mNotificationManager.notify(appName, notId, mBuilder.build()); + } + + private void createActions(Bundle extras, NotificationCompat.Builder mBuilder, Resources resources, String packageName) { + Log.d(LOG_TAG, "create actions"); + String actions = extras.getString(ACTIONS); + if (actions != null) { + try { + JSONArray actionsArray = new JSONArray(actions); + for (int i=0; i < actionsArray.length(); i++) { + Log.d(LOG_TAG, "adding action"); + JSONObject action = actionsArray.getJSONObject(i); + Log.d(LOG_TAG, "adding callback = " + action.getString(CALLBACK)); + Intent intent = new Intent(this, PushHandlerActivity.class); + intent.putExtra(CALLBACK, action.getString(CALLBACK)); + intent.putExtra(PUSH_BUNDLE, extras); + PendingIntent pIntent = PendingIntent.getActivity(this, i, intent, PendingIntent.FLAG_UPDATE_CURRENT); + + mBuilder.addAction(resources.getIdentifier(action.getString(ICON), DRAWABLE, packageName), + action.getString(TITLE), pIntent); + } + } catch(JSONException e) { + // nope + } + } + } + + private void setNotificationCount(Bundle extras, NotificationCompat.Builder mBuilder) { + String msgcnt = extras.getString(MSGCNT); + if (msgcnt == null) { + msgcnt = extras.getString(BADGE); + } + if (msgcnt != null) { + mBuilder.setNumber(Integer.parseInt(msgcnt)); + } + } + + private void setNotificationVibration(Bundle extras, Boolean vibrateOption, NotificationCompat.Builder mBuilder) { + String vibrationPattern = extras.getString(VIBRATION_PATTERN); + if (vibrationPattern != null) { + String[] items = vibrationPattern.replaceAll("\\[", "").replaceAll("\\]", "").split(","); + long[] results = new long[items.length]; + for (int i = 0; i < items.length; i++) { + try { + results[i] = Long.parseLong(items[i]); + } catch (NumberFormatException nfe) {} + } + mBuilder.setVibrate(results); + } else { + if (vibrateOption) { + mBuilder.setDefaults(Notification.DEFAULT_VIBRATE); + } + } + } + + private void setNotificationMessage(int notId, Bundle extras, NotificationCompat.Builder mBuilder) { + String message = extras.getString(MESSAGE); + + String style = extras.getString(STYLE, STYLE_TEXT); + if(STYLE_INBOX.equals(style)) { + setNotification(notId, message); + + mBuilder.setContentText(message); + + ArrayList<String> messageList = messageMap.get(notId); + Integer sizeList = messageList.size(); + if (sizeList > 1) { + String sizeListMessage = sizeList.toString(); + String stacking = sizeList + " more"; + if (extras.getString(SUMMARY_TEXT) != null) { + stacking = extras.getString(SUMMARY_TEXT); + stacking = stacking.replace("%n%", sizeListMessage); + } + NotificationCompat.InboxStyle notificationInbox = new NotificationCompat.InboxStyle() + .setBigContentTitle(extras.getString(TITLE)) + .setSummaryText(stacking); + + for (int i = messageList.size() - 1; i >= 0; i--) { + notificationInbox.addLine(Html.fromHtml(messageList.get(i))); + } + + mBuilder.setStyle(notificationInbox); + } else { + NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle(); + if (message != null) { + bigText.bigText(message); + bigText.setBigContentTitle(extras.getString(TITLE)); + mBuilder.setStyle(bigText); + } + } + } else if (STYLE_PICTURE.equals(style)) { + setNotification(notId, ""); + + NotificationCompat.BigPictureStyle bigPicture = new NotificationCompat.BigPictureStyle(); + bigPicture.bigPicture(getBitmapFromURL(extras.getString(PICTURE))); + bigPicture.setBigContentTitle(extras.getString(TITLE)); + bigPicture.setSummaryText(extras.getString(SUMMARY_TEXT)); + + mBuilder.setContentTitle(extras.getString(TITLE)); + mBuilder.setContentText(message); + + mBuilder.setStyle(bigPicture); + } else { + setNotification(notId, ""); + + NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle(); + + if (message != null) { + mBuilder.setContentText(Html.fromHtml(message)); + + bigText.bigText(message); + bigText.setBigContentTitle(extras.getString(TITLE)); + + String summaryText = extras.getString(SUMMARY_TEXT); + if (summaryText != null) { + bigText.setSummaryText(summaryText); + } + + mBuilder.setStyle(bigText); + } + /* + else { + mBuilder.setContentText("<missing message content>"); + } + */ + } + } + + private void setNotificationSound(Context context, Bundle extras, NotificationCompat.Builder mBuilder) { + String soundname = extras.getString(SOUNDNAME); + if (soundname == null) { + soundname = extras.getString(SOUND); + } + if (soundname != null && !soundname.contentEquals(SOUND_DEFAULT)) { + Uri sound = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + + "://" + context.getPackageName() + "/raw/" + soundname); + Log.d(LOG_TAG, sound.toString()); + mBuilder.setSound(sound); + } else { + mBuilder.setSound(android.provider.Settings.System.DEFAULT_NOTIFICATION_URI); + } + } + + private void setNotificationLedColor(Bundle extras, NotificationCompat.Builder mBuilder) { + String ledColor = extras.getString(LED_COLOR); + if (ledColor != null) { + // Converts parse Int Array from ledColor + String[] items = ledColor.replaceAll("\\[", "").replaceAll("\\]", "").split(","); + int[] results = new int[items.length]; + for (int i = 0; i < items.length; i++) { + try { + results[i] = Integer.parseInt(items[i]); + } catch (NumberFormatException nfe) {} + } + if (results.length == 4) { + mBuilder.setLights(Color.argb(results[0], results[1], results[2], results[3]), 500, 500); + } else { + Log.e(LOG_TAG, "ledColor parameter must be an array of length == 4 (ARGB)"); + } + } + } + + private void setNotificationPriority(Bundle extras, NotificationCompat.Builder mBuilder) { + String priorityStr = extras.getString(PRIORITY); + if (priorityStr != null) { + try { + Integer priority = Integer.parseInt(priorityStr); + if (priority >= NotificationCompat.PRIORITY_MIN && priority <= NotificationCompat.PRIORITY_MAX) { + mBuilder.setPriority(priority); + } else { + Log.e(LOG_TAG, "Priority parameter must be between -2 and 2"); + } + } catch (NumberFormatException e) { + e.printStackTrace(); + } + } + } + + private void setNotificationLargeIcon(Bundle extras, String packageName, Resources resources, NotificationCompat.Builder mBuilder) { + String gcmLargeIcon = extras.getString(IMAGE); // from gcm + if (gcmLargeIcon != null) { + if (gcmLargeIcon.startsWith("http://") || gcmLargeIcon.startsWith("https://")) { + mBuilder.setLargeIcon(getBitmapFromURL(gcmLargeIcon)); + Log.d(LOG_TAG, "using remote large-icon from gcm"); + } else { + AssetManager assetManager = getAssets(); + InputStream istr; + try { + istr = assetManager.open(gcmLargeIcon); + Bitmap bitmap = BitmapFactory.decodeStream(istr); + mBuilder.setLargeIcon(bitmap); + Log.d(LOG_TAG, "using assets large-icon from gcm"); + } catch (IOException e) { + int largeIconId = 0; + largeIconId = resources.getIdentifier(gcmLargeIcon, DRAWABLE, packageName); + if (largeIconId != 0) { + Bitmap largeIconBitmap = BitmapFactory.decodeResource(resources, largeIconId); + mBuilder.setLargeIcon(largeIconBitmap); + Log.d(LOG_TAG, "using resources large-icon from gcm"); + } else { + Log.d(LOG_TAG, "Not setting large icon"); + } + } + } + } + } + + private void setNotificationSmallIcon(Context context, Bundle extras, String packageName, Resources resources, NotificationCompat.Builder mBuilder, String localIcon) { + int iconId = 0; + String icon = extras.getString(ICON); + if (icon != null) { + iconId = resources.getIdentifier(icon, DRAWABLE, packageName); + Log.d(LOG_TAG, "using icon from plugin options"); + } + else if (localIcon != null) { + iconId = resources.getIdentifier(localIcon, DRAWABLE, packageName); + Log.d(LOG_TAG, "using icon from plugin options"); + } + if (iconId == 0) { + Log.d(LOG_TAG, "no icon resource found - using application icon"); + iconId = context.getApplicationInfo().icon; + } + mBuilder.setSmallIcon(iconId); + } + + private void setNotificationIconColor(String color, NotificationCompat.Builder mBuilder, String localIconColor) { + int iconColor = 0; + if (color != null) { + try { + iconColor = Color.parseColor(color); + } catch (IllegalArgumentException e) { + Log.e(LOG_TAG, "couldn't parse color from android options"); + } + } + else if (localIconColor != null) { + try { + iconColor = Color.parseColor(localIconColor); + } catch (IllegalArgumentException e) { + Log.e(LOG_TAG, "couldn't parse color from android options"); + } + } + if (iconColor != 0) { + mBuilder.setColor(iconColor); + } + } + + public Bitmap getBitmapFromURL(String strURL) { + try { + URL url = new URL(strURL); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setDoInput(true); + connection.connect(); + InputStream input = connection.getInputStream(); + return BitmapFactory.decodeStream(input); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + private static String getAppName(Context context) { + CharSequence appName = context.getPackageManager().getApplicationLabel(context.getApplicationInfo()); + return (String)appName; + } + + private int parseInt(String value, Bundle extras) { + int retval = 0; + + try { + retval = Integer.parseInt(extras.getString(value)); + } + catch(NumberFormatException e) { + Log.e(LOG_TAG, "Number format exception - Error parsing " + value + ": " + e.getMessage()); + } + catch(Exception e) { + Log.e(LOG_TAG, "Number format exception - Error parsing " + value + ": " + e.getMessage()); + } + + return retval; + } +} diff --git a/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushConstants.java b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushConstants.java new file mode 100644 index 00000000..aeb49c9b --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushConstants.java @@ -0,0 +1,53 @@ +package com.adobe.phonegap.push; + +public interface PushConstants { + public static final String COM_ADOBE_PHONEGAP_PUSH = "com.adobe.phonegap.push"; + public static final String REGISTRATION_ID = "registrationId"; + public static final String FOREGROUND = "foreground"; + public static final String TITLE = "title"; + public static final String NOT_ID = "notId"; + public static final String PUSH_BUNDLE = "pushBundle"; + public static final String ICON = "icon"; + public static final String ICON_COLOR = "iconColor"; + public static final String SOUND = "sound"; + public static final String SOUND_DEFAULT = "default"; + public static final String VIBRATE = "vibrate"; + public static final String ACTIONS = "actions"; + public static final String CALLBACK = "callback"; + public static final String DRAWABLE = "drawable"; + public static final String MSGCNT = "msgcnt"; + public static final String VIBRATION_PATTERN = "vibrationPattern"; + public static final String STYLE = "style"; + public static final String SUMMARY_TEXT = "summaryText"; + public static final String PICTURE = "picture"; + public static final String GCM_N = "gcm.n."; + public static final String GCM_NOTIFICATION = "gcm.notification"; + public static final String GCM_NOTIFICATION_BODY = "gcm.notification.body"; + public static final String UA_PREFIX = "com.urbanairship.push"; + public static final String PARSE_COM_DATA = "data"; + public static final String ALERT = "alert"; + public static final String MESSAGE = "message"; + public static final String BODY = "body"; + public static final String SOUNDNAME = "soundname"; + public static final String LED_COLOR = "ledColor"; + public static final String PRIORITY = "priority"; + public static final String IMAGE = "image"; + public static final String STYLE_INBOX = "inbox"; + public static final String STYLE_PICTURE = "picture"; + public static final String STYLE_TEXT = "text"; + public static final String BADGE = "badge"; + public static final String INITIALIZE = "init"; + public static final String UNREGISTER = "unregister"; + public static final String EXIT = "exit"; + public static final String FINISH = "finish"; + public static final String ANDROID = "android"; + public static final String SENDER_ID = "senderID"; + public static final String CLEAR_NOTIFICATIONS = "clearNotifications"; + public static final String COLDSTART = "coldstart"; + public static final String ADDITIONAL_DATA = "additionalData"; + public static final String COUNT = "count"; + public static final String FROM = "from"; + public static final String COLLAPSE_KEY = "collapse_key"; + public static final String FORCE_SHOW = "forceShow"; + public static final String GCM = "GCM"; +} diff --git a/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushHandlerActivity.java b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushHandlerActivity.java new file mode 100644 index 00000000..dd9fbd36 --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushHandlerActivity.java @@ -0,0 +1,70 @@ +package com.adobe.phonegap.push; + +import android.app.Activity; +import android.app.NotificationManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.util.Log; + +public class PushHandlerActivity extends Activity implements PushConstants { + private static String LOG_TAG = "PushPlugin_PushHandlerActivity"; + + /* + * this activity will be started if the user touches a notification that we own. + * We send it's data off to the push plugin for processing. + * If needed, we boot up the main activity to kickstart the application. + * @see android.app.Activity#onCreate(android.os.Bundle) + */ + @Override + public void onCreate(Bundle savedInstanceState) { + GCMIntentService gcm = new GCMIntentService(); + gcm.setNotification(getIntent().getIntExtra(NOT_ID, 0), ""); + super.onCreate(savedInstanceState); + Log.v(LOG_TAG, "onCreate"); + + boolean isPushPluginActive = PushPlugin.isActive(); + processPushBundle(isPushPluginActive); + + finish(); + + if (!isPushPluginActive) { + forceMainActivityReload(); + } + } + + /** + * Takes the pushBundle extras from the intent, + * and sends it through to the PushPlugin for processing. + */ + private void processPushBundle(boolean isPushPluginActive) { + Bundle extras = getIntent().getExtras(); + + if (extras != null) { + Bundle originalExtras = extras.getBundle(PUSH_BUNDLE); + + originalExtras.putBoolean(FOREGROUND, false); + originalExtras.putBoolean(COLDSTART, !isPushPluginActive); + originalExtras.putString(CALLBACK, extras.getString("callback")); + + PushPlugin.sendExtras(originalExtras); + } + } + + /** + * Forces the main activity to re-launch if it's unloaded. + */ + private void forceMainActivityReload() { + PackageManager pm = getPackageManager(); + Intent launchIntent = pm.getLaunchIntentForPackage(getApplicationContext().getPackageName()); + startActivity(launchIntent); + } + + @Override + protected void onResume() { + super.onResume(); + final NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.cancelAll(); + } +}
\ No newline at end of file diff --git a/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushInstanceIDListenerService.java b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushInstanceIDListenerService.java new file mode 100644 index 00000000..eaa39a48 --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushInstanceIDListenerService.java @@ -0,0 +1,27 @@ +package com.adobe.phonegap.push; + +import android.content.Intent; +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import com.google.android.gms.iid.InstanceID; +import com.google.android.gms.iid.InstanceIDListenerService; + +import org.json.JSONException; + +import java.io.IOException; + +public class PushInstanceIDListenerService extends InstanceIDListenerService implements PushConstants { + public static final String LOG_TAG = "PushPlugin_PushInstanceIDListenerService"; + + @Override + public void onTokenRefresh() { + SharedPreferences sharedPref = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE); + String senderID = sharedPref.getString(SENDER_ID, ""); + if (!"".equals(senderID)) { + Intent intent = new Intent(this, RegistrationIntentService.class); + startService(intent); + } + } +} diff --git a/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushPlugin.java b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushPlugin.java new file mode 100644 index 00000000..41a91819 --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushPlugin.java @@ -0,0 +1,294 @@ +package com.adobe.phonegap.push; + +import android.app.NotificationManager; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.util.Log; + +import com.google.android.gms.iid.InstanceID; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaInterface; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.CordovaWebView; +import org.apache.cordova.PluginResult; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; + +public class PushPlugin extends CordovaPlugin implements PushConstants { + + public static final String LOG_TAG = "PushPlugin"; + + private static CallbackContext pushContext; + private static CordovaWebView gWebView; + private static Bundle gCachedExtras = null; + private static boolean gForeground = false; + + /** + * Gets the application context from cordova's main activity. + * @return the application context + */ + private Context getApplicationContext() { + return this.cordova.getActivity().getApplicationContext(); + } + + @Override + public boolean execute(final String action, final JSONArray data, final CallbackContext callbackContext) { + Log.v(LOG_TAG, "execute: action=" + action); + gWebView = this.webView; + + if (INITIALIZE.equals(action)) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + pushContext = callbackContext; + JSONObject jo = null; + + Log.v(LOG_TAG, "execute: data=" + data.toString()); + SharedPreferences sharedPref = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE); + String token = null; + String senderID = null; + + try { + jo = data.getJSONObject(0).getJSONObject(ANDROID); + + Log.v(LOG_TAG, "execute: jo=" + jo.toString()); + + senderID = jo.getString(SENDER_ID); + + Log.v(LOG_TAG, "execute: senderID=" + senderID); + + String savedSenderID = sharedPref.getString(SENDER_ID, ""); + String savedRegID = sharedPref.getString(REGISTRATION_ID, ""); + + // first time run get new token + if ("".equals(savedRegID)) { + token = InstanceID.getInstance(getApplicationContext()).getToken(senderID, GCM); + } + // new sender ID, re-register + else if (!savedSenderID.equals(senderID)) { + token = InstanceID.getInstance(getApplicationContext()).getToken(senderID, GCM); + } + // use the saved one + else { + token = sharedPref.getString(REGISTRATION_ID, ""); + } + + if (!"".equals(token)) { + JSONObject json = new JSONObject().put(REGISTRATION_ID, token); + + Log.v(LOG_TAG, "onRegistered: " + json.toString()); + + PushPlugin.sendEvent( json ); + } else { + callbackContext.error("Empty registration ID received from GCM"); + return; + } + } catch (JSONException e) { + Log.e(LOG_TAG, "execute: Got JSON Exception " + e.getMessage()); + callbackContext.error(e.getMessage()); + } catch (IOException e) { + Log.e(LOG_TAG, "execute: Got JSON Exception " + e.getMessage()); + callbackContext.error(e.getMessage()); + } + + if (jo != null) { + SharedPreferences.Editor editor = sharedPref.edit(); + try { + editor.putString(ICON, jo.getString(ICON)); + } catch (JSONException e) { + Log.d(LOG_TAG, "no icon option"); + } + try { + editor.putString(ICON_COLOR, jo.getString(ICON_COLOR)); + } catch (JSONException e) { + Log.d(LOG_TAG, "no iconColor option"); + } + editor.putBoolean(SOUND, jo.optBoolean(SOUND, true)); + editor.putBoolean(VIBRATE, jo.optBoolean(VIBRATE, true)); + editor.putBoolean(CLEAR_NOTIFICATIONS, jo.optBoolean(CLEAR_NOTIFICATIONS, true)); + editor.putBoolean(FORCE_SHOW, jo.optBoolean(FORCE_SHOW, false)); + editor.putString(SENDER_ID, senderID); + editor.putString(REGISTRATION_ID, token); + editor.commit(); + } + + if (gCachedExtras != null) { + Log.v(LOG_TAG, "sending cached extras"); + sendExtras(gCachedExtras); + gCachedExtras = null; + } + } + }); + } else if (UNREGISTER.equals(action)) { + cordova.getThreadPool().execute(new Runnable() { + public void run() { + try { + InstanceID.getInstance(getApplicationContext()).deleteInstanceID(); + Log.v(LOG_TAG, "UNREGISTER"); + + // Remove shared prefs + SharedPreferences sharedPref = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPref.edit(); + editor.remove(SOUND); + editor.remove(VIBRATE); + editor.remove(CLEAR_NOTIFICATIONS); + editor.remove(FORCE_SHOW); + editor.remove(SENDER_ID); + editor.remove(REGISTRATION_ID); + editor.commit(); + + callbackContext.success(); + } catch (IOException e) { + Log.e(LOG_TAG, "execute: Got JSON Exception " + e.getMessage()); + callbackContext.error(e.getMessage()); + } + } + }); + } else if (FINISH.equals(action)) { + callbackContext.success(); + } else { + Log.e(LOG_TAG, "Invalid action : " + action); + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.INVALID_ACTION)); + return false; + } + + return true; + } + + public static void sendEvent(JSONObject _json) { + PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, _json); + pluginResult.setKeepCallback(true); + if (pushContext != null) { + pushContext.sendPluginResult(pluginResult); + } + } + + public static void sendError(String message) { + PluginResult pluginResult = new PluginResult(PluginResult.Status.ERROR, message); + pluginResult.setKeepCallback(true); + if (pushContext != null) { + pushContext.sendPluginResult(pluginResult); + } + } + + /* + * Sends the pushbundle extras to the client application. + * If the client application isn't currently active, it is cached for later processing. + */ + public static void sendExtras(Bundle extras) { + if (extras != null) { + if (gWebView != null) { + sendEvent(convertBundleToJson(extras)); + } else { + Log.v(LOG_TAG, "sendExtras: caching extras to send at a later time."); + gCachedExtras = extras; + } + } + } + + @Override + public void initialize(CordovaInterface cordova, CordovaWebView webView) { + super.initialize(cordova, webView); + gForeground = true; + } + + @Override + public void onPause(boolean multitasking) { + super.onPause(multitasking); + gForeground = false; + + SharedPreferences prefs = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE); + if (prefs.getBoolean(CLEAR_NOTIFICATIONS, true)) { + final NotificationManager notificationManager = (NotificationManager) cordova.getActivity().getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.cancelAll(); + } + } + + @Override + public void onResume(boolean multitasking) { + super.onResume(multitasking); + gForeground = true; + } + + @Override + public void onDestroy() { + super.onDestroy(); + gForeground = false; + gWebView = null; + } + + /* + * serializes a bundle to JSON. + */ + private static JSONObject convertBundleToJson(Bundle extras) { + Log.d(LOG_TAG, "convert extras to json"); + try { + JSONObject json = new JSONObject(); + JSONObject additionalData = new JSONObject(); + + // Add any keys that need to be in top level json to this set + HashSet<String> jsonKeySet = new HashSet(); + Collections.addAll(jsonKeySet, TITLE,MESSAGE,COUNT,SOUND,IMAGE); + + Iterator<String> it = extras.keySet().iterator(); + while (it.hasNext()) { + String key = it.next(); + Object value = extras.get(key); + + Log.d(LOG_TAG, "key = " + key); + + if (jsonKeySet.contains(key)) { + json.put(key, value); + } + else if (key.equals(COLDSTART)) { + additionalData.put(key, extras.getBoolean(COLDSTART)); + } + else if (key.equals(FOREGROUND)) { + additionalData.put(key, extras.getBoolean(FOREGROUND)); + } + else if ( value instanceof String ) { + String strValue = (String)value; + try { + // Try to figure out if the value is another JSON object + if (strValue.startsWith("{")) { + additionalData.put(key, new JSONObject(strValue)); + } + // Try to figure out if the value is another JSON array + else if (strValue.startsWith("[")) { + additionalData.put(key, new JSONArray(strValue)); + } + else { + additionalData.put(key, value); + } + } catch (Exception e) { + additionalData.put(key, value); + } + } + } // while + + json.put(ADDITIONAL_DATA, additionalData); + Log.v(LOG_TAG, "extrasToJSON: " + json.toString()); + + return json; + } + catch( JSONException e) { + Log.e(LOG_TAG, "extrasToJSON: JSON exception"); + } + return null; + } + + public static boolean isInForeground() { + return gForeground; + } + + public static boolean isActive() { + return gWebView != null; + } +}
\ No newline at end of file diff --git a/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/RegistrationIntentService.java b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/RegistrationIntentService.java new file mode 100644 index 00000000..c4489fc1 --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/RegistrationIntentService.java @@ -0,0 +1,42 @@ +package com.adobe.phonegap.push; + +import android.content.Context; + +import android.app.IntentService; +import android.content.Intent; +import android.content.SharedPreferences; +import android.util.Log; + +import com.google.android.gms.gcm.GoogleCloudMessaging; +import com.google.android.gms.iid.InstanceID; + +import java.io.IOException; + +public class RegistrationIntentService extends IntentService implements PushConstants { + public static final String LOG_TAG = "PushPlugin_RegistrationIntentService"; + + public RegistrationIntentService() { + super(LOG_TAG); + } + + @Override + protected void onHandleIntent(Intent intent) { + SharedPreferences sharedPreferences = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE); + + try { + InstanceID instanceID = InstanceID.getInstance(this); + String senderID = sharedPreferences.getString(SENDER_ID, ""); + String token = instanceID.getToken(senderID, + GoogleCloudMessaging.INSTANCE_ID_SCOPE, null); + Log.i(LOG_TAG, "new GCM Registration Token: " + token); + + // save new token + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(REGISTRATION_ID, token); + editor.commit(); + + } catch (Exception e) { + Log.d(LOG_TAG, "Failed to complete token refresh", e); + } + } +}
\ No newline at end of file diff --git a/StoneIsland/plugins/phonegap-plugin-push/src/ios/AppDelegate+notification.h b/StoneIsland/plugins/phonegap-plugin-push/src/ios/AppDelegate+notification.h new file mode 100644 index 00000000..be79903e --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-push/src/ios/AppDelegate+notification.h @@ -0,0 +1,21 @@ +// +// AppDelegate+notification.h +// pushtest +// +// Created by Robert Easterday on 10/26/12. +// +// + +#import "AppDelegate.h" + +@interface AppDelegate (notification) +- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; +- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error; +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo; +- (void)applicationDidBecomeActive:(UIApplication *)application; +- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void(^)())completionHandler; +- (id) getCommandInstance:(NSString*)className; + +@property (nonatomic, retain) NSDictionary *launchNotification; + +@end diff --git a/StoneIsland/plugins/phonegap-plugin-push/src/ios/AppDelegate+notification.m b/StoneIsland/plugins/phonegap-plugin-push/src/ios/AppDelegate+notification.m new file mode 100644 index 00000000..8b4ed161 --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-push/src/ios/AppDelegate+notification.m @@ -0,0 +1,167 @@ +// +// AppDelegate+notification.m +// pushtest +// +// Created by Robert Easterday on 10/26/12. +// +// + +#import "AppDelegate+notification.h" +#import "PushPlugin.h" +#import <objc/runtime.h> + +static char launchNotificationKey; + +@implementation AppDelegate (notification) + +- (id) getCommandInstance:(NSString*)className +{ + return [self.viewController getCommandInstance:className]; +} + +// its dangerous to override a method from within a category. +// Instead we will use method swizzling. we set this up in the load call. ++ (void)load +{ + Method original, swizzled; + + original = class_getInstanceMethod(self, @selector(init)); + swizzled = class_getInstanceMethod(self, @selector(swizzled_init)); + method_exchangeImplementations(original, swizzled); +} + +- (AppDelegate *)swizzled_init +{ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(createNotificationChecker:) + name:@"UIApplicationDidFinishLaunchingNotification" object:nil]; + + // This actually calls the original init method over in AppDelegate. Equivilent to calling super + // on an overrided method, this is not recursive, although it appears that way. neat huh? + return [self swizzled_init]; +} + +// This code will be called immediately after application:didFinishLaunchingWithOptions:. We need +// to process notifications in cold-start situations +- (void)createNotificationChecker:(NSNotification *)notification +{ + if (notification) + { + NSDictionary *launchOptions = [notification userInfo]; + if (launchOptions) + self.launchNotification = [launchOptions objectForKey: @"UIApplicationLaunchOptionsRemoteNotificationKey"]; + } +} + +- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"]; + [pushHandler didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; +} + +- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { + PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"]; + [pushHandler didFailToRegisterForRemoteNotificationsWithError:error]; +} + +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + NSLog(@"didReceiveNotification with fetchCompletionHandler"); + + // app is in the foreground so call notification callback + if (application.applicationState == UIApplicationStateActive) { + NSLog(@"app active"); + PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"]; + pushHandler.notificationMessage = userInfo; + pushHandler.isInline = YES; + [pushHandler notificationReceived]; + + completionHandler(UIBackgroundFetchResultNewData); + } + // app is in background or in stand by + else { + NSLog(@"app in-active"); + + // do some convoluted logic to find out if this should be a silent push. + long silent = 0; + id aps = [userInfo objectForKey:@"aps"]; + id contentAvailable = [aps objectForKey:@"content-available"]; + if ([contentAvailable isKindOfClass:[NSString class]] && [contentAvailable isEqualToString:@"1"]) { + silent = 1; + } else if ([contentAvailable isKindOfClass:[NSNumber class]]) { + silent = [contentAvailable integerValue]; + } + + if (silent == 1) { + NSLog(@"this should be a silent push"); + void (^safeHandler)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result){ + dispatch_async(dispatch_get_main_queue(), ^{ + completionHandler(result); + }); + }; + + NSMutableDictionary* params = [NSMutableDictionary dictionaryWithCapacity:2]; + [params setObject:safeHandler forKey:@"handler"]; + + PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"]; + pushHandler.notificationMessage = userInfo; + pushHandler.isInline = NO; + pushHandler.handlerObj = params; + [pushHandler notificationReceived]; + } else { + NSLog(@"just put it in the shade"); + //save it for later + self.launchNotification = userInfo; + + completionHandler(UIBackgroundFetchResultNewData); + } + } +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + + NSLog(@"active"); + + PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"]; + if (pushHandler.clearBadge) { + NSLog(@"PushPlugin clearing badge"); + //zero badge + application.applicationIconBadgeNumber = 0; + } else { + NSLog(@"PushPlugin skip clear badge"); + } + + if (self.launchNotification) { + pushHandler.isInline = NO; + pushHandler.notificationMessage = self.launchNotification; + self.launchNotification = nil; + [pushHandler performSelectorOnMainThread:@selector(notificationReceived) withObject:pushHandler waitUntilDone:NO]; + } +} + +//For interactive notification only +- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void(^)())completionHandler +{ + //handle the actions + if ([identifier isEqualToString:@"declineAction"]){ + } + else if ([identifier isEqualToString:@"answerAction"]){ + } +} + + +// The accessors use an Associative Reference since you can't define a iVar in a category +// http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocAssociativeReferences.html +- (NSMutableArray *)launchNotification +{ + return objc_getAssociatedObject(self, &launchNotificationKey); +} + +- (void)setLaunchNotification:(NSDictionary *)aDictionary +{ + objc_setAssociatedObject(self, &launchNotificationKey, aDictionary, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (void)dealloc +{ + self.launchNotification = nil; // clear the association and release the object +} + +@end diff --git a/StoneIsland/plugins/phonegap-plugin-push/src/ios/PushPlugin.h b/StoneIsland/plugins/phonegap-plugin-push/src/ios/PushPlugin.h new file mode 100644 index 00000000..5b88398e --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-push/src/ios/PushPlugin.h @@ -0,0 +1,62 @@ +/* + Copyright 2009-2011 Urban Airship Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binaryform must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided withthe distribution. + + THIS SOFTWARE IS PROVIDED BY THE URBAN AIRSHIP INC``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + EVENT SHALL URBAN AIRSHIP INC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import <Foundation/Foundation.h> +#import <Cordova/CDV.h> +#import <Cordova/CDVPlugin.h> + +@interface PushPlugin : CDVPlugin +{ + NSDictionary *notificationMessage; + BOOL isInline; + NSString *notificationCallbackId; + NSString *callback; + BOOL clearBadge; + + NSDictionary *handlerObj; + void (^completionHandler)(UIBackgroundFetchResult); + + BOOL ready; +} + +@property (nonatomic, copy) NSString *callbackId; +@property (nonatomic, copy) NSString *notificationCallbackId; +@property (nonatomic, copy) NSString *callback; + +@property (nonatomic, strong) NSDictionary *notificationMessage; +@property BOOL isInline; +@property BOOL clearBadge; +@property (nonatomic, strong) NSDictionary *handlerObj; + +- (void)init:(CDVInvokedUrlCommand*)command; +- (void)unregister:(CDVInvokedUrlCommand*)command; + +- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken; +- (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error; + +- (void)setNotificationMessage:(NSDictionary *)notification; +- (void)notificationReceived; + +@end diff --git a/StoneIsland/plugins/phonegap-plugin-push/src/ios/PushPlugin.m b/StoneIsland/plugins/phonegap-plugin-push/src/ios/PushPlugin.m new file mode 100644 index 00000000..40f494d1 --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-push/src/ios/PushPlugin.m @@ -0,0 +1,347 @@ +/* + Copyright 2009-2011 Urban Airship Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binaryform must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided withthe distribution. + + THIS SOFTWARE IS PROVIDED BY THE URBAN AIRSHIP INC``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + EVENT SHALL URBAN AIRSHIP INC OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#import "PushPlugin.h" + +@implementation PushPlugin + +@synthesize notificationMessage; +@synthesize isInline; + +@synthesize callbackId; +@synthesize notificationCallbackId; +@synthesize callback; +@synthesize clearBadge; +@synthesize handlerObj; + +- (void)unregister:(CDVInvokedUrlCommand*)command; +{ + self.callbackId = command.callbackId; + + [[UIApplication sharedApplication] unregisterForRemoteNotifications]; + [self successWithMessage:@"unregistered"]; +} + +- (void)init:(CDVInvokedUrlCommand*)command; +{ + [self.commandDelegate runInBackground:^ { + + NSLog(@"Push Plugin register called"); + self.callbackId = command.callbackId; + + NSMutableDictionary* options = [command.arguments objectAtIndex:0]; + NSMutableDictionary* iosOptions = [options objectForKey:@"ios"]; + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 + UIUserNotificationType UserNotificationTypes = UIUserNotificationTypeNone; +#endif + UIRemoteNotificationType notificationTypes = UIRemoteNotificationTypeNone; + + id badgeArg = [iosOptions objectForKey:@"badge"]; + id soundArg = [iosOptions objectForKey:@"sound"]; + id alertArg = [iosOptions objectForKey:@"alert"]; + id clearBadgeArg = [iosOptions objectForKey:@"clearBadge"]; + + if (([badgeArg isKindOfClass:[NSString class]] && [badgeArg isEqualToString:@"true"]) || [badgeArg boolValue]) + { + notificationTypes |= UIRemoteNotificationTypeBadge; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 + UserNotificationTypes |= UIUserNotificationTypeBadge; +#endif + } + + if (([soundArg isKindOfClass:[NSString class]] && [soundArg isEqualToString:@"true"]) || [soundArg boolValue]) + { + notificationTypes |= UIRemoteNotificationTypeSound; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 + UserNotificationTypes |= UIUserNotificationTypeSound; +#endif + } + + if (([alertArg isKindOfClass:[NSString class]] && [alertArg isEqualToString:@"true"]) || [alertArg boolValue]) + { + notificationTypes |= UIRemoteNotificationTypeAlert; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 + UserNotificationTypes |= UIUserNotificationTypeAlert; +#endif + } + + notificationTypes |= UIRemoteNotificationTypeNewsstandContentAvailability; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 + UserNotificationTypes |= UIUserNotificationActivationModeBackground; +#endif + + if (clearBadgeArg == nil || ([clearBadgeArg isKindOfClass:[NSString class]] && [clearBadgeArg isEqualToString:@"false"]) || ![clearBadgeArg boolValue]) { + NSLog(@"PushPlugin.register: setting badge to false"); + clearBadge = NO; + } else { + NSLog(@"PushPlugin.register: setting badge to true"); + clearBadge = YES; + [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; + } + NSLog(@"PushPlugin.register: clear badge is set to %d", clearBadge); + + if (notificationTypes == UIRemoteNotificationTypeNone) + NSLog(@"PushPlugin.register: Push notification type is set to none"); + + isInline = NO; + +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000 + if ([[UIApplication sharedApplication]respondsToSelector:@selector(registerUserNotificationSettings:)]) { + UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UserNotificationTypes categories:nil]; + [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; + [[UIApplication sharedApplication] registerForRemoteNotifications]; + } else { + [[UIApplication sharedApplication] registerForRemoteNotificationTypes: + (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)]; + } +#else + [[UIApplication sharedApplication] registerForRemoteNotificationTypes: + (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)]; +#endif + + if (notificationMessage) // if there is a pending startup notification + [self notificationReceived]; // go ahead and process it + + }]; +} + +- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { + if (self.callbackId == nil) { + NSLog(@"Unexpected call to didRegisterForRemoteNotificationsWithDeviceToken, ignoring: %@", deviceToken); + return; + } + NSLog(@"Push Plugin register success: %@", deviceToken); + + NSMutableDictionary *results = [NSMutableDictionary dictionary]; + NSString *token = [[[[deviceToken description] stringByReplacingOccurrencesOfString:@"<"withString:@""] + stringByReplacingOccurrencesOfString:@">" withString:@""] + stringByReplacingOccurrencesOfString: @" " withString: @""]; + [results setValue:token forKey:@"deviceToken"]; + +#if !TARGET_IPHONE_SIMULATOR + // Get Bundle Info for Remote Registration (handy if you have more than one app) + [results setValue:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"] forKey:@"appName"]; + [results setValue:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] forKey:@"appVersion"]; + + // Check what Notifications the user has turned on. We registered for all three, but they may have manually disabled some or all of them. +#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) + + NSUInteger rntypes; + if (!SYSTEM_VERSION_LESS_THAN(@"8.0")) { + rntypes = [[[UIApplication sharedApplication] currentUserNotificationSettings] types]; + } else { + rntypes = [[UIApplication sharedApplication] enabledRemoteNotificationTypes]; + } + + // Set the defaults to disabled unless we find otherwise... + NSString *pushBadge = @"disabled"; + NSString *pushAlert = @"disabled"; + NSString *pushSound = @"disabled"; + + // Check what Registered Types are turned on. This is a bit tricky since if two are enabled, and one is off, it will return a number 2... not telling you which + // one is actually disabled. So we are literally checking to see if rnTypes matches what is turned on, instead of by number. The "tricky" part is that the + // single notification types will only match if they are the ONLY one enabled. Likewise, when we are checking for a pair of notifications, it will only be + // true if those two notifications are on. This is why the code is written this way + if(rntypes & UIRemoteNotificationTypeBadge){ + pushBadge = @"enabled"; + } + if(rntypes & UIRemoteNotificationTypeAlert) { + pushAlert = @"enabled"; + } + if(rntypes & UIRemoteNotificationTypeSound) { + pushSound = @"enabled"; + } + + [results setValue:pushBadge forKey:@"pushBadge"]; + [results setValue:pushAlert forKey:@"pushAlert"]; + [results setValue:pushSound forKey:@"pushSound"]; + + // Get the users Device Model, Display Name, Token & Version Number + UIDevice *dev = [UIDevice currentDevice]; + [results setValue:dev.name forKey:@"deviceName"]; + [results setValue:dev.model forKey:@"deviceModel"]; + [results setValue:dev.systemVersion forKey:@"deviceSystemVersion"]; + + // Send result to trigger 'registration' event but keep callback + NSMutableDictionary* message = [NSMutableDictionary dictionaryWithCapacity:1]; + [message setObject:token forKey:@"registrationId"]; + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:message]; + [pluginResult setKeepCallbackAsBool:YES]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; +#endif +} + +- (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error +{ + if (self.callbackId == nil) { + NSLog(@"Unexpected call to didFailToRegisterForRemoteNotificationsWithError, ignoring: %@", error); + return; + } + NSLog(@"Push Plugin register failed"); + [self failWithMessage:@"" withError:error]; +} + +- (void)notificationReceived { + NSLog(@"Notification received"); + + if (notificationMessage && self.callbackId != nil) + { + NSMutableDictionary* message = [NSMutableDictionary dictionaryWithCapacity:4]; + NSMutableDictionary* additionalData = [NSMutableDictionary dictionaryWithCapacity:4]; + + + for (id key in notificationMessage) { + if ([key isEqualToString:@"aps"]) { + id aps = [notificationMessage objectForKey:@"aps"]; + + for(id key in aps) { + NSLog(@"Push Plugin key: %@", key); + id value = [aps objectForKey:key]; + + if ([key isEqualToString:@"alert"]) { + if ([value isKindOfClass:[NSDictionary class]]) { + for (id messageKey in value) { + id messageValue = [value objectForKey:messageKey]; + if ([messageKey isEqualToString:@"body"]) { + [message setObject:messageValue forKey:@"message"]; + } else if ([messageKey isEqualToString:@"title"]) { + [message setObject:messageValue forKey:@"title"]; + } else { + [additionalData setObject:messageValue forKey:messageKey]; + } + } + } + else { + [message setObject:value forKey:@"message"]; + } + } else if ([key isEqualToString:@"title"]) { + [message setObject:value forKey:@"title"]; + } else if ([key isEqualToString:@"badge"]) { + [message setObject:value forKey:@"count"]; + } else if ([key isEqualToString:@"sound"]) { + [message setObject:value forKey:@"sound"]; + } else if ([key isEqualToString:@"image"]) { + [message setObject:value forKey:@"image"]; + } else { + [additionalData setObject:value forKey:key]; + } + } + } else { + [additionalData setObject:[notificationMessage objectForKey:key] forKey:key]; + } + } + + if (isInline) { + [additionalData setObject:[NSNumber numberWithBool:YES] forKey:@"foreground"]; + } else { + [additionalData setObject:[NSNumber numberWithBool:NO] forKey:@"foreground"]; + } + + [message setObject:additionalData forKey:@"additionalData"]; + + // send notification message + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:message]; + [pluginResult setKeepCallbackAsBool:YES]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:self.callbackId]; + + self.notificationMessage = nil; + } +} + +- (void)setApplicationIconBadgeNumber:(CDVInvokedUrlCommand *)command +{ + NSMutableDictionary* options = [command.arguments objectAtIndex:0]; + int badge = [[options objectForKey:@"badge"] intValue] ?: 0; + + [[UIApplication sharedApplication] setApplicationIconBadgeNumber:badge]; + + NSString* message = [NSString stringWithFormat:@"app badge count set to %d", badge]; + CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message]; + [self.commandDelegate sendPluginResult:commandResult callbackId:command.callbackId]; +} + +- (void)getApplicationIconBadgeNumber:(CDVInvokedUrlCommand *)command +{ + NSInteger badge = [UIApplication sharedApplication].applicationIconBadgeNumber; + + CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:(int)badge]; + [self.commandDelegate sendPluginResult:commandResult callbackId:command.callbackId]; +} + +-(void)successWithMessage:(NSString *)message +{ + if (self.callbackId != nil) + { + CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message]; + [self.commandDelegate sendPluginResult:commandResult callbackId:self.callbackId]; + } +} + +-(void)failWithMessage:(NSString *)message withError:(NSError *)error +{ + NSString *errorMessage = (error) ? [NSString stringWithFormat:@"%@ - %@", message, [error localizedDescription]] : message; + CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage]; + + [self.commandDelegate sendPluginResult:commandResult callbackId:self.callbackId]; +} + +-(void) finish:(CDVInvokedUrlCommand*)command +{ + NSLog(@"Push Plugin finish called"); + + [self.commandDelegate runInBackground:^ { + UIApplication *app = [UIApplication sharedApplication]; + float finishTimer = (app.backgroundTimeRemaining > 20.0) ? 20.0 : app.backgroundTimeRemaining; + + [NSTimer scheduledTimerWithTimeInterval:finishTimer + target:self + selector:@selector(stopBackgroundTask:) + userInfo:nil + repeats:NO]; + + CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]; +} + +-(void)stopBackgroundTask:(NSTimer*)timer +{ + UIApplication *app = [UIApplication sharedApplication]; + + NSLog(@"Push Plugin stopBackgroundTask called"); + + if (handlerObj) { + NSLog(@"Push Plugin handlerObj"); + completionHandler = [handlerObj[@"handler"] copy]; + if (completionHandler) { + NSLog(@"Push Plugin: stopBackgroundTask (remaining t: %f)", app.backgroundTimeRemaining); + completionHandler(UIBackgroundFetchResultNewData); + completionHandler = nil; + } + } +} + +@end diff --git a/StoneIsland/plugins/phonegap-plugin-push/src/windows/PushPluginProxy.js b/StoneIsland/plugins/phonegap-plugin-push/src/windows/PushPluginProxy.js new file mode 100644 index 00000000..ca8087b2 --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-push/src/windows/PushPluginProxy.js @@ -0,0 +1,86 @@ +var myApp = {}; +var pushNotifications = Windows.Networking.PushNotifications; + +var createNotificationJSON = function (e) { + var result = { message: '' }; //Added to identify callback as notification type in the API in case where notification has no message + var notificationPayload; + + switch (e.notificationType) { + case pushNotifications.PushNotificationType.toast: + case pushNotifications.PushNotificationType.tile: + if (e.notificationType === pushNotifications.PushNotificationType.toast) { + notificationPayload = e.toastNotification.content; + } + else { + notificationPayload = e.tileNotification.content; + } + var texts = notificationPayload.getElementsByTagName("text"); + if (texts.length > 1) { + result.title = texts[0].innerText; + result.message = texts[1].innerText; + } + else if(texts.length === 1) { + result.message = texts[0].innerText; + } + var images = notificationPayload.getElementsByTagName("image"); + if (images.length > 0) { + result.image = images[0].getAttribute("src"); + } + var soundFile = notificationPayload.getElementsByTagName("audio"); + if (soundFile.length > 0) { + result.sound = soundFile[0].getAttribute("src"); + } + break; + + case pushNotifications.PushNotificationType.badge: + notificationPayload = e.badgeNotification.content; + result.count = notificationPayload.getElementsByTagName("badge")[0].getAttribute("value"); + break; + + case pushNotifications.PushNotificationType.raw: + result.message = e.rawNotification.content; + break; + } + + result.additionalData = {}; + result.additionalData.pushNotificationReceivedEventArgs = e; + return result; +} + +module.exports = { + init: function (onSuccess, onFail, args) { + + var onNotificationReceived = function (e) { + var result = createNotificationJSON(e); + onSuccess(result, { keepCallback: true }); + } + + try { + pushNotifications.PushNotificationChannelManager.createPushNotificationChannelForApplicationAsync().done( + function (channel) { + var result = {}; + result.registrationId = channel.uri; + myApp.channel = channel; + channel.addEventListener("pushnotificationreceived", onNotificationReceived); + myApp.notificationEvent = onNotificationReceived; + onSuccess(result, { keepCallback: true }); + }, function (error) { + onFail(error); + }); + } catch (ex) { + onFail(ex); + } + }, + unregister: function (onSuccess, onFail, args) { + try { + myApp.channel.removeEventListener("pushnotificationreceived", myApp.notificationEvent); + myApp.channel.close(); + onSuccess(); + } catch(ex) { + onFail(ex); + } + } +}; +require("cordova/exec/proxy").add("PushNotification", module.exports); + + |
