diff options
Diffstat (limited to 'StoneIsland/platforms/android/src')
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); + } +} |
