diff --git a/platforms/android/src/nl/xservices/plugins/SocialSharing.java b/platforms/android/src/nl/xservices/plugins/SocialSharing.java index 905cac5..521c546 100644 --- a/platforms/android/src/nl/xservices/plugins/SocialSharing.java +++ b/platforms/android/src/nl/xservices/plugins/SocialSharing.java @@ -8,15 +8,16 @@ 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.apache.http.util.ByteArrayBuffer; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -42,10 +43,13 @@ public class SocialSharing extends CordovaPlugin { 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_SEND = 1; private static final int ACTIVITY_CODE_SENDVIAEMAIL = 2; + private static final int ACTIVITY_CODE_SENDVIAWHATSAPP = 3; private CallbackContext _callbackContext; @@ -75,7 +79,16 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo 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); + if (notEmpty(args.getString(4))) { + return shareViaWhatsAppDirectly(callbackContext, args.getString(0), args.getString(1), args.getJSONArray(2), args.getString(3), args.getString(4)); + } else { + 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)) { @@ -100,7 +113,7 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo 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() > 1; + 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 { @@ -133,16 +146,18 @@ public void run() { draft.putExtra(android.content.Intent.EXTRA_BCC, toStringArray(bcc)); } if (files.length() > 0) { - ArrayList fileUris = new ArrayList(); final String dir = getDownloadDir(); - 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 (dir != null) { + ArrayList fileUris = new ArrayList(); + 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); } - } - if (!fileUris.isEmpty()) { - draft.putExtra(Intent.EXTRA_STREAM, fileUris); } } } catch (Exception e) { @@ -158,11 +173,15 @@ public void run() { } private String getDownloadDir() throws IOException { - final String dir = webView.getContext().getExternalFilesDir(null) + "/socialsharing-downloads"; // external - -// final String dir = webView.getContext().getCacheDir() + "/socialsharing-downloads"; // internal (no external permission needed) - createOrCleanDir(dir); - return dir; + // 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) { @@ -177,34 +196,39 @@ public void run() { final Intent sendIntent = new Intent(hasMultipleAttachments ? Intent.ACTION_SEND_MULTIPLE : Intent.ACTION_SEND); sendIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); - if (files.length() > 0) { - ArrayList fileUris = new ArrayList(); - try { + try { + if (files.length() > 0 && !"".equals(files.getString(0))) { final String dir = getDownloadDir(); - 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 (dir != null) { + ArrayList fileUris = new ArrayList(); + 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); + if (!fileUris.isEmpty()) { + if (hasMultipleAttachments) { + sendIntent.putExtra(Intent.EXTRA_STREAM, fileUris); + } else { + sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri); + } } + } else { + sendIntent.setType("text/plain"); } - } catch (Exception e) { - callbackContext.error(e.getMessage()); + } 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)) { @@ -215,7 +239,10 @@ public void run() { } if (notEmpty(message)) { sendIntent.putExtra(android.content.Intent.EXTRA_TEXT, message); - sendIntent.putExtra("sms_body", message); // sometimes required when the user picks share via sms + // sometimes required when the user picks share via sms + if (Build.VERSION.SDK_INT < 21) { // LOLLIPOP + sendIntent.putExtra("sms_body", message); + } } if (appPackageName != null) { @@ -242,7 +269,8 @@ public void run() { public void run() { cordova.getActivity().runOnUiThread(new Runnable() { public void run() { - showPasteMessage(msg, pasteMessage); + copyHintToClipboard(msg, pasteMessage); + showPasteMessage(pasteMessage); } }); } @@ -254,7 +282,7 @@ public void run() { if (peek) { callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK)); } else { - mycordova.startActivityForResult(plugin, Intent.createChooser(sendIntent, null), 1); + mycordova.startActivityForResult(plugin, Intent.createChooser(sendIntent, null), ACTIVITY_CODE_SEND); } } } @@ -263,16 +291,20 @@ public void run() { } @SuppressLint("NewApi") - private void showPasteMessage(String msg, String label) { + private void copyHintToClipboard(String msg, String label) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { return; } - // copy to clipboard final ClipboardManager clipboard = (android.content.ClipboardManager) cordova.getActivity().getSystemService(Context.CLIPBOARD_SERVICE); final ClipData clip = android.content.ClipData.newPlainText(label, msg); clipboard.setPrimaryClip(clip); + } - // show a toast + @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(); @@ -294,6 +326,10 @@ private Uri getFileUriAndSetType(Intent sendIntent, String dir, String image, St Matcher matcher = dispositionPattern.matcher(disposition); if (matcher.find()) { filename = matcher.group(1).replaceAll("[^a-zA-Z0-9._-]", ""); + if (filename.length() == 0) { + // in this case we can't determine a filetype so some targets (gmail) may not render it correctly + filename = "file"; + } localImage = "file://" + dir + "/" + filename; } } @@ -325,12 +361,80 @@ private Uri getFileUriAndSetType(Intent sendIntent, String dir, String image, St } 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 shareViaWhatsAppDirectly(final CallbackContext callbackContext, String message, final String subject, final JSONArray files, final String url, final String number) { + // 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; + } + } + final String shareMessage = message; + final SocialSharing plugin = this; + cordova.getThreadPool().execute(new SocialSharingRunnable(callbackContext) { + public void run() { + Intent intent = new Intent(Intent.ACTION_SENDTO); + intent.setData(Uri.parse("smsto:" + number)); + + intent.putExtra("sms_body", shareMessage); + intent.putExtra("sms_subject", subject); + intent.setPackage("com.whatsapp"); + + try { + if (files.length() > 0 && !"".equals(files.getString(0))) { + final boolean hasMultipleAttachments = files.length() > 1; + final String dir = getDownloadDir(); + if (dir != null) { + ArrayList fileUris = new ArrayList(); + Uri fileUri = null; + for (int i = 0; i < files.length(); i++) { + fileUri = getFileUriAndSetType(intent, dir, files.getString(i), subject, i); + if (fileUri != null) { + fileUris.add(fileUri); + } + } + if (!fileUris.isEmpty()) { + if (hasMultipleAttachments) { + intent.putExtra(Intent.EXTRA_STREAM, fileUris); + } else { + intent.putExtra(Intent.EXTRA_STREAM, fileUri); + } + } + } + } + } catch (Exception e) { + callbackContext.error(e.getMessage()); + } + try { + cordova.startActivityForResult(plugin, intent, ACTIVITY_CODE_SENDVIAWHATSAPP); + } catch (Exception e) { + callbackContext.error(e.getMessage()); + } + } + }); + return true; + } + 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 @@ -411,11 +515,13 @@ private JSONArray getShareActivities(List activityList) { @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { - if (ACTIVITY_CODE_SENDVIAEMAIL == requestCode) { - super.onActivityResult(requestCode, resultCode, intent); - _callbackContext.success(); - } else { - _callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, resultCode == Activity.RESULT_OK)); + 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)); + } } } @@ -430,23 +536,29 @@ private void createOrCleanDir(final String downloadDir) throws IOException { } } - private String getFileName(String url) { - final int lastIndexOfSlash = url.lastIndexOf('/'); - if (lastIndexOfSlash == -1) { - return url; + private static String getFileName(String url) { + if (url.endsWith("/")) { + url = url.substring(0, url.length()-1); + } + final String pattern = ".*/([^?#]+)?"; + Pattern r = Pattern.compile(pattern); + Matcher m = r.matcher(url); + if (m.find()) { + return m.group(1); } else { - return url.substring(lastIndexOfSlash + 1); + return "file"; } } private byte[] getBytes(InputStream is) throws IOException { - BufferedInputStream bis = new BufferedInputStream(is); - ByteArrayBuffer baf = new ByteArrayBuffer(5000); - int current; - while ((current = bis.read()) != -1) { - baf.append((byte) current); + 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); } - return baf.toByteArray(); + buffer.flush(); + return buffer.toByteArray(); } private void saveFile(byte[] bytes, String dirName, String fileName) throws IOException {