Java tutorial
/* * 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.appplant.cordova.emailcomposer; import android.accounts.AccountManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Resources; import android.net.Uri; import android.os.Build; 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; /** * 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("EmailComposer", "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 = isEmailAccountConfigured(ctx) || withScheme; boolean[] result = { isPossible, withScheme }; return result; } /** * 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 = new Intent(Intent.ACTION_SEND_MULTIPLE); String app = params.optString("app", null); 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); } mail.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 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) { if (isHTML) { draft.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(body)); draft.setType("text/html"); if (Build.VERSION.SDK_INT > 15) { draft.putExtra(Intent.EXTRA_HTML_TEXT, body); } } else { draft.putExtra(Intent.EXTRA_TEXT, body); draft.setType("text/plain"); } } /** * 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); } draft.putParcelableArrayListExtra(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) { if (path.startsWith("res:")) { return getUriForResourcePath(path, ctx); } else if (path.startsWith("file:///")) { return getUriForAbsolutePath(path); } else if (path.startsWith("file://")) { return getUriForAssetPath(path, ctx); } else if (path.startsWith("base64:")) { return getUriForBase64Content(path, ctx); } return Uri.parse(path); } /** * 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("EmailComposer", "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("EmailComposer", "Missing external cache dir"); return Uri.EMPTY; } String storage = dir.toString() + ATTACHMENT_FOLDER; File file = new File(storage, fileName); new File(storage).mkdir(); try { AssetManager assets = ctx.getAssets(); FileOutputStream outStream = new FileOutputStream(file); InputStream inputStream = assets.open(resPath); copyFile(inputStream, outStream); outStream.flush(); outStream.close(); } catch (Exception e) { Log.e("EmailComposer", "File not found: assets/" + resPath); e.printStackTrace(); } 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("EmailComposer", "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("EmailComposer", "File not found: " + resPath); } new File(storage).mkdir(); try { Resources res = ctx.getResources(); FileOutputStream outStream = new FileOutputStream(file); InputStream inputStream = res.openRawResource(resId); copyFile(inputStream, outStream); outStream.flush(); outStream.close(); } catch (Exception e) { e.printStackTrace(); } 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("EmailComposer", "Invalid Base64 string"); return Uri.EMPTY; } if (dir == null) { Log.e("EmailComposer", "Missing external cache dir"); return Uri.EMPTY; } String storage = dir.toString() + ATTACHMENT_FOLDER; File file = new File(storage, resName); new File(storage).mkdir(); try { FileOutputStream outStream = new FileOutputStream(file); outStream.write(bytes); outStream.flush(); outStream.close(); } catch (Exception e) { e.printStackTrace(); } return Uri.fromFile(file); } /** * 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 isEmailAccountConfigured(Context ctx) { Uri uri = Uri.fromParts("mailto", "max@mustermann.com", null); Intent intent = new Intent(Intent.ACTION_SENDTO, uri); PackageManager pm = ctx.getPackageManager(); int apps = pm.queryIntentActivities(intent, 0).size(); if (apps == 0) { return false; } AccountManager am = AccountManager.get(ctx); int accounts; try { accounts = am.getAccounts().length; } catch (Exception e) { Log.e("EmailComposer", "Missing GET_ACCOUNTS permission."); return true; } return accounts > 0; } /** * 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) { try { ctx.getPackageManager().getPackageInfo(id, 0); return true; } catch (PackageManager.NameNotFoundException e) { return false; } } }