123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681 |
- /*
- * Copyright (c) 2014-2015 by appPlant UG. All rights reserved.
- *
- * @APPPLANT_LICENSE_HEADER_START@
- *
- * This file contains Original Code and/or Modifications of Original Code
- * as defined in and that are subject to the Apache License
- * Version 2.0 (the 'License'). You may not use this file except in
- * compliance with the License. Please obtain a copy of the License at
- * http://opensource.org/licenses/Apache-2.0/ and read it before using this
- * file.
- *
- * The Original Code and all software distributed under the License are
- * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
- * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
- * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
- * Please see the License for the specific language governing rights and
- * limitations under the License.
- *
- * @APPPLANT_LICENSE_HEADER_END@
- */
-
- package de.martinreinhardt.cordova.plugins.email;
-
- import android.content.Context;
- import android.content.Intent;
- import android.content.pm.PackageManager;
- import android.content.pm.ResolveInfo;
- import android.content.res.AssetManager;
- import android.content.res.Resources;
- import android.media.MediaScannerConnection;
- import android.net.Uri;
- import android.text.Html;
- import android.util.Base64;
- import android.util.Log;
-
- import org.json.JSONArray;
- import org.json.JSONException;
- import org.json.JSONObject;
-
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.concurrent.atomic.AtomicReference;
-
- import static de.martinreinhardt.cordova.plugins.email.EmailComposer.LOG_TAG;
-
- /**
- * Implements the interface methods of the plugin.
- */
- public class EmailComposerImpl {
-
- /**
- * The default mailto: scheme.
- */
- static private final String MAILTO_SCHEME = "mailto:";
-
- /**
- * Path where to put tmp the attachments.
- */
- static private final String ATTACHMENT_FOLDER = "/email_composer";
-
- /**
- * Cleans the attachment folder.
- *
- * @param ctx
- * The application context.
- */
- @SuppressWarnings("ResultOfMethodCallIgnored")
- public void cleanupAttachmentFolder (Context ctx) {
- try {
- File dir = new File(ctx.getExternalCacheDir() + ATTACHMENT_FOLDER);
-
- if (!dir.isDirectory())
- return;
-
- File[] files = dir.listFiles();
-
- for (File file : files) { file.delete(); }
- } catch (Exception npe){
- Log.w(LOG_TAG, "Missing external cache dir");
- }
- }
-
- /**
- * Tells if the device has the capability to send emails.
- *
- * @param id
- * The app id.
- * @param ctx
- * The application context.
- */
- public boolean[] canSendMail (String id, Context ctx) {
- // is possible with specified app
- boolean withScheme = isAppInstalled(id, ctx);
- // is possible in general
- boolean isPossible = isEmailClientExist(ctx);
-
- return new boolean[] { isPossible, withScheme };
- }
-
- /**
- * The intent with the containing email properties.
- *
- * @param params
- * The email properties like subject or body
- * @param ctx
- * The context of the application.
- * @return
- * The resulting intent.
- * @throws JSONException
- */
- public Intent getDraftWithProperties (JSONObject params, Context ctx)
- throws JSONException {
-
- Intent mail = getEmailIntent();
- String app = params.optString("app", MAILTO_SCHEME);
-
- if (params.has("subject"))
- setSubject(params.getString("subject"), mail);
- if (params.has("body"))
- setBody(params.getString("body"), params.optBoolean("isHtml"), mail);
- if (params.has("to"))
- setRecipients(params.getJSONArray("to"), mail);
- if (params.has("cc"))
- setCcRecipients(params.getJSONArray("cc"), mail);
- if (params.has("bcc"))
- setBccRecipients(params.getJSONArray("bcc"), mail);
- if (params.has("attachments"))
- setAttachments(params.getJSONArray("attachments"), mail, ctx);
-
- if (!app.equals(MAILTO_SCHEME) && isAppInstalled(app, ctx)) {
- mail.setPackage(app);
- }
-
- return mail;
- }
-
- /**
- * Setter for the subject.
- *
- * @param subject
- * The subject of the email.
- * @param draft
- * The intent to send.
- */
- private void setSubject (String subject, Intent draft) {
- draft.putExtra(Intent.EXTRA_SUBJECT, subject);
- }
-
- /**
- * Setter for the body.
- *
- * @param body
- * The body of the email.
- * @param isHTML
- * Indicates the encoding (HTML or plain text).
- * @param draft
- * The intent to send.
- */
- private void setBody (String body, Boolean isHTML, Intent draft) {
- CharSequence text = isHTML ? Html.fromHtml(body) : body;
-
- draft.putExtra(Intent.EXTRA_TEXT, text);
- }
-
- /**
- * Setter for the recipients.
- *
- * @param recipients
- * List of email addresses.
- * @param draft
- * The intent to send.
- * @throws JSONException
- */
- private void setRecipients (JSONArray recipients, Intent draft) throws JSONException {
- String[] receivers = new String[recipients.length()];
-
- for (int i = 0; i < recipients.length(); i++) {
- receivers[i] = recipients.getString(i);
- }
-
- draft.putExtra(Intent.EXTRA_EMAIL, receivers);
- }
-
- /**
- * Setter for the cc recipients.
- *
- * @param recipients
- * List of email addresses.
- * @param draft
- * The intent to send.
- * @throws JSONException
- */
- private void setCcRecipients (JSONArray recipients, Intent draft) throws JSONException {
- String[] receivers = new String[recipients.length()];
-
- for (int i = 0; i < recipients.length(); i++) {
- receivers[i] = recipients.getString(i);
- }
-
- draft.putExtra(Intent.EXTRA_CC, receivers);
- }
-
- /**
- * Setter for the bcc recipients.
- *
- * @param recipients
- * List of email addresses.
- * @param draft
- * The intent to send.
- * @throws JSONException
- */
- private void setBccRecipients (JSONArray recipients, Intent draft) throws JSONException {
- String[] receivers = new String[recipients.length()];
-
- for (int i = 0; i < recipients.length(); i++) {
- receivers[i] = recipients.getString(i);
- }
-
- draft.putExtra(Intent.EXTRA_BCC, receivers);
- }
-
- /**
- * Setter for the attachments.
- *
- * @param attachments
- * List of URIs to attach.
- * @param draft
- * The intent to send.
- * @param ctx
- * The application context.
- * @throws JSONException
- */
- private void setAttachments (JSONArray attachments, Intent draft,
- Context ctx) throws JSONException {
-
- ArrayList<Uri> uris = new ArrayList<Uri>();
-
- for (int i = 0; i < attachments.length(); i++) {
- Uri uri = getUriForPath(attachments.getString(i), ctx);
-
- uris.add(uri);
- }
-
- if (uris.isEmpty())
- return;
-
- if (uris.size() == 1) {
- draft.setAction(Intent.ACTION_SEND)
- .setType("message/rfc822")
- .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- .putExtra(Intent.EXTRA_STREAM, uris.get(0));
- } else {
- draft.setAction(Intent.ACTION_SEND_MULTIPLE)
- .setType("message/rfc822")
- .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- .putExtra(Intent.EXTRA_STREAM, uris);
- }
- }
-
- /**
- * The URI for an attachment path.
- *
- * @param path
- * The given path to the attachment.
- * @param ctx
- * The application context.
- * @return
- * The URI pointing to the given path.
- */
- private Uri getUriForPath (String path, Context ctx) {
- Uri result = null;
- if (path.startsWith("res:")) {
- result = getUriForResourcePath(path, ctx);
- } else if (path.startsWith("file:///")) {
- result = getUriForAbsolutePath(path);
- } else if (path.startsWith("file://")) {
- result = getUriForAssetPath(path, ctx);
- } else if (path.startsWith("base64:")) {
- result = getUriForBase64Content(path, ctx);
- }
-
- if (result == null) {
- result = Uri.parse(path);
- }
-
- return getCorrespondingMediaFileUriIfPossible(result, ctx);
- }
-
- /**
- * The URI for a file.
- *
- * @param path
- * The given absolute path.
- * @return
- * The URI pointing to the given path.
- */
- private Uri getUriForAbsolutePath (String path) {
- String absPath = path.replaceFirst("file://", "");
- File file = new File(absPath);
-
- if (!file.exists()) {
- Log.e(LOG_TAG, "File not found: " + file.getAbsolutePath());
- }
-
- return Uri.fromFile(file);
- }
-
- /**
- * The URI for an asset.
- *
- * @param path
- * The given asset path.
- * @param ctx
- * The application context.
- * @return
- * The URI pointing to the given path.
- */
- @SuppressWarnings("ResultOfMethodCallIgnored")
- private Uri getUriForAssetPath (String path, Context ctx) {
- String resPath = path.replaceFirst("file:/", "www");
- String fileName = resPath.substring(resPath.lastIndexOf('/') + 1);
- File dir = ctx.getExternalCacheDir();
-
- if (dir == null) {
- Log.e(LOG_TAG, "Missing external cache dir");
- return Uri.EMPTY;
- }
-
- String storage = dir.toString() + ATTACHMENT_FOLDER;
- File file = new File(storage, fileName);
-
- new File(storage).mkdir();
-
- FileOutputStream outStream = null;
-
- try {
- AssetManager assets = ctx.getAssets();
-
- outStream = new FileOutputStream(file);
- InputStream inputStream = assets.open(resPath);
-
- copyFile(inputStream, outStream);
- outStream.flush();
- outStream.close();
- } catch (Exception e) {
- Log.e(LOG_TAG, "File not found: assets/" + resPath);
- e.printStackTrace();
- } finally {
- if (outStream != null) {
- safeClose(outStream);
- }
- }
-
- return Uri.fromFile(file);
- }
-
- /**
- * The URI for a resource.
- *
- * @param path
- * The given relative path.
- * @param ctx
- * The application context.
- * @return
- * The URI pointing to the given path
- */
- @SuppressWarnings("ResultOfMethodCallIgnored")
- private Uri getUriForResourcePath (String path, Context ctx) {
- String resPath = path.replaceFirst("res://", "");
- String fileName = resPath.substring(resPath.lastIndexOf('/') + 1);
- String resName = fileName.substring(0, fileName.lastIndexOf('.'));
- String extension = resPath.substring(resPath.lastIndexOf('.'));
- File dir = ctx.getExternalCacheDir();
-
- if (dir == null) {
- Log.e(LOG_TAG, "Missing external cache dir");
- return Uri.EMPTY;
- }
-
- String storage = dir.toString() + ATTACHMENT_FOLDER;
- int resId = getResId(resPath, ctx);
- File file = new File(storage, resName + extension);
-
- if (resId == 0) {
- Log.e(LOG_TAG, "File not found: " + resPath);
- }
-
- new File(storage).mkdir();
-
- FileOutputStream outStream = null;
-
- try {
- Resources res = ctx.getResources();
- outStream = new FileOutputStream(file);
- InputStream inputStream = res.openRawResource(resId);
-
- copyFile(inputStream, outStream);
- outStream.flush();
- outStream.close();
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- if (outStream != null) {
- safeClose(outStream);
- }
- }
-
- return Uri.fromFile(file);
- }
-
- /**
- * The URI for a base64 encoded content.
- *
- * @param content
- * The given base64 encoded content.
- * @param ctx
- * The application context.
- * @return
- * The URI including the given content.
- */
- @SuppressWarnings("ResultOfMethodCallIgnored")
- private Uri getUriForBase64Content (String content, Context ctx) {
- String resName = content.substring(content.indexOf(":") + 1, content.indexOf("//"));
- String resData = content.substring(content.indexOf("//") + 2);
- File dir = ctx.getExternalCacheDir();
- byte[] bytes;
-
- try {
- bytes = Base64.decode(resData, 0);
- } catch (Exception ignored) {
- Log.e(LOG_TAG, "Invalid Base64 string");
- return Uri.EMPTY;
- }
-
- if (dir == null) {
- Log.e(LOG_TAG, "Missing external cache dir");
- return Uri.EMPTY;
- }
-
- String storage = dir.toString() + ATTACHMENT_FOLDER;
- File file = new File(storage, resName);
-
- new File(storage).mkdir();
-
- FileOutputStream outStream = null;
-
- try {
- outStream = new FileOutputStream(file);
-
- outStream.write(bytes);
- outStream.flush();
- outStream.close();
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- if (outStream != null) {
- safeClose(outStream);
- }
- }
-
- return Uri.fromFile(file);
- }
-
- /**
- * Get corresponding Media File URI for a givin URI
- * if available, otherwise it returns the same input URI.
- *
- * NOTE: Becuase MediaScannerConnection works in callback
- * fashion, we instead wait for its result using a timing-out
- * while loop, and we might further think for a better solution.
- *
- * @param uri
- * The given uri.
- * @param ctx
- * The application context.
- * @return
- * The URI pointing to the corresponding Media File if available,
- * otherwise it returns the same given uri.
- */
- private Uri getCorrespondingMediaFileUriIfPossible(Uri uri, Context ctx) {
- return getCorrespondingMediaFileUriIfPossible(uri.toString(), ctx);
- }
-
- /**
- * Get corresponding Media File URI for a givin path String
- * if available, otherwise it returns the same input URI.
- *
- * NOTE: Becuase MediaScannerConnection works in callback
- * fashion, we instead wait for its result using a timing-out
- * while loop, and we might further think for a better solution.
- *
- * @param path
- * The given path.
- * @param ctx
- * The application context.
- * @return
- * The URI pointing to the corresponding Media File if available,
- * otherwise it returns the same given path.
- */
- private Uri getCorrespondingMediaFileUriIfPossible(String path, Context ctx) {
- final AtomicReference<Uri> result = new AtomicReference<Uri>();
- MediaScannerConnection.scanFile(ctx, new String[]{path}, null, new MediaScannerConnection.OnScanCompletedListener() {
- @Override
- public void onScanCompleted(String path, Uri uri) {
- if (uri != null) {
- result.set(uri);
- } else {
- result.set(Uri.parse(path));
- }
- }
- });
-
- // Wait until media scanner scans path and gets
- // its corresponding content:// uri
- long startTime = System.currentTimeMillis();
- long maxWait = 5 * 1000; // 5 secs
- while (result.get() == null && System.currentTimeMillis() - startTime < maxWait) {
- try {
- Thread.sleep(100);
- } catch (Exception e) {
- // ignore
- }
- }
-
- return result.get() != null? result.get() : Uri.parse(path);
- }
-
- /**
- * Writes an InputStream to an OutputStream
- *
- * @param in
- * The input stream.
- * @param out
- * The output stream.
- */
- private void copyFile (InputStream in, OutputStream out) throws IOException {
- byte[] buffer = new byte[1024];
- int read;
-
- while ((read = in.read(buffer)) != -1) {
- out.write(buffer, 0, read);
- }
- }
-
- /**
- * Returns the resource ID for the given resource path.
- *
- * @param ctx
- * The application context.
- * @return
- * The resource ID for the given resource.
- */
- private int getResId (String resPath, Context ctx) {
- Resources res = ctx.getResources();
- int resId;
-
- String pkgName = ctx.getPackageName();
- String dirName = "drawable";
- String fileName = resPath;
-
- if (resPath.contains("/")) {
- dirName = resPath.substring(0, resPath.lastIndexOf('/'));
- fileName = resPath.substring(resPath.lastIndexOf('/') + 1);
- }
-
- String resName = fileName.substring(0, fileName.lastIndexOf('.'));
-
- resId = res.getIdentifier(resName, dirName, pkgName);
-
- if (resId == 0) {
- resId = res.getIdentifier(resName, "drawable", pkgName);
- }
-
- return resId;
- }
-
- /**
- * If email apps are available.
- *
- * @param ctx
- * The application context.
- * @return
- * true if available, otherwise false
- */
- private boolean isEmailClientExist (Context ctx) {
- Log.i(LOG_TAG, "isEmailClientExist()");
-
- return getAppsCountHandlesIntent(ctx, getEmailIntent()) > 0;
- }
-
- /**
- * Get apps count which are able to handle specific Intent.
- *
- * @param ctx
- * The application context.
- * @param intent
- * The intent to test against.
- * @return
- * The apps count
- */
- private int getAppsCountHandlesIntent(Context ctx, Intent intent) {
- if (ctx == null || intent == null) return 0;
-
- PackageManager manager = ctx.getPackageManager();
- List<ResolveInfo> infos = manager.queryIntentActivities(intent, 0);
-
- return infos.size();
- }
-
- /**
- * Ask the package manager if the app is installed on the device.
- *
- * @param id
- * The app id.
- * @param ctx
- * The application context.
- * @return
- * true if yes otherwise false.
- */
- private boolean isAppInstalled (String id, Context ctx) {
-
- if (id == null || id.equalsIgnoreCase(MAILTO_SCHEME)) {
- Intent intent = getEmailIntent();
- PackageManager pm = ctx.getPackageManager();
- int apps = pm.queryIntentActivities(intent, 0).size();
-
- return (apps > 0);
- }
-
- try {
- ctx.getPackageManager().getPackageInfo(id, 0);
- return true;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- }
-
- /**
- * Setup an intent to send to email apps only.
- *
- * @return intent
- */
- private static Intent getEmailIntent() {
- Intent intent = new Intent(Intent.ACTION_SENDTO,
- Uri.parse(MAILTO_SCHEME));
-
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- return intent;
- }
-
- /**
- * Attempt to safely close the given stream.
- *
- * @param outStream
- * The stream to close.
- * @return
- * true if successful, false otherwise
- */
- private static boolean safeClose (final FileOutputStream outStream) {
-
- if (outStream != null) {
- try {
- outStream.close();
- return true;
- } catch (IOException e) {
- Log.e(LOG_TAG, "Error attempting to safely close resource: " + e.getMessage());
- }
- }
-
- return false;
- }
-
- }
|