package i2p.bote.android.util;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import com.lambdaworks.codec.Base64;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.iconics.typeface.IIcon;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.text.NumberFormat;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.Part;
import i2p.bote.android.Constants;
import i2p.bote.android.R;
import i2p.bote.android.provider.AttachmentProvider;
import i2p.bote.email.Email;
import i2p.bote.email.EmailDestination;
import i2p.bote.email.EmailIdentity;
import i2p.bote.fileencryption.PasswordException;
import i2p.bote.folder.EmailFolder;
import i2p.bote.folder.Outbox.EmailStatus;
import i2p.bote.packet.dht.Contact;
import i2p.bote.util.GeneralHelper;
import im.delight.android.identicons.Identicon;
public class BoteHelper extends GeneralHelper {
public static int getNumNewEmails(Context ctx, EmailFolder folder) throws PasswordException, GeneralSecurityException, IOException, MessagingException {
String selectedIdentityKey = ctx.getSharedPreferences(Constants.SHARED_PREFS, 0)
.getString(Constants.PREF_SELECTED_IDENTITY, null);
if (selectedIdentityKey == null)
return folder.getNumNewEmails();
int numNew = 0;
for (Email email : BoteHelper.getEmails(folder, null, true)) {
if (email.getMetadata().isUnread()) {
if (BoteHelper.isSentEmail(email)) {
String senderDest = BoteHelper.extractEmailDestination(email.getOneFromAddress());
if (selectedIdentityKey.equals(senderDest))
numNew++;
} else {
for (Address recipient : email.getAllRecipients()) {
String recipientDest = BoteHelper.extractEmailDestination(recipient.toString());
if (selectedIdentityKey.equals(recipientDest)) {
numNew++;
break;
}
}
}
}
}
return numNew;
}
/**
* Get the translated name of the folder.
* Built-in folders are special-cased; other folders are created by the
* user, so their name is already "translated".
*
* @param ctx Android Context to get strings from.
* @param folder The folder.
* @return The name of the folder.
*/
public static String getFolderDisplayName(Context ctx, EmailFolder folder) {
String name = folder.getName();
if ("inbox".equals(name))
return ctx.getResources().getString(R.string.folder_inbox);
else if ("outbox".equals(name))
return ctx.getResources().getString(R.string.folder_outbox);
else if ("sent".equals(name))
return ctx.getResources().getString(R.string.folder_sent);
else if ("trash".equals(name))
return ctx.getResources().getString(R.string.folder_trash);
else
return name;
}
/**
* Get the translated name of the folder with the number of
* new messages it contains appended.
*
* @param ctx Android Context to get strings from.
* @param folder The folder.
* @return The name of the folder.
* @throws PasswordException
*/
public static String getFolderDisplayNameWithNew(Context ctx, EmailFolder folder) throws PasswordException, GeneralSecurityException, IOException, MessagingException {
String displayName = getFolderDisplayName(ctx, folder);
int numNew = getNumNewEmails(ctx, folder);
if (numNew > 0)
displayName = displayName + " (" + numNew + ")";
return displayName;
}
public static Drawable getFolderIcon(Context ctx, EmailFolder folder) {
IIcon icon;
int padding;
switch (folder.getName()) {
case "inbox":
icon = GoogleMaterial.Icon.gmd_inbox;
padding = 3;
break;
case "outbox":
icon = GoogleMaterial.Icon.gmd_cloud_upload;
padding = 0;
break;
case "sent":
icon = GoogleMaterial.Icon.gmd_send;
padding = 1;
break;
case "trash":
icon = GoogleMaterial.Icon.gmd_delete;
padding = 3;
break;
default:
icon = null;
padding = 0;
}
return new IconicsDrawable(ctx, icon).colorRes(R.color.md_grey_600).sizeDp(24).paddingDp(padding);
}
public static Drawable getMenuIcon(Context ctx, GoogleMaterial.Icon icon) {
IconicsDrawable iconic = new IconicsDrawable(ctx, icon).color(Color.WHITE).sizeDp(24);
switch (icon) {
case gmd_attach_file:
case gmd_lock:
case gmd_lock_open:
case gmd_person_add:
case gmd_send:
iconic.paddingDp(1);
break;
case gmd_drafts:
case gmd_folder:
case gmd_markunread:
iconic.paddingDp(2);
break;
case gmd_create:
case gmd_delete:
case gmd_reply:
case gmd_save:
iconic.paddingDp(3);
break;
case gmd_forward:
iconic.paddingDp(4);
break;
case gmd_reply_all:
default:
break;
}
return iconic;
}
public static String getDisplayAddress(String address) throws PasswordException, IOException, GeneralSecurityException, MessagingException {
String fullAdr = getNameAndDestination(address);
String emailDest = extractEmailDestination(fullAdr);
String name = extractName(fullAdr);
return (emailDest == null ? address
: (name.isEmpty() ? emailDest.substring(0, 10)
: name + " <" + emailDest.substring(0, 10) + "...>"));
}
/**
* Get a Bitmap containing the picture for the contact or identity
* corresponding to the given address.
*
* @param address the address to get a picture for.
* @return a Bitmap, or null if no picture was found.
* @throws PasswordException
* @throws IOException
* @throws GeneralSecurityException
*/
public static Bitmap getPictureForAddress(String address) throws PasswordException, IOException, GeneralSecurityException {
String base64dest = extractEmailDestination(address);
if (base64dest != null) {
return getPictureForDestination(base64dest);
}
// Address not found anywhere, or found and has no picture
return null;
}
/**
* Get a Bitmap containing the picture for the contact or identity
* corresponding to the given Destination.
*
* @param base64dest the Destination to get a picture for.
* @return a Bitmap, or null if no picture was found.
* @throws PasswordException
* @throws IOException
* @throws GeneralSecurityException
*/
public static Bitmap getPictureForDestination(String base64dest) throws PasswordException, IOException, GeneralSecurityException {
// Address was found; try address book first
Contact c = getContact(base64dest);
if (c != null) {
// Address is in address book
String pic = c.getPictureBase64();
if (pic != null) {
return decodePicture(pic);
}
} else {
// Address is an identity
EmailIdentity i = getIdentity(base64dest);
if (i != null) {
String pic = i.getPictureBase64();
if (pic != null) {
return decodePicture(pic);
}
}
}
// Address is not known
return null;
}
public static Bitmap decodePicture(String picB64) {
if (picB64 == null)
return null;
byte[] decodedPic = Base64.decode(picB64.toCharArray());
return BitmapFactory.decodeByteArray(decodedPic, 0, decodedPic.length);
}
public static String encodePicture(Bitmap picture) {
if (picture == null)
return null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
picture.compress(CompressFormat.PNG, 0, baos);
return new String(Base64.encode(baos.toByteArray()));
}
public static Bitmap getIdenticonForAddress(String address, int width, int height) {
String identifier = extractEmailDestination(address);
if (identifier == null) {
// Check if the string contains chars in angle brackets
int ltIndex = address.indexOf('<');
int gtIndex = address.indexOf('>', ltIndex);
if (ltIndex >= 0 && gtIndex > 0)
identifier = address.substring(ltIndex + 1, gtIndex);
else
identifier = address;
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Identicon identicon = new Identicon();
identicon.show(identifier);
identicon.updateSize(canvas.getWidth(), canvas.getHeight());
identicon.draw(canvas);
return bitmap;
}
public static Bitmap getIdentityPicture(EmailIdentity identity, int identiconWidth, int identiconHeight) {
String pic = identity.getPictureBase64();
if (pic != null && !pic.isEmpty())
return BoteHelper.decodePicture(pic);
else
return BoteHelper.getIdenticonForAddress(identity.getKey(), identiconWidth, identiconHeight);
}
private static final String PROPERTY_SENT = "sent";
public static void setEmailSent(Email email, boolean isSent) {
email.getMetadata().setProperty(PROPERTY_SENT, isSent ? "true" : "false");
}
/**
* Determines if we sent this email, either anonymously or from a local identity.
*
* @param email The Email to query metadata for
* @return true if we sent this email, false otherwise
* @throws PasswordException
* @throws IOException
* @throws GeneralSecurityException
* @throws MessagingException
*/
public static boolean isSentEmail(Email email) throws PasswordException, IOException, GeneralSecurityException, MessagingException {
boolean isSent;
if (email.getMetadata().containsKey(PROPERTY_SENT)) {
String sentStr = email.getMetadata().getProperty(PROPERTY_SENT);
isSent = "true".equals(sentStr);
} else {
// Figure it out
// Is the sender anonymous?
if (email.isAnonymous()) {
// Assume we sent it unless we are a recipient
isSent = true;
Address[] recipients = email.getAllRecipients();
for (Address recipient : recipients) {
String toDest = EmailDestination.extractBase64Dest(recipient.toString());
if (toDest != null && getIdentity(toDest) != null) {
// We are a recipient
isSent = false;
break;
}
}
} else {
// Are we the sender?
String fromAddress = email.getOneFromAddress();
String fromDest = EmailDestination.extractBase64Dest(fromAddress);
isSent = (fromDest != null && getIdentity(fromDest) != null);
}
// Cache for next time
setEmailSent(email, isSent);
}
return isSent;
}
public static String getEmailStatusText(Context ctx, Email email, boolean full) {
Resources res = ctx.getResources();
EmailStatus emailStatus = getEmailStatus(email);
switch (emailStatus.getStatus()) {
case QUEUED:
return res.getString(R.string.queued);
case SENDING:
return res.getString(R.string.sending);
case SENT_TO:
if (full)
return res.getString(R.string.sent_to,
(Integer) emailStatus.getParam1(), (Integer) emailStatus.getParam2());
else
return res.getString(R.string.sent_to_short,
(Integer) emailStatus.getParam1(), (Integer) emailStatus.getParam2());
case EMAIL_SENT:
return res.getString(R.string.email_sent);
case GATEWAY_DISABLED:
return res.getString(R.string.gateway_disabled);
case NO_IDENTITY_MATCHES:
if (full)
return res.getString(R.string.no_identity_matches,
emailStatus.getParam1());
case INVALID_RECIPIENT:
if (full)
return res.getString(R.string.invalid_recipient,
emailStatus.getParam1());
case ERROR_CREATING_PACKETS:
if (full)
return res.getString(R.string.error_creating_packets,
emailStatus.getParam1());
case ERROR_SENDING:
if (full)
return res.getString(R.string.error_sending,
emailStatus.getParam1());
case ERROR_SAVING_METADATA:
if (full)
return res.getString(R.string.error_saving_metadata,
emailStatus.getParam1());
default:
// Short string for errors and unknown status
return res.getString(R.string.error);
}
}
public static boolean isInbox(EmailFolder folder) {
return isInbox(folder.getName());
}
public static boolean isInbox(String folderName) {
return "Inbox".equalsIgnoreCase(folderName);
}
public static boolean isOutbox(EmailFolder folder) {
return isOutbox(folder.getName());
}
public static boolean isOutbox(String folderName) {
return "Outbox".equalsIgnoreCase(folderName);
}
public static boolean isTrash(EmailFolder folder) {
return isTrash(folder.getName());
}
public static boolean isTrash(String folderName) {
return "Trash".equalsIgnoreCase(folderName);
}
public static List<Email> getRecentEmails(EmailFolder folder) throws PasswordException, MessagingException {
List<Email> emails = folder.getElements();
Iterator<Email> iter = emails.iterator();
while (iter.hasNext()) {
Email email = iter.next();
if (!email.isRecent())
iter.remove();
}
return emails;
}
public interface RequestPasswordListener {
public void onPasswordVerified();
public void onPasswordCanceled();
}
/**
* Request the password from the user, and try it.
*/
public static void requestPassword(final Context context, final RequestPasswordListener listener) {
requestPassword(context, listener, null);
}
/**
* Request the password from the user, and try it.
*
* @param error is pre-filled in the dialog if not null.
*/
public static void requestPassword(final Context context, final RequestPasswordListener listener, String error) {
LayoutInflater li = LayoutInflater.from(context);
View promptView = li.inflate(R.layout.dialog_password, null);
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setView(promptView);
final EditText passwordInput = (EditText) promptView.findViewById(R.id.passwordInput);
if (error != null) {
TextView passwordError = (TextView) promptView.findViewById(R.id.passwordError);
passwordError.setText(error);
passwordError.setVisibility(View.VISIBLE);
}
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(passwordInput.getWindowToken(), 0);
dialog.dismiss();
new PasswordWaiter(context, listener).execute(passwordInput.getText().toString());
}
}).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
if (listener != null)
listener.onPasswordCanceled();
}
}).setCancelable(false);
AlertDialog passwordDialog = builder.create();
passwordDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
passwordDialog.show();
}
private static class PasswordWaiter extends AsyncTask<String, Void, String> {
private final Context mContext;
private final ProgressDialog mDialog;
private final RequestPasswordListener mListener;
public PasswordWaiter(Context context, RequestPasswordListener listener) {
super();
mContext = context;
mDialog = new ProgressDialog(context);
mListener = listener;
}
protected void onPreExecute() {
mDialog.setMessage(mContext.getResources().getString(
R.string.checking_password));
mDialog.setCancelable(false);
mDialog.show();
}
protected String doInBackground(String... params) {
try {
if (BoteHelper.tryPassword(params[0]))
return null;
else {
cancel(false);
return mContext.getResources().getString(
R.string.password_incorrect);
}
} catch (IOException e) {
cancel(false);
return mContext.getResources().getString(
R.string.password_file_error);
} catch (GeneralSecurityException e) {
cancel(false);
return mContext.getResources().getString(
R.string.password_file_error);
}
}
protected void onCancelled(String result) {
mDialog.dismiss();
requestPassword(mContext, mListener, result);
}
protected void onPostExecute(String result) {
// Password is valid
mDialog.dismiss();
if (mListener != null)
mListener.onPasswordVerified();
}
}
public static String joinAddressNames(Collection<Address> s) throws PasswordException, GeneralSecurityException, IOException {
StringBuilder builder = new StringBuilder();
Iterator<Address> iter = s.iterator();
while (iter.hasNext()) {
String name = getName(iter.next().toString());
builder.append(name);
if (!iter.hasNext()) {
break;
}
builder.append(", ");
}
return builder.toString();
}
/**
* Attempt to revoke any URI permissions that were granted on an Email's attachments.
* This is best-effort; exceptions are silently ignored.
*
* @param context the Context in which permissions were granted
* @param folderName where the Email is
* @param email the Email to revoke permissions for
*/
public static void revokeAttachmentUriPermissions(Context context, String folderName, Email email) {
List<Part> parts;
try {
parts = email.getParts();
} catch (Exception e) {
// Nothing we can do, abort
return;
}
for (Part part : parts) {
try {
if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) {
Uri uri = AttachmentProvider.getUriForAttachment(folderName,
email.getMessageID(), parts.indexOf(part));
context.revokeUriPermission(uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
} catch (MessagingException e) {
// Ignore and carry on
}
}
}
public static void copyStream(InputStream in, OutputStream out) {
byte[] buf = new byte[8192];
int len;
try {
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.flush();
out.close();
} catch (IOException e) {
Log.e(Constants.ANDROID_LOG_TAG, "Exception copying streams", e);
}
}
public static String getHumanReadableSize(Context context, long size) {
int unit = (63 - Long.numberOfLeadingZeros(size)) / 10; // 0 if totalBytes<1K, 1 if 1K<=totalBytes<1M, etc.
double value = (double) size / (1 << (10 * unit));
int formatStr;
switch (unit) {
case 0:
formatStr = R.string.n_bytes;
break;
case 1:
formatStr = R.string.n_kilobytes;
break;
default:
formatStr = R.string.n_megabytes;
}
NumberFormat formatter = NumberFormat.getInstance(Locale.getDefault());
if (value < 100)
formatter.setMaximumFractionDigits(1);
else
formatter.setMaximumFractionDigits(0);
return context.getString(formatStr, formatter.format(value));
}
}