summaryrefslogtreecommitdiff
path: root/StoneIsland/platforms/android/src
diff options
context:
space:
mode:
Diffstat (limited to 'StoneIsland/platforms/android/src')
-rwxr-xr-xStoneIsland/platforms/android/src/com/adobe/phonegap/push/GCMIntentService.java603
-rwxr-xr-xStoneIsland/platforms/android/src/com/adobe/phonegap/push/PushConstants.java53
-rwxr-xr-xStoneIsland/platforms/android/src/com/adobe/phonegap/push/PushHandlerActivity.java70
-rwxr-xr-xStoneIsland/platforms/android/src/com/adobe/phonegap/push/PushInstanceIDListenerService.java27
-rwxr-xr-xStoneIsland/platforms/android/src/com/adobe/phonegap/push/PushPlugin.java294
-rwxr-xr-xStoneIsland/platforms/android/src/com/adobe/phonegap/push/RegistrationIntentService.java42
-rwxr-xr-xStoneIsland/platforms/android/src/com/ionic/keyboard/IonicKeyboard.java96
-rwxr-xr-xStoneIsland/platforms/android/src/nl/xservices/plugins/LaunchMyApp.java136
-rwxr-xr-xStoneIsland/platforms/android/src/nl/xservices/plugins/SocialSharing.java533
-rwxr-xr-xStoneIsland/platforms/android/src/org/apache/cordova/device/Device.java161
-rwxr-xr-xStoneIsland/platforms/android/src/org/apache/cordova/dialogs/Notification.java483
-rwxr-xr-xStoneIsland/platforms/android/src/org/apache/cordova/inappbrowser/InAppBrowser.java869
-rwxr-xr-xStoneIsland/platforms/android/src/org/apache/cordova/inappbrowser/InAppBrowserDialog.java58
-rwxr-xr-xStoneIsland/platforms/android/src/org/apache/cordova/inappbrowser/InAppChromeClient.java133
-rwxr-xr-xStoneIsland/platforms/android/src/org/apache/cordova/networkinformation/NetworkManager.java267
-rwxr-xr-xStoneIsland/platforms/android/src/org/apache/cordova/splashscreen/SplashScreen.java328
-rwxr-xr-xStoneIsland/platforms/android/src/org/apache/cordova/whitelist/WhitelistPlugin.java161
-rwxr-xr-xStoneIsland/platforms/android/src/us/okfoc/stoneisland/MainActivity.java34
18 files changed, 4348 insertions, 0 deletions
diff --git a/StoneIsland/platforms/android/src/com/adobe/phonegap/push/GCMIntentService.java b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/GCMIntentService.java
new file mode 100755
index 00000000..24daa6a5
--- /dev/null
+++ b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/GCMIntentService.java
@@ -0,0 +1,603 @@
+package com.adobe.phonegap.push;
+
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v4.app.NotificationCompat;
+import android.text.Html;
+import android.util.Log;
+
+import com.google.android.gms.gcm.GcmListenerService;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Random;
+
+@SuppressLint("NewApi")
+public class GCMIntentService extends GcmListenerService implements PushConstants {
+
+ private static final String LOG_TAG = "PushPlugin_GCMIntentService";
+ private static HashMap<Integer, ArrayList<String>> messageMap = new HashMap<Integer, ArrayList<String>>();
+
+ public void setNotification(int notId, String message){
+ ArrayList<String> messageList = messageMap.get(notId);
+ if(messageList == null) {
+ messageList = new ArrayList<String>();
+ messageMap.put(notId, messageList);
+ }
+
+ if(message.isEmpty()){
+ messageList.clear();
+ }else{
+ messageList.add(message);
+ }
+ }
+
+ @Override
+ public void onMessageReceived(String from, Bundle extras) {
+ Log.d(LOG_TAG, "onMessage - from: " + from);
+
+ if (extras != null) {
+
+ SharedPreferences prefs = getApplicationContext().getSharedPreferences(PushPlugin.COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
+ boolean forceShow = prefs.getBoolean(FORCE_SHOW, false);
+
+ extras = normalizeExtras(extras);
+
+ // if we are in the foreground and forceShow is `false` only send data
+ if (!forceShow && PushPlugin.isInForeground()) {
+ Log.d(LOG_TAG, "foreground");
+ extras.putBoolean(FOREGROUND, true);
+ PushPlugin.sendExtras(extras);
+ }
+ // if we are in the foreground and forceShow is `true`, force show the notification if the data has at least a message or title
+ else if (forceShow && PushPlugin.isInForeground()) {
+ Log.d(LOG_TAG, "foreground force");
+ extras.putBoolean(FOREGROUND, true);
+
+ showNotificationIfPossible(getApplicationContext(), extras);
+ }
+ // if we are not in the foreground always send notification if the data has at least a message or title
+ else {
+ Log.d(LOG_TAG, "background");
+ extras.putBoolean(FOREGROUND, false);
+
+ showNotificationIfPossible(getApplicationContext(), extras);
+ }
+ }
+ }
+
+ /*
+ * Change a values key in the extras bundle
+ */
+ private void replaceKey(String oldKey, String newKey, Bundle extras, Bundle newExtras) {
+ Object value = extras.get(oldKey);
+ if ( value != null ) {
+ if (value instanceof String) {
+ newExtras.putString(newKey, (String) value);
+ } else if (value instanceof Boolean) {
+ newExtras.putBoolean(newKey, (Boolean) value);
+ } else if (value instanceof Number) {
+ newExtras.putDouble(newKey, ((Number) value).doubleValue());
+ } else {
+ newExtras.putString(newKey, String.valueOf(value));
+ }
+ }
+ }
+
+ /*
+ * Replace alternate keys with our canonical value
+ */
+ private String normalizeKey(String key) {
+ if (key.equals(BODY) || key.equals(ALERT) || key.equals(GCM_NOTIFICATION_BODY)) {
+ return MESSAGE;
+ } else if (key.equals(MSGCNT) || key.equals(BADGE)) {
+ return COUNT;
+ } else if (key.equals(SOUNDNAME)) {
+ return SOUND;
+ } else if (key.startsWith(GCM_NOTIFICATION)) {
+ return key.substring(GCM_NOTIFICATION.length()+1, key.length());
+ } else if (key.startsWith(GCM_N)) {
+ return key.substring(GCM_N.length()+1, key.length());
+ } else if (key.startsWith(UA_PREFIX)) {
+ key = key.substring(UA_PREFIX.length()+1, key.length());
+ return key.toLowerCase();
+ } else {
+ return key;
+ }
+ }
+
+ /*
+ * Parse bundle into normalized keys.
+ */
+ private Bundle normalizeExtras(Bundle extras) {
+ Log.d(LOG_TAG, "normalize extras");
+ Iterator<String> it = extras.keySet().iterator();
+ Bundle newExtras = new Bundle();
+
+ while (it.hasNext()) {
+ String key = it.next();
+
+ Log.d(LOG_TAG, "key = " + key);
+
+ // If normalizeKeythe key is "data" or "message" and the value is a json object extract
+ // This is to support parse.com and other services. Issue #147 and pull #218
+ if (key.equals(PARSE_COM_DATA) || key.equals(MESSAGE)) {
+ Object json = extras.get(key);
+ // Make sure data is json object stringified
+ if ( json instanceof String && ((String) json).startsWith("{") ) {
+ Log.d(LOG_TAG, "extracting nested message data from key = " + key);
+ try {
+ // If object contains message keys promote each value to the root of the bundle
+ JSONObject data = new JSONObject((String) json);
+ if ( data.has(ALERT) || data.has(MESSAGE) || data.has(BODY) || data.has(TITLE) ) {
+ Iterator<String> jsonIter = data.keys();
+ while (jsonIter.hasNext()) {
+ String jsonKey = jsonIter.next();
+
+ Log.d(LOG_TAG, "key = data/" + jsonKey);
+
+ String value = data.getString(jsonKey);
+ jsonKey = normalizeKey(jsonKey);
+ newExtras.putString(jsonKey, value);
+ }
+ }
+ } catch( JSONException e) {
+ Log.e(LOG_TAG, "normalizeExtras: JSON exception");
+ }
+ }
+ } else if (key.equals(("notification"))) {
+ Bundle value = extras.getBundle(key);
+ Iterator<String> iterator = value.keySet().iterator();
+ while (iterator.hasNext()) {
+ String notifkey = iterator.next();
+
+ Log.d(LOG_TAG, "notifkey = " + notifkey);
+ String newKey = normalizeKey(notifkey);
+ Log.d(LOG_TAG, "replace key " + notifkey + " with " + newKey);
+
+ newExtras.putString(newKey, value.getString(notifkey));
+ }
+ continue;
+ }
+
+ String newKey = normalizeKey(key);
+ Log.d(LOG_TAG, "replace key " + key + " with " + newKey);
+ replaceKey(key, newKey, extras, newExtras);
+
+ } // while
+
+ return newExtras;
+ }
+
+ private void showNotificationIfPossible (Context context, Bundle extras) {
+
+ // Send a notification if there is a message or title, otherwise just send data
+ String message = extras.getString(MESSAGE);
+ String title = extras.getString(TITLE);
+
+ Log.d(LOG_TAG, "message =[" + message + "]");
+ Log.d(LOG_TAG, "title =[" + title + "]");
+
+ if ((message != null && message.length() != 0) ||
+ (title != null && title.length() != 0)) {
+
+ Log.d(LOG_TAG, "create notification");
+
+ createNotification(context, extras);
+ } else {
+ Log.d(LOG_TAG, "send notification event");
+ PushPlugin.sendExtras(extras);
+ }
+ }
+
+ public void createNotification(Context context, Bundle extras) {
+ NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ String appName = getAppName(this);
+ String packageName = context.getPackageName();
+ Resources resources = context.getResources();
+
+ int notId = parseInt(NOT_ID, extras);
+ Intent notificationIntent = new Intent(this, PushHandlerActivity.class);
+ notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ notificationIntent.putExtra(PUSH_BUNDLE, extras);
+ notificationIntent.putExtra(NOT_ID, notId);
+
+ int requestCode = new Random().nextInt();
+ PendingIntent contentIntent = PendingIntent.getActivity(this, requestCode, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ NotificationCompat.Builder mBuilder =
+ new NotificationCompat.Builder(context)
+ .setWhen(System.currentTimeMillis())
+ .setContentTitle(extras.getString(TITLE))
+ .setTicker(extras.getString(TITLE))
+ .setContentIntent(contentIntent)
+ .setAutoCancel(true);
+
+ SharedPreferences prefs = context.getSharedPreferences(PushPlugin.COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
+ String localIcon = prefs.getString(ICON, null);
+ String localIconColor = prefs.getString(ICON_COLOR, null);
+ boolean soundOption = prefs.getBoolean(SOUND, true);
+ boolean vibrateOption = prefs.getBoolean(VIBRATE, true);
+ Log.d(LOG_TAG, "stored icon=" + localIcon);
+ Log.d(LOG_TAG, "stored iconColor=" + localIconColor);
+ Log.d(LOG_TAG, "stored sound=" + soundOption);
+ Log.d(LOG_TAG, "stored vibrate=" + vibrateOption);
+
+ /*
+ * Notification Vibration
+ */
+
+ setNotificationVibration(extras, vibrateOption, mBuilder);
+
+ /*
+ * Notification Icon Color
+ *
+ * Sets the small-icon background color of the notification.
+ * To use, add the `iconColor` key to plugin android options
+ *
+ */
+ setNotificationIconColor(extras.getString("color"), mBuilder, localIconColor);
+
+ /*
+ * Notification Icon
+ *
+ * Sets the small-icon of the notification.
+ *
+ * - checks the plugin options for `icon` key
+ * - if none, uses the application icon
+ *
+ * The icon value must be a string that maps to a drawable resource.
+ * If no resource is found, falls
+ *
+ */
+ setNotificationSmallIcon(context, extras, packageName, resources, mBuilder, localIcon);
+
+ /*
+ * Notification Large-Icon
+ *
+ * Sets the large-icon of the notification
+ *
+ * - checks the gcm data for the `image` key
+ * - checks to see if remote image, loads it.
+ * - checks to see if assets image, Loads It.
+ * - checks to see if resource image, LOADS IT!
+ * - if none, we don't set the large icon
+ *
+ */
+ setNotificationLargeIcon(extras, packageName, resources, mBuilder);
+
+ /*
+ * Notification Sound
+ */
+ if (soundOption) {
+ setNotificationSound(context, extras, mBuilder);
+ }
+
+ /*
+ * LED Notification
+ */
+ setNotificationLedColor(extras, mBuilder);
+
+ /*
+ * Priority Notification
+ */
+ setNotificationPriority(extras, mBuilder);
+
+ /*
+ * Notification message
+ */
+ setNotificationMessage(notId, extras, mBuilder);
+
+ /*
+ * Notification count
+ */
+ setNotificationCount(extras, mBuilder);
+
+ /*
+ * Notification add actions
+ */
+ createActions(extras, mBuilder, resources, packageName);
+
+ mNotificationManager.notify(appName, notId, mBuilder.build());
+ }
+
+ private void createActions(Bundle extras, NotificationCompat.Builder mBuilder, Resources resources, String packageName) {
+ Log.d(LOG_TAG, "create actions");
+ String actions = extras.getString(ACTIONS);
+ if (actions != null) {
+ try {
+ JSONArray actionsArray = new JSONArray(actions);
+ for (int i=0; i < actionsArray.length(); i++) {
+ Log.d(LOG_TAG, "adding action");
+ JSONObject action = actionsArray.getJSONObject(i);
+ Log.d(LOG_TAG, "adding callback = " + action.getString(CALLBACK));
+ Intent intent = new Intent(this, PushHandlerActivity.class);
+ intent.putExtra(CALLBACK, action.getString(CALLBACK));
+ intent.putExtra(PUSH_BUNDLE, extras);
+ PendingIntent pIntent = PendingIntent.getActivity(this, i, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ mBuilder.addAction(resources.getIdentifier(action.getString(ICON), DRAWABLE, packageName),
+ action.getString(TITLE), pIntent);
+ }
+ } catch(JSONException e) {
+ // nope
+ }
+ }
+ }
+
+ private void setNotificationCount(Bundle extras, NotificationCompat.Builder mBuilder) {
+ String msgcnt = extras.getString(MSGCNT);
+ if (msgcnt == null) {
+ msgcnt = extras.getString(BADGE);
+ }
+ if (msgcnt != null) {
+ mBuilder.setNumber(Integer.parseInt(msgcnt));
+ }
+ }
+
+ private void setNotificationVibration(Bundle extras, Boolean vibrateOption, NotificationCompat.Builder mBuilder) {
+ String vibrationPattern = extras.getString(VIBRATION_PATTERN);
+ if (vibrationPattern != null) {
+ String[] items = vibrationPattern.replaceAll("\\[", "").replaceAll("\\]", "").split(",");
+ long[] results = new long[items.length];
+ for (int i = 0; i < items.length; i++) {
+ try {
+ results[i] = Long.parseLong(items[i]);
+ } catch (NumberFormatException nfe) {}
+ }
+ mBuilder.setVibrate(results);
+ } else {
+ if (vibrateOption) {
+ mBuilder.setDefaults(Notification.DEFAULT_VIBRATE);
+ }
+ }
+ }
+
+ private void setNotificationMessage(int notId, Bundle extras, NotificationCompat.Builder mBuilder) {
+ String message = extras.getString(MESSAGE);
+
+ String style = extras.getString(STYLE, STYLE_TEXT);
+ if(STYLE_INBOX.equals(style)) {
+ setNotification(notId, message);
+
+ mBuilder.setContentText(message);
+
+ ArrayList<String> messageList = messageMap.get(notId);
+ Integer sizeList = messageList.size();
+ if (sizeList > 1) {
+ String sizeListMessage = sizeList.toString();
+ String stacking = sizeList + " more";
+ if (extras.getString(SUMMARY_TEXT) != null) {
+ stacking = extras.getString(SUMMARY_TEXT);
+ stacking = stacking.replace("%n%", sizeListMessage);
+ }
+ NotificationCompat.InboxStyle notificationInbox = new NotificationCompat.InboxStyle()
+ .setBigContentTitle(extras.getString(TITLE))
+ .setSummaryText(stacking);
+
+ for (int i = messageList.size() - 1; i >= 0; i--) {
+ notificationInbox.addLine(Html.fromHtml(messageList.get(i)));
+ }
+
+ mBuilder.setStyle(notificationInbox);
+ } else {
+ NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle();
+ if (message != null) {
+ bigText.bigText(message);
+ bigText.setBigContentTitle(extras.getString(TITLE));
+ mBuilder.setStyle(bigText);
+ }
+ }
+ } else if (STYLE_PICTURE.equals(style)) {
+ setNotification(notId, "");
+
+ NotificationCompat.BigPictureStyle bigPicture = new NotificationCompat.BigPictureStyle();
+ bigPicture.bigPicture(getBitmapFromURL(extras.getString(PICTURE)));
+ bigPicture.setBigContentTitle(extras.getString(TITLE));
+ bigPicture.setSummaryText(extras.getString(SUMMARY_TEXT));
+
+ mBuilder.setContentTitle(extras.getString(TITLE));
+ mBuilder.setContentText(message);
+
+ mBuilder.setStyle(bigPicture);
+ } else {
+ setNotification(notId, "");
+
+ NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle();
+
+ if (message != null) {
+ mBuilder.setContentText(Html.fromHtml(message));
+
+ bigText.bigText(message);
+ bigText.setBigContentTitle(extras.getString(TITLE));
+
+ String summaryText = extras.getString(SUMMARY_TEXT);
+ if (summaryText != null) {
+ bigText.setSummaryText(summaryText);
+ }
+
+ mBuilder.setStyle(bigText);
+ }
+ /*
+ else {
+ mBuilder.setContentText("<missing message content>");
+ }
+ */
+ }
+ }
+
+ private void setNotificationSound(Context context, Bundle extras, NotificationCompat.Builder mBuilder) {
+ String soundname = extras.getString(SOUNDNAME);
+ if (soundname == null) {
+ soundname = extras.getString(SOUND);
+ }
+ if (soundname != null && !soundname.contentEquals(SOUND_DEFAULT)) {
+ Uri sound = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE
+ + "://" + context.getPackageName() + "/raw/" + soundname);
+ Log.d(LOG_TAG, sound.toString());
+ mBuilder.setSound(sound);
+ } else {
+ mBuilder.setSound(android.provider.Settings.System.DEFAULT_NOTIFICATION_URI);
+ }
+ }
+
+ private void setNotificationLedColor(Bundle extras, NotificationCompat.Builder mBuilder) {
+ String ledColor = extras.getString(LED_COLOR);
+ if (ledColor != null) {
+ // Converts parse Int Array from ledColor
+ String[] items = ledColor.replaceAll("\\[", "").replaceAll("\\]", "").split(",");
+ int[] results = new int[items.length];
+ for (int i = 0; i < items.length; i++) {
+ try {
+ results[i] = Integer.parseInt(items[i]);
+ } catch (NumberFormatException nfe) {}
+ }
+ if (results.length == 4) {
+ mBuilder.setLights(Color.argb(results[0], results[1], results[2], results[3]), 500, 500);
+ } else {
+ Log.e(LOG_TAG, "ledColor parameter must be an array of length == 4 (ARGB)");
+ }
+ }
+ }
+
+ private void setNotificationPriority(Bundle extras, NotificationCompat.Builder mBuilder) {
+ String priorityStr = extras.getString(PRIORITY);
+ if (priorityStr != null) {
+ try {
+ Integer priority = Integer.parseInt(priorityStr);
+ if (priority >= NotificationCompat.PRIORITY_MIN && priority <= NotificationCompat.PRIORITY_MAX) {
+ mBuilder.setPriority(priority);
+ } else {
+ Log.e(LOG_TAG, "Priority parameter must be between -2 and 2");
+ }
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private void setNotificationLargeIcon(Bundle extras, String packageName, Resources resources, NotificationCompat.Builder mBuilder) {
+ String gcmLargeIcon = extras.getString(IMAGE); // from gcm
+ if (gcmLargeIcon != null) {
+ if (gcmLargeIcon.startsWith("http://") || gcmLargeIcon.startsWith("https://")) {
+ mBuilder.setLargeIcon(getBitmapFromURL(gcmLargeIcon));
+ Log.d(LOG_TAG, "using remote large-icon from gcm");
+ } else {
+ AssetManager assetManager = getAssets();
+ InputStream istr;
+ try {
+ istr = assetManager.open(gcmLargeIcon);
+ Bitmap bitmap = BitmapFactory.decodeStream(istr);
+ mBuilder.setLargeIcon(bitmap);
+ Log.d(LOG_TAG, "using assets large-icon from gcm");
+ } catch (IOException e) {
+ int largeIconId = 0;
+ largeIconId = resources.getIdentifier(gcmLargeIcon, DRAWABLE, packageName);
+ if (largeIconId != 0) {
+ Bitmap largeIconBitmap = BitmapFactory.decodeResource(resources, largeIconId);
+ mBuilder.setLargeIcon(largeIconBitmap);
+ Log.d(LOG_TAG, "using resources large-icon from gcm");
+ } else {
+ Log.d(LOG_TAG, "Not setting large icon");
+ }
+ }
+ }
+ }
+ }
+
+ private void setNotificationSmallIcon(Context context, Bundle extras, String packageName, Resources resources, NotificationCompat.Builder mBuilder, String localIcon) {
+ int iconId = 0;
+ String icon = extras.getString(ICON);
+ if (icon != null) {
+ iconId = resources.getIdentifier(icon, DRAWABLE, packageName);
+ Log.d(LOG_TAG, "using icon from plugin options");
+ }
+ else if (localIcon != null) {
+ iconId = resources.getIdentifier(localIcon, DRAWABLE, packageName);
+ Log.d(LOG_TAG, "using icon from plugin options");
+ }
+ if (iconId == 0) {
+ Log.d(LOG_TAG, "no icon resource found - using application icon");
+ iconId = context.getApplicationInfo().icon;
+ }
+ mBuilder.setSmallIcon(iconId);
+ }
+
+ private void setNotificationIconColor(String color, NotificationCompat.Builder mBuilder, String localIconColor) {
+ int iconColor = 0;
+ if (color != null) {
+ try {
+ iconColor = Color.parseColor(color);
+ } catch (IllegalArgumentException e) {
+ Log.e(LOG_TAG, "couldn't parse color from android options");
+ }
+ }
+ else if (localIconColor != null) {
+ try {
+ iconColor = Color.parseColor(localIconColor);
+ } catch (IllegalArgumentException e) {
+ Log.e(LOG_TAG, "couldn't parse color from android options");
+ }
+ }
+ if (iconColor != 0) {
+ mBuilder.setColor(iconColor);
+ }
+ }
+
+ public Bitmap getBitmapFromURL(String strURL) {
+ try {
+ URL url = new URL(strURL);
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setDoInput(true);
+ connection.connect();
+ InputStream input = connection.getInputStream();
+ return BitmapFactory.decodeStream(input);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private static String getAppName(Context context) {
+ CharSequence appName = context.getPackageManager().getApplicationLabel(context.getApplicationInfo());
+ return (String)appName;
+ }
+
+ private int parseInt(String value, Bundle extras) {
+ int retval = 0;
+
+ try {
+ retval = Integer.parseInt(extras.getString(value));
+ }
+ catch(NumberFormatException e) {
+ Log.e(LOG_TAG, "Number format exception - Error parsing " + value + ": " + e.getMessage());
+ }
+ catch(Exception e) {
+ Log.e(LOG_TAG, "Number format exception - Error parsing " + value + ": " + e.getMessage());
+ }
+
+ return retval;
+ }
+}
diff --git a/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushConstants.java b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushConstants.java
new file mode 100755
index 00000000..aeb49c9b
--- /dev/null
+++ b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushConstants.java
@@ -0,0 +1,53 @@
+package com.adobe.phonegap.push;
+
+public interface PushConstants {
+ public static final String COM_ADOBE_PHONEGAP_PUSH = "com.adobe.phonegap.push";
+ public static final String REGISTRATION_ID = "registrationId";
+ public static final String FOREGROUND = "foreground";
+ public static final String TITLE = "title";
+ public static final String NOT_ID = "notId";
+ public static final String PUSH_BUNDLE = "pushBundle";
+ public static final String ICON = "icon";
+ public static final String ICON_COLOR = "iconColor";
+ public static final String SOUND = "sound";
+ public static final String SOUND_DEFAULT = "default";
+ public static final String VIBRATE = "vibrate";
+ public static final String ACTIONS = "actions";
+ public static final String CALLBACK = "callback";
+ public static final String DRAWABLE = "drawable";
+ public static final String MSGCNT = "msgcnt";
+ public static final String VIBRATION_PATTERN = "vibrationPattern";
+ public static final String STYLE = "style";
+ public static final String SUMMARY_TEXT = "summaryText";
+ public static final String PICTURE = "picture";
+ public static final String GCM_N = "gcm.n.";
+ public static final String GCM_NOTIFICATION = "gcm.notification";
+ public static final String GCM_NOTIFICATION_BODY = "gcm.notification.body";
+ public static final String UA_PREFIX = "com.urbanairship.push";
+ public static final String PARSE_COM_DATA = "data";
+ public static final String ALERT = "alert";
+ public static final String MESSAGE = "message";
+ public static final String BODY = "body";
+ public static final String SOUNDNAME = "soundname";
+ public static final String LED_COLOR = "ledColor";
+ public static final String PRIORITY = "priority";
+ public static final String IMAGE = "image";
+ public static final String STYLE_INBOX = "inbox";
+ public static final String STYLE_PICTURE = "picture";
+ public static final String STYLE_TEXT = "text";
+ public static final String BADGE = "badge";
+ public static final String INITIALIZE = "init";
+ public static final String UNREGISTER = "unregister";
+ public static final String EXIT = "exit";
+ public static final String FINISH = "finish";
+ public static final String ANDROID = "android";
+ public static final String SENDER_ID = "senderID";
+ public static final String CLEAR_NOTIFICATIONS = "clearNotifications";
+ public static final String COLDSTART = "coldstart";
+ public static final String ADDITIONAL_DATA = "additionalData";
+ public static final String COUNT = "count";
+ public static final String FROM = "from";
+ public static final String COLLAPSE_KEY = "collapse_key";
+ public static final String FORCE_SHOW = "forceShow";
+ public static final String GCM = "GCM";
+}
diff --git a/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushHandlerActivity.java b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushHandlerActivity.java
new file mode 100755
index 00000000..dd9fbd36
--- /dev/null
+++ b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushHandlerActivity.java
@@ -0,0 +1,70 @@
+package com.adobe.phonegap.push;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.util.Log;
+
+public class PushHandlerActivity extends Activity implements PushConstants {
+ private static String LOG_TAG = "PushPlugin_PushHandlerActivity";
+
+ /*
+ * this activity will be started if the user touches a notification that we own.
+ * We send it's data off to the push plugin for processing.
+ * If needed, we boot up the main activity to kickstart the application.
+ * @see android.app.Activity#onCreate(android.os.Bundle)
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ GCMIntentService gcm = new GCMIntentService();
+ gcm.setNotification(getIntent().getIntExtra(NOT_ID, 0), "");
+ super.onCreate(savedInstanceState);
+ Log.v(LOG_TAG, "onCreate");
+
+ boolean isPushPluginActive = PushPlugin.isActive();
+ processPushBundle(isPushPluginActive);
+
+ finish();
+
+ if (!isPushPluginActive) {
+ forceMainActivityReload();
+ }
+ }
+
+ /**
+ * Takes the pushBundle extras from the intent,
+ * and sends it through to the PushPlugin for processing.
+ */
+ private void processPushBundle(boolean isPushPluginActive) {
+ Bundle extras = getIntent().getExtras();
+
+ if (extras != null) {
+ Bundle originalExtras = extras.getBundle(PUSH_BUNDLE);
+
+ originalExtras.putBoolean(FOREGROUND, false);
+ originalExtras.putBoolean(COLDSTART, !isPushPluginActive);
+ originalExtras.putString(CALLBACK, extras.getString("callback"));
+
+ PushPlugin.sendExtras(originalExtras);
+ }
+ }
+
+ /**
+ * Forces the main activity to re-launch if it's unloaded.
+ */
+ private void forceMainActivityReload() {
+ PackageManager pm = getPackageManager();
+ Intent launchIntent = pm.getLaunchIntentForPackage(getApplicationContext().getPackageName());
+ startActivity(launchIntent);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ final NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancelAll();
+ }
+} \ No newline at end of file
diff --git a/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushInstanceIDListenerService.java b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushInstanceIDListenerService.java
new file mode 100755
index 00000000..eaa39a48
--- /dev/null
+++ b/StoneIsland/platforms/android/src/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/platforms/android/src/com/adobe/phonegap/push/PushPlugin.java b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushPlugin.java
new file mode 100755
index 00000000..41a91819
--- /dev/null
+++ b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/PushPlugin.java
@@ -0,0 +1,294 @@
+package com.adobe.phonegap.push;
+
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.google.android.gms.iid.InstanceID;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+
+public class PushPlugin extends CordovaPlugin implements PushConstants {
+
+ public static final String LOG_TAG = "PushPlugin";
+
+ private static CallbackContext pushContext;
+ private static CordovaWebView gWebView;
+ private static Bundle gCachedExtras = null;
+ private static boolean gForeground = false;
+
+ /**
+ * Gets the application context from cordova's main activity.
+ * @return the application context
+ */
+ private Context getApplicationContext() {
+ return this.cordova.getActivity().getApplicationContext();
+ }
+
+ @Override
+ public boolean execute(final String action, final JSONArray data, final CallbackContext callbackContext) {
+ Log.v(LOG_TAG, "execute: action=" + action);
+ gWebView = this.webView;
+
+ if (INITIALIZE.equals(action)) {
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ pushContext = callbackContext;
+ JSONObject jo = null;
+
+ Log.v(LOG_TAG, "execute: data=" + data.toString());
+ SharedPreferences sharedPref = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
+ String token = null;
+ String senderID = null;
+
+ try {
+ jo = data.getJSONObject(0).getJSONObject(ANDROID);
+
+ Log.v(LOG_TAG, "execute: jo=" + jo.toString());
+
+ senderID = jo.getString(SENDER_ID);
+
+ Log.v(LOG_TAG, "execute: senderID=" + senderID);
+
+ String savedSenderID = sharedPref.getString(SENDER_ID, "");
+ String savedRegID = sharedPref.getString(REGISTRATION_ID, "");
+
+ // first time run get new token
+ if ("".equals(savedRegID)) {
+ token = InstanceID.getInstance(getApplicationContext()).getToken(senderID, GCM);
+ }
+ // new sender ID, re-register
+ else if (!savedSenderID.equals(senderID)) {
+ token = InstanceID.getInstance(getApplicationContext()).getToken(senderID, GCM);
+ }
+ // use the saved one
+ else {
+ token = sharedPref.getString(REGISTRATION_ID, "");
+ }
+
+ if (!"".equals(token)) {
+ JSONObject json = new JSONObject().put(REGISTRATION_ID, token);
+
+ Log.v(LOG_TAG, "onRegistered: " + json.toString());
+
+ PushPlugin.sendEvent( json );
+ } else {
+ callbackContext.error("Empty registration ID received from GCM");
+ return;
+ }
+ } catch (JSONException e) {
+ Log.e(LOG_TAG, "execute: Got JSON Exception " + e.getMessage());
+ callbackContext.error(e.getMessage());
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "execute: Got JSON Exception " + e.getMessage());
+ callbackContext.error(e.getMessage());
+ }
+
+ if (jo != null) {
+ SharedPreferences.Editor editor = sharedPref.edit();
+ try {
+ editor.putString(ICON, jo.getString(ICON));
+ } catch (JSONException e) {
+ Log.d(LOG_TAG, "no icon option");
+ }
+ try {
+ editor.putString(ICON_COLOR, jo.getString(ICON_COLOR));
+ } catch (JSONException e) {
+ Log.d(LOG_TAG, "no iconColor option");
+ }
+ editor.putBoolean(SOUND, jo.optBoolean(SOUND, true));
+ editor.putBoolean(VIBRATE, jo.optBoolean(VIBRATE, true));
+ editor.putBoolean(CLEAR_NOTIFICATIONS, jo.optBoolean(CLEAR_NOTIFICATIONS, true));
+ editor.putBoolean(FORCE_SHOW, jo.optBoolean(FORCE_SHOW, false));
+ editor.putString(SENDER_ID, senderID);
+ editor.putString(REGISTRATION_ID, token);
+ editor.commit();
+ }
+
+ if (gCachedExtras != null) {
+ Log.v(LOG_TAG, "sending cached extras");
+ sendExtras(gCachedExtras);
+ gCachedExtras = null;
+ }
+ }
+ });
+ } else if (UNREGISTER.equals(action)) {
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ try {
+ InstanceID.getInstance(getApplicationContext()).deleteInstanceID();
+ Log.v(LOG_TAG, "UNREGISTER");
+
+ // Remove shared prefs
+ SharedPreferences sharedPref = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.remove(SOUND);
+ editor.remove(VIBRATE);
+ editor.remove(CLEAR_NOTIFICATIONS);
+ editor.remove(FORCE_SHOW);
+ editor.remove(SENDER_ID);
+ editor.remove(REGISTRATION_ID);
+ editor.commit();
+
+ callbackContext.success();
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "execute: Got JSON Exception " + e.getMessage());
+ callbackContext.error(e.getMessage());
+ }
+ }
+ });
+ } else if (FINISH.equals(action)) {
+ callbackContext.success();
+ } else {
+ Log.e(LOG_TAG, "Invalid action : " + action);
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.INVALID_ACTION));
+ return false;
+ }
+
+ return true;
+ }
+
+ public static void sendEvent(JSONObject _json) {
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, _json);
+ pluginResult.setKeepCallback(true);
+ if (pushContext != null) {
+ pushContext.sendPluginResult(pluginResult);
+ }
+ }
+
+ public static void sendError(String message) {
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.ERROR, message);
+ pluginResult.setKeepCallback(true);
+ if (pushContext != null) {
+ pushContext.sendPluginResult(pluginResult);
+ }
+ }
+
+ /*
+ * Sends the pushbundle extras to the client application.
+ * If the client application isn't currently active, it is cached for later processing.
+ */
+ public static void sendExtras(Bundle extras) {
+ if (extras != null) {
+ if (gWebView != null) {
+ sendEvent(convertBundleToJson(extras));
+ } else {
+ Log.v(LOG_TAG, "sendExtras: caching extras to send at a later time.");
+ gCachedExtras = extras;
+ }
+ }
+ }
+
+ @Override
+ public void initialize(CordovaInterface cordova, CordovaWebView webView) {
+ super.initialize(cordova, webView);
+ gForeground = true;
+ }
+
+ @Override
+ public void onPause(boolean multitasking) {
+ super.onPause(multitasking);
+ gForeground = false;
+
+ SharedPreferences prefs = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
+ if (prefs.getBoolean(CLEAR_NOTIFICATIONS, true)) {
+ final NotificationManager notificationManager = (NotificationManager) cordova.getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancelAll();
+ }
+ }
+
+ @Override
+ public void onResume(boolean multitasking) {
+ super.onResume(multitasking);
+ gForeground = true;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ gForeground = false;
+ gWebView = null;
+ }
+
+ /*
+ * serializes a bundle to JSON.
+ */
+ private static JSONObject convertBundleToJson(Bundle extras) {
+ Log.d(LOG_TAG, "convert extras to json");
+ try {
+ JSONObject json = new JSONObject();
+ JSONObject additionalData = new JSONObject();
+
+ // Add any keys that need to be in top level json to this set
+ HashSet<String> jsonKeySet = new HashSet();
+ Collections.addAll(jsonKeySet, TITLE,MESSAGE,COUNT,SOUND,IMAGE);
+
+ Iterator<String> it = extras.keySet().iterator();
+ while (it.hasNext()) {
+ String key = it.next();
+ Object value = extras.get(key);
+
+ Log.d(LOG_TAG, "key = " + key);
+
+ if (jsonKeySet.contains(key)) {
+ json.put(key, value);
+ }
+ else if (key.equals(COLDSTART)) {
+ additionalData.put(key, extras.getBoolean(COLDSTART));
+ }
+ else if (key.equals(FOREGROUND)) {
+ additionalData.put(key, extras.getBoolean(FOREGROUND));
+ }
+ else if ( value instanceof String ) {
+ String strValue = (String)value;
+ try {
+ // Try to figure out if the value is another JSON object
+ if (strValue.startsWith("{")) {
+ additionalData.put(key, new JSONObject(strValue));
+ }
+ // Try to figure out if the value is another JSON array
+ else if (strValue.startsWith("[")) {
+ additionalData.put(key, new JSONArray(strValue));
+ }
+ else {
+ additionalData.put(key, value);
+ }
+ } catch (Exception e) {
+ additionalData.put(key, value);
+ }
+ }
+ } // while
+
+ json.put(ADDITIONAL_DATA, additionalData);
+ Log.v(LOG_TAG, "extrasToJSON: " + json.toString());
+
+ return json;
+ }
+ catch( JSONException e) {
+ Log.e(LOG_TAG, "extrasToJSON: JSON exception");
+ }
+ return null;
+ }
+
+ public static boolean isInForeground() {
+ return gForeground;
+ }
+
+ public static boolean isActive() {
+ return gWebView != null;
+ }
+} \ No newline at end of file
diff --git a/StoneIsland/platforms/android/src/com/adobe/phonegap/push/RegistrationIntentService.java b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/RegistrationIntentService.java
new file mode 100755
index 00000000..c4489fc1
--- /dev/null
+++ b/StoneIsland/platforms/android/src/com/adobe/phonegap/push/RegistrationIntentService.java
@@ -0,0 +1,42 @@
+package com.adobe.phonegap.push;
+
+import android.content.Context;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import com.google.android.gms.gcm.GoogleCloudMessaging;
+import com.google.android.gms.iid.InstanceID;
+
+import java.io.IOException;
+
+public class RegistrationIntentService extends IntentService implements PushConstants {
+ public static final String LOG_TAG = "PushPlugin_RegistrationIntentService";
+
+ public RegistrationIntentService() {
+ super(LOG_TAG);
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ SharedPreferences sharedPreferences = getApplicationContext().getSharedPreferences(COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE);
+
+ try {
+ InstanceID instanceID = InstanceID.getInstance(this);
+ String senderID = sharedPreferences.getString(SENDER_ID, "");
+ String token = instanceID.getToken(senderID,
+ GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
+ Log.i(LOG_TAG, "new GCM Registration Token: " + token);
+
+ // save new token
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putString(REGISTRATION_ID, token);
+ editor.commit();
+
+ } catch (Exception e) {
+ Log.d(LOG_TAG, "Failed to complete token refresh", e);
+ }
+ }
+} \ No newline at end of file
diff --git a/StoneIsland/platforms/android/src/com/ionic/keyboard/IonicKeyboard.java b/StoneIsland/platforms/android/src/com/ionic/keyboard/IonicKeyboard.java
new file mode 100755
index 00000000..deb914ab
--- /dev/null
+++ b/StoneIsland/platforms/android/src/com/ionic/keyboard/IonicKeyboard.java
@@ -0,0 +1,96 @@
+package com.ionic.keyboard;
+
+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.Status;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.inputmethod.InputMethodManager;
+
+public class IonicKeyboard extends CordovaPlugin{
+
+ public void initialize(CordovaInterface cordova, CordovaWebView webView) {
+ super.initialize(cordova, webView);
+
+ //calculate density-independent pixels (dp)
+ //http://developer.android.com/guide/practices/screens_support.html
+ DisplayMetrics dm = new DisplayMetrics();
+ cordova.getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
+ final float density = dm.density;
+
+ final CordovaWebView appView = webView;
+
+ //http://stackoverflow.com/a/4737265/1091751 detect if keyboard is showing
+ final View rootView = cordova.getActivity().getWindow().getDecorView().findViewById(android.R.id.content).getRootView();
+ OnGlobalLayoutListener list = new OnGlobalLayoutListener() {
+ int previousHeightDiff = 0;
+ @Override
+ public void onGlobalLayout() {
+ Rect r = new Rect();
+ //r will be populated with the coordinates of your view that area still visible.
+ rootView.getWindowVisibleDisplayFrame(r);
+
+ int heightDiff = rootView.getRootView().getHeight() - (r.bottom - r.top);
+ int pixelHeightDiff = (int)(heightDiff / density);
+ if (pixelHeightDiff > 100 && pixelHeightDiff != previousHeightDiff) { // if more than 100 pixels, its probably a keyboard...
+ appView.sendJavascript("cordova.plugins.Keyboard.isVisible = true");
+ appView.sendJavascript("cordova.fireWindowEvent('native.keyboardshow', { 'keyboardHeight':" + Integer.toString(pixelHeightDiff)+"});");
+
+ //deprecated
+ appView.sendJavascript("cordova.fireWindowEvent('native.showkeyboard', { 'keyboardHeight':" + Integer.toString(pixelHeightDiff)+"});");
+ }
+ else if ( pixelHeightDiff != previousHeightDiff && ( previousHeightDiff - pixelHeightDiff ) > 100 ){
+ appView.sendJavascript("cordova.plugins.Keyboard.isVisible = false");
+ appView.sendJavascript("cordova.fireWindowEvent('native.keyboardhide')");
+
+ //deprecated
+ appView.sendJavascript("cordova.fireWindowEvent('native.hidekeyboard')");
+ }
+ previousHeightDiff = pixelHeightDiff;
+ }
+ };
+
+ rootView.getViewTreeObserver().addOnGlobalLayoutListener(list);
+ }
+
+ public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
+ if ("close".equals(action)) {
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ //http://stackoverflow.com/a/7696791/1091751
+ InputMethodManager inputManager = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+ View v = cordova.getActivity().getCurrentFocus();
+
+ if (v == null) {
+ callbackContext.error("No current focus");
+ } else {
+ inputManager.hideSoftInputFromWindow(v.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
+ callbackContext.success(); // Thread-safe.
+ }
+ }
+ });
+ return true;
+ }
+ if ("show".equals(action)) {
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ ((InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE)).toggleSoftInput(0, InputMethodManager.HIDE_IMPLICIT_ONLY);
+ callbackContext.success(); // Thread-safe.
+ }
+ });
+ return true;
+ }
+ return false; // Returning false results in a "MethodNotFound" error.
+ }
+
+
+}
+
diff --git a/StoneIsland/platforms/android/src/nl/xservices/plugins/LaunchMyApp.java b/StoneIsland/platforms/android/src/nl/xservices/plugins/LaunchMyApp.java
new file mode 100755
index 00000000..9d3f3d63
--- /dev/null
+++ b/StoneIsland/platforms/android/src/nl/xservices/plugins/LaunchMyApp.java
@@ -0,0 +1,136 @@
+package nl.xservices.plugins;
+
+import android.content.Intent;
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaActivity;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Locale;
+
+public class LaunchMyApp extends CordovaPlugin {
+
+ private static final String ACTION_CHECKINTENT = "checkIntent";
+
+ @Override
+ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+ if (ACTION_CHECKINTENT.equalsIgnoreCase(action)) {
+ final Intent intent = ((CordovaActivity) this.webView.getContext()).getIntent();
+ final String intentString = intent.getDataString();
+ if (intentString != null && intentString.contains("://") && intent.getScheme() != null) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, intent.getDataString()));
+ intent.setData(null);
+ } else {
+ callbackContext.error("App was not started via the launchmyapp URL scheme. Ignoring this errorcallback is the best approach.");
+ }
+ return true;
+ } else {
+ callbackContext.error("This plugin only responds to the " + ACTION_CHECKINTENT + " action.");
+ return false;
+ }
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ final String intentString = intent.getDataString();
+ if (intentString != null && intentString.contains("://") && intent.getScheme() != null) {
+ intent.setData(null);
+ try {
+ StringWriter writer = new StringWriter(intentString.length() * 2);
+ escapeJavaStyleString(writer, intentString, true, false);
+ webView.loadUrl("javascript:handleOpenURL('" + writer.toString() + "');");
+ } catch (IOException ignore) {
+ }
+ }
+ }
+
+ // Taken from commons StringEscapeUtils
+ private static void escapeJavaStyleString(Writer out, String str, boolean escapeSingleQuote,
+ boolean escapeForwardSlash) throws IOException {
+ if (out == null) {
+ throw new IllegalArgumentException("The Writer must not be null");
+ }
+ if (str == null) {
+ return;
+ }
+ int sz;
+ sz = str.length();
+ for (int i = 0; i < sz; i++) {
+ char ch = str.charAt(i);
+
+ // handle unicode
+ if (ch > 0xfff) {
+ out.write("\\u" + hex(ch));
+ } else if (ch > 0xff) {
+ out.write("\\u0" + hex(ch));
+ } else if (ch > 0x7f) {
+ out.write("\\u00" + hex(ch));
+ } else if (ch < 32) {
+ switch (ch) {
+ case '\b':
+ out.write('\\');
+ out.write('b');
+ break;
+ case '\n':
+ out.write('\\');
+ out.write('n');
+ break;
+ case '\t':
+ out.write('\\');
+ out.write('t');
+ break;
+ case '\f':
+ out.write('\\');
+ out.write('f');
+ break;
+ case '\r':
+ out.write('\\');
+ out.write('r');
+ break;
+ default:
+ if (ch > 0xf) {
+ out.write("\\u00" + hex(ch));
+ } else {
+ out.write("\\u000" + hex(ch));
+ }
+ break;
+ }
+ } else {
+ switch (ch) {
+ case '\'':
+ if (escapeSingleQuote) {
+ out.write('\\');
+ }
+ out.write('\'');
+ break;
+ case '"':
+ out.write('\\');
+ out.write('"');
+ break;
+ case '\\':
+ out.write('\\');
+ out.write('\\');
+ break;
+ case '/':
+ if (escapeForwardSlash) {
+ out.write('\\');
+ }
+ out.write('/');
+ break;
+ default:
+ out.write(ch);
+ break;
+ }
+ }
+ }
+ }
+
+ private static String hex(char ch) {
+ return Integer.toHexString(ch).toUpperCase(Locale.ENGLISH);
+ }
+} \ No newline at end of file
diff --git a/StoneIsland/platforms/android/src/nl/xservices/plugins/SocialSharing.java b/StoneIsland/platforms/android/src/nl/xservices/plugins/SocialSharing.java
new file mode 100755
index 00000000..f1168f89
--- /dev/null
+++ b/StoneIsland/platforms/android/src/nl/xservices/plugins/SocialSharing.java
@@ -0,0 +1,533 @@
+package nl.xservices.plugins;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.*;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.text.Html;
+import android.util.Base64;
+import android.view.Gravity;
+import android.widget.Toast;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class SocialSharing extends CordovaPlugin {
+
+ private static final String ACTION_AVAILABLE_EVENT = "available";
+ private static final String ACTION_SHARE_EVENT = "share";
+ private static final String ACTION_CAN_SHARE_VIA = "canShareVia";
+ private static final String ACTION_CAN_SHARE_VIA_EMAIL = "canShareViaEmail";
+ private static final String ACTION_SHARE_VIA = "shareVia";
+ private static final String ACTION_SHARE_VIA_TWITTER_EVENT = "shareViaTwitter";
+ private static final String ACTION_SHARE_VIA_FACEBOOK_EVENT = "shareViaFacebook";
+ private static final String ACTION_SHARE_VIA_FACEBOOK_WITH_PASTEMESSAGEHINT = "shareViaFacebookWithPasteMessageHint";
+ private static final String ACTION_SHARE_VIA_WHATSAPP_EVENT = "shareViaWhatsApp";
+ private static final String ACTION_SHARE_VIA_INSTAGRAM_EVENT = "shareViaInstagram";
+ private static final String ACTION_SHARE_VIA_SMS_EVENT = "shareViaSMS";
+ private static final String ACTION_SHARE_VIA_EMAIL_EVENT = "shareViaEmail";
+
+ private static final int ACTIVITY_CODE_SENDVIAEMAIL = 2;
+
+ private CallbackContext _callbackContext;
+
+ private String pasteMessage;
+
+ private abstract class SocialSharingRunnable implements Runnable {
+ public CallbackContext callbackContext;
+ SocialSharingRunnable(CallbackContext cb) {
+ this.callbackContext = cb;
+ }
+ }
+
+ @Override
+ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+ this._callbackContext = callbackContext; // only used for onActivityResult
+ this.pasteMessage = null;
+ if (ACTION_AVAILABLE_EVENT.equals(action)) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK));
+ return true;
+ } else if (ACTION_SHARE_EVENT.equals(action)) {
+ return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), null, false);
+ } else if (ACTION_SHARE_VIA_TWITTER_EVENT.equals(action)) {
+ return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "twitter", false);
+ } else if (ACTION_SHARE_VIA_FACEBOOK_EVENT.equals(action)) {
+ return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "com.facebook.katana", false);
+ } else if (ACTION_SHARE_VIA_FACEBOOK_WITH_PASTEMESSAGEHINT.equals(action)) {
+ this.pasteMessage = args.getString(4);
+ return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "com.facebook.katana", false);
+ } else if (ACTION_SHARE_VIA_WHATSAPP_EVENT.equals(action)) {
+ return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "whatsapp", false);
+ } else if (ACTION_SHARE_VIA_INSTAGRAM_EVENT.equals(action)) {
+ if (notEmpty(args.getString(0))) {
+ copyHintToClipboard(args.getString(0), "Instagram paste message");
+ }
+ return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), "instagram", false);
+ } else if (ACTION_CAN_SHARE_VIA.equals(action)) {
+ return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), args.getString(4), true);
+ } else if (ACTION_CAN_SHARE_VIA_EMAIL.equals(action)) {
+ if (isEmailAvailable()) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK));
+ return true;
+ } else {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, "not available"));
+ return false;
+ }
+ } else if (ACTION_SHARE_VIA.equals(action)) {
+ return doSendIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), args.getString(4), false);
+ } else if (ACTION_SHARE_VIA_SMS_EVENT.equals(action)) {
+ return invokeSMSIntent(callbackContext, args.getJSONObject(0), args.getString(1));
+ } else if (ACTION_SHARE_VIA_EMAIL_EVENT.equals(action)) {
+ return invokeEmailIntent(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.isNull(3) ? null : args.getJSONArray(3), args.isNull(4) ? null : args.getJSONArray(4), args.isNull(5) ? null : args.getJSONArray(5));
+ } else {
+ callbackContext.error("socialSharing." + action + " is not a supported function. Did you mean '" + ACTION_SHARE_EVENT + "'?");
+ return false;
+ }
+ }
+
+ private boolean isEmailAvailable() {
+ final Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", "someone@domain.com", null));
+ return cordova.getActivity().getPackageManager().queryIntentActivities(intent, 0).size() > 0;
+ }
+
+ private boolean invokeEmailIntent(final CallbackContext callbackContext, final String message, final String subject, final JSONArray to, final JSONArray cc, final JSONArray bcc, final JSONArray files) throws JSONException {
+
+ final SocialSharing plugin = this;
+ cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) {
+ public void run() {
+ final Intent draft = new Intent(Intent.ACTION_SEND_MULTIPLE);
+ if (notEmpty(message)) {
+ Pattern htmlPattern = Pattern.compile(".*\\<[^>]+>.*", Pattern.DOTALL);
+ if (htmlPattern.matcher(message).matches()) {
+ draft.putExtra(android.content.Intent.EXTRA_TEXT, Html.fromHtml(message));
+ draft.setType("text/html");
+ } else {
+ draft.putExtra(android.content.Intent.EXTRA_TEXT, message);
+ draft.setType("text/plain");
+ }
+ }
+ if (notEmpty(subject)) {
+ draft.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
+ }
+ try {
+ if (to != null && to.length() > 0) {
+ draft.putExtra(android.content.Intent.EXTRA_EMAIL, toStringArray(to));
+ }
+ if (cc != null && cc.length() > 0) {
+ draft.putExtra(android.content.Intent.EXTRA_CC, toStringArray(cc));
+ }
+ if (bcc != null && bcc.length() > 0) {
+ draft.putExtra(android.content.Intent.EXTRA_BCC, toStringArray(bcc));
+ }
+ if (files.length() > 0) {
+ final String dir = getDownloadDir();
+ if (dir != null) {
+ ArrayList<Uri> fileUris = new ArrayList<Uri>();
+ for (int i = 0; i < files.length(); i++) {
+ final Uri fileUri = getFileUriAndSetType(draft, dir, files.getString(i), subject, i);
+ if (fileUri != null) {
+ fileUris.add(fileUri);
+ }
+ }
+ if (!fileUris.isEmpty()) {
+ draft.putExtra(Intent.EXTRA_STREAM, fileUris);
+ }
+ }
+ }
+ } catch (Exception e) {
+ callbackContext.error(e.getMessage());
+ }
+
+ draft.setType("application/octet-stream");
+ cordova.startActivityForResult(plugin, Intent.createChooser(draft, "Choose Email App"), ACTIVITY_CODE_SENDVIAEMAIL);
+ }
+ });
+
+ return true;
+ }
+
+ private String getDownloadDir() throws IOException {
+ // better check, otherwise it may crash the app
+ if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
+ // we need to use external storage since we need to share to another app
+ final String dir = webView.getContext().getExternalFilesDir(null) + "/socialsharing-downloads";
+ createOrCleanDir(dir);
+ return dir;
+ } else {
+ return null;
+ }
+ }
+
+ private boolean doSendIntent(final CallbackContext callbackContext, final String msg, final String subject, final JSONArray files, final String url, final String appPackageName, final boolean peek) {
+
+ final CordovaInterface mycordova = cordova;
+ final CordovaPlugin plugin = this;
+
+ cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) {
+ public void run() {
+ String message = msg;
+ final boolean hasMultipleAttachments = files.length() > 1;
+ final Intent sendIntent = new Intent(hasMultipleAttachments ? Intent.ACTION_SEND_MULTIPLE : Intent.ACTION_SEND);
+ sendIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+
+ try {
+ if (files.length() > 0 && !"".equals(files.getString(0))) {
+ final String dir = getDownloadDir();
+ if (dir != null) {
+ ArrayList<Uri> fileUris = new ArrayList<Uri>();
+ Uri fileUri = null;
+ for (int i = 0; i < files.length(); i++) {
+ fileUri = getFileUriAndSetType(sendIntent, dir, files.getString(i), subject, i);
+ if (fileUri != null) {
+ fileUris.add(fileUri);
+ }
+ }
+ if (!fileUris.isEmpty()) {
+ if (hasMultipleAttachments) {
+ sendIntent.putExtra(Intent.EXTRA_STREAM, fileUris);
+ } else {
+ sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri);
+ }
+ }
+ } else {
+ sendIntent.setType("text/plain");
+ }
+ } else {
+ sendIntent.setType("text/plain");
+ }
+ } catch (Exception e) {
+ callbackContext.error(e.getMessage());
+ }
+
+ if (notEmpty(subject)) {
+ sendIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
+ }
+ // add the URL to the message, as there seems to be no separate field
+ if (notEmpty(url)) {
+ if (notEmpty(message)) {
+ message += " " + url;
+ } else {
+ message = url;
+ }
+ }
+ if (notEmpty(message)) {
+ sendIntent.putExtra(android.content.Intent.EXTRA_TEXT, message);
+ // sometimes required when the user picks share via sms
+ if (Build.VERSION.SDK_INT < 21) { // LOLLIPOP
+ sendIntent.putExtra("sms_body", message);
+ }
+ }
+
+ if (appPackageName != null) {
+ String packageName = appPackageName;
+ String passedActivityName = null;
+ if (packageName.contains("/")) {
+ String[] items = appPackageName.split("/");
+ packageName = items[0];
+ passedActivityName = items[1];
+ }
+ final ActivityInfo activity = getActivity(callbackContext, sendIntent, packageName);
+ if (activity != null) {
+ if (peek) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK));
+ } else {
+ sendIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ sendIntent.setComponent(new ComponentName(activity.applicationInfo.packageName,
+ passedActivityName != null ? passedActivityName : activity.name));
+ mycordova.startActivityForResult(plugin, sendIntent, 0);
+
+ if (pasteMessage != null) {
+ // add a little delay because target app (facebook only atm) needs to be started first
+ new Timer().schedule(new TimerTask() {
+ public void run() {
+ cordova.getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ copyHintToClipboard(msg, pasteMessage);
+ showPasteMessage(pasteMessage);
+ }
+ });
+ }
+ }, 2000);
+ }
+ }
+ }
+ } else {
+ if (peek) {
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK));
+ } else {
+ mycordova.startActivityForResult(plugin, Intent.createChooser(sendIntent, null), 1);
+ }
+ }
+ }
+ });
+ return true;
+ }
+
+ @SuppressLint("NewApi")
+ private void copyHintToClipboard(String msg, String label) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ return;
+ }
+ final ClipboardManager clipboard = (android.content.ClipboardManager) cordova.getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
+ final ClipData clip = android.content.ClipData.newPlainText(label, msg);
+ clipboard.setPrimaryClip(clip);
+ }
+
+ @SuppressLint("NewApi")
+ private void showPasteMessage(String label) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ return;
+ }
+ final Toast toast = Toast.makeText(webView.getContext(), label, Toast.LENGTH_LONG);
+ toast.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL, 0, 0);
+ toast.show();
+ }
+
+ private Uri getFileUriAndSetType(Intent sendIntent, String dir, String image, String subject, int nthFile) throws IOException {
+ // we're assuming an image, but this can be any filetype you like
+ String localImage = image;
+ sendIntent.setType("image/*");
+ if (image.startsWith("http") || image.startsWith("www/")) {
+ String filename = getFileName(image);
+ localImage = "file://" + dir + "/" + filename;
+ if (image.startsWith("http")) {
+ // filename optimisation taken from https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin/pull/56
+ URLConnection connection = new URL(image).openConnection();
+ String disposition = connection.getHeaderField("Content-Disposition");
+ if (disposition != null) {
+ final Pattern dispositionPattern = Pattern.compile("filename=([^;]+)");
+ Matcher matcher = dispositionPattern.matcher(disposition);
+ if (matcher.find()) {
+ filename = matcher.group(1).replaceAll("[^a-zA-Z0-9._-]", "");
+ localImage = "file://" + dir + "/" + filename;
+ }
+ }
+ saveFile(getBytes(connection.getInputStream()), dir, filename);
+ } else {
+ saveFile(getBytes(webView.getContext().getAssets().open(image)), dir, filename);
+ }
+ } else if (image.startsWith("data:")) {
+ // safeguard for https://code.google.com/p/android/issues/detail?id=7901#c43
+ if (!image.contains(";base64,")) {
+ sendIntent.setType("text/plain");
+ return null;
+ }
+ // image looks like this: data:image/png;base64,R0lGODlhDAA...
+ final String encodedImg = image.substring(image.indexOf(";base64,") + 8);
+ // correct the intent type if anything else was passed, like a pdf: data:application/pdf;base64,..
+ if (!image.contains("data:image/")) {
+ sendIntent.setType(image.substring(image.indexOf("data:") + 5, image.indexOf(";base64")));
+ }
+ // the filename needs a valid extension, so it renders correctly in target apps
+ final String imgExtension = image.substring(image.indexOf("/") + 1, image.indexOf(";base64"));
+ String fileName;
+ // if a subject was passed, use it as the filename
+ // filenames must be unique when passing in multiple files [#158]
+ if (notEmpty(subject)) {
+ fileName = sanitizeFilename(subject) + (nthFile == 0 ? "" : "_" + nthFile) + "." + imgExtension;
+ } else {
+ fileName = "file" + (nthFile == 0 ? "" : "_" + nthFile) + "." + imgExtension;
+ }
+ saveFile(Base64.decode(encodedImg, Base64.DEFAULT), dir, fileName);
+ localImage = "file://" + dir + "/" + fileName;
+ } else if (image.startsWith("df:")) {
+ // safeguard for https://code.google.com/p/android/issues/detail?id=7901#c43
+ if (!image.contains(";base64,")) {
+ sendIntent.setType("text/plain");
+ return null;
+ }
+ // format looks like this : df:filename.txt;data:image/png;base64,R0lGODlhDAA...
+ final String fileName = image.substring(image.indexOf("df:") + 3, image.indexOf(";data:"));
+ final String fileType = image.substring(image.indexOf(";data:") + 6, image.indexOf(";base64,"));
+ final String encodedImg = image.substring(image.indexOf(";base64,") + 8);
+ sendIntent.setType(fileType);
+ saveFile(Base64.decode(encodedImg, Base64.DEFAULT), dir, sanitizeFilename(fileName));
+ localImage = "file://" + dir + "/" + fileName;
+ } else if (!image.startsWith("file://")) {
+ throw new IllegalArgumentException("URL_NOT_SUPPORTED");
+ }
+ return Uri.parse(localImage);
+ }
+
+ private boolean invokeSMSIntent(final CallbackContext callbackContext, JSONObject options, String p_phonenumbers) {
+ final String message = options.optString("message");
+ // TODO test this on a real SMS enabled device before releasing it
+// final String subject = options.optString("subject");
+// final String image = options.optString("image");
+ final String subject = null; //options.optString("subject");
+ final String image = null; // options.optString("image");
+ final String phonenumbers = getPhoneNumbersWithManufacturerSpecificSeparators(p_phonenumbers);
+ final SocialSharing plugin = this;
+ cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) {
+ public void run() {
+ Intent intent;
+
+ if (Build.VERSION.SDK_INT >= 19) { // Build.VERSION_CODES.KITKAT) {
+ // passing in no phonenumbers for kitkat may result in an error,
+ // but it may also work for some devices, so documentation will need to cover this case
+ intent = new Intent(Intent.ACTION_SENDTO);
+ intent.setData(Uri.parse("smsto:" + (notEmpty(phonenumbers) ? phonenumbers : "")));
+ } else {
+ intent = new Intent(Intent.ACTION_VIEW);
+ intent.setType("vnd.android-dir/mms-sms");
+ if (phonenumbers != null) {
+ intent.putExtra("address", phonenumbers);
+ }
+ }
+ intent.putExtra("sms_body", message);
+ intent.putExtra("sms_subject", subject);
+
+ try {
+ if (image != null && !"".equals(image)) {
+ final Uri fileUri = getFileUriAndSetType(intent, getDownloadDir(), image, subject, 0);
+ if (fileUri != null) {
+ intent.putExtra(Intent.EXTRA_STREAM, fileUri);
+ }
+ }
+ cordova.startActivityForResult(plugin, intent, 0);
+ } catch (Exception e) {
+ callbackContext.error(e.getMessage());
+ }
+ }
+ });
+ return true;
+ }
+
+ private static String getPhoneNumbersWithManufacturerSpecificSeparators(String phonenumbers) {
+ if (notEmpty(phonenumbers)) {
+ char separator;
+ if (android.os.Build.MANUFACTURER.equalsIgnoreCase("samsung")) {
+ separator = ',';
+ } else {
+ separator = ';';
+ }
+ return phonenumbers.replace(';', separator).replace(',', separator);
+ }
+ return null;
+ }
+
+ private ActivityInfo getActivity(final CallbackContext callbackContext, final Intent shareIntent, final String appPackageName) {
+ final PackageManager pm = webView.getContext().getPackageManager();
+ List<ResolveInfo> activityList = pm.queryIntentActivities(shareIntent, 0);
+ for (final ResolveInfo app : activityList) {
+ if ((app.activityInfo.packageName).contains(appPackageName)) {
+ return app.activityInfo;
+ }
+ }
+ // no matching app found
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, getShareActivities(activityList)));
+ return null;
+ }
+
+ private JSONArray getShareActivities(List<ResolveInfo> activityList) {
+ List<String> packages = new ArrayList<String>();
+ for (final ResolveInfo app : activityList) {
+ packages.add(app.activityInfo.packageName);
+ }
+ return new JSONArray(packages);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ super.onActivityResult(requestCode, resultCode, intent);
+ if (_callbackContext != null) {
+ if (ACTIVITY_CODE_SENDVIAEMAIL == requestCode) {
+ _callbackContext.success();
+ } else {
+ _callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, resultCode == Activity.RESULT_OK));
+ }
+ }
+ }
+
+ private void createOrCleanDir(final String downloadDir) throws IOException {
+ final File dir = new File(downloadDir);
+ if (!dir.exists()) {
+ if (!dir.mkdirs()) {
+ throw new IOException("CREATE_DIRS_FAILED");
+ }
+ } else {
+ cleanupOldFiles(dir);
+ }
+ }
+
+ private static String getFileName(String url) {
+ final String pattern = ".*/([^?#]+)?";
+ Pattern r = Pattern.compile(pattern);
+ Matcher m = r.matcher(url);
+ if (m.find()) {
+ return m.group(1);
+ } else {
+ return null;
+ }
+ }
+
+ private byte[] getBytes(InputStream is) throws IOException {
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ int nRead;
+ byte[] data = new byte[16384];
+ while ((nRead = is.read(data, 0, data.length)) != -1) {
+ buffer.write(data, 0, nRead);
+ }
+ buffer.flush();
+ return buffer.toByteArray();
+ }
+
+ private void saveFile(byte[] bytes, String dirName, String fileName) throws IOException {
+ final File dir = new File(dirName);
+ final FileOutputStream fos = new FileOutputStream(new File(dir, fileName));
+ fos.write(bytes);
+ fos.flush();
+ fos.close();
+ }
+
+ /**
+ * As file.deleteOnExit does not work on Android, we need to delete files manually.
+ * Deleting them in onActivityResult is not a good idea, because for example a base64 encoded file
+ * will not be available for upload to Facebook (it's deleted before it's uploaded).
+ * So the best approach is deleting old files when saving (sharing) a new one.
+ */
+ private void cleanupOldFiles(File dir) {
+ for (File f : dir.listFiles()) {
+ //noinspection ResultOfMethodCallIgnored
+ f.delete();
+ }
+ }
+
+ private static boolean notEmpty(String what) {
+ return what != null &&
+ !"".equals(what) &&
+ !"null".equalsIgnoreCase(what);
+ }
+
+ private static String[] toStringArray(JSONArray jsonArray) throws JSONException {
+ String[] result = new String[jsonArray.length()];
+ for (int i = 0; i < jsonArray.length(); i++) {
+ result[i] = jsonArray.getString(i);
+ }
+ return result;
+ }
+
+ public static String sanitizeFilename(String name) {
+ return name.replaceAll("[:\\\\/*?|<> ]", "_");
+ }
+}
diff --git a/StoneIsland/platforms/android/src/org/apache/cordova/device/Device.java b/StoneIsland/platforms/android/src/org/apache/cordova/device/Device.java
new file mode 100755
index 00000000..5eded907
--- /dev/null
+++ b/StoneIsland/platforms/android/src/org/apache/cordova/device/Device.java
@@ -0,0 +1,161 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.cordova.device;
+
+import java.util.TimeZone;
+
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaInterface;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.provider.Settings;
+
+public class Device extends CordovaPlugin {
+ public static final String TAG = "Device";
+
+ public static String platform; // Device OS
+ public static String uuid; // Device UUID
+
+ private static final String ANDROID_PLATFORM = "Android";
+ private static final String AMAZON_PLATFORM = "amazon-fireos";
+ private static final String AMAZON_DEVICE = "Amazon";
+
+ /**
+ * Constructor.
+ */
+ public Device() {
+ }
+
+ /**
+ * Sets the context of the Command. This can then be used to do things like
+ * get file paths associated with the Activity.
+ *
+ * @param cordova The context of the main Activity.
+ * @param webView The CordovaWebView Cordova is running in.
+ */
+ public void initialize(CordovaInterface cordova, CordovaWebView webView) {
+ super.initialize(cordova, webView);
+ Device.uuid = getUuid();
+ }
+
+ /**
+ * Executes the request and returns PluginResult.
+ *
+ * @param action The action to execute.
+ * @param args JSONArry of arguments for the plugin.
+ * @param callbackContext The callback id used when calling back into JavaScript.
+ * @return True if the action was valid, false if not.
+ */
+ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+ if (action.equals("getDeviceInfo")) {
+ JSONObject r = new JSONObject();
+ r.put("uuid", Device.uuid);
+ r.put("version", this.getOSVersion());
+ r.put("platform", this.getPlatform());
+ r.put("model", this.getModel());
+ r.put("manufacturer", this.getManufacturer());
+ callbackContext.success(r);
+ }
+ else {
+ return false;
+ }
+ return true;
+ }
+
+ //--------------------------------------------------------------------------
+ // LOCAL METHODS
+ //--------------------------------------------------------------------------
+
+ /**
+ * Get the OS name.
+ *
+ * @return
+ */
+ public String getPlatform() {
+ String platform;
+ if (isAmazonDevice()) {
+ platform = AMAZON_PLATFORM;
+ } else {
+ platform = ANDROID_PLATFORM;
+ }
+ return platform;
+ }
+
+ /**
+ * Get the device's Universally Unique Identifier (UUID).
+ *
+ * @return
+ */
+ public String getUuid() {
+ String uuid = Settings.Secure.getString(this.cordova.getActivity().getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);
+ return uuid;
+ }
+
+ public String getModel() {
+ String model = android.os.Build.MODEL;
+ return model;
+ }
+
+ public String getProductName() {
+ String productname = android.os.Build.PRODUCT;
+ return productname;
+ }
+
+ public String getManufacturer() {
+ String manufacturer = android.os.Build.MANUFACTURER;
+ return manufacturer;
+ }
+ /**
+ * Get the OS version.
+ *
+ * @return
+ */
+ public String getOSVersion() {
+ String osversion = android.os.Build.VERSION.RELEASE;
+ return osversion;
+ }
+
+ public String getSDKVersion() {
+ @SuppressWarnings("deprecation")
+ String sdkversion = android.os.Build.VERSION.SDK;
+ return sdkversion;
+ }
+
+ public String getTimeZoneID() {
+ TimeZone tz = TimeZone.getDefault();
+ return (tz.getID());
+ }
+
+ /**
+ * Function to check if the device is manufactured by Amazon
+ *
+ * @return
+ */
+ public boolean isAmazonDevice() {
+ if (android.os.Build.MANUFACTURER.equals(AMAZON_DEVICE)) {
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/StoneIsland/platforms/android/src/org/apache/cordova/dialogs/Notification.java b/StoneIsland/platforms/android/src/org/apache/cordova/dialogs/Notification.java
new file mode 100755
index 00000000..3bc3cee6
--- /dev/null
+++ b/StoneIsland/platforms/android/src/org/apache/cordova/dialogs/Notification.java
@@ -0,0 +1,483 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.cordova.dialogs;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.annotation.SuppressLint;
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.widget.EditText;
+import android.widget.TextView;
+
+
+/**
+ * This class provides access to notifications on the device.
+ *
+ * Be aware that this implementation gets called on
+ * navigator.notification.{alert|confirm|prompt}, and that there is a separate
+ * implementation in org.apache.cordova.CordovaChromeClient that gets
+ * called on a simple window.{alert|confirm|prompt}.
+ */
+public class Notification extends CordovaPlugin {
+
+ public int confirmResult = -1;
+ public ProgressDialog spinnerDialog = null;
+ public ProgressDialog progressDialog = null;
+
+ /**
+ * Constructor.
+ */
+ public Notification() {
+ }
+
+ /**
+ * Executes the request and returns PluginResult.
+ *
+ * @param action The action to execute.
+ * @param args JSONArray of arguments for the plugin.
+ * @param callbackContext The callback context used when calling back into JavaScript.
+ * @return True when the action was valid, false otherwise.
+ */
+ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+ /*
+ * Don't run any of these if the current activity is finishing
+ * in order to avoid android.view.WindowManager$BadTokenException
+ * crashing the app. Just return true here since false should only
+ * be returned in the event of an invalid action.
+ */
+ if(this.cordova.getActivity().isFinishing()) return true;
+
+ if (action.equals("beep")) {
+ this.beep(args.getLong(0));
+ }
+ else if (action.equals("alert")) {
+ this.alert(args.getString(0), args.getString(1), args.getString(2), callbackContext);
+ return true;
+ }
+ else if (action.equals("confirm")) {
+ this.confirm(args.getString(0), args.getString(1), args.getJSONArray(2), callbackContext);
+ return true;
+ }
+ else if (action.equals("prompt")) {
+ this.prompt(args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), callbackContext);
+ return true;
+ }
+ else if (action.equals("activityStart")) {
+ this.activityStart(args.getString(0), args.getString(1));
+ }
+ else if (action.equals("activityStop")) {
+ this.activityStop();
+ }
+ else if (action.equals("progressStart")) {
+ this.progressStart(args.getString(0), args.getString(1));
+ }
+ else if (action.equals("progressValue")) {
+ this.progressValue(args.getInt(0));
+ }
+ else if (action.equals("progressStop")) {
+ this.progressStop();
+ }
+ else {
+ return false;
+ }
+
+ // Only alert and confirm are async.
+ callbackContext.success();
+ return true;
+ }
+
+ //--------------------------------------------------------------------------
+ // LOCAL METHODS
+ //--------------------------------------------------------------------------
+
+ /**
+ * Beep plays the default notification ringtone.
+ *
+ * @param count Number of times to play notification
+ */
+ public void beep(final long count) {
+ cordova.getThreadPool().execute(new Runnable() {
+ public void run() {
+ Uri ringtone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
+ Ringtone notification = RingtoneManager.getRingtone(cordova.getActivity().getBaseContext(), ringtone);
+
+ // If phone is not set to silent mode
+ if (notification != null) {
+ for (long i = 0; i < count; ++i) {
+ notification.play();
+ long timeout = 5000;
+ while (notification.isPlaying() && (timeout > 0)) {
+ timeout = timeout - 100;
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Builds and shows a native Android alert with given Strings
+ * @param message The message the alert should display
+ * @param title The title of the alert
+ * @param buttonLabel The label of the button
+ * @param callbackContext The callback context
+ */
+ public synchronized void alert(final String message, final String title, final String buttonLabel, final CallbackContext callbackContext) {
+ final CordovaInterface cordova = this.cordova;
+
+ Runnable runnable = new Runnable() {
+ public void run() {
+
+ AlertDialog.Builder dlg = createDialog(cordova); // new AlertDialog.Builder(cordova.getActivity(), AlertDialog.THEME_DEVICE_DEFAULT_LIGHT);
+ dlg.setMessage(message);
+ dlg.setTitle(title);
+ dlg.setCancelable(true);
+ dlg.setPositiveButton(buttonLabel,
+ new AlertDialog.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0));
+ }
+ });
+ dlg.setOnCancelListener(new AlertDialog.OnCancelListener() {
+ public void onCancel(DialogInterface dialog)
+ {
+ dialog.dismiss();
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0));
+ }
+ });
+
+ changeTextDirection(dlg);
+ };
+ };
+ this.cordova.getActivity().runOnUiThread(runnable);
+ }
+
+ /**
+ * Builds and shows a native Android confirm dialog with given title, message, buttons.
+ * This dialog only shows up to 3 buttons. Any labels after that will be ignored.
+ * The index of the button pressed will be returned to the JavaScript callback identified by callbackId.
+ *
+ * @param message The message the dialog should display
+ * @param title The title of the dialog
+ * @param buttonLabels A comma separated list of button labels (Up to 3 buttons)
+ * @param callbackContext The callback context.
+ */
+ public synchronized void confirm(final String message, final String title, final JSONArray buttonLabels, final CallbackContext callbackContext) {
+ final CordovaInterface cordova = this.cordova;
+
+ Runnable runnable = new Runnable() {
+ public void run() {
+ AlertDialog.Builder dlg = createDialog(cordova); // new AlertDialog.Builder(cordova.getActivity(), AlertDialog.THEME_DEVICE_DEFAULT_LIGHT);
+ dlg.setMessage(message);
+ dlg.setTitle(title);
+ dlg.setCancelable(true);
+
+ // First button
+ if (buttonLabels.length() > 0) {
+ try {
+ dlg.setNegativeButton(buttonLabels.getString(0),
+ new AlertDialog.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 1));
+ }
+ });
+ } catch (JSONException e) { }
+ }
+
+ // Second button
+ if (buttonLabels.length() > 1) {
+ try {
+ dlg.setNeutralButton(buttonLabels.getString(1),
+ new AlertDialog.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 2));
+ }
+ });
+ } catch (JSONException e) { }
+ }
+
+ // Third button
+ if (buttonLabels.length() > 2) {
+ try {
+ dlg.setPositiveButton(buttonLabels.getString(2),
+ new AlertDialog.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 3));
+ }
+ });
+ } catch (JSONException e) { }
+ }
+ dlg.setOnCancelListener(new AlertDialog.OnCancelListener() {
+ public void onCancel(DialogInterface dialog)
+ {
+ dialog.dismiss();
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0));
+ }
+ });
+
+ changeTextDirection(dlg);
+ };
+ };
+ this.cordova.getActivity().runOnUiThread(runnable);
+ }
+
+ /**
+ * Builds and shows a native Android prompt dialog with given title, message, buttons.
+ * This dialog only shows up to 3 buttons. Any labels after that will be ignored.
+ * The following results are returned to the JavaScript callback identified by callbackId:
+ * buttonIndex Index number of the button selected
+ * input1 The text entered in the prompt dialog box
+ *
+ * @param message The message the dialog should display
+ * @param title The title of the dialog
+ * @param buttonLabels A comma separated list of button labels (Up to 3 buttons)
+ * @param callbackContext The callback context.
+ */
+ public synchronized void prompt(final String message, final String title, final JSONArray buttonLabels, final String defaultText, final CallbackContext callbackContext) {
+
+ final CordovaInterface cordova = this.cordova;
+
+ Runnable runnable = new Runnable() {
+ public void run() {
+ final EditText promptInput = new EditText(cordova.getActivity());
+ promptInput.setHint(defaultText);
+ AlertDialog.Builder dlg = createDialog(cordova); // new AlertDialog.Builder(cordova.getActivity(), AlertDialog.THEME_DEVICE_DEFAULT_LIGHT);
+ dlg.setMessage(message);
+ dlg.setTitle(title);
+ dlg.setCancelable(true);
+
+ dlg.setView(promptInput);
+
+ final JSONObject result = new JSONObject();
+
+ // First button
+ if (buttonLabels.length() > 0) {
+ try {
+ dlg.setNegativeButton(buttonLabels.getString(0),
+ new AlertDialog.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ try {
+ result.put("buttonIndex",1);
+ result.put("input1", promptInput.getText().toString().trim().length()==0 ? defaultText : promptInput.getText());
+ } catch (JSONException e) { e.printStackTrace(); }
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result));
+ }
+ });
+ } catch (JSONException e) { }
+ }
+
+ // Second button
+ if (buttonLabels.length() > 1) {
+ try {
+ dlg.setNeutralButton(buttonLabels.getString(1),
+ new AlertDialog.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ try {
+ result.put("buttonIndex",2);
+ result.put("input1", promptInput.getText().toString().trim().length()==0 ? defaultText : promptInput.getText());
+ } catch (JSONException e) { e.printStackTrace(); }
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result));
+ }
+ });
+ } catch (JSONException e) { }
+ }
+
+ // Third button
+ if (buttonLabels.length() > 2) {
+ try {
+ dlg.setPositiveButton(buttonLabels.getString(2),
+ new AlertDialog.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ try {
+ result.put("buttonIndex",3);
+ result.put("input1", promptInput.getText().toString().trim().length()==0 ? defaultText : promptInput.getText());
+ } catch (JSONException e) { e.printStackTrace(); }
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result));
+ }
+ });
+ } catch (JSONException e) { }
+ }
+ dlg.setOnCancelListener(new AlertDialog.OnCancelListener() {
+ public void onCancel(DialogInterface dialog){
+ dialog.dismiss();
+ try {
+ result.put("buttonIndex",0);
+ result.put("input1", promptInput.getText().toString().trim().length()==0 ? defaultText : promptInput.getText());
+ } catch (JSONException e) { e.printStackTrace(); }
+ callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result));
+ }
+ });
+
+ changeTextDirection(dlg);
+ };
+ };
+ this.cordova.getActivity().runOnUiThread(runnable);
+ }
+
+ /**
+ * Show the spinner.
+ *
+ * @param title Title of the dialog
+ * @param message The message of the dialog
+ */
+ public synchronized void activityStart(final String title, final String message) {
+ if (this.spinnerDialog != null) {
+ this.spinnerDialog.dismiss();
+ this.spinnerDialog = null;
+ }
+ final Notification notification = this;
+ final CordovaInterface cordova = this.cordova;
+ Runnable runnable = new Runnable() {
+ public void run() {
+ notification.spinnerDialog = createProgressDialog(cordova); // new ProgressDialog(cordova.getActivity(), AlertDialog.THEME_DEVICE_DEFAULT_LIGHT);
+ notification.spinnerDialog.setTitle(title);
+ notification.spinnerDialog.setMessage(message);
+ notification.spinnerDialog.setCancelable(true);
+ notification.spinnerDialog.setIndeterminate(true);
+ notification.spinnerDialog.setOnCancelListener(
+ new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ notification.spinnerDialog = null;
+ }
+ });
+ notification.spinnerDialog.show();
+ }
+ };
+ this.cordova.getActivity().runOnUiThread(runnable);
+ }
+
+ /**
+ * Stop spinner.
+ */
+ public synchronized void activityStop() {
+ if (this.spinnerDialog != null) {
+ this.spinnerDialog.dismiss();
+ this.spinnerDialog = null;
+ }
+ }
+
+ /**
+ * Show the progress dialog.
+ *
+ * @param title Title of the dialog
+ * @param message The message of the dialog
+ */
+ public synchronized void progressStart(final String title, final String message) {
+ if (this.progressDialog != null) {
+ this.progressDialog.dismiss();
+ this.progressDialog = null;
+ }
+ final Notification notification = this;
+ final CordovaInterface cordova = this.cordova;
+ Runnable runnable = new Runnable() {
+ public void run() {
+ notification.progressDialog = createProgressDialog(cordova); // new ProgressDialog(cordova.getActivity(), AlertDialog.THEME_DEVICE_DEFAULT_LIGHT);
+ notification.progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+ notification.progressDialog.setTitle(title);
+ notification.progressDialog.setMessage(message);
+ notification.progressDialog.setCancelable(true);
+ notification.progressDialog.setMax(100);
+ notification.progressDialog.setProgress(0);
+ notification.progressDialog.setOnCancelListener(
+ new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ notification.progressDialog = null;
+ }
+ });
+ notification.progressDialog.show();
+ }
+ };
+ this.cordova.getActivity().runOnUiThread(runnable);
+ }
+
+ /**
+ * Set value of progress bar.
+ *
+ * @param value 0-100
+ */
+ public synchronized void progressValue(int value) {
+ if (this.progressDialog != null) {
+ this.progressDialog.setProgress(value);
+ }
+ }
+
+ /**
+ * Stop progress dialog.
+ */
+ public synchronized void progressStop() {
+ if (this.progressDialog != null) {
+ this.progressDialog.dismiss();
+ this.progressDialog = null;
+ }
+ }
+
+ @SuppressLint("NewApi")
+ private AlertDialog.Builder createDialog(CordovaInterface cordova) {
+ int currentapiVersion = android.os.Build.VERSION.SDK_INT;
+ if (currentapiVersion >= android.os.Build.VERSION_CODES.HONEYCOMB) {
+ return new AlertDialog.Builder(cordova.getActivity(), AlertDialog.THEME_DEVICE_DEFAULT_LIGHT);
+ } else {
+ return new AlertDialog.Builder(cordova.getActivity());
+ }
+ }
+
+ @SuppressLint("InlinedApi")
+ private ProgressDialog createProgressDialog(CordovaInterface cordova) {
+ int currentapiVersion = android.os.Build.VERSION.SDK_INT;
+ if (currentapiVersion >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ return new ProgressDialog(cordova.getActivity(), AlertDialog.THEME_DEVICE_DEFAULT_LIGHT);
+ } else {
+ return new ProgressDialog(cordova.getActivity());
+ }
+ }
+
+ @SuppressLint("NewApi")
+ private void changeTextDirection(Builder dlg){
+ int currentapiVersion = android.os.Build.VERSION.SDK_INT;
+ dlg.create();
+ AlertDialog dialog = dlg.show();
+ if (currentapiVersion >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ TextView messageview = (TextView)dialog.findViewById(android.R.id.message);
+ messageview.setTextDirection(android.view.View.TEXT_DIRECTION_LOCALE);
+ }
+ }
+}
diff --git a/StoneIsland/platforms/android/src/org/apache/cordova/inappbrowser/InAppBrowser.java b/StoneIsland/platforms/android/src/org/apache/cordova/inappbrowser/InAppBrowser.java
new file mode 100755
index 00000000..60437451
--- /dev/null
+++ b/StoneIsland/platforms/android/src/org/apache/cordova/inappbrowser/InAppBrowser.java
@@ -0,0 +1,869 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.cordova.inappbrowser;
+
+import android.annotation.SuppressLint;
+import org.apache.cordova.inappbrowser.InAppBrowserDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Browser;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.InputType;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.webkit.CookieManager;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.Config;
+import org.apache.cordova.CordovaArgs;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.LOG;
+import org.apache.cordova.PluginManager;
+import org.apache.cordova.PluginResult;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+@SuppressLint("SetJavaScriptEnabled")
+public class InAppBrowser extends CordovaPlugin {
+
+ private static final String NULL = "null";
+ protected static final String LOG_TAG = "InAppBrowser";
+ private static final String SELF = "_self";
+ private static final String SYSTEM = "_system";
+ private static final String EXIT_EVENT = "exit";
+ private static final String LOCATION = "location";
+ private static final String ZOOM = "zoom";
+ private static final String HIDDEN = "hidden";
+ private static final String LOAD_START_EVENT = "loadstart";
+ private static final String LOAD_STOP_EVENT = "loadstop";
+ private static final String LOAD_ERROR_EVENT = "loaderror";
+ private static final String CLEAR_ALL_CACHE = "clearcache";
+ private static final String CLEAR_SESSION_CACHE = "clearsessioncache";
+ private static final String HARDWARE_BACK_BUTTON = "hardwareback";
+
+ private InAppBrowserDialog dialog;
+ private WebView inAppWebView;
+ private EditText edittext;
+ private CallbackContext callbackContext;
+ private boolean showLocationBar = true;
+ private boolean showZoomControls = true;
+ private boolean openWindowHidden = false;
+ private boolean clearAllCache = false;
+ private boolean clearSessionCache = false;
+ private boolean hadwareBackButton = true;
+
+ /**
+ * Executes the request and returns PluginResult.
+ *
+ * @param action the action to execute.
+ * @param args JSONArry of arguments for the plugin.
+ * @param callbackContext the callbackContext used when calling back into JavaScript.
+ * @return A PluginResult object with a status and message.
+ */
+ public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
+ if (action.equals("open")) {
+ this.callbackContext = callbackContext;
+ final String url = args.getString(0);
+ String t = args.optString(1);
+ if (t == null || t.equals("") || t.equals(NULL)) {
+ t = SELF;
+ }
+ final String target = t;
+ final HashMap<String, Boolean> features = parseFeature(args.optString(2));
+
+ Log.d(LOG_TAG, "target = " + target);
+
+ this.cordova.getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ String result = "";
+ // SELF
+ if (SELF.equals(target)) {
+ Log.d(LOG_TAG, "in self");
+ /* This code exists for compatibility between 3.x and 4.x versions of Cordova.
+ * Previously the Config class had a static method, isUrlWhitelisted(). That
+ * responsibility has been moved to the plugins, with an aggregating method in
+ * PluginManager.
+ */
+ Boolean shouldAllowNavigation = null;
+ if (url.startsWith("javascript:")) {
+ shouldAllowNavigation = true;
+ }
+ if (shouldAllowNavigation == null) {
+ try {
+ Method iuw = Config.class.getMethod("isUrlWhiteListed", String.class);
+ shouldAllowNavigation = (Boolean)iuw.invoke(null, url);
+ } catch (NoSuchMethodException e) {
+ } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException e) {
+ }
+ }
+ if (shouldAllowNavigation == null) {
+ try {
+ Method gpm = webView.getClass().getMethod("getPluginManager");
+ PluginManager pm = (PluginManager)gpm.invoke(webView);
+ Method san = pm.getClass().getMethod("shouldAllowNavigation", String.class);
+ shouldAllowNavigation = (Boolean)san.invoke(pm, url);
+ } catch (NoSuchMethodException e) {
+ } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException e) {
+ }
+ }
+ // load in webview
+ if (Boolean.TRUE.equals(shouldAllowNavigation)) {
+ Log.d(LOG_TAG, "loading in webview");
+ webView.loadUrl(url);
+ }
+ //Load the dialer
+ else if (url.startsWith(WebView.SCHEME_TEL))
+ {
+ try {
+ Log.d(LOG_TAG, "loading in dialer");
+ Intent intent = new Intent(Intent.ACTION_DIAL);
+ intent.setData(Uri.parse(url));
+ cordova.getActivity().startActivity(intent);
+ } catch (android.content.ActivityNotFoundException e) {
+ LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString());
+ }
+ }
+ // load in InAppBrowser
+ else {
+ Log.d(LOG_TAG, "loading in InAppBrowser");
+ result = showWebPage(url, features);
+ }
+ }
+ // SYSTEM
+ else if (SYSTEM.equals(target)) {
+ Log.d(LOG_TAG, "in system");
+ result = openExternal(url);
+ }
+ // BLANK - or anything else
+ else {
+ Log.d(LOG_TAG, "in blank");
+ result = showWebPage(url, features);
+ }
+
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, result);
+ pluginResult.setKeepCallback(true);
+ callbackContext.sendPluginResult(pluginResult);
+ }
+ });
+ }
+ else if (action.equals("close")) {
+ closeDialog();
+ }
+ else if (action.equals("injectScriptCode")) {
+ String jsWrapper = null;
+ if (args.getBoolean(1)) {
+ jsWrapper = String.format("prompt(JSON.stringify([eval(%%s)]), 'gap-iab://%s')", callbackContext.getCallbackId());
+ }
+ injectDeferredObject(args.getString(0), jsWrapper);
+ }
+ else if (action.equals("injectScriptFile")) {
+ String jsWrapper;
+ if (args.getBoolean(1)) {
+ jsWrapper = String.format("(function(d) { var c = d.createElement('script'); c.src = %%s; c.onload = function() { prompt('', 'gap-iab://%s'); }; d.body.appendChild(c); })(document)", callbackContext.getCallbackId());
+ } else {
+ jsWrapper = "(function(d) { var c = d.createElement('script'); c.src = %s; d.body.appendChild(c); })(document)";
+ }
+ injectDeferredObject(args.getString(0), jsWrapper);
+ }
+ else if (action.equals("injectStyleCode")) {
+ String jsWrapper;
+ if (args.getBoolean(1)) {
+ jsWrapper = String.format("(function(d) { var c = d.createElement('style'); c.innerHTML = %%s; d.body.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId());
+ } else {
+ jsWrapper = "(function(d) { var c = d.createElement('style'); c.innerHTML = %s; d.body.appendChild(c); })(document)";
+ }
+ injectDeferredObject(args.getString(0), jsWrapper);
+ }
+ else if (action.equals("injectStyleFile")) {
+ String jsWrapper;
+ if (args.getBoolean(1)) {
+ jsWrapper = String.format("(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %%s; d.head.appendChild(c); prompt('', 'gap-iab://%s');})(document)", callbackContext.getCallbackId());
+ } else {
+ jsWrapper = "(function(d) { var c = d.createElement('link'); c.rel='stylesheet'; c.type='text/css'; c.href = %s; d.head.appendChild(c); })(document)";
+ }
+ injectDeferredObject(args.getString(0), jsWrapper);
+ }
+ else if (action.equals("show")) {
+ this.cordova.getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ dialog.show();
+ }
+ });
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.OK);
+ pluginResult.setKeepCallback(true);
+ this.callbackContext.sendPluginResult(pluginResult);
+ }
+ else {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Called when the view navigates.
+ */
+ @Override
+ public void onReset() {
+ closeDialog();
+ }
+
+ /**
+ * Called by AccelBroker when listener is to be shut down.
+ * Stop listener.
+ */
+ public void onDestroy() {
+ closeDialog();
+ }
+
+ /**
+ * Inject an object (script or style) into the InAppBrowser WebView.
+ *
+ * This is a helper method for the inject{Script|Style}{Code|File} API calls, which
+ * provides a consistent method for injecting JavaScript code into the document.
+ *
+ * If a wrapper string is supplied, then the source string will be JSON-encoded (adding
+ * quotes) and wrapped using string formatting. (The wrapper string should have a single
+ * '%s' marker)
+ *
+ * @param source The source object (filename or script/style text) to inject into
+ * the document.
+ * @param jsWrapper A JavaScript string to wrap the source string in, so that the object
+ * is properly injected, or null if the source string is JavaScript text
+ * which should be executed directly.
+ */
+ private void injectDeferredObject(String source, String jsWrapper) {
+ String scriptToInject;
+ if (jsWrapper != null) {
+ org.json.JSONArray jsonEsc = new org.json.JSONArray();
+ jsonEsc.put(source);
+ String jsonRepr = jsonEsc.toString();
+ String jsonSourceString = jsonRepr.substring(1, jsonRepr.length()-1);
+ scriptToInject = String.format(jsWrapper, jsonSourceString);
+ } else {
+ scriptToInject = source;
+ }
+ final String finalScriptToInject = scriptToInject;
+ this.cordova.getActivity().runOnUiThread(new Runnable() {
+ @SuppressLint("NewApi")
+ @Override
+ public void run() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
+ // This action will have the side-effect of blurring the currently focused element
+ inAppWebView.loadUrl("javascript:" + finalScriptToInject);
+ } else {
+ inAppWebView.evaluateJavascript(finalScriptToInject, null);
+ }
+ }
+ });
+ }
+
+ /**
+ * Put the list of features into a hash map
+ *
+ * @param optString
+ * @return
+ */
+ private HashMap<String, Boolean> parseFeature(String optString) {
+ if (optString.equals(NULL)) {
+ return null;
+ } else {
+ HashMap<String, Boolean> map = new HashMap<String, Boolean>();
+ StringTokenizer features = new StringTokenizer(optString, ",");
+ StringTokenizer option;
+ while(features.hasMoreElements()) {
+ option = new StringTokenizer(features.nextToken(), "=");
+ if (option.hasMoreElements()) {
+ String key = option.nextToken();
+ Boolean value = option.nextToken().equals("no") ? Boolean.FALSE : Boolean.TRUE;
+ map.put(key, value);
+ }
+ }
+ return map;
+ }
+ }
+
+ /**
+ * Display a new browser with the specified URL.
+ *
+ * @param url the url to load.
+ * @return "" if ok, or error message.
+ */
+ public String openExternal(String url) {
+ try {
+ Intent intent = null;
+ intent = new Intent(Intent.ACTION_VIEW);
+ // Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
+ // Adding the MIME type to http: URLs causes them to not be handled by the downloader.
+ Uri uri = Uri.parse(url);
+ if ("file".equals(uri.getScheme())) {
+ intent.setDataAndType(uri, webView.getResourceApi().getMimeType(uri));
+ } else {
+ intent.setData(uri);
+ }
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, cordova.getActivity().getPackageName());
+ this.cordova.getActivity().startActivity(intent);
+ return "";
+ } catch (android.content.ActivityNotFoundException e) {
+ Log.d(LOG_TAG, "InAppBrowser: Error loading url "+url+":"+ e.toString());
+ return e.toString();
+ }
+ }
+
+ /**
+ * Closes the dialog
+ */
+ public void closeDialog() {
+ final WebView childView = this.inAppWebView;
+ // The JS protects against multiple calls, so this should happen only when
+ // closeDialog() is called by other native code.
+ if (childView == null) {
+ return;
+ }
+ this.cordova.getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ childView.setWebViewClient(new WebViewClient() {
+ // NB: wait for about:blank before dismissing
+ public void onPageFinished(WebView view, String url) {
+ if (dialog != null) {
+ dialog.dismiss();
+ }
+ }
+ });
+ // NB: From SDK 19: "If you call methods on WebView from any thread
+ // other than your app's UI thread, it can cause unexpected results."
+ // http://developer.android.com/guide/webapps/migrating.html#Threads
+ childView.loadUrl("about:blank");
+ }
+ });
+
+ try {
+ JSONObject obj = new JSONObject();
+ obj.put("type", EXIT_EVENT);
+ sendUpdate(obj, false);
+ } catch (JSONException ex) {
+ Log.d(LOG_TAG, "Should never happen");
+ }
+ }
+
+ /**
+ * Checks to see if it is possible to go back one page in history, then does so.
+ */
+ public void goBack() {
+ if (this.inAppWebView.canGoBack()) {
+ this.inAppWebView.goBack();
+ }
+ }
+
+ /**
+ * Can the web browser go back?
+ * @return boolean
+ */
+ public boolean canGoBack() {
+ return this.inAppWebView.canGoBack();
+ }
+
+ /**
+ * Has the user set the hardware back button to go back
+ * @return boolean
+ */
+ public boolean hardwareBack() {
+ return hadwareBackButton;
+ }
+
+ /**
+ * Checks to see if it is possible to go forward one page in history, then does so.
+ */
+ private void goForward() {
+ if (this.inAppWebView.canGoForward()) {
+ this.inAppWebView.goForward();
+ }
+ }
+
+ /**
+ * Navigate to the new page
+ *
+ * @param url to load
+ */
+ private void navigate(String url) {
+ InputMethodManager imm = (InputMethodManager)this.cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(edittext.getWindowToken(), 0);
+
+ if (!url.startsWith("http") && !url.startsWith("file:")) {
+ this.inAppWebView.loadUrl("http://" + url);
+ } else {
+ this.inAppWebView.loadUrl(url);
+ }
+ this.inAppWebView.requestFocus();
+ }
+
+
+ /**
+ * Should we show the location bar?
+ *
+ * @return boolean
+ */
+ private boolean getShowLocationBar() {
+ return this.showLocationBar;
+ }
+
+ private InAppBrowser getInAppBrowser(){
+ return this;
+ }
+
+ /**
+ * Display a new browser with the specified URL.
+ *
+ * @param url the url to load.
+ * @param features jsonObject
+ */
+ public String showWebPage(final String url, HashMap<String, Boolean> features) {
+ // Determine if we should hide the location bar.
+ showLocationBar = true;
+ showZoomControls = true;
+ openWindowHidden = false;
+ if (features != null) {
+ Boolean show = features.get(LOCATION);
+ if (show != null) {
+ showLocationBar = show.booleanValue();
+ }
+ Boolean zoom = features.get(ZOOM);
+ if (zoom != null) {
+ showZoomControls = zoom.booleanValue();
+ }
+ Boolean hidden = features.get(HIDDEN);
+ if (hidden != null) {
+ openWindowHidden = hidden.booleanValue();
+ }
+ Boolean hardwareBack = features.get(HARDWARE_BACK_BUTTON);
+ if (hardwareBack != null) {
+ hadwareBackButton = hardwareBack.booleanValue();
+ }
+ Boolean cache = features.get(CLEAR_ALL_CACHE);
+ if (cache != null) {
+ clearAllCache = cache.booleanValue();
+ } else {
+ cache = features.get(CLEAR_SESSION_CACHE);
+ if (cache != null) {
+ clearSessionCache = cache.booleanValue();
+ }
+ }
+ }
+
+ final CordovaWebView thatWebView = this.webView;
+
+ // Create dialog in new thread
+ Runnable runnable = new Runnable() {
+ /**
+ * Convert our DIP units to Pixels
+ *
+ * @return int
+ */
+ private int dpToPixels(int dipValue) {
+ int value = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP,
+ (float) dipValue,
+ cordova.getActivity().getResources().getDisplayMetrics()
+ );
+
+ return value;
+ }
+
+ @SuppressLint("NewApi")
+ public void run() {
+ // Let's create the main dialog
+ dialog = new InAppBrowserDialog(cordova.getActivity(), android.R.style.Theme_NoTitleBar);
+ dialog.getWindow().getAttributes().windowAnimations = android.R.style.Animation_Dialog;
+ dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ dialog.setCancelable(true);
+ dialog.setInAppBroswer(getInAppBrowser());
+
+ // Main container layout
+ LinearLayout main = new LinearLayout(cordova.getActivity());
+ main.setOrientation(LinearLayout.VERTICAL);
+
+ // Toolbar layout
+ RelativeLayout toolbar = new RelativeLayout(cordova.getActivity());
+ //Please, no more black!
+ toolbar.setBackgroundColor(android.graphics.Color.LTGRAY);
+ toolbar.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, this.dpToPixels(44)));
+ toolbar.setPadding(this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2));
+ toolbar.setHorizontalGravity(Gravity.LEFT);
+ toolbar.setVerticalGravity(Gravity.TOP);
+
+ // Action Button Container layout
+ RelativeLayout actionButtonContainer = new RelativeLayout(cordova.getActivity());
+ actionButtonContainer.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ actionButtonContainer.setHorizontalGravity(Gravity.LEFT);
+ actionButtonContainer.setVerticalGravity(Gravity.CENTER_VERTICAL);
+ actionButtonContainer.setId(1);
+
+ // Back button
+ Button back = new Button(cordova.getActivity());
+ RelativeLayout.LayoutParams backLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+ backLayoutParams.addRule(RelativeLayout.ALIGN_LEFT);
+ back.setLayoutParams(backLayoutParams);
+ back.setContentDescription("Back Button");
+ back.setId(2);
+ Resources activityRes = cordova.getActivity().getResources();
+ int backResId = activityRes.getIdentifier("ic_action_previous_item", "drawable", cordova.getActivity().getPackageName());
+ Drawable backIcon = activityRes.getDrawable(backResId);
+ if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN)
+ {
+ back.setBackgroundDrawable(backIcon);
+ }
+ else
+ {
+ back.setBackground(backIcon);
+ }
+ back.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ goBack();
+ }
+ });
+
+ // Forward button
+ Button forward = new Button(cordova.getActivity());
+ RelativeLayout.LayoutParams forwardLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+ forwardLayoutParams.addRule(RelativeLayout.RIGHT_OF, 2);
+ forward.setLayoutParams(forwardLayoutParams);
+ forward.setContentDescription("Forward Button");
+ forward.setId(3);
+ int fwdResId = activityRes.getIdentifier("ic_action_next_item", "drawable", cordova.getActivity().getPackageName());
+ Drawable fwdIcon = activityRes.getDrawable(fwdResId);
+ if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN)
+ {
+ forward.setBackgroundDrawable(fwdIcon);
+ }
+ else
+ {
+ forward.setBackground(fwdIcon);
+ }
+ forward.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ goForward();
+ }
+ });
+
+ // Edit Text Box
+ edittext = new EditText(cordova.getActivity());
+ RelativeLayout.LayoutParams textLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ textLayoutParams.addRule(RelativeLayout.RIGHT_OF, 1);
+ textLayoutParams.addRule(RelativeLayout.LEFT_OF, 5);
+ edittext.setLayoutParams(textLayoutParams);
+ edittext.setId(4);
+ edittext.setSingleLine(true);
+ edittext.setText(url);
+ edittext.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
+ edittext.setImeOptions(EditorInfo.IME_ACTION_GO);
+ edittext.setInputType(InputType.TYPE_NULL); // Will not except input... Makes the text NON-EDITABLE
+ edittext.setOnKeyListener(new View.OnKeyListener() {
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ // If the event is a key-down event on the "enter" button
+ if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
+ navigate(edittext.getText().toString());
+ return true;
+ }
+ return false;
+ }
+ });
+
+ // Close/Done button
+ Button close = new Button(cordova.getActivity());
+ RelativeLayout.LayoutParams closeLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+ closeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+ close.setLayoutParams(closeLayoutParams);
+ forward.setContentDescription("Close Button");
+ close.setId(5);
+ int closeResId = activityRes.getIdentifier("ic_action_remove", "drawable", cordova.getActivity().getPackageName());
+ Drawable closeIcon = activityRes.getDrawable(closeResId);
+ if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN)
+ {
+ close.setBackgroundDrawable(closeIcon);
+ }
+ else
+ {
+ close.setBackground(closeIcon);
+ }
+ close.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ closeDialog();
+ }
+ });
+
+ // WebView
+ inAppWebView = new WebView(cordova.getActivity());
+ inAppWebView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ inAppWebView.setWebChromeClient(new InAppChromeClient(thatWebView));
+ WebViewClient client = new InAppBrowserClient(thatWebView, edittext);
+ inAppWebView.setWebViewClient(client);
+ WebSettings settings = inAppWebView.getSettings();
+ settings.setJavaScriptEnabled(true);
+ settings.setJavaScriptCanOpenWindowsAutomatically(true);
+ settings.setBuiltInZoomControls(showZoomControls);
+ settings.setPluginState(android.webkit.WebSettings.PluginState.ON);
+
+ //Toggle whether this is enabled or not!
+ Bundle appSettings = cordova.getActivity().getIntent().getExtras();
+ boolean enableDatabase = appSettings == null ? true : appSettings.getBoolean("InAppBrowserStorageEnabled", true);
+ if (enableDatabase) {
+ String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath();
+ settings.setDatabasePath(databasePath);
+ settings.setDatabaseEnabled(true);
+ }
+ settings.setDomStorageEnabled(true);
+
+ if (clearAllCache) {
+ CookieManager.getInstance().removeAllCookie();
+ } else if (clearSessionCache) {
+ CookieManager.getInstance().removeSessionCookie();
+ }
+
+ inAppWebView.loadUrl(url);
+ inAppWebView.setId(6);
+ inAppWebView.getSettings().setLoadWithOverviewMode(true);
+ inAppWebView.getSettings().setUseWideViewPort(true);
+ inAppWebView.requestFocus();
+ inAppWebView.requestFocusFromTouch();
+
+ // Add the back and forward buttons to our action button container layout
+ actionButtonContainer.addView(back);
+ actionButtonContainer.addView(forward);
+
+ // Add the views to our toolbar
+ toolbar.addView(actionButtonContainer);
+ toolbar.addView(edittext);
+ toolbar.addView(close);
+
+ // Don't add the toolbar if its been disabled
+ if (getShowLocationBar()) {
+ // Add our toolbar to our main view/layout
+ main.addView(toolbar);
+ }
+
+ // Add our webview to our main view/layout
+ main.addView(inAppWebView);
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
+ lp.copyFrom(dialog.getWindow().getAttributes());
+ lp.width = WindowManager.LayoutParams.MATCH_PARENT;
+ lp.height = WindowManager.LayoutParams.MATCH_PARENT;
+
+ dialog.setContentView(main);
+ dialog.show();
+ dialog.getWindow().setAttributes(lp);
+ // the goal of openhidden is to load the url and not display it
+ // Show() needs to be called to cause the URL to be loaded
+ if(openWindowHidden) {
+ dialog.hide();
+ }
+ }
+ };
+ this.cordova.getActivity().runOnUiThread(runnable);
+ return "";
+ }
+
+ /**
+ * Create a new plugin success result and send it back to JavaScript
+ *
+ * @param obj a JSONObject contain event payload information
+ */
+ private void sendUpdate(JSONObject obj, boolean keepCallback) {
+ sendUpdate(obj, keepCallback, PluginResult.Status.OK);
+ }
+
+ /**
+ * Create a new plugin result and send it back to JavaScript
+ *
+ * @param obj a JSONObject contain event payload information
+ * @param status the status code to return to the JavaScript environment
+ */
+ private void sendUpdate(JSONObject obj, boolean keepCallback, PluginResult.Status status) {
+ if (callbackContext != null) {
+ PluginResult result = new PluginResult(status, obj);
+ result.setKeepCallback(keepCallback);
+ callbackContext.sendPluginResult(result);
+ if (!keepCallback) {
+ callbackContext = null;
+ }
+ }
+ }
+
+ /**
+ * The webview client receives notifications about appView
+ */
+ public class InAppBrowserClient extends WebViewClient {
+ EditText edittext;
+ CordovaWebView webView;
+
+ /**
+ * Constructor.
+ *
+ * @param webView
+ * @param mEditText
+ */
+ public InAppBrowserClient(CordovaWebView webView, EditText mEditText) {
+ this.webView = webView;
+ this.edittext = mEditText;
+ }
+
+ /**
+ * Notify the host application that a page has started loading.
+ *
+ * @param view The webview initiating the callback.
+ * @param url The url of the page.
+ */
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ super.onPageStarted(view, url, favicon);
+ String newloc = "";
+ if (url.startsWith("http:") || url.startsWith("https:") || url.startsWith("file:")) {
+ newloc = url;
+ }
+ // If dialing phone (tel:5551212)
+ else if (url.startsWith(WebView.SCHEME_TEL)) {
+ try {
+ Intent intent = new Intent(Intent.ACTION_DIAL);
+ intent.setData(Uri.parse(url));
+ cordova.getActivity().startActivity(intent);
+ } catch (android.content.ActivityNotFoundException e) {
+ LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString());
+ }
+ }
+
+ else if (url.startsWith("geo:") || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith("market:")) {
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(url));
+ cordova.getActivity().startActivity(intent);
+ } catch (android.content.ActivityNotFoundException e) {
+ LOG.e(LOG_TAG, "Error with " + url + ": " + e.toString());
+ }
+ }
+ // If sms:5551212?body=This is the message
+ else if (url.startsWith("sms:")) {
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+
+ // Get address
+ String address = null;
+ int parmIndex = url.indexOf('?');
+ if (parmIndex == -1) {
+ address = url.substring(4);
+ }
+ else {
+ address = url.substring(4, parmIndex);
+
+ // If body, then set sms body
+ Uri uri = Uri.parse(url);
+ String query = uri.getQuery();
+ if (query != null) {
+ if (query.startsWith("body=")) {
+ intent.putExtra("sms_body", query.substring(5));
+ }
+ }
+ }
+ intent.setData(Uri.parse("sms:" + address));
+ intent.putExtra("address", address);
+ intent.setType("vnd.android-dir/mms-sms");
+ cordova.getActivity().startActivity(intent);
+ } catch (android.content.ActivityNotFoundException e) {
+ LOG.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString());
+ }
+ }
+ else {
+ newloc = "http://" + url;
+ }
+
+ if (!newloc.equals(edittext.getText().toString())) {
+ edittext.setText(newloc);
+ }
+
+ try {
+ JSONObject obj = new JSONObject();
+ obj.put("type", LOAD_START_EVENT);
+ obj.put("url", newloc);
+
+ sendUpdate(obj, true);
+ } catch (JSONException ex) {
+ Log.d(LOG_TAG, "Should never happen");
+ }
+ }
+
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+
+ try {
+ JSONObject obj = new JSONObject();
+ obj.put("type", LOAD_STOP_EVENT);
+ obj.put("url", url);
+
+ sendUpdate(obj, true);
+ } catch (JSONException ex) {
+ Log.d(LOG_TAG, "Should never happen");
+ }
+ }
+
+ public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
+ super.onReceivedError(view, errorCode, description, failingUrl);
+
+ try {
+ JSONObject obj = new JSONObject();
+ obj.put("type", LOAD_ERROR_EVENT);
+ obj.put("url", failingUrl);
+ obj.put("code", errorCode);
+ obj.put("message", description);
+
+ sendUpdate(obj, true, PluginResult.Status.ERROR);
+ } catch (JSONException ex) {
+ Log.d(LOG_TAG, "Should never happen");
+ }
+ }
+ }
+}
+
diff --git a/StoneIsland/platforms/android/src/org/apache/cordova/inappbrowser/InAppBrowserDialog.java b/StoneIsland/platforms/android/src/org/apache/cordova/inappbrowser/InAppBrowserDialog.java
new file mode 100755
index 00000000..d7017202
--- /dev/null
+++ b/StoneIsland/platforms/android/src/org/apache/cordova/inappbrowser/InAppBrowserDialog.java
@@ -0,0 +1,58 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.cordova.inappbrowser;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Created by Oliver on 22/11/2013.
+ */
+public class InAppBrowserDialog extends Dialog {
+ Context context;
+ InAppBrowser inAppBrowser = null;
+
+ public InAppBrowserDialog(Context context, int theme) {
+ super(context, theme);
+ this.context = context;
+ }
+
+ public void setInAppBroswer(InAppBrowser browser) {
+ this.inAppBrowser = browser;
+ }
+
+ public void onBackPressed () {
+ if (this.inAppBrowser == null) {
+ this.dismiss();
+ } else {
+ // better to go through the in inAppBrowser
+ // because it does a clean up
+ if (this.inAppBrowser.hardwareBack() && this.inAppBrowser.canGoBack()) {
+ this.inAppBrowser.goBack();
+ } else {
+ this.inAppBrowser.closeDialog();
+ }
+ }
+ }
+}
diff --git a/StoneIsland/platforms/android/src/org/apache/cordova/inappbrowser/InAppChromeClient.java b/StoneIsland/platforms/android/src/org/apache/cordova/inappbrowser/InAppChromeClient.java
new file mode 100755
index 00000000..a2145e6a
--- /dev/null
+++ b/StoneIsland/platforms/android/src/org/apache/cordova/inappbrowser/InAppChromeClient.java
@@ -0,0 +1,133 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.cordova.inappbrowser;
+
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.LOG;
+import org.apache.cordova.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import android.webkit.JsPromptResult;
+import android.webkit.WebChromeClient;
+import android.webkit.WebStorage;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.webkit.GeolocationPermissions.Callback;
+
+public class InAppChromeClient extends WebChromeClient {
+
+ private CordovaWebView webView;
+ private String LOG_TAG = "InAppChromeClient";
+ private long MAX_QUOTA = 100 * 1024 * 1024;
+
+ public InAppChromeClient(CordovaWebView webView) {
+ super();
+ this.webView = webView;
+ }
+ /**
+ * Handle database quota exceeded notification.
+ *
+ * @param url
+ * @param databaseIdentifier
+ * @param currentQuota
+ * @param estimatedSize
+ * @param totalUsedQuota
+ * @param quotaUpdater
+ */
+ @Override
+ public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
+ long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
+ {
+ LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d currentQuota: %d totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
+ quotaUpdater.updateQuota(MAX_QUOTA);
+ }
+
+ /**
+ * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
+ *
+ * @param origin
+ * @param callback
+ */
+ @Override
+ public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
+ super.onGeolocationPermissionsShowPrompt(origin, callback);
+ callback.invoke(origin, true, false);
+ }
+
+ /**
+ * Tell the client to display a prompt dialog to the user.
+ * If the client returns true, WebView will assume that the client will
+ * handle the prompt dialog and call the appropriate JsPromptResult method.
+ *
+ * The prompt bridge provided for the InAppBrowser is capable of executing any
+ * oustanding callback belonging to the InAppBrowser plugin. Care has been
+ * taken that other callbacks cannot be triggered, and that no other code
+ * execution is possible.
+ *
+ * To trigger the bridge, the prompt default value should be of the form:
+ *
+ * gap-iab://<callbackId>
+ *
+ * where <callbackId> is the string id of the callback to trigger (something
+ * like "InAppBrowser0123456789")
+ *
+ * If present, the prompt message is expected to be a JSON-encoded value to
+ * pass to the callback. A JSON_EXCEPTION is returned if the JSON is invalid.
+ *
+ * @param view
+ * @param url
+ * @param message
+ * @param defaultValue
+ * @param result
+ */
+ @Override
+ public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
+ // See if the prompt string uses the 'gap-iab' protocol. If so, the remainder should be the id of a callback to execute.
+ if (defaultValue != null && defaultValue.startsWith("gap")) {
+ if(defaultValue.startsWith("gap-iab://")) {
+ PluginResult scriptResult;
+ String scriptCallbackId = defaultValue.substring(10);
+ if (scriptCallbackId.startsWith("InAppBrowser")) {
+ if(message == null || message.length() == 0) {
+ scriptResult = new PluginResult(PluginResult.Status.OK, new JSONArray());
+ } else {
+ try {
+ scriptResult = new PluginResult(PluginResult.Status.OK, new JSONArray(message));
+ } catch(JSONException e) {
+ scriptResult = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());
+ }
+ }
+ this.webView.sendPluginResult(scriptResult, scriptCallbackId);
+ result.confirm("");
+ return true;
+ }
+ }
+ else
+ {
+ // Anything else with a gap: prefix should get this message
+ LOG.w(LOG_TAG, "InAppBrowser does not support Cordova API calls: " + url + " " + defaultValue);
+ result.cancel();
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/StoneIsland/platforms/android/src/org/apache/cordova/networkinformation/NetworkManager.java b/StoneIsland/platforms/android/src/org/apache/cordova/networkinformation/NetworkManager.java
new file mode 100755
index 00000000..4c85ddab
--- /dev/null
+++ b/StoneIsland/platforms/android/src/org/apache/cordova/networkinformation/NetworkManager.java
@@ -0,0 +1,267 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+package org.apache.cordova.networkinformation;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.PluginResult;
+import org.apache.cordova.CordovaWebView;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.util.Log;
+
+public class NetworkManager extends CordovaPlugin {
+
+ public static int NOT_REACHABLE = 0;
+ public static int REACHABLE_VIA_CARRIER_DATA_NETWORK = 1;
+ public static int REACHABLE_VIA_WIFI_NETWORK = 2;
+
+ public static final String WIFI = "wifi";
+ public static final String WIMAX = "wimax";
+ // mobile
+ public static final String MOBILE = "mobile";
+
+ // Android L calls this Cellular, because I have no idea!
+ public static final String CELLULAR = "cellular";
+ // 2G network types
+ public static final String GSM = "gsm";
+ public static final String GPRS = "gprs";
+ public static final String EDGE = "edge";
+ // 3G network types
+ public static final String CDMA = "cdma";
+ public static final String UMTS = "umts";
+ public static final String HSPA = "hspa";
+ public static final String HSUPA = "hsupa";
+ public static final String HSDPA = "hsdpa";
+ public static final String ONEXRTT = "1xrtt";
+ public static final String EHRPD = "ehrpd";
+ // 4G network types
+ public static final String LTE = "lte";
+ public static final String UMB = "umb";
+ public static final String HSPA_PLUS = "hspa+";
+ // return type
+ public static final String TYPE_UNKNOWN = "unknown";
+ public static final String TYPE_ETHERNET = "ethernet";
+ public static final String TYPE_WIFI = "wifi";
+ public static final String TYPE_2G = "2g";
+ public static final String TYPE_3G = "3g";
+ public static final String TYPE_4G = "4g";
+ public static final String TYPE_NONE = "none";
+
+ private static final String LOG_TAG = "NetworkManager";
+
+ private CallbackContext connectionCallbackContext;
+
+ ConnectivityManager sockMan;
+ BroadcastReceiver receiver;
+ private JSONObject lastInfo = null;
+
+ /**
+ * Sets the context of the Command. This can then be used to do things like
+ * get file paths associated with the Activity.
+ *
+ * @param cordova The context of the main Activity.
+ * @param webView The CordovaWebView Cordova is running in.
+ */
+ public void initialize(CordovaInterface cordova, CordovaWebView webView) {
+ super.initialize(cordova, webView);
+ this.sockMan = (ConnectivityManager) cordova.getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
+ this.connectionCallbackContext = null;
+
+ // We need to listen to connectivity events to update navigator.connection
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ if (this.receiver == null) {
+ this.receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // (The null check is for the ARM Emulator, please use Intel Emulator for better results)
+ if(NetworkManager.this.webView != null)
+ updateConnectionInfo(sockMan.getActiveNetworkInfo());
+ }
+ };
+ webView.getContext().registerReceiver(this.receiver, intentFilter);
+ }
+
+ }
+
+ /**
+ * Executes the request and returns PluginResult.
+ *
+ * @param action The action to execute.
+ * @param args JSONArry of arguments for the plugin.
+ * @param callbackContext The callback id used when calling back into JavaScript.
+ * @return True if the action was valid, false otherwise.
+ */
+ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
+ if (action.equals("getConnectionInfo")) {
+ this.connectionCallbackContext = callbackContext;
+ NetworkInfo info = sockMan.getActiveNetworkInfo();
+ String connectionType = "";
+ try {
+ connectionType = this.getConnectionInfo(info).get("type").toString();
+ } catch (JSONException e) { }
+
+ PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, connectionType);
+ pluginResult.setKeepCallback(true);
+ callbackContext.sendPluginResult(pluginResult);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Stop network receiver.
+ */
+ public void onDestroy() {
+ if (this.receiver != null) {
+ try {
+ webView.getContext().unregisterReceiver(this.receiver);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Error unregistering network receiver: " + e.getMessage(), e);
+ } finally {
+ receiver = null;
+ }
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // LOCAL METHODS
+ //--------------------------------------------------------------------------
+
+ /**
+ * Updates the JavaScript side whenever the connection changes
+ *
+ * @param info the current active network info
+ * @return
+ */
+ private void updateConnectionInfo(NetworkInfo info) {
+ // send update to javascript "navigator.network.connection"
+ // Jellybean sends its own info
+ JSONObject thisInfo = this.getConnectionInfo(info);
+ if(!thisInfo.equals(lastInfo))
+ {
+ String connectionType = "";
+ try {
+ connectionType = thisInfo.get("type").toString();
+ } catch (JSONException e) { }
+
+ sendUpdate(connectionType);
+ lastInfo = thisInfo;
+ }
+ }
+
+ /**
+ * Get the latest network connection information
+ *
+ * @param info the current active network info
+ * @return a JSONObject that represents the network info
+ */
+ private JSONObject getConnectionInfo(NetworkInfo info) {
+ String type = TYPE_NONE;
+ String extraInfo = "";
+ if (info != null) {
+ // If we are not connected to any network set type to none
+ if (!info.isConnected()) {
+ type = TYPE_NONE;
+ }
+ else {
+ type = getType(info);
+ }
+ extraInfo = info.getExtraInfo();
+ }
+
+ Log.d("CordovaNetworkManager", "Connection Type: " + type);
+ Log.d("CordovaNetworkManager", "Connection Extra Info: " + extraInfo);
+
+ JSONObject connectionInfo = new JSONObject();
+
+ try {
+ connectionInfo.put("type", type);
+ connectionInfo.put("extraInfo", extraInfo);
+ } catch (JSONException e) { }
+
+ return connectionInfo;
+ }
+
+ /**
+ * Create a new plugin result and send it back to JavaScript
+ *
+ * @param connection the network info to set as navigator.connection
+ */
+ private void sendUpdate(String type) {
+ if (connectionCallbackContext != null) {
+ PluginResult result = new PluginResult(PluginResult.Status.OK, type);
+ result.setKeepCallback(true);
+ connectionCallbackContext.sendPluginResult(result);
+ }
+ webView.postMessage("networkconnection", type);
+ }
+
+ /**
+ * Determine the type of connection
+ *
+ * @param info the network info so we can determine connection type.
+ * @return the type of mobile network we are on
+ */
+ private String getType(NetworkInfo info) {
+ if (info != null) {
+ String type = info.getTypeName();
+
+ if (type.toLowerCase().equals(WIFI)) {
+ return TYPE_WIFI;
+ }
+ else if (type.toLowerCase().equals(MOBILE) || type.toLowerCase().equals(CELLULAR)) {
+ type = info.getSubtypeName();
+ if (type.toLowerCase().equals(GSM) ||
+ type.toLowerCase().equals(GPRS) ||
+ type.toLowerCase().equals(EDGE)) {
+ return TYPE_2G;
+ }
+ else if (type.toLowerCase().startsWith(CDMA) ||
+ type.toLowerCase().equals(UMTS) ||
+ type.toLowerCase().equals(ONEXRTT) ||
+ type.toLowerCase().equals(EHRPD) ||
+ type.toLowerCase().equals(HSUPA) ||
+ type.toLowerCase().equals(HSDPA) ||
+ type.toLowerCase().equals(HSPA)) {
+ return TYPE_3G;
+ }
+ else if (type.toLowerCase().equals(LTE) ||
+ type.toLowerCase().equals(UMB) ||
+ type.toLowerCase().equals(HSPA_PLUS)) {
+ return TYPE_4G;
+ }
+ }
+ }
+ else {
+ return TYPE_NONE;
+ }
+ return TYPE_UNKNOWN;
+ }
+}
diff --git a/StoneIsland/platforms/android/src/org/apache/cordova/splashscreen/SplashScreen.java b/StoneIsland/platforms/android/src/org/apache/cordova/splashscreen/SplashScreen.java
new file mode 100755
index 00000000..75ad724c
--- /dev/null
+++ b/StoneIsland/platforms/android/src/org/apache/cordova/splashscreen/SplashScreen.java
@@ -0,0 +1,328 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+package org.apache.cordova.splashscreen;
+
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.os.Handler;
+import android.view.Display;
+import android.view.View;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaWebView;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+public class SplashScreen extends CordovaPlugin {
+ private static final String LOG_TAG = "SplashScreen";
+ // Cordova 3.x.x has a copy of this plugin bundled with it (SplashScreenInternal.java).
+ // Enable functionality only if running on 4.x.x.
+ private static final boolean HAS_BUILT_IN_SPLASH_SCREEN = Integer.valueOf(CordovaWebView.CORDOVA_VERSION.split("\\.")[0]) < 4;
+ private static Dialog splashDialog;
+ private static ProgressDialog spinnerDialog;
+ private static boolean firstShow = true;
+
+ /**
+ * Displays the splash drawable.
+ */
+ private ImageView splashImageView;
+
+ /**
+ * Remember last device orientation to detect orientation changes.
+ */
+ private int orientation;
+
+ // Helper to be compile-time compatible with both Cordova 3.x and 4.x.
+ private View getView() {
+ try {
+ return (View)webView.getClass().getMethod("getView").invoke(webView);
+ } catch (Exception e) {
+ return (View)webView;
+ }
+ }
+
+ @Override
+ protected void pluginInitialize() {
+ if (HAS_BUILT_IN_SPLASH_SCREEN || !firstShow) {
+ return;
+ }
+ // Make WebView invisible while loading URL
+ getView().setVisibility(View.INVISIBLE);
+ int drawableId = preferences.getInteger("SplashDrawableId", 0);
+ if (drawableId == 0) {
+ String splashResource = preferences.getString("SplashScreen", "screen");
+ if (splashResource != null) {
+ drawableId = cordova.getActivity().getResources().getIdentifier(splashResource, "drawable", cordova.getActivity().getClass().getPackage().getName());
+ if (drawableId == 0) {
+ drawableId = cordova.getActivity().getResources().getIdentifier(splashResource, "drawable", cordova.getActivity().getPackageName());
+ }
+ preferences.set("SplashDrawableId", drawableId);
+ }
+ }
+
+ // Save initial orientation.
+ orientation = cordova.getActivity().getResources().getConfiguration().orientation;
+
+ firstShow = false;
+ loadSpinner();
+ showSplashScreen(true);
+ }
+
+ /**
+ * Shorter way to check value of "SplashMaintainAspectRatio" preference.
+ */
+ private boolean isMaintainAspectRatio () {
+ return preferences.getBoolean("SplashMaintainAspectRatio", false);
+ }
+
+ @Override
+ public void onPause(boolean multitasking) {
+ if (HAS_BUILT_IN_SPLASH_SCREEN) {
+ return;
+ }
+ // hide the splash screen to avoid leaking a window
+ this.removeSplashScreen();
+ }
+
+ @Override
+ public void onDestroy() {
+ if (HAS_BUILT_IN_SPLASH_SCREEN) {
+ return;
+ }
+ // hide the splash screen to avoid leaking a window
+ this.removeSplashScreen();
+ // If we set this to true onDestroy, we lose track when we go from page to page!
+ //firstShow = true;
+ }
+
+ @Override
+ public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+ if (action.equals("hide")) {
+ cordova.getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ webView.postMessage("splashscreen", "hide");
+ }
+ });
+ } else if (action.equals("show")) {
+ cordova.getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ webView.postMessage("splashscreen", "show");
+ }
+ });
+ } else if (action.equals("spinnerStart")) {
+ if (!HAS_BUILT_IN_SPLASH_SCREEN) {
+ final String title = args.getString(0);
+ final String message = args.getString(1);
+ cordova.getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ spinnerStart(title, message);
+ }
+ });
+ }
+ } else {
+ return false;
+ }
+
+ callbackContext.success();
+ return true;
+ }
+
+ @Override
+ public Object onMessage(String id, Object data) {
+ if (HAS_BUILT_IN_SPLASH_SCREEN) {
+ return null;
+ }
+ if ("splashscreen".equals(id)) {
+ if ("hide".equals(data.toString())) {
+ this.removeSplashScreen();
+ } else {
+ this.showSplashScreen(false);
+ }
+ } else if ("spinner".equals(id)) {
+ if ("stop".equals(data.toString())) {
+ this.spinnerStop();
+ getView().setVisibility(View.VISIBLE);
+ }
+ } else if ("onReceivedError".equals(id)) {
+ spinnerStop();
+ }
+ return null;
+ }
+
+ // Don't add @Override so that plugin still compiles on 3.x.x for a while
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (newConfig.orientation != orientation) {
+ orientation = newConfig.orientation;
+
+ // Splash drawable may change with orientation, so reload it.
+ if (splashImageView != null) {
+ int drawableId = preferences.getInteger("SplashDrawableId", 0);
+ if (drawableId != 0) {
+ splashImageView.setImageDrawable(cordova.getActivity().getResources().getDrawable(drawableId));
+ }
+ }
+ }
+ }
+
+ private void removeSplashScreen() {
+ cordova.getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ if (splashDialog != null && splashDialog.isShowing()) {
+ splashDialog.dismiss();
+ splashDialog = null;
+ splashImageView = null;
+ }
+ }
+ });
+ }
+
+ /**
+ * Shows the splash screen over the full Activity
+ */
+ @SuppressWarnings("deprecation")
+ private void showSplashScreen(final boolean hideAfterDelay) {
+ final int splashscreenTime = preferences.getInteger("SplashScreenDelay", 3000);
+ final int drawableId = preferences.getInteger("SplashDrawableId", 0);
+
+ // If the splash dialog is showing don't try to show it again
+ if (splashDialog != null && splashDialog.isShowing()) {
+ return;
+ }
+ if (drawableId == 0 || (splashscreenTime <= 0 && hideAfterDelay)) {
+ return;
+ }
+
+ cordova.getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ // Get reference to display
+ Display display = cordova.getActivity().getWindowManager().getDefaultDisplay();
+ Context context = webView.getContext();
+
+ // Use an ImageView to render the image because of its flexible scaling options.
+ splashImageView = new ImageView(context);
+ splashImageView.setImageResource(drawableId);
+ LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ splashImageView.setLayoutParams(layoutParams);
+
+ splashImageView.setMinimumHeight(display.getHeight());
+ splashImageView.setMinimumWidth(display.getWidth());
+
+ // TODO: Use the background color of the webView's parent instead of using the preference.
+ splashImageView.setBackgroundColor(preferences.getInteger("backgroundColor", Color.BLACK));
+
+ if (isMaintainAspectRatio()) {
+ // CENTER_CROP scale mode is equivalent to CSS "background-size:cover"
+ splashImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ }
+ else {
+ // FIT_XY scales image non-uniformly to fit into image view.
+ splashImageView.setScaleType(ImageView.ScaleType.FIT_XY);
+ }
+
+ // Create and show the dialog
+ splashDialog = new Dialog(context, android.R.style.Theme_Translucent_NoTitleBar);
+ // check to see if the splash screen should be full screen
+ if ((cordova.getActivity().getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN)
+ == WindowManager.LayoutParams.FLAG_FULLSCREEN) {
+ splashDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ }
+ splashDialog.setContentView(splashImageView);
+ splashDialog.setCancelable(false);
+ splashDialog.show();
+
+ // Set Runnable to remove splash screen just in case
+ if (hideAfterDelay) {
+ final Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ public void run() {
+ removeSplashScreen();
+ }
+ }, splashscreenTime);
+ }
+ }
+ });
+ }
+
+ /*
+ * Load the spinner
+ */
+ private void loadSpinner() {
+ // If loadingDialog property, then show the App loading dialog for first page of app
+ String loading = null;
+ if (webView.canGoBack()) {
+ loading = preferences.getString("LoadingDialog", null);
+ }
+ else {
+ loading = preferences.getString("LoadingPageDialog", null);
+ }
+ if (loading != null) {
+ String title = "";
+ String message = "Loading Application...";
+
+ if (loading.length() > 0) {
+ int comma = loading.indexOf(',');
+ if (comma > 0) {
+ title = loading.substring(0, comma);
+ message = loading.substring(comma + 1);
+ }
+ else {
+ title = "";
+ message = loading;
+ }
+ }
+ spinnerStart(title, message);
+ }
+ }
+
+ private void spinnerStart(final String title, final String message) {
+ cordova.getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ spinnerStop();
+ spinnerDialog = ProgressDialog.show(webView.getContext(), title, message, true, true,
+ new DialogInterface.OnCancelListener() {
+ public void onCancel(DialogInterface dialog) {
+ spinnerDialog = null;
+ }
+ });
+ }
+ });
+ }
+
+ private void spinnerStop() {
+ cordova.getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ if (spinnerDialog != null && spinnerDialog.isShowing()) {
+ spinnerDialog.dismiss();
+ spinnerDialog = null;
+ }
+ }
+ });
+ }
+}
diff --git a/StoneIsland/platforms/android/src/org/apache/cordova/whitelist/WhitelistPlugin.java b/StoneIsland/platforms/android/src/org/apache/cordova/whitelist/WhitelistPlugin.java
new file mode 100755
index 00000000..4e4f57e1
--- /dev/null
+++ b/StoneIsland/platforms/android/src/org/apache/cordova/whitelist/WhitelistPlugin.java
@@ -0,0 +1,161 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+package org.apache.cordova.whitelist;
+
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.ConfigXmlParser;
+import org.apache.cordova.Whitelist;
+import org.xmlpull.v1.XmlPullParser;
+
+import android.content.Context;
+import android.util.Log;
+
+public class WhitelistPlugin extends CordovaPlugin {
+ private static final String LOG_TAG = "WhitelistPlugin";
+ private Whitelist allowedNavigations;
+ private Whitelist allowedIntents;
+ private Whitelist allowedRequests;
+
+ // Used when instantiated via reflection by PluginManager
+ public WhitelistPlugin() {
+ }
+ // These can be used by embedders to allow Java-configuration of whitelists.
+ public WhitelistPlugin(Context context) {
+ this(new Whitelist(), new Whitelist(), null);
+ new CustomConfigXmlParser().parse(context);
+ }
+ public WhitelistPlugin(XmlPullParser xmlParser) {
+ this(new Whitelist(), new Whitelist(), null);
+ new CustomConfigXmlParser().parse(xmlParser);
+ }
+ public WhitelistPlugin(Whitelist allowedNavigations, Whitelist allowedIntents, Whitelist allowedRequests) {
+ if (allowedRequests == null) {
+ allowedRequests = new Whitelist();
+ allowedRequests.addWhiteListEntry("file:///*", false);
+ allowedRequests.addWhiteListEntry("data:*", false);
+ }
+ this.allowedNavigations = allowedNavigations;
+ this.allowedIntents = allowedIntents;
+ this.allowedRequests = allowedRequests;
+ }
+ @Override
+ public void pluginInitialize() {
+ if (allowedNavigations == null) {
+ allowedNavigations = new Whitelist();
+ allowedIntents = new Whitelist();
+ allowedRequests = new Whitelist();
+ new CustomConfigXmlParser().parse(webView.getContext());
+ }
+ }
+
+ private class CustomConfigXmlParser extends ConfigXmlParser {
+ @Override
+ public void handleStartTag(XmlPullParser xml) {
+ String strNode = xml.getName();
+ if (strNode.equals("content")) {
+ String startPage = xml.getAttributeValue(null, "src");
+ allowedNavigations.addWhiteListEntry(startPage, false);
+ } else if (strNode.equals("allow-navigation")) {
+ String origin = xml.getAttributeValue(null, "href");
+ if ("*".equals(origin)) {
+ allowedNavigations.addWhiteListEntry("http://*/*", false);
+ allowedNavigations.addWhiteListEntry("https://*/*", false);
+ allowedNavigations.addWhiteListEntry("data:*", false);
+ } else {
+ allowedNavigations.addWhiteListEntry(origin, false);
+ }
+ } else if (strNode.equals("allow-intent")) {
+ String origin = xml.getAttributeValue(null, "href");
+ allowedIntents.addWhiteListEntry(origin, false);
+ } else if (strNode.equals("access")) {
+ String origin = xml.getAttributeValue(null, "origin");
+ String subdomains = xml.getAttributeValue(null, "subdomains");
+ boolean external = (xml.getAttributeValue(null, "launch-external") != null);
+ if (origin != null) {
+ if (external) {
+ Log.w(LOG_TAG, "Found <access launch-external> within config.xml. Please use <allow-intent> instead.");
+ allowedIntents.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
+ } else {
+ if ("*".equals(origin)) {
+ allowedRequests.addWhiteListEntry("http://*/*", false);
+ allowedRequests.addWhiteListEntry("https://*/*", false);
+ } else {
+ allowedRequests.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0));
+ }
+ }
+ }
+ }
+ }
+ @Override
+ public void handleEndTag(XmlPullParser xml) {
+ }
+ }
+
+ @Override
+ public Boolean shouldAllowNavigation(String url) {
+ if (allowedNavigations.isUrlWhiteListed(url)) {
+ return true;
+ }
+ return null; // Default policy
+ }
+
+ @Override
+ public Boolean shouldAllowRequest(String url) {
+ if (Boolean.TRUE == shouldAllowNavigation(url)) {
+ return true;
+ }
+ if (allowedRequests.isUrlWhiteListed(url)) {
+ return true;
+ }
+ return null; // Default policy
+ }
+
+ @Override
+ public Boolean shouldOpenExternalUrl(String url) {
+ if (allowedIntents.isUrlWhiteListed(url)) {
+ return true;
+ }
+ return null; // Default policy
+ }
+
+ public Whitelist getAllowedNavigations() {
+ return allowedNavigations;
+ }
+
+ public void setAllowedNavigations(Whitelist allowedNavigations) {
+ this.allowedNavigations = allowedNavigations;
+ }
+
+ public Whitelist getAllowedIntents() {
+ return allowedIntents;
+ }
+
+ public void setAllowedIntents(Whitelist allowedIntents) {
+ this.allowedIntents = allowedIntents;
+ }
+
+ public Whitelist getAllowedRequests() {
+ return allowedRequests;
+ }
+
+ public void setAllowedRequests(Whitelist allowedRequests) {
+ this.allowedRequests = allowedRequests;
+ }
+}
diff --git a/StoneIsland/platforms/android/src/us/okfoc/stoneisland/MainActivity.java b/StoneIsland/platforms/android/src/us/okfoc/stoneisland/MainActivity.java
new file mode 100755
index 00000000..f7a7bc56
--- /dev/null
+++ b/StoneIsland/platforms/android/src/us/okfoc/stoneisland/MainActivity.java
@@ -0,0 +1,34 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+
+package us.okfoc.stoneisland;
+
+import android.os.Bundle;
+import org.apache.cordova.*;
+
+public class MainActivity extends CordovaActivity
+{
+ @Override
+ public void onCreate(Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ // Set by <content src="index.html" /> in config.xml
+ loadUrl(launchUrl);
+ }
+}