summaryrefslogtreecommitdiff
path: root/StoneIsland/plugins/phonegap-plugin-push/src
diff options
context:
space:
mode:
authorJules Laplace <jules@okfoc.us>2017-02-16 01:24:12 +0100
committerJules Laplace <jules@okfoc.us>2017-02-16 01:24:12 +0100
commit30c49550c89c1b69c680170d2dc247eac76bd463 (patch)
tree8732652298b630b9ba15def97e59738f1c9bf7b6 /StoneIsland/plugins/phonegap-plugin-push/src
parent8f1f626384e6ba75f4fb24c27e0973260a74421b (diff)
push plugin
Diffstat (limited to 'StoneIsland/plugins/phonegap-plugin-push/src')
-rw-r--r--StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/BackgroundActionButtonHandler.java41
-rw-r--r--StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/GCMIntentService.java802
-rw-r--r--StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PermissionUtils.java55
-rw-r--r--StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushConstants.java72
-rw-r--r--StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushHandlerActivity.java120
-rw-r--r--StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushInstanceIDListenerService.java27
-rw-r--r--StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushPlugin.java458
-rw-r--r--StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/RegistrationIntentService.java38
-rw-r--r--StoneIsland/plugins/phonegap-plugin-push/src/browser/ServiceWorker.js51
-rw-r--r--StoneIsland/plugins/phonegap-plugin-push/src/browser/manifest.json4
-rw-r--r--StoneIsland/plugins/phonegap-plugin-push/src/ios/AppDelegate+notification.h22
-rw-r--r--StoneIsland/plugins/phonegap-plugin-push/src/ios/AppDelegate+notification.m278
-rw-r--r--StoneIsland/plugins/phonegap-plugin-push/src/ios/PushPlugin.h80
-rw-r--r--StoneIsland/plugins/phonegap-plugin-push/src/ios/PushPlugin.m677
-rw-r--r--StoneIsland/plugins/phonegap-plugin-push/src/windows/PushPluginProxy.js93
15 files changed, 2818 insertions, 0 deletions
diff --git a/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/BackgroundActionButtonHandler.java b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/BackgroundActionButtonHandler.java
new file mode 100644
index 00000000..3ccea6cb
--- /dev/null
+++ b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/BackgroundActionButtonHandler.java
@@ -0,0 +1,41 @@
+package com.adobe.phonegap.push;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.support.v4.app.RemoteInput;
+
+public class BackgroundActionButtonHandler extends BroadcastReceiver implements PushConstants {
+ private static String LOG_TAG = "PushPlugin_BackgroundActionButtonHandler";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Bundle extras = intent.getExtras();
+ Log.d(LOG_TAG, "BackgroundActionButtonHandler = " + extras);
+
+ int notId = intent.getIntExtra(NOT_ID, 0);
+ Log.d(LOG_TAG, "not id = " + notId);
+ NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(GCMIntentService.getAppName(context), notId);
+
+ if (extras != null) {
+ Bundle originalExtras = extras.getBundle(PUSH_BUNDLE);
+
+ originalExtras.putBoolean(FOREGROUND, false);
+ originalExtras.putBoolean(COLDSTART, false);
+ originalExtras.putString(ACTION_CALLBACK, extras.getString(CALLBACK));
+
+ Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
+ if (remoteInput != null) {
+ String inputString = remoteInput.getCharSequence(INLINE_REPLY).toString();
+ Log.d(LOG_TAG, "response: " + inputString);
+ originalExtras.putString(INLINE_REPLY, inputString);
+ }
+
+ PushPlugin.sendExtras(originalExtras);
+ }
+ }
+}
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..e1a2b75c
--- /dev/null
+++ b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/GCMIntentService.java
@@ -0,0 +1,802 @@
+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.support.v4.app.NotificationManagerCompat;
+import android.support.v4.app.NotificationCompat.WearableExtender;
+import android.support.v4.app.RemoteInput;
+import android.text.Html;
+import android.text.Spanned;
+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) {
+ Context applicationContext = getApplicationContext();
+
+ SharedPreferences prefs = applicationContext.getSharedPreferences(PushPlugin.COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
+ boolean forceShow = prefs.getBoolean(FORCE_SHOW, false);
+ boolean clearBadge = prefs.getBoolean(CLEAR_BADGE, false);
+
+ extras = normalizeExtras(applicationContext, extras);
+
+ if (clearBadge) {
+ PushPlugin.setApplicationIconBadgeNumber(getApplicationContext(), 0);
+ }
+
+ // 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);
+ extras.putBoolean(COLDSTART, false);
+ 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);
+ extras.putBoolean(COLDSTART, false);
+
+ showNotificationIfPossible(applicationContext, 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);
+ extras.putBoolean(COLDSTART, PushPlugin.isActive());
+
+ showNotificationIfPossible(applicationContext, extras);
+ }
+ }
+ }
+
+ /*
+ * Change a values key in the extras bundle
+ */
+ private void replaceKey(Context context, String oldKey, String newKey, Bundle extras, Bundle newExtras) {
+ Object value = extras.get(oldKey);
+ if ( value != null ) {
+ if (value instanceof String) {
+ value = localizeKey(context, newKey, (String) value);
+
+ 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));
+ }
+ }
+ }
+
+ /*
+ * Normalize localization for key
+ */
+ private String localizeKey(Context context, String key, String value) {
+ if (key.equals(TITLE) || key.equals(MESSAGE) || key.equals(SUMMARY_TEXT)) {
+ try {
+ JSONObject localeObject = new JSONObject(value);
+
+ String localeKey = localeObject.getString(LOC_KEY);
+
+ ArrayList<String> localeFormatData = new ArrayList<String>();
+ if (!localeObject.isNull(LOC_DATA)) {
+ String localeData = localeObject.getString(LOC_DATA);
+ JSONArray localeDataArray = new JSONArray(localeData);
+ for (int i = 0 ; i < localeDataArray.length(); i++) {
+ localeFormatData.add(localeDataArray.getString(i));
+ }
+ }
+
+ String packageName = context.getPackageName();
+ Resources resources = context.getResources();
+
+ int resourceId = resources.getIdentifier(localeKey, "string", packageName);
+
+ if (resourceId != 0) {
+ return resources.getString(resourceId, localeFormatData.toArray());
+ }
+ else {
+ Log.d(LOG_TAG, "can't find resource for locale key = " + localeKey);
+
+ return value;
+ }
+ }
+ catch(JSONException e) {
+ Log.d(LOG_TAG, "no locale found for key = " + key + ", error " + e.getMessage());
+
+ return value;
+ }
+ }
+
+ return 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) || key.equals(TWILIO_BODY)) {
+ return MESSAGE;
+ } else if (key.equals(TWILIO_TITLE)) {
+ return TITLE;
+ }else if (key.equals(MSGCNT) || key.equals(BADGE)) {
+ return COUNT;
+ } else if (key.equals(SOUNDNAME) || key.equals(TWILIO_SOUND)) {
+ 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(Context context, 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);
+ value = localizeKey(context, jsonKey, value);
+
+ 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);
+
+ String valueData = value.getString(notifkey);
+ valueData = localizeKey(context, newKey, valueData);
+
+ newExtras.putString(newKey, valueData);
+ }
+ continue;
+ }
+
+ String newKey = normalizeKey(key);
+ Log.d(LOG_TAG, "replace key " + key + " with " + newKey);
+ replaceKey(context, key, newKey, extras, newExtras);
+
+ } // while
+
+ return newExtras;
+ }
+
+ private int extractBadgeCount(Bundle extras) {
+ int count = -1;
+ String msgcnt = extras.getString(COUNT);
+
+ try {
+ if (msgcnt != null) {
+ count = Integer.parseInt(msgcnt);
+ }
+ } catch (NumberFormatException e) {
+ Log.e(LOG_TAG, e.getLocalizedMessage(), e);
+ }
+
+ return count;
+ }
+
+ 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);
+ String contentAvailable = extras.getString(CONTENT_AVAILABLE);
+ String forceStart = extras.getString(FORCE_START);
+ int badgeCount = extractBadgeCount(extras);
+ if (badgeCount >= 0) {
+ Log.d(LOG_TAG, "count =[" + badgeCount + "]");
+ PushPlugin.setApplicationIconBadgeNumber(context, badgeCount);
+ }
+
+ Log.d(LOG_TAG, "message =[" + message + "]");
+ Log.d(LOG_TAG, "title =[" + title + "]");
+ Log.d(LOG_TAG, "contentAvailable =[" + contentAvailable + "]");
+ Log.d(LOG_TAG, "forceStart =[" + forceStart + "]");
+
+ if ((message != null && message.length() != 0) ||
+ (title != null && title.length() != 0)) {
+
+ Log.d(LOG_TAG, "create notification");
+
+ if(title == null || title.isEmpty()){
+ extras.putString(TITLE, getAppName(this));
+ }
+
+ createNotification(context, extras);
+ }
+
+ if(!PushPlugin.isActive() && "1".equals(forceStart)){
+ Log.d(LOG_TAG, "app is not running but we should start it and put in background");
+ Intent intent = new Intent(this, PushHandlerActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(PUSH_BUNDLE, extras);
+ intent.putExtra(START_IN_BACKGROUND, true);
+ intent.putExtra(FOREGROUND, false);
+ startActivity(intent);
+ } else if ("1".equals(contentAvailable)) {
+ Log.d(LOG_TAG, "app is not running and content available true");
+ 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(fromHtml(extras.getString(TITLE)))
+ .setTicker(fromHtml(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(context, extras, mBuilder);
+
+ /*
+ * Notification count
+ */
+ setVisibility(context, extras, mBuilder);
+
+ /*
+ * Notification add actions
+ */
+ createActions(extras, mBuilder, resources, packageName, notId);
+
+ mNotificationManager.notify(appName, notId, mBuilder.build());
+ }
+
+ private void updateIntent(Intent intent, String callback, Bundle extras, boolean foreground, int notId) {
+ intent.putExtra(CALLBACK, callback);
+ intent.putExtra(PUSH_BUNDLE, extras);
+ intent.putExtra(FOREGROUND, foreground);
+ intent.putExtra(NOT_ID, notId);
+ }
+
+ private void createActions(Bundle extras, NotificationCompat.Builder mBuilder, Resources resources, String packageName, int notId) {
+ Log.d(LOG_TAG, "create actions: with in-line");
+ String actions = extras.getString(ACTIONS);
+ if (actions != null) {
+ try {
+ JSONArray actionsArray = new JSONArray(actions);
+ ArrayList<NotificationCompat.Action> wActions = new ArrayList<NotificationCompat.Action>();
+ for (int i=0; i < actionsArray.length(); i++) {
+ int min = 1;
+ int max = 2000000000;
+ Random random = new Random();
+ int uniquePendingIntentRequestCode = random.nextInt((max - min) + 1) + min;
+ Log.d(LOG_TAG, "adding action");
+ JSONObject action = actionsArray.getJSONObject(i);
+ Log.d(LOG_TAG, "adding callback = " + action.getString(CALLBACK));
+ boolean foreground = action.optBoolean(FOREGROUND, true);
+ boolean inline = action.optBoolean("inline", false);
+ Intent intent = null;
+ PendingIntent pIntent = null;
+ if (inline) {
+ Log.d(LOG_TAG, "Version: " + android.os.Build.VERSION.SDK_INT + " = " + android.os.Build.VERSION_CODES.M);
+ if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.M) {
+ Log.d(LOG_TAG, "push activity");
+ intent = new Intent(this, PushHandlerActivity.class);
+ } else {
+ Log.d(LOG_TAG, "push receiver");
+ intent = new Intent(this, BackgroundActionButtonHandler.class);
+ }
+
+ updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId);
+
+ if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.M) {
+ Log.d(LOG_TAG, "push activity for notId " + notId);
+ pIntent = PendingIntent.getActivity(this, uniquePendingIntentRequestCode, intent, PendingIntent.FLAG_ONE_SHOT);
+ } else {
+ Log.d(LOG_TAG, "push receiver for notId " + notId);
+ pIntent = PendingIntent.getBroadcast(this, uniquePendingIntentRequestCode, intent, PendingIntent.FLAG_ONE_SHOT);
+ }
+ } else if (foreground) {
+ intent = new Intent(this, PushHandlerActivity.class);
+ updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId);
+ pIntent = PendingIntent.getActivity(this, uniquePendingIntentRequestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ } else {
+ intent = new Intent(this, BackgroundActionButtonHandler.class);
+ updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId);
+ pIntent = PendingIntent.getBroadcast(this, uniquePendingIntentRequestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ NotificationCompat.Action.Builder actionBuilder =
+ new NotificationCompat.Action.Builder(resources.getIdentifier(action.optString(ICON, ""), DRAWABLE, packageName),
+ action.getString(TITLE), pIntent);
+
+ RemoteInput remoteInput = null;
+ if (inline) {
+ Log.d(LOG_TAG, "create remote input");
+ String replyLabel = "Enter your reply here";
+ remoteInput =
+ new RemoteInput.Builder(INLINE_REPLY)
+ .setLabel(replyLabel)
+ .build();
+ actionBuilder.addRemoteInput(remoteInput);
+ }
+
+ NotificationCompat.Action wAction = actionBuilder.build();
+ wActions.add(actionBuilder.build());
+
+ if (inline) {
+ mBuilder.addAction(wAction);
+ } else {
+ mBuilder.addAction(resources.getIdentifier(action.optString(ICON, ""), DRAWABLE, packageName),
+ action.getString(TITLE), pIntent);
+ }
+ wAction = null;
+ pIntent = null;
+ }
+ mBuilder.extend(new WearableExtender().addActions(wActions));
+ wActions.clear();
+ } catch(JSONException e) {
+ // nope
+ }
+ }
+ }
+
+ private void setNotificationCount(Context context, Bundle extras, NotificationCompat.Builder mBuilder) {
+ int count = extractBadgeCount(extras);
+ if (count >= 0) {
+ Log.d(LOG_TAG, "count =[" + count + "]");
+ mBuilder.setNumber(count);
+ }
+ }
+
+
+ private void setVisibility(Context context, Bundle extras, NotificationCompat.Builder mBuilder) {
+ String visibilityStr = extras.getString(VISIBILITY);
+ if (visibilityStr != null) {
+ try {
+ Integer visibility = Integer.parseInt(visibilityStr);
+ if (visibility >= NotificationCompat.VISIBILITY_SECRET && visibility <= NotificationCompat.VISIBILITY_PUBLIC) {
+ mBuilder.setVisibility(visibility);
+ } else {
+ Log.e(LOG_TAG, "Visibility parameter must be between -1 and 1");
+ }
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ 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].trim());
+ } 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(fromHtml(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(fromHtml(extras.getString(TITLE)))
+ .setSummaryText(fromHtml(stacking));
+
+ for (int i = messageList.size() - 1; i >= 0; i--) {
+ notificationInbox.addLine(fromHtml(messageList.get(i)));
+ }
+
+ mBuilder.setStyle(notificationInbox);
+ } else {
+ NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle();
+ if (message != null) {
+ bigText.bigText(fromHtml(message));
+ bigText.setBigContentTitle(fromHtml(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(fromHtml(extras.getString(TITLE)));
+ bigPicture.setSummaryText(fromHtml(extras.getString(SUMMARY_TEXT)));
+
+ mBuilder.setContentTitle(fromHtml(extras.getString(TITLE)));
+ mBuilder.setContentText(fromHtml(message));
+
+ mBuilder.setStyle(bigPicture);
+ } else {
+ setNotification(notId, "");
+
+ NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle();
+
+ if (message != null) {
+ mBuilder.setContentText(fromHtml(message));
+
+ bigText.bigText(fromHtml(message));
+ bigText.setBigContentTitle(fromHtml(extras.getString(TITLE)));
+
+ String summaryText = extras.getString(SUMMARY_TEXT);
+ if (summaryText != null) {
+ bigText.setSummaryText(fromHtml(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 (SOUND_RINGTONE.equals(soundname)) {
+ mBuilder.setSound(android.provider.Settings.System.DEFAULT_RINGTONE_URI);
+ } else 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].trim());
+ } 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 && !"".equals(gcmLargeIcon)) {
+ 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 && !"".equals(icon)) {
+ iconId = resources.getIdentifier(icon, DRAWABLE, packageName);
+ Log.d(LOG_TAG, "using icon from plugin options");
+ }
+ else if (localIcon != null && !"".equals(localIcon)) {
+ 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 && !"".equals(color)) {
+ try {
+ iconColor = Color.parseColor(color);
+ } catch (IllegalArgumentException e) {
+ Log.e(LOG_TAG, "couldn't parse color from android options");
+ }
+ }
+ else if (localIconColor != null && !"".equals(localIconColor)) {
+ 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;
+ }
+ }
+
+ public 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;
+ }
+
+ private Spanned fromHtml(String source) {
+ if (source != null)
+ return Html.fromHtml(source);
+ else
+ return null;
+ }
+}
diff --git a/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PermissionUtils.java b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PermissionUtils.java
new file mode 100644
index 00000000..6aa5c9bf
--- /dev/null
+++ b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PermissionUtils.java
@@ -0,0 +1,55 @@
+package com.adobe.phonegap.push;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class PermissionUtils {
+
+ private static final String CHECK_OP_NO_THROW = "checkOpNoThrow";
+
+ public static boolean hasPermission(Context appContext, String appOpsServiceId) throws UnknownError {
+
+ ApplicationInfo appInfo = appContext.getApplicationInfo();
+
+ String pkg = appContext.getPackageName();
+ int uid = appInfo.uid;
+ Class appOpsClass = null;
+ Object appOps = appContext.getSystemService("appops");
+
+ try {
+
+ appOpsClass = Class.forName("android.app.AppOpsManager");
+
+ Method checkOpNoThrowMethod = appOpsClass.getMethod(
+ CHECK_OP_NO_THROW,
+ Integer.TYPE,
+ Integer.TYPE,
+ String.class
+ );
+
+ Field opValue = appOpsClass.getDeclaredField(appOpsServiceId);
+
+ int value = (int) opValue.getInt(Integer.class);
+ Object result = checkOpNoThrowMethod.invoke(appOps, value, uid, pkg);
+
+ return Integer.parseInt(result.toString()) == 0; // AppOpsManager.MODE_ALLOWED
+
+ } catch (ClassNotFoundException e) {
+ throw new UnknownError("class not found");
+ } catch (NoSuchMethodException e) {
+ throw new UnknownError("no such method");
+ } catch (NoSuchFieldException e) {
+ throw new UnknownError("no such field");
+ } catch (InvocationTargetException e) {
+ throw new UnknownError("invocation target");
+ } catch (IllegalAccessException e) {
+ throw new UnknownError("illegal access");
+ }
+
+ }
+
+}
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..37874e04
--- /dev/null
+++ b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushConstants.java
@@ -0,0 +1,72 @@
+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 SOUND_RINGTONE = "ringtone";
+ public static final String VIBRATE = "vibrate";
+ public static final String ACTIONS = "actions";
+ public static final String CALLBACK = "callback";
+ public static final String ACTION_CALLBACK = "actionCallback";
+ 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 SUBSCRIBE = "subscribe";
+ public static final String UNSUBSCRIBE = "unsubscribe";
+ public static final String UNREGISTER = "unregister";
+ public static final String EXIT = "exit";
+ public static final String FINISH = "finish";
+ public static final String HAS_PERMISSION = "hasPermission";
+ public static final String ANDROID = "android";
+ public static final String SENDER_ID = "senderID";
+ public static final String CLEAR_BADGE = "clearBadge";
+ 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";
+ public static final String CONTENT_AVAILABLE = "content-available";
+ public static final String TOPICS = "topics";
+ public static final String SET_APPLICATION_ICON_BADGE_NUMBER = "setApplicationIconBadgeNumber";
+ public static final String CLEAR_ALL_NOTIFICATIONS = "clearAllNotifications";
+ public static final String VISIBILITY = "visibility";
+ public static final String INLINE_REPLY = "inlineReply";
+ public static final String LOC_KEY = "locKey";
+ public static final String LOC_DATA = "locData";
+ public static final String TWILIO_BODY = "twi_body";
+ public static final String TWILIO_TITLE = "twi_title";
+ public static final String TWILIO_SOUND = "twi_sound";
+ public static final String START_IN_BACKGROUND = "cdvStartInBackground";
+ public static final String FORCE_START = "force-start";
+}
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..23682ac8
--- /dev/null
+++ b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushHandlerActivity.java
@@ -0,0 +1,120 @@
+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;
+import android.support.v4.app.RemoteInput;
+
+
+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();
+
+ Intent intent = getIntent();
+
+ int notId = intent.getExtras().getInt(NOT_ID, 0);
+ Log.d(LOG_TAG, "not id = " + notId);
+ gcm.setNotification(notId, "");
+ super.onCreate(savedInstanceState);
+ Log.v(LOG_TAG, "onCreate");
+ String callback = getIntent().getExtras().getString("callback");
+ Log.d(LOG_TAG, "callback = " + callback);
+ boolean foreground = getIntent().getExtras().getBoolean("foreground", true);
+ boolean startOnBackground = getIntent().getExtras().getBoolean(START_IN_BACKGROUND, false);
+
+ if(!startOnBackground){
+ NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(GCMIntentService.getAppName(this), notId);
+ }
+
+ boolean isPushPluginActive = PushPlugin.isActive();
+ boolean inline = processPushBundle(isPushPluginActive, intent);
+
+ if(inline && android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.N){
+ foreground = true;
+ }
+
+ Log.d(LOG_TAG, "bringToForeground = " + foreground);
+
+ finish();
+
+ Log.d(LOG_TAG, "isPushPluginActive = " + isPushPluginActive);
+ if (!isPushPluginActive && foreground && inline) {
+ Log.d(LOG_TAG, "forceMainActivityReload");
+ forceMainActivityReload(false);
+ } else if(startOnBackground) {
+ Log.d(LOG_TAG, "startOnBackgroundTrue");
+ forceMainActivityReload(true);
+ } else {
+ Log.d(LOG_TAG, "don't want main activity");
+ }
+ }
+
+ /**
+ * Takes the pushBundle extras from the intent,
+ * and sends it through to the PushPlugin for processing.
+ */
+ private boolean processPushBundle(boolean isPushPluginActive, Intent intent) {
+ Bundle extras = getIntent().getExtras();
+ Bundle remoteInput = null;
+
+ if (extras != null) {
+ Bundle originalExtras = extras.getBundle(PUSH_BUNDLE);
+
+ originalExtras.putBoolean(FOREGROUND, false);
+ originalExtras.putBoolean(COLDSTART, !isPushPluginActive);
+ originalExtras.putString(ACTION_CALLBACK, extras.getString(CALLBACK));
+
+ remoteInput = RemoteInput.getResultsFromIntent(intent);
+ if (remoteInput != null) {
+ String inputString = remoteInput.getCharSequence(INLINE_REPLY).toString();
+ Log.d(LOG_TAG, "response: " + inputString);
+ originalExtras.putString(INLINE_REPLY, inputString);
+ }
+
+ PushPlugin.sendExtras(originalExtras);
+ }
+ return remoteInput == null;
+ }
+
+ /**
+ * Forces the main activity to re-launch if it's unloaded.
+ */
+ private void forceMainActivityReload(boolean startOnBackground) {
+ PackageManager pm = getPackageManager();
+ Intent launchIntent = pm.getLaunchIntentForPackage(getApplicationContext().getPackageName());
+
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ Bundle originalExtras = extras.getBundle(PUSH_BUNDLE);
+ if (originalExtras != null) {
+ launchIntent.putExtras(originalExtras);
+ }
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ launchIntent.addFlags(Intent.FLAG_FROM_BACKGROUND);
+ launchIntent.putExtra(START_IN_BACKGROUND, startOnBackground);
+ }
+
+ startActivity(launchIntent);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ final NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancelAll();
+ }
+}
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..f6faaa2b
--- /dev/null
+++ b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/PushPlugin.java
@@ -0,0 +1,458 @@
+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.gcm.GcmPubSub;
+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;
+import java.util.ArrayList;
+import java.util.List;
+
+import me.leolin.shortcutbadger.ShortcutBadger;
+
+public class PushPlugin extends CordovaPlugin implements PushConstants {
+
+ public static final String LOG_TAG = "PushPlugin";
+
+ private static CallbackContext pushContext;
+ private static CordovaWebView gWebView;
+ private static List<Bundle> gCachedExtras = Collections.synchronizedList(new ArrayList<Bundle>());
+ private static boolean gForeground = false;
+
+ private static String registration_id = "";
+
+ /**
+ * 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 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, "");
+ registration_id = InstanceID.getInstance(getApplicationContext()).getToken(senderID, GCM);
+
+ if (!"".equals(registration_id)) {
+ JSONObject json = new JSONObject().put(REGISTRATION_ID, registration_id);
+
+ Log.v(LOG_TAG, "onRegistered: " + json.toString());
+
+ JSONArray topics = jo.optJSONArray(TOPICS);
+ subscribeToTopics(topics, registration_id);
+
+ 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");
+ }
+
+ boolean clearBadge = jo.optBoolean(CLEAR_BADGE, false);
+ if (clearBadge) {
+ setApplicationIconBadgeNumber(getApplicationContext(), 0);
+ }
+
+ editor.putBoolean(SOUND, jo.optBoolean(SOUND, true));
+ editor.putBoolean(VIBRATE, jo.optBoolean(VIBRATE, true));
+ editor.putBoolean(CLEAR_BADGE, clearBadge);
+ editor.putBoolean(CLEAR_NOTIFICATIONS, jo.optBoolean(CLEAR_NOTIFICATIONS, true));
+ editor.putBoolean(FORCE_SHOW, jo.optBoolean(FORCE_SHOW, false));
+ editor.putString(SENDER_ID, senderID);
+ editor.commit();
+
+ }
+
+ if (!gCachedExtras.isEmpty()) {
+ Log.v(LOG_TAG, "sending cached extras");
+ synchronized(gCachedExtras) {
+ Iterator<Bundle> gCachedExtrasIterator = gCachedExtras.iterator();
+ while (gCachedExtrasIterator.hasNext()) {
+ sendExtras(gCachedExtrasIterator.next());
+ }
+ }
+ gCachedExtras.clear();
+ }
+ }
+ });
+ } else if (UNREGISTER.equals(action)) {
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ try {
+ SharedPreferences sharedPref = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
+ JSONArray topics = data.optJSONArray(0);
+ if (topics != null && !"".equals(registration_id)) {
+ unsubscribeFromTopics(topics, registration_id);
+ } else {
+ InstanceID.getInstance(getApplicationContext()).deleteInstanceID();
+ Log.v(LOG_TAG, "UNREGISTER");
+
+ // Remove shared prefs
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.remove(SOUND);
+ editor.remove(VIBRATE);
+ editor.remove(CLEAR_BADGE);
+ editor.remove(CLEAR_NOTIFICATIONS);
+ editor.remove(FORCE_SHOW);
+ editor.remove(SENDER_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 if (HAS_PERMISSION.equals(action)) {
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ JSONObject jo = new JSONObject();
+ try {
+ jo.put("isEnabled", PermissionUtils.hasPermission(getApplicationContext(), "OP_POST_NOTIFICATION"));
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, jo);
+ pluginResult.setKeepCallback(true);
+ callbackContext.sendPluginResult(pluginResult);
+ } catch (UnknownError e) {
+ callbackContext.error(e.getMessage());
+ } catch (JSONException e) {
+ callbackContext.error(e.getMessage());
+ }
+ }
+ });
+ } else if (SET_APPLICATION_ICON_BADGE_NUMBER.equals(action)) {
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ Log.v(LOG_TAG, "setApplicationIconBadgeNumber: data=" + data.toString());
+ try {
+ setApplicationIconBadgeNumber(getApplicationContext(), data.getJSONObject(0).getInt(BADGE));
+ } catch (JSONException e) {
+ callbackContext.error(e.getMessage());
+ }
+ callbackContext.success();
+ }
+ });
+ } else if (CLEAR_ALL_NOTIFICATIONS.equals(action)) {
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ Log.v(LOG_TAG, "clearAllNotifications");
+ clearAllNotifications();
+ callbackContext.success();
+ }
+ });
+ } else if (SUBSCRIBE.equals(action)){
+ // Subscribing for a topic
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ try {
+ String topic = data.getString(0);
+ subscribeToTopic(topic, registration_id);
+ callbackContext.success();
+ } catch (JSONException e) {
+ callbackContext.error(e.getMessage());
+ } catch (IOException e) {
+ callbackContext.error(e.getMessage());
+ }
+ }
+ });
+ } else if (UNSUBSCRIBE.equals(action)){
+ // un-subscribing for a topic
+ cordova.getThreadPool().execute(new Runnable(){
+ public void run() {
+ try {
+ String topic = data.getString(0);
+ unsubscribeFromTopic(topic, registration_id);
+ callbackContext.success();
+ } catch (JSONException e) {
+ callbackContext.error(e.getMessage());
+ } catch (IOException e) {
+ callbackContext.error(e.getMessage());
+ }
+ }
+ });
+ } 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.add(extras);
+ }
+ }
+ }
+
+ public static void setApplicationIconBadgeNumber(Context context, int badgeCount) {
+ if (badgeCount > 0) {
+ ShortcutBadger.applyCount(context, badgeCount);
+ } else {
+ ShortcutBadger.removeCount(context);
+ }
+ }
+
+ @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)) {
+ clearAllNotifications();
+ }
+ }
+
+ @Override
+ public void onResume(boolean multitasking) {
+ super.onResume(multitasking);
+ gForeground = true;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ gForeground = false;
+ gWebView = null;
+ }
+
+ private void clearAllNotifications() {
+ final NotificationManager notificationManager = (NotificationManager) cordova.getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancelAll();
+ }
+
+ /**
+ * Transform `topic name` to `topic path`
+ * Normally, the `topic` inputed from end-user is `topic name` only.
+ * We should convert them to GCM `topic path`
+ * Example:
+ * when topic name = 'my-topic'
+ * then topic path = '/topics/my-topic'
+ *
+ * @param String topic The topic name
+ * @return The topic path
+ */
+ private String getTopicPath(String topic)
+ {
+ return "/topics/" + topic;
+ }
+
+ private void subscribeToTopics(JSONArray topics, String registrationToken) throws IOException {
+ if (topics != null) {
+ String topic = null;
+ for (int i=0; i<topics.length(); i++) {
+ topic = topics.optString(i, null);
+ subscribeToTopic(topic, registrationToken);
+ }
+ }
+ }
+
+ private void subscribeToTopic(String topic, String registrationToken) throws IOException
+ {
+ try {
+ if (topic != null) {
+ Log.d(LOG_TAG, "Subscribing to topic: " + topic);
+ GcmPubSub.getInstance(getApplicationContext()).subscribe(registrationToken, getTopicPath(topic), null);
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Failed to subscribe to topic: " + topic, e);
+ throw e;
+ }
+ }
+
+ private void unsubscribeFromTopics(JSONArray topics, String registrationToken) {
+ if (topics != null) {
+ String topic = null;
+ for (int i=0; i<topics.length(); i++) {
+ try {
+ topic = topics.optString(i, null);
+ if (topic != null) {
+ Log.d(LOG_TAG, "Unsubscribing to topic: " + topic);
+ GcmPubSub.getInstance(getApplicationContext()).unsubscribe(registrationToken, getTopicPath(topic));
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Failed to unsubscribe to topic: " + topic, e);
+ }
+ }
+ }
+ }
+
+ private void unsubscribeFromTopic(String topic, String registrationToken) throws IOException
+ {
+ try {
+ if (topic != null) {
+ Log.d(LOG_TAG, "Unsubscribing to topic: " + topic);
+ GcmPubSub.getInstance(getApplicationContext()).unsubscribe(registrationToken, getTopicPath(topic));
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Failed to unsubscribe to topic: " + topic, e);
+ throw e;
+ }
+ }
+
+ /*
+ * 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;
+ }
+
+ protected static void setRegistrationID(String token) {
+ registration_id = token;
+ }
+}
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..b181e88e
--- /dev/null
+++ b/StoneIsland/plugins/phonegap-plugin-push/src/android/com/adobe/phonegap/push/RegistrationIntentService.java
@@ -0,0 +1,38 @@
+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);
+ PushPlugin.setRegistrationID(token);
+ Log.i(LOG_TAG, "new GCM Registration Token: " + token);
+
+ } catch (Exception e) {
+ Log.d(LOG_TAG, "Failed to complete token refresh", e);
+ }
+ }
+}
diff --git a/StoneIsland/plugins/phonegap-plugin-push/src/browser/ServiceWorker.js b/StoneIsland/plugins/phonegap-plugin-push/src/browser/ServiceWorker.js
new file mode 100644
index 00000000..0fc06d09
--- /dev/null
+++ b/StoneIsland/plugins/phonegap-plugin-push/src/browser/ServiceWorker.js
@@ -0,0 +1,51 @@
+var messageChannel;
+
+self.addEventListener('install', function(event) {
+ self.skipWaiting();
+});
+
+self.addEventListener('push', function(event) {
+ // parse incoming message
+ var obj = {};
+ var pushData = {
+ image: 'https://avatars1.githubusercontent.com/u/60365?v=3&s=200',
+ additionalData: {}
+ };
+ if (event.data) {
+ obj = event.data.json();
+ }
+
+ console.log(obj);
+
+ // convert to push plugin API
+ for (var key in obj) {
+ if (key === 'title') {
+ pushData.title = obj[key];
+ } else if (key === 'message' || key === 'body') {
+ pushData.message = obj[key];
+ } else if (key === 'count' || key === 'msgcnt' || key === 'badge') {
+ pushData.count = obj[key];
+ } else if (key === 'sound' || key === 'soundname') {
+ pushData.sound = obj[key];
+ } else if (key === 'image') {
+ pushData.image = obj[key];
+ } else {
+ pushData.additionalData[key] = obj[key];
+ }
+ }
+
+ event.waitUntil(
+ self.registration.showNotification(pushData.title, {
+ body: pushData.message,
+ icon: pushData.image,
+ tag: 'simple-push-demo-notification-tag'
+ })
+ );
+
+ messageChannel.ports[0].postMessage(pushData);
+
+});
+
+self.addEventListener('message', function(event) {
+ messageChannel = event;
+});
diff --git a/StoneIsland/plugins/phonegap-plugin-push/src/browser/manifest.json b/StoneIsland/plugins/phonegap-plugin-push/src/browser/manifest.json
new file mode 100644
index 00000000..ce8390ab
--- /dev/null
+++ b/StoneIsland/plugins/phonegap-plugin-push/src/browser/manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "Push Demo",
+ "gcm_sender_id": "85075801930"
+}
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..9970762b
--- /dev/null
+++ b/StoneIsland/plugins/phonegap-plugin-push/src/ios/AppDelegate+notification.h
@@ -0,0 +1,22 @@
+//
+// 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 fetchCompletionHandler:( void (^)(UIBackgroundFetchResult))completionHandler;
+- (void)pushPluginOnApplicationDidBecomeActive:(UIApplication *)application;
+- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void(^)())completionHandler;
+- (id) getCommandInstance:(NSString*)className;
+
+@property (nonatomic, retain) NSDictionary *launchNotification;
+@property (nonatomic, retain) NSNumber *coldstart;
+
+@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..fc18dd79
--- /dev/null
+++ b/StoneIsland/plugins/phonegap-plugin-push/src/ios/AppDelegate+notification.m
@@ -0,0 +1,278 @@
+//
+// 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;
+static char coldstartKey;
+
+@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
+{
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ Class class = [self class];
+
+ SEL originalSelector = @selector(init);
+ SEL swizzledSelector = @selector(pushPluginSwizzledInit);
+
+ Method original = class_getInstanceMethod(class, originalSelector);
+ Method swizzled = class_getInstanceMethod(class, swizzledSelector);
+
+ BOOL didAddMethod =
+ class_addMethod(class,
+ originalSelector,
+ method_getImplementation(swizzled),
+ method_getTypeEncoding(swizzled));
+
+ if (didAddMethod) {
+ class_replaceMethod(class,
+ swizzledSelector,
+ method_getImplementation(original),
+ method_getTypeEncoding(original));
+ } else {
+ method_exchangeImplementations(original, swizzled);
+ }
+ });
+}
+
+- (AppDelegate *)pushPluginSwizzledInit
+{
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(createNotificationChecker:)
+ name:UIApplicationDidFinishLaunchingNotification
+ object:nil];
+ [[NSNotificationCenter defaultCenter]addObserver:self
+ selector:@selector(pushPluginOnApplicationDidBecomeActive:)
+ name:UIApplicationDidBecomeActiveNotification
+ 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 pushPluginSwizzledInit];
+}
+
+// This code will be called immediately after application:didFinishLaunchingWithOptions:. We need
+// to process notifications in cold-start situations
+- (void)createNotificationChecker:(NSNotification *)notification
+{
+ NSLog(@"createNotificationChecker");
+ if (notification)
+ {
+ NSDictionary *launchOptions = [notification userInfo];
+ if (launchOptions) {
+ NSLog(@"coldstart");
+ self.launchNotification = [launchOptions objectForKey: @"UIApplicationLaunchOptionsRemoteNotificationKey"];
+ self.coldstart = [NSNumber numberWithBool:YES];
+ } else {
+ NSLog(@"not coldstart");
+ self.coldstart = [NSNumber numberWithBool:NO];
+ }
+ }
+}
+
+- (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 {
+ NSLog(@"clicked on the shade");
+ PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
+ pushHandler.notificationMessage = userInfo;
+ pushHandler.isInline = NO;
+ [pushHandler notificationReceived];
+}
+
+- (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);
+ });
+ };
+
+ PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
+
+ if (pushHandler.handlerObj == nil) {
+ pushHandler.handlerObj = [NSMutableDictionary dictionaryWithCapacity:2];
+ }
+
+ id notId = [userInfo objectForKey:@"notId"];
+ if (notId != nil) {
+ NSLog(@"Push Plugin notId %@", notId);
+ [pushHandler.handlerObj setObject:safeHandler forKey:notId];
+ } else {
+ NSLog(@"Push Plugin notId handler");
+ [pushHandler.handlerObj setObject:safeHandler forKey:@"handler"];
+ }
+
+ pushHandler.notificationMessage = userInfo;
+ pushHandler.isInline = NO;
+ [pushHandler notificationReceived];
+ } else {
+ NSLog(@"just put it in the shade");
+ //save it for later
+ self.launchNotification = userInfo;
+
+ completionHandler(UIBackgroundFetchResultNewData);
+ }
+ }
+}
+
+- (BOOL)userHasRemoteNotificationsEnabled {
+ UIApplication *application = [UIApplication sharedApplication];
+ if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) {
+ return application.currentUserNotificationSettings.types != UIUserNotificationTypeNone;
+ } else {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ return application.enabledRemoteNotificationTypes != UIRemoteNotificationTypeNone;
+#pragma GCC diagnostic pop
+ }
+}
+
+- (void)pushPluginOnApplicationDidBecomeActive:(NSNotification *)notification {
+
+ NSLog(@"active");
+
+ UIApplication *application = notification.object;
+
+ 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.coldstart = [self.coldstart boolValue];
+ pushHandler.notificationMessage = self.launchNotification;
+ self.launchNotification = nil;
+ self.coldstart = [NSNumber numberWithBool:NO];
+ [pushHandler performSelectorOnMainThread:@selector(notificationReceived) withObject:pushHandler waitUntilDone:NO];
+ }
+}
+
+
+- (void)application:(UIApplication *) application handleActionWithIdentifier: (NSString *) identifier
+forRemoteNotification: (NSDictionary *) notification completionHandler: (void (^)()) completionHandler {
+
+ NSLog(@"Push Plugin handleActionWithIdentifier %@", identifier);
+ NSMutableDictionary *userInfo = [notification mutableCopy];
+ [userInfo setObject:identifier forKey:@"actionCallback"];
+ NSLog(@"Push Plugin userInfo %@", userInfo);
+
+ if (application.applicationState == UIApplicationStateActive) {
+ PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
+ pushHandler.notificationMessage = userInfo;
+ pushHandler.isInline = NO;
+ [pushHandler notificationReceived];
+ } else {
+ void (^safeHandler)() = ^(void){
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completionHandler();
+ });
+ };
+
+ PushPlugin *pushHandler = [self getCommandInstance:@"PushNotification"];
+
+ if (pushHandler.handlerObj == nil) {
+ pushHandler.handlerObj = [NSMutableDictionary dictionaryWithCapacity:2];
+ }
+
+ id notId = [userInfo objectForKey:@"notId"];
+ if (notId != nil) {
+ NSLog(@"Push Plugin notId %@", notId);
+ [pushHandler.handlerObj setObject:safeHandler forKey:notId];
+ } else {
+ NSLog(@"Push Plugin notId handler");
+ [pushHandler.handlerObj setObject:safeHandler forKey:@"handler"];
+ }
+
+ pushHandler.notificationMessage = userInfo;
+ pushHandler.isInline = NO;
+
+ [pushHandler performSelectorOnMainThread:@selector(notificationReceived) withObject:pushHandler waitUntilDone:NO];
+ }
+}
+
+// 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);
+}
+
+- (NSNumber *)coldstart
+{
+ return objc_getAssociatedObject(self, &coldstartKey);
+}
+
+- (void)setColdstart:(NSNumber *)aNumber
+{
+ objc_setAssociatedObject(self, &coldstartKey, aNumber, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
+- (void)dealloc
+{
+ self.launchNotification = nil; // clear the association and release the object
+ self.coldstart = nil;
+}
+
+@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..276a0080
--- /dev/null
+++ b/StoneIsland/plugins/phonegap-plugin-push/src/ios/PushPlugin.h
@@ -0,0 +1,80 @@
+/*
+ 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>
+
+@protocol GGLInstanceIDDelegate;
+@protocol GCMReceiverDelegate;
+@interface PushPlugin : CDVPlugin<GGLInstanceIDDelegate, GCMReceiverDelegate>
+{
+ NSDictionary *notificationMessage;
+ BOOL isInline;
+ NSString *notificationCallbackId;
+ NSString *callback;
+ BOOL clearBadge;
+
+ NSMutableDictionary *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 coldstart;
+@property BOOL clearBadge;
+@property (nonatomic, strong) NSMutableDictionary *handlerObj;
+
+- (void)init:(CDVInvokedUrlCommand*)command;
+- (void)unregister:(CDVInvokedUrlCommand*)command;
+- (void)subscribe:(CDVInvokedUrlCommand*)command;
+- (void)unsubscribe:(CDVInvokedUrlCommand*)command;
+
+- (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
+- (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
+
+- (void)setNotificationMessage:(NSDictionary *)notification;
+- (void)notificationReceived;
+
+- (void)willSendDataMessageWithID:(NSString *)messageID error:(NSError *)error;
+- (void)didSendDataMessageWithID:(NSString *)messageID;
+- (void)didDeleteMessagesOnServer;
+
+// GCM Features
+@property(nonatomic, assign) BOOL usesGCM;
+@property(nonatomic, strong) NSNumber* gcmSandbox;
+@property(nonatomic, strong) NSString *gcmSenderId;
+@property(nonatomic, strong) NSDictionary *gcmRegistrationOptions;
+@property(nonatomic, strong) void (^gcmRegistrationHandler) (NSString *registrationToken, NSError *error);
+@property(nonatomic, strong) NSString *gcmRegistrationToken;
+@property(nonatomic, strong) NSArray *gcmTopics;
+
+@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..a176b9af
--- /dev/null
+++ b/StoneIsland/plugins/phonegap-plugin-push/src/ios/PushPlugin.m
@@ -0,0 +1,677 @@
+/*
+ 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.
+ */
+
+// See GGLInstanceID.h
+#define GMP_NO_MODULES true
+
+#import "PushPlugin.h"
+#import "GoogleCloudMessaging.h"
+#import "GGLInstanceIDHeaders.h"
+
+@implementation PushPlugin : CDVPlugin
+
+@synthesize notificationMessage;
+@synthesize isInline;
+@synthesize coldstart;
+
+@synthesize callbackId;
+@synthesize notificationCallbackId;
+@synthesize callback;
+@synthesize clearBadge;
+@synthesize handlerObj;
+
+@synthesize usesGCM;
+@synthesize gcmSandbox;
+@synthesize gcmSenderId;
+@synthesize gcmRegistrationOptions;
+@synthesize gcmRegistrationHandler;
+@synthesize gcmRegistrationToken;
+@synthesize gcmTopics;
+
+-(void)initGCMRegistrationHandler;
+{
+ __weak __block PushPlugin *weakSelf = self;
+ gcmRegistrationHandler = ^(NSString *registrationToken, NSError *error){
+ if (registrationToken != nil) {
+ NSLog(@"GCM Registration Token: %@", registrationToken);
+ [weakSelf setGcmRegistrationToken: registrationToken];
+
+ id topics = [weakSelf gcmTopics];
+ if (topics != nil) {
+ for (NSString *topic in topics) {
+ NSLog(@"subscribe from topic: %@", topic);
+ id pubSub = [GCMPubSub sharedInstance];
+ [pubSub subscribeWithToken: [weakSelf gcmRegistrationToken]
+ topic:[NSString stringWithFormat:@"/topics/%@", topic]
+ options:nil
+ handler:^void(NSError *error) {
+ if (error) {
+ if (error.code == 3001) {
+ NSLog(@"Already subscribed to %@", topic);
+ } else {
+ NSLog(@"Failed to subscribe to topic %@: %@", topic, error);
+ }
+ }
+ else {
+ NSLog(@"Successfully subscribe to topic %@", topic);
+ }
+ }];
+ }
+ }
+
+ [weakSelf registerWithToken:registrationToken];
+ } else {
+ NSLog(@"Registration to GCM failed with error: %@", error.localizedDescription);
+ [weakSelf failWithMessage:self.callbackId withMsg:@"" withError:error];
+ }
+ };
+}
+
+// GCM refresh token
+// Unclear how this is testable under normal circumstances
+- (void)onTokenRefresh {
+#if !TARGET_IPHONE_SIMULATOR
+ // A rotation of the registration tokens is happening, so the app needs to request a new token.
+ NSLog(@"The GCM registration token needs to be changed.");
+ [[GGLInstanceID sharedInstance] tokenWithAuthorizedEntity:[self gcmSenderId]
+ scope:kGGLInstanceIDScopeGCM
+ options:[self gcmRegistrationOptions]
+ handler:[self gcmRegistrationHandler]];
+#endif
+}
+
+- (void)willSendDataMessageWithID:(NSString *)messageID error:(NSError *)error {
+ NSLog(@"willSendDataMessageWithID");
+ if (error) {
+ // Failed to send the message.
+ } else {
+ // Will send message, you can save the messageID to track the message
+ }
+}
+
+- (void)didSendDataMessageWithID:(NSString *)messageID {
+ NSLog(@"willSendDataMessageWithID");
+ // Did successfully send message identified by messageID
+}
+
+- (void)didDeleteMessagesOnServer {
+ NSLog(@"didDeleteMessagesOnServer");
+ // Some messages sent to this device were deleted on the GCM server before reception, likely
+ // because the TTL expired. The client should notify the app server of this, so that the app
+ // server can resend those messages.
+}
+
+- (void)unregister:(CDVInvokedUrlCommand*)command;
+{
+ NSArray* topics = [command argumentAtIndex:0];
+
+ if (topics != nil) {
+ id pubSub = [GCMPubSub sharedInstance];
+ for (NSString *topic in topics) {
+ NSLog(@"unsubscribe from topic: %@", topic);
+ [pubSub unsubscribeWithToken: [self gcmRegistrationToken]
+ topic:[NSString stringWithFormat:@"/topics/%@", topic]
+ options:nil
+ handler:^void(NSError *error) {
+ if (error) {
+ NSLog(@"Failed to unsubscribe from topic %@: %@", topic, error);
+ }
+ else {
+ NSLog(@"Successfully unsubscribe from topic %@", topic);
+ }
+ }];
+ }
+ } else {
+ [[UIApplication sharedApplication] unregisterForRemoteNotifications];
+ [self successWithMessage:command.callbackId withMsg:@"unregistered"];
+ }
+}
+
+- (void)subscribe:(CDVInvokedUrlCommand*)command;
+{
+ NSString* topic = [command argumentAtIndex:0];
+
+ if (topic != nil) {
+ NSLog(@"subscribe from topic: %@", topic);
+ id pubSub = [GCMPubSub sharedInstance];
+ [pubSub subscribeWithToken: [self gcmRegistrationToken]
+ topic:[NSString stringWithFormat:@"/topics/%@", topic]
+ options:nil
+ handler:^void(NSError *error) {
+ if (error) {
+ if (error.code == 3001) {
+ NSLog(@"Already subscribed to %@", topic);
+ [self successWithMessage:command.callbackId withMsg:[NSString stringWithFormat:@"Already subscribed to %@", topic]];
+ } else {
+ NSLog(@"Failed to subscribe to topic %@: %@", topic, error);
+ [self failWithMessage:command.callbackId withMsg:[NSString stringWithFormat:@"Failed to subscribe to topic %@", topic] withError:error];
+ }
+ }
+ else {
+ NSLog(@"Successfully subscribe to topic %@", topic);
+ [self successWithMessage:command.callbackId withMsg:[NSString stringWithFormat:@"Successfully subscribe to topic %@", topic]];
+ }
+ }];
+ } else {
+ NSLog(@"There is no topic to subscribe");
+ [self successWithMessage:command.callbackId withMsg:@"There is no topic to subscribe"];
+ }
+}
+
+- (void)unsubscribe:(CDVInvokedUrlCommand*)command;
+{
+ NSString* topic = [command argumentAtIndex:0];
+
+ if (topic != nil) {
+ NSLog(@"unsubscribe from topic: %@", topic);
+ id pubSub = [GCMPubSub sharedInstance];
+ [pubSub unsubscribeWithToken: [self gcmRegistrationToken]
+ topic:[NSString stringWithFormat:@"/topics/%@", topic]
+ options:nil
+ handler:^void(NSError *error) {
+ if (error) {
+ NSLog(@"Failed to unsubscribe to topic %@: %@", topic, error);
+ [self failWithMessage:command.callbackId withMsg:[NSString stringWithFormat:@"Failed to unsubscribe to topic %@", topic] withError:error];
+ } else {
+ NSLog(@"Successfully unsubscribe to topic %@", topic);
+ [self successWithMessage:command.callbackId withMsg:[NSString stringWithFormat:@"Successfully unsubscribe to topic %@", topic]];
+ }
+ }];
+ } else {
+ NSLog(@"There is no topic to unsubscribe");
+ [self successWithMessage:command.callbackId withMsg:@"There is no topic to unsubscribe"];
+ }
+}
+
+- (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"];
+
+ NSArray* topics = [iosOptions objectForKey:@"topics"];
+ [self setGcmTopics:topics];
+
+#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
+ NSLog(@"PushPlugin.register: better button setup");
+ // setup action buttons
+ NSMutableSet *categories = [[NSMutableSet alloc] init];
+ id categoryOptions = [iosOptions objectForKey:@"categories"];
+ if (categoryOptions != nil && [categoryOptions isKindOfClass:[NSDictionary class]]) {
+ for (id key in categoryOptions) {
+ NSLog(@"categories: key %@", key);
+ id category = [categoryOptions objectForKey:key];
+
+ id yesButton = [category objectForKey:@"yes"];
+ UIMutableUserNotificationAction *yesAction;
+ if (yesButton != nil && [yesButton isKindOfClass:[NSDictionary class]]) {
+ yesAction = [self createAction: yesButton];
+ }
+ id noButton = [category objectForKey:@"no"];
+ UIMutableUserNotificationAction *noAction;
+ if (noButton != nil && [noButton isKindOfClass:[NSDictionary class]]) {
+ noAction = [self createAction: noButton];
+ }
+ id maybeButton = [category objectForKey:@"maybe"];
+ UIMutableUserNotificationAction *maybeAction;
+ if (maybeButton != nil && [maybeButton isKindOfClass:[NSDictionary class]]) {
+ maybeAction = [self createAction: maybeButton];
+ }
+
+ // First create the category
+ UIMutableUserNotificationCategory *notificationCategory = [[UIMutableUserNotificationCategory alloc] init];
+
+ // Identifier to include in your push payload and local notification
+ notificationCategory.identifier = key;
+
+ NSMutableArray *categoryArray = [[NSMutableArray alloc] init];
+ NSMutableArray *minimalCategoryArray = [[NSMutableArray alloc] init];
+ if (yesButton != nil) {
+ [categoryArray addObject:yesAction];
+ [minimalCategoryArray addObject:yesAction];
+ }
+ if (noButton != nil) {
+ [categoryArray addObject:noAction];
+ [minimalCategoryArray addObject:noAction];
+ }
+ if (maybeButton != nil) {
+ [categoryArray addObject:maybeAction];
+ }
+
+ // Add the actions to the category and set the action context
+ [notificationCategory setActions:categoryArray forContext:UIUserNotificationActionContextDefault];
+
+ // Set the actions to present in a minimal context
+ [notificationCategory setActions:minimalCategoryArray forContext:UIUserNotificationActionContextMinimal];
+
+ NSLog(@"Adding category %@", key);
+ [categories addObject:notificationCategory];
+ }
+
+ }
+#else
+ NSLog(@"PushPlugin.register: action buttons only supported on iOS8 and above");
+#endif
+
+
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
+ if ([[UIApplication sharedApplication]respondsToSelector:@selector(registerUserNotificationSettings:)]) {
+ UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UserNotificationTypes categories:categories];
+ [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
+ [[UIApplication sharedApplication] registerForRemoteNotifications];
+ } else {
+ [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
+ (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
+ }
+#else
+ [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
+ (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
+#endif
+
+ // GCM options
+ [self setGcmSenderId: [iosOptions objectForKey:@"senderID"]];
+ NSLog(@"GCM Sender ID %@", gcmSenderId);
+ if([[self gcmSenderId] length] > 0) {
+ NSLog(@"Using GCM Notification");
+ [self setUsesGCM: YES];
+ [self initGCMRegistrationHandler];
+ } else {
+ NSLog(@"Using APNS Notification");
+ [self setUsesGCM:NO];
+ }
+ id gcmSandBoxArg = [iosOptions objectForKey:@"gcmSandbox"];
+
+ [self setGcmSandbox:@NO];
+ if ([self usesGCM] &&
+ (([gcmSandBoxArg isKindOfClass:[NSString class]] && [gcmSandBoxArg isEqualToString:@"true"]) ||
+ [gcmSandBoxArg boolValue]))
+ {
+ NSLog(@"Using GCM Sandbox");
+ [self setGcmSandbox:@YES];
+ }
+
+ if (notificationMessage) { // if there is a pending startup notification
+ dispatch_async(dispatch_get_main_queue(), ^{
+ // delay to allow JS event handlers to be setup
+ [self performSelector:@selector(notificationReceived) withObject:nil afterDelay: 0.5];
+ });
+ }
+ }];
+}
+
+- (UIMutableUserNotificationAction *)createAction:(NSDictionary *)dictionary {
+
+ UIMutableUserNotificationAction *myAction = [[UIMutableUserNotificationAction alloc] init];
+
+ myAction = [[UIMutableUserNotificationAction alloc] init];
+ myAction.identifier = [dictionary objectForKey:@"callback"];
+ myAction.title = [dictionary objectForKey:@"title"];
+ id mode =[dictionary objectForKey:@"foreground"];
+ if (mode == nil || ([mode isKindOfClass:[NSString class]] && [mode isEqualToString:@"false"]) || ![mode boolValue]) {
+ myAction.activationMode = UIUserNotificationActivationModeBackground;
+ } else {
+ myAction.activationMode = UIUserNotificationActivationModeForeground;
+ }
+ id destructive = [dictionary objectForKey:@"destructive"];
+ if (destructive == nil || ([destructive isKindOfClass:[NSString class]] && [destructive isEqualToString:@"false"]) || ![destructive boolValue]) {
+ myAction.destructive = NO;
+ } else {
+ myAction.destructive = YES;
+ }
+ myAction.authenticationRequired = NO;
+
+ return myAction;
+}
+
+- (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"];
+
+ if([self usesGCM]) {
+ GGLInstanceIDConfig *instanceIDConfig = [GGLInstanceIDConfig defaultConfig];
+ instanceIDConfig.delegate = self;
+ [[GGLInstanceID sharedInstance] startWithConfig:instanceIDConfig];
+
+ [self setGcmRegistrationOptions: @{kGGLInstanceIDRegisterAPNSOption:deviceToken,
+ kGGLInstanceIDAPNSServerTypeSandboxOption:[self gcmSandbox]}];
+
+ [[GGLInstanceID sharedInstance] tokenWithAuthorizedEntity:[self gcmSenderId]
+ scope:kGGLInstanceIDScopeGCM
+ options:[self gcmRegistrationOptions]
+ handler:[self gcmRegistrationHandler]];
+
+ GCMConfig *gcmConfig = [GCMConfig defaultConfig];
+ gcmConfig.receiverDelegate = self;
+ [[GCMService sharedInstance] startWithConfig:gcmConfig];
+
+ } else {
+ [self registerWithToken: token];
+ }
+#endif
+}
+
+- (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
+{
+ if (self.callbackId == nil) {
+ NSLog(@"Unexpected call to didFailToRegisterForRemoteNotificationsWithError, ignoring: %@", error);
+ return;
+ }
+ NSLog(@"Push Plugin register failed");
+ [self failWithMessage:self.callbackId withMsg:@"" 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"];
+ }
+
+ if (coldstart) {
+ [additionalData setObject:[NSNumber numberWithBool:YES] forKey:@"coldstart"];
+ } else {
+ [additionalData setObject:[NSNumber numberWithBool:NO] forKey:@"coldstart"];
+ }
+
+ [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.coldstart = NO;
+ 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)clearAllNotifications:(CDVInvokedUrlCommand *)command
+{
+ [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
+
+ NSString* message = [NSString stringWithFormat:@"cleared all notifications"];
+ CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message];
+ [self.commandDelegate sendPluginResult:commandResult callbackId:command.callbackId];
+}
+
+- (void)hasPermission:(CDVInvokedUrlCommand *)command
+{
+ BOOL enabled = NO;
+ id<UIApplicationDelegate> appDelegate = [UIApplication sharedApplication].delegate;
+ if ([appDelegate respondsToSelector:@selector(userHasRemoteNotificationsEnabled)]) {
+ enabled = [appDelegate performSelector:@selector(userHasRemoteNotificationsEnabled)];
+ }
+
+ NSMutableDictionary* message = [NSMutableDictionary dictionaryWithCapacity:1];
+ [message setObject:[NSNumber numberWithBool:enabled] forKey:@"isEnabled"];
+ CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:message];
+ [self.commandDelegate sendPluginResult:commandResult callbackId:command.callbackId];
+}
+
+-(void)successWithMessage:(NSString *)callbackId withMsg:(NSString *)message
+{
+ if (callbackId != nil)
+ {
+ CDVPluginResult *commandResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:message];
+ [self.commandDelegate sendPluginResult:commandResult callbackId:callbackId];
+ }
+}
+
+-(void)registerWithToken:(NSString*)token; {
+ // 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];
+}
+
+
+-(void)failWithMessage:(NSString *)callbackId withMsg:(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:callbackId];
+}
+
+-(void) finish:(CDVInvokedUrlCommand*)command
+{
+ NSLog(@"Push Plugin finish called");
+
+ [self.commandDelegate runInBackground:^ {
+ NSString* notId = [command.arguments objectAtIndex:0];
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [NSTimer scheduledTimerWithTimeInterval:0.1
+ target:self
+ selector:@selector(stopBackgroundTask:)
+ userInfo:notId
+ 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[[timer userInfo]] 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..ac04f39d
--- /dev/null
+++ b/StoneIsland/plugins/phonegap-plugin-push/src/windows/PushPluginProxy.js
@@ -0,0 +1,93 @@
+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 = { coldstart: false }; // this gets called only when the app is running
+ 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 });
+
+ var context = cordova.require('cordova/platform').activationContext;
+ var launchArgs = context ? context.args : null;
+ if (launchArgs) { //If present, app launched through push notification
+ var result = { message: '' }; //Added to identify callback as notification type in the API
+ result.launchArgs = launchArgs;
+ result.additionalData = { coldstart: true };
+ 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);