mirror of
https://github.com/M66B/FairEmail.git
synced 2025-02-21 13:47:04 +00:00
Prepared signed-only messages
This commit is contained in:
parent
fc18f916a8
commit
cc05ff3937
18 changed files with 524 additions and 182 deletions
2
FAQ.md
2
FAQ.md
|
@ -86,7 +86,7 @@ Related questions:
|
|||
* ~~Unified starred messages view~~ (there is already a special search for this)
|
||||
* ~~Notification move action~~
|
||||
* Search for settings: low priority
|
||||
* Signed-only messages: waiting for sponsoring
|
||||
* Signed-only messages
|
||||
* S/MIME: waiting for sponsoring
|
||||
|
||||
Anything on this list is in random order and *might* be added in the near future.
|
||||
|
|
|
@ -266,6 +266,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
private ImageButton ibAuth;
|
||||
private ImageView ivPriorityHigh;
|
||||
private ImageView ivPriorityLow;
|
||||
private ImageView ivSigned;
|
||||
private ImageView ivEncrypted;
|
||||
private TextView tvFrom;
|
||||
private TextView tvSize;
|
||||
|
@ -333,6 +334,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
private ImageButton ibFull;
|
||||
private ImageButton ibImages;
|
||||
private ImageButton ibUnsubscribe;
|
||||
private ImageButton ibVerify;
|
||||
private ImageButton ibDecrypt;
|
||||
|
||||
private TextView tvBody;
|
||||
|
@ -385,6 +387,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ibAuth = itemView.findViewById(R.id.ibAuth);
|
||||
ivPriorityHigh = itemView.findViewById(R.id.ivPriorityHigh);
|
||||
ivPriorityLow = itemView.findViewById(R.id.ivPriorityLow);
|
||||
ivSigned = itemView.findViewById(R.id.ivSigned);
|
||||
ivEncrypted = itemView.findViewById(R.id.ivEncrypted);
|
||||
tvFrom = itemView.findViewById(subject_top ? R.id.tvSubject : R.id.tvFrom);
|
||||
tvSize = itemView.findViewById(R.id.tvSize);
|
||||
|
@ -499,6 +502,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ibFull = vsBody.findViewById(R.id.ibFull);
|
||||
ibImages = vsBody.findViewById(R.id.ibImages);
|
||||
ibUnsubscribe = vsBody.findViewById(R.id.ibUnsubscribe);
|
||||
ibVerify = vsBody.findViewById(R.id.ibVerify);
|
||||
ibDecrypt = vsBody.findViewById(R.id.ibDecrypt);
|
||||
|
||||
tvBody = vsBody.findViewById(R.id.tvBody);
|
||||
|
@ -573,6 +577,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ibFull.setOnClickListener(this);
|
||||
ibImages.setOnClickListener(this);
|
||||
ibUnsubscribe.setOnClickListener(this);
|
||||
ibVerify.setOnClickListener(this);
|
||||
ibDecrypt.setOnClickListener(this);
|
||||
|
||||
ibDownloading.setOnClickListener(this);
|
||||
|
@ -629,6 +634,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ibFull.setOnClickListener(null);
|
||||
ibImages.setOnClickListener(null);
|
||||
ibUnsubscribe.setOnClickListener(null);
|
||||
ibVerify.setOnClickListener(null);
|
||||
ibDecrypt.setOnClickListener(null);
|
||||
|
||||
ibDownloading.setOnClickListener(null);
|
||||
|
@ -651,6 +657,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ibAuth.setVisibility(View.GONE);
|
||||
ivPriorityHigh.setVisibility(View.GONE);
|
||||
ivPriorityLow.setVisibility(View.GONE);
|
||||
ivSigned.setVisibility(View.GONE);
|
||||
ivEncrypted.setVisibility(View.GONE);
|
||||
tvFrom.setText(null);
|
||||
tvSize.setText(null);
|
||||
|
@ -718,6 +725,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ibAuth.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
|
||||
ivPriorityHigh.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
|
||||
ivPriorityLow.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
|
||||
ivSigned.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
|
||||
ivEncrypted.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
|
||||
tvFrom.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
|
||||
tvSize.setAlpha(dim ? Helper.LOW_LIGHT : 1.0f);
|
||||
|
@ -783,6 +791,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ibAuth.setVisibility(authentication && !authenticated ? View.VISIBLE : View.GONE);
|
||||
ivPriorityHigh.setVisibility(EntityMessage.PRIORITIY_HIGH.equals(message.priority) ? View.VISIBLE : View.GONE);
|
||||
ivPriorityLow.setVisibility(EntityMessage.PRIORITIY_LOW.equals(message.priority) ? View.VISIBLE : View.GONE);
|
||||
ivSigned.setVisibility(message.signed > 0 ? View.VISIBLE : View.GONE);
|
||||
ivEncrypted.setVisibility(message.encrypted > 0 ? View.VISIBLE : View.GONE);
|
||||
tvFrom.setText(MessageHelper.formatAddresses(addresses, name_email, false));
|
||||
tvFrom.setPaintFlags(tvFrom.getPaintFlags() & ~Paint.UNDERLINE_TEXT_FLAG);
|
||||
|
@ -995,6 +1004,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ibFull.setVisibility(View.GONE);
|
||||
ibImages.setVisibility(View.GONE);
|
||||
ibUnsubscribe.setVisibility(View.GONE);
|
||||
ibVerify.setVisibility(View.GONE);
|
||||
ibDecrypt.setVisibility(View.GONE);
|
||||
|
||||
tvBody.setVisibility(View.GONE);
|
||||
|
@ -1577,6 +1587,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
Log.i("Show inline=" + show_inline + " encrypted=" + inline_encrypted);
|
||||
|
||||
boolean has_inline = false;
|
||||
boolean is_signed = false;
|
||||
boolean is_encrypted = false;
|
||||
boolean download = false;
|
||||
boolean save = (attachments.size() > 1);
|
||||
|
@ -1590,6 +1601,8 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
has_inline = true;
|
||||
if (Objects.equals(attachment.encryption, EntityAttachment.PGP_MESSAGE))
|
||||
is_encrypted = true;
|
||||
if (Objects.equals(attachment.encryption, EntityAttachment.PGP_CONTENT))
|
||||
is_signed = true;
|
||||
if (attachment.progress == null && !attachment.available)
|
||||
download = true;
|
||||
if (!attachment.available)
|
||||
|
@ -1625,11 +1638,12 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
btnDownloadAttachments.setVisibility(download && suitable ? View.VISIBLE : View.GONE);
|
||||
tvNoInternetAttachments.setVisibility(downloading && !suitable ? View.VISIBLE : View.GONE);
|
||||
|
||||
ibVerify.setVisibility(is_signed ? View.VISIBLE : View.GONE);
|
||||
ibDecrypt.setVisibility(inline_encrypted || is_encrypted ? View.VISIBLE : View.GONE);
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean auto_decrypt = prefs.getBoolean("auto_decrypt", false);
|
||||
if (auto_decrypt && is_encrypted)
|
||||
if (auto_decrypt && (is_signed || is_encrypted))
|
||||
onActionDecrypt(message, true);
|
||||
|
||||
cbInline.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
|
@ -1977,6 +1991,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
case R.id.ibUnsubscribe:
|
||||
onActionUnsubscribe(message);
|
||||
break;
|
||||
case R.id.ibVerify:
|
||||
case R.id.ibDecrypt:
|
||||
onActionDecrypt(message, false);
|
||||
break;
|
||||
|
@ -4039,6 +4054,10 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
same = false;
|
||||
Log.i("drafts changed id=" + next.id);
|
||||
}
|
||||
if (prev.signed != next.signed) {
|
||||
same = false;
|
||||
Log.i("signed changed id=" + next.id);
|
||||
}
|
||||
if (prev.encrypted != next.encrypted) {
|
||||
same = false;
|
||||
Log.i("encrypted changed id=" + next.id);
|
||||
|
|
|
@ -2183,7 +2183,7 @@ class Core {
|
|||
attachment.sequence = sequence++;
|
||||
attachment.id = db.attachment().insertAttachment(attachment);
|
||||
if (EntityAttachment.PGP_MESSAGE.equals(attachment.encryption))
|
||||
db.message().setMessageEncrypt(message.id, true);
|
||||
db.message().setMessageEncrypt(message.id, EntityMessage.ENCRYPTION_SIGNENCRYPT);
|
||||
}
|
||||
|
||||
runRules(context, imessage, message, rules);
|
||||
|
|
|
@ -51,6 +51,7 @@ public interface DaoMessage {
|
|||
", SUM(1 - message.ui_seen) AS unseen" +
|
||||
", SUM(1 - message.ui_flagged) AS unflagged" +
|
||||
", SUM(folder.type = '" + EntityFolder.DRAFTS + "') AS drafts" +
|
||||
", SUM(message.encrypt = 2) AS signed" +
|
||||
", SUM(message.encrypt = 1) AS encrypted" +
|
||||
", COUNT(DISTINCT CASE WHEN message.msgid IS NULL THEN message.id ELSE message.msgid END) AS visible" +
|
||||
", SUM(message.total) AS totalSize" +
|
||||
|
@ -101,6 +102,7 @@ public interface DaoMessage {
|
|||
", SUM(1 - message.ui_seen) AS unseen" +
|
||||
", SUM(1 - message.ui_flagged) AS unflagged" +
|
||||
", SUM(folder.type = '" + EntityFolder.DRAFTS + "') AS drafts" +
|
||||
", SUM(message.encrypt = 2) AS signed" +
|
||||
", SUM(message.encrypt = 1) AS encrypted" +
|
||||
", COUNT(DISTINCT CASE WHEN message.msgid IS NULL THEN message.id ELSE message.msgid END) AS visible" +
|
||||
", SUM(message.total) AS totalSize" +
|
||||
|
@ -145,6 +147,7 @@ public interface DaoMessage {
|
|||
", CASE WHEN message.ui_seen THEN 0 ELSE 1 END AS unseen" +
|
||||
", CASE WHEN message.ui_flagged THEN 0 ELSE 1 END AS unflagged" +
|
||||
", (folder.type = '" + EntityFolder.DRAFTS + "') AS drafts" +
|
||||
", (message.encrypt = 2) AS signed" +
|
||||
", (message.encrypt = 1) AS encrypted" +
|
||||
", 1 AS visible" +
|
||||
", message.total AS totalSize" +
|
||||
|
@ -234,7 +237,7 @@ public interface DaoMessage {
|
|||
" AND (:seen IS NULL OR ui_seen = :seen)" +
|
||||
" AND (:flagged IS NULL OR ui_flagged = :flagged)" +
|
||||
" AND (:hidden IS NULL OR (CASE WHEN ui_snoozed IS NULL THEN 0 ELSE 1 END) = :hidden)" +
|
||||
" AND (:encrypted IS NULL OR encrypt = 1)" +
|
||||
" AND (:encrypted IS NULL OR encrypt > 0)" +
|
||||
" ORDER BY received DESC" +
|
||||
" LIMIT :limit OFFSET :offset")
|
||||
List<TupleMatch> matchMessages(
|
||||
|
@ -284,6 +287,7 @@ public interface DaoMessage {
|
|||
", CASE WHEN message.ui_seen THEN 0 ELSE 1 END AS unseen" +
|
||||
", CASE WHEN message.ui_flagged THEN 0 ELSE 1 END AS unflagged" +
|
||||
", (folder.type = '" + EntityFolder.DRAFTS + "') AS drafts" +
|
||||
", (message.encrypt = 2) AS signed" +
|
||||
", (message.encrypt = 1) AS encrypted" +
|
||||
", 1 AS visible" +
|
||||
", message.total AS totalSize" +
|
||||
|
@ -318,6 +322,7 @@ public interface DaoMessage {
|
|||
", 1 AS unseen" +
|
||||
", 0 AS unflagged" +
|
||||
", 0 AS drafts" +
|
||||
", (message.encrypt = 2) AS signed" +
|
||||
", (message.encrypt = 1) AS encrypted" +
|
||||
", 1 AS visible" +
|
||||
", message.total AS totalSize" +
|
||||
|
@ -485,7 +490,7 @@ public interface DaoMessage {
|
|||
int setMessagePlainOnly(long id, boolean plain_only);
|
||||
|
||||
@Query("UPDATE message SET encrypt = :encrypt WHERE id = :id")
|
||||
int setMessageEncrypt(long id, boolean encrypt);
|
||||
int setMessageEncrypt(long id, Integer encrypt);
|
||||
|
||||
@Query("UPDATE message SET last_attempt = :last_attempt WHERE id = :id")
|
||||
int setMessageLastAttempt(long id, long last_attempt);
|
||||
|
|
|
@ -59,6 +59,7 @@ public class EntityAttachment {
|
|||
static final Integer PGP_MESSAGE = 1;
|
||||
static final Integer PGP_SIGNATURE = 2;
|
||||
static final Integer PGP_KEY = 3;
|
||||
static final Integer PGP_CONTENT = 4;
|
||||
|
||||
// https://developer.android.com/guide/topics/media/media-formats#image-formats
|
||||
private static final List<String> IMAGE_TYPES = Collections.unmodifiableList(Arrays.asList(
|
||||
|
|
|
@ -80,6 +80,10 @@ import static androidx.room.ForeignKey.SET_NULL;
|
|||
public class EntityMessage implements Serializable {
|
||||
static final String TABLE_NAME = "message";
|
||||
|
||||
static final Integer ENCRYPTION_NONE = 0;
|
||||
static final Integer ENCRYPTION_SIGNENCRYPT = 1;
|
||||
static final Integer ENCRYPTION_SIGNONLY = 2;
|
||||
|
||||
static final Integer PRIORITIY_LOW = 0;
|
||||
static final Integer PRIORITIY_NORMAL = 1;
|
||||
static final Integer PRIORITIY_HIGH = 2;
|
||||
|
@ -127,7 +131,7 @@ public class EntityMessage implements Serializable {
|
|||
@NonNull
|
||||
public Boolean content = false;
|
||||
public Boolean plain_only = null;
|
||||
public Boolean encrypt = null;
|
||||
public Integer encrypt = null;
|
||||
public String preview;
|
||||
@NonNull
|
||||
public Boolean signature = true;
|
||||
|
|
|
@ -121,7 +121,7 @@ public class EntityOperation {
|
|||
for (Object value : values)
|
||||
jargs.put(value);
|
||||
|
||||
if (MOVE.equals(name) && message.encrypt != null && message.encrypt) {
|
||||
if (MOVE.equals(name) && EntityMessage.ENCRYPTION_SIGNENCRYPT.equals(message.encrypt)) {
|
||||
EntityFolder folder = db.folder().getFolder(message.folder);
|
||||
if (folder != null && EntityFolder.DRAFTS.equals(folder.type))
|
||||
name = DELETE;
|
||||
|
|
|
@ -131,13 +131,17 @@ import java.util.Properties;
|
|||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.mail.Address;
|
||||
import javax.mail.BodyPart;
|
||||
import javax.mail.MessageRemovedException;
|
||||
import javax.mail.Multipart;
|
||||
import javax.mail.Part;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.internet.AddressException;
|
||||
import javax.mail.internet.ContentType;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeBodyPart;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import javax.mail.internet.MimeMultipart;
|
||||
import javax.mail.internet.ParseException;
|
||||
|
||||
import static android.app.Activity.RESULT_CANCELED;
|
||||
|
@ -188,7 +192,7 @@ public class FragmentCompose extends FragmentBase {
|
|||
|
||||
private boolean prefix_once = false;
|
||||
private boolean monospaced = false;
|
||||
private boolean encrypt = false;
|
||||
private Integer encrypt = null;
|
||||
private boolean media = true;
|
||||
private boolean compact = false;
|
||||
private int zoom = 0;
|
||||
|
@ -205,6 +209,8 @@ public class FragmentCompose extends FragmentBase {
|
|||
private String[] pgpUserIds;
|
||||
private long[] pgpKeyIds;
|
||||
private long pgpSignKeyId;
|
||||
private String pgpContent;
|
||||
private String pgpContentType;
|
||||
|
||||
static final int REDUCED_IMAGE_SIZE = 1440; // pixels
|
||||
static final int REDUCED_IMAGE_QUALITY = 90; // percent
|
||||
|
@ -930,14 +936,20 @@ public class FragmentCompose extends FragmentBase {
|
|||
int colorEncrypt = Helper.resolveColor(getContext(), R.attr.colorEncrypt);
|
||||
ImageButton ib = (ImageButton) menu.findItem(R.id.menu_encrypt).getActionView();
|
||||
ib.setEnabled(!busy);
|
||||
ib.setImageResource(encrypt ? R.drawable.baseline_lock_24 : R.drawable.baseline_lock_open_24);
|
||||
ib.setImageTintList(encrypt ? ColorStateList.valueOf(colorEncrypt) : null);
|
||||
ib.setImageResource(EntityMessage.ENCRYPTION_SIGNENCRYPT.equals(encrypt)
|
||||
? R.drawable.baseline_lock_24 : R.drawable.baseline_lock_open_24);
|
||||
ib.setImageTintList(EntityMessage.ENCRYPTION_SIGNENCRYPT.equals(encrypt)
|
||||
? ColorStateList.valueOf(colorEncrypt) : null);
|
||||
|
||||
menu.findItem(R.id.menu_media).setChecked(media);
|
||||
menu.findItem(R.id.menu_compact).setChecked(compact);
|
||||
|
||||
bottom_navigation.getMenu().findItem(R.id.action_send)
|
||||
.setTitle(encrypt ? R.string.title_encrypt : R.string.title_send);
|
||||
if (EntityMessage.ENCRYPTION_SIGNONLY.equals(encrypt))
|
||||
bottom_navigation.getMenu().findItem(R.id.action_send).setTitle(R.string.title_sign);
|
||||
else if (EntityMessage.ENCRYPTION_SIGNENCRYPT.equals(encrypt))
|
||||
bottom_navigation.getMenu().findItem(R.id.action_send).setTitle(R.string.title_encrypt);
|
||||
else
|
||||
bottom_navigation.getMenu().findItem(R.id.action_send).setTitle(R.string.title_send);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -987,21 +999,25 @@ public class FragmentCompose extends FragmentBase {
|
|||
}
|
||||
|
||||
private void onMenuEncrypt() {
|
||||
encrypt = !encrypt;
|
||||
encrypt = (EntityMessage.ENCRYPTION_SIGNENCRYPT.equals(encrypt)
|
||||
? EntityMessage.ENCRYPTION_NONE : EntityMessage.ENCRYPTION_SIGNENCRYPT);
|
||||
getActivity().invalidateOptionsMenu();
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", working);
|
||||
args.putBoolean("encrypt", encrypt);
|
||||
args.putInt("encrypt", encrypt);
|
||||
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
protected Void onExecute(Context context, Bundle args) {
|
||||
long id = args.getLong("id");
|
||||
boolean encrypt = args.getBoolean("encrypt");
|
||||
int encrypt = args.getInt("encrypt");
|
||||
|
||||
DB db = DB.getInstance(context);
|
||||
db.message().setMessageEncrypt(id, encrypt);
|
||||
if (EntityMessage.ENCRYPTION_NONE.equals(encrypt))
|
||||
db.message().setMessageEncrypt(id, null);
|
||||
else
|
||||
db.message().setMessageEncrypt(id, encrypt);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -1200,9 +1216,17 @@ public class FragmentCompose extends FragmentBase {
|
|||
pgpUserIds[i] = recipient.getAddress().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
Intent intent = new Intent(OpenPgpApi.ACTION_GET_KEY_IDS);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, pgpUserIds);
|
||||
intent.putExtra(BuildConfig.APPLICATION_ID, working);
|
||||
Intent intent;
|
||||
if (EntityMessage.ENCRYPTION_SIGNONLY.equals(draft.encrypt)) {
|
||||
intent = new Intent(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
|
||||
intent.putExtra(BuildConfig.APPLICATION_ID, working);
|
||||
} else if (EntityMessage.ENCRYPTION_SIGNENCRYPT.equals(draft.encrypt)) {
|
||||
intent = new Intent(OpenPgpApi.ACTION_GET_KEY_IDS);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, pgpUserIds);
|
||||
intent.putExtra(BuildConfig.APPLICATION_ID, working);
|
||||
} else
|
||||
throw new IllegalArgumentException("Invalid encrypt=" + draft.encrypt);
|
||||
|
||||
onPgp(intent);
|
||||
} catch (Throwable ex) {
|
||||
if (ex instanceof IllegalArgumentException)
|
||||
|
@ -1479,36 +1503,70 @@ public class FragmentCompose extends FragmentBase {
|
|||
DB db = DB.getInstance(context);
|
||||
|
||||
// Get data
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
if (message == null)
|
||||
throw new MessageRemovedException();
|
||||
EntityIdentity identity = db.identity().getIdentity(message.identity);
|
||||
EntityMessage draft = db.message().getMessage(id);
|
||||
if (draft == null)
|
||||
throw new MessageRemovedException("PGP");
|
||||
EntityIdentity identity = db.identity().getIdentity(draft.identity);
|
||||
if (identity == null)
|
||||
throw new IllegalArgumentException(getString(R.string.title_from_missing));
|
||||
|
||||
List<EntityAttachment> attachments = db.attachment().getAttachments(id);
|
||||
for (EntityAttachment attachment : new ArrayList<>(attachments))
|
||||
if (attachment.encryption != null) {
|
||||
if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(data.getAction()))
|
||||
db.attachment().deleteAttachment(attachment.id);
|
||||
attachments.remove(attachment);
|
||||
}
|
||||
|
||||
// Create files
|
||||
File input = new File(context.getCacheDir(), "input." + id);
|
||||
File output = new File(context.getCacheDir(), "output." + id);
|
||||
|
||||
// Serializing messages is NOT reproducible
|
||||
if (OpenPgpApi.ACTION_GET_KEY_IDS.equals(data.getAction())) {
|
||||
if ((EntityMessage.ENCRYPTION_SIGNONLY.equals(draft.encrypt) &&
|
||||
OpenPgpApi.ACTION_GET_SIGN_KEY_ID.equals(data.getAction())) ||
|
||||
(EntityMessage.ENCRYPTION_SIGNENCRYPT.equals(draft.encrypt) &&
|
||||
OpenPgpApi.ACTION_GET_KEY_IDS.equals(data.getAction()))) {
|
||||
// Get attachments
|
||||
List<EntityAttachment> attachments = db.attachment().getAttachments(id);
|
||||
for (EntityAttachment attachment : new ArrayList<>(attachments))
|
||||
if (attachment.encryption != null) {
|
||||
db.attachment().deleteAttachment(attachment.id);
|
||||
attachments.remove(attachment);
|
||||
}
|
||||
|
||||
// Build message
|
||||
Properties props = MessageHelper.getSessionProperties();
|
||||
Session isession = Session.getInstance(props, null);
|
||||
MimeMessage imessage = new MimeMessage(isession);
|
||||
MessageHelper.build(context, message, attachments, identity, imessage);
|
||||
MessageHelper.build(context, draft, attachments, identity, imessage);
|
||||
|
||||
// Serialize message
|
||||
try (OutputStream out = new FileOutputStream(input)) {
|
||||
imessage.writeTo(out);
|
||||
if (OpenPgpApi.ACTION_GET_SIGN_KEY_ID.equals(data.getAction())) {
|
||||
// Serialize content
|
||||
imessage.saveChanges();
|
||||
Object content = imessage.getContent();
|
||||
if (content instanceof String) {
|
||||
pgpContent = (String) content;
|
||||
pgpContentType = imessage.getContentType();
|
||||
|
||||
// Build plain text part with headers
|
||||
BodyPart plainPart = new MimeBodyPart();
|
||||
plainPart.setContent(pgpContent, pgpContentType);
|
||||
Multipart plainMultiPart = new MimeMultipart();
|
||||
plainMultiPart.addBodyPart(plainPart);
|
||||
MimeMessage m = new MimeMessage(isession);
|
||||
m.setContent(plainMultiPart);
|
||||
m.saveChanges();
|
||||
|
||||
try (OutputStream out = new FileOutputStream(input)) {
|
||||
plainPart.writeTo(out);
|
||||
}
|
||||
} else if (content instanceof Multipart) {
|
||||
pgpContent = null;
|
||||
pgpContentType = ((MimeMultipart) content).getContentType();
|
||||
|
||||
try (OutputStream out = new FileOutputStream(input)) {
|
||||
((MimeMultipart) content).writeTo(out);
|
||||
}
|
||||
} else
|
||||
throw new ParseException(content.getClass().getName());
|
||||
} else {
|
||||
// Serialize message
|
||||
try (OutputStream out = new FileOutputStream(input)) {
|
||||
imessage.writeTo(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1533,6 +1591,7 @@ public class FragmentCompose extends FragmentBase {
|
|||
db.beginTransaction();
|
||||
|
||||
String name;
|
||||
String type = "application/octet-stream";
|
||||
int encryption;
|
||||
if (OpenPgpApi.ACTION_GET_KEY.equals(data.getAction())) {
|
||||
name = "keydata.asc";
|
||||
|
@ -1543,6 +1602,8 @@ public class FragmentCompose extends FragmentBase {
|
|||
} else if (OpenPgpApi.ACTION_DETACHED_SIGN.equals(data.getAction())) {
|
||||
name = "signature.asc";
|
||||
encryption = EntityAttachment.PGP_SIGNATURE;
|
||||
type = "application/pgp-signature; micalg=\"" +
|
||||
result.getStringExtra(OpenPgpApi.RESULT_SIGNATURE_MICALG) + "\"";
|
||||
} else
|
||||
throw new IllegalStateException(data.getAction());
|
||||
|
||||
|
@ -1550,7 +1611,7 @@ public class FragmentCompose extends FragmentBase {
|
|||
attachment.message = id;
|
||||
attachment.sequence = db.attachment().getAttachmentSequence(id) + 1;
|
||||
attachment.name = name;
|
||||
attachment.type = "application/octet-stream";
|
||||
attachment.type = type;
|
||||
attachment.disposition = Part.INLINE;
|
||||
attachment.encryption = encryption;
|
||||
attachment.id = db.attachment().insertAttachment(attachment);
|
||||
|
@ -1593,43 +1654,79 @@ public class FragmentCompose extends FragmentBase {
|
|||
|
||||
if (OpenPgpApi.ACTION_GET_KEY.equals(data.getAction()) ||
|
||||
(OpenPgpApi.ACTION_GET_KEY_IDS.equals(data.getAction()) && pgpKeyIds.length > 1)) {
|
||||
if (identity.sign_key != null) {
|
||||
// Encrypt message
|
||||
Intent intent = new Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, pgpKeyIds);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, identity.sign_key);
|
||||
if (EntityMessage.ENCRYPTION_SIGNONLY.equals(draft.encrypt)) {
|
||||
// Sign message
|
||||
Intent intent = new Intent(OpenPgpApi.ACTION_DETACHED_SIGN);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, pgpSignKeyId);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
intent.putExtra(BuildConfig.APPLICATION_ID, id);
|
||||
return intent;
|
||||
} else {
|
||||
// Get sign key
|
||||
Intent intent = new Intent(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, pgpUserIds);
|
||||
intent.putExtra(BuildConfig.APPLICATION_ID, id);
|
||||
return intent;
|
||||
if (identity.sign_key != null) {
|
||||
// Encrypt message
|
||||
Intent intent = new Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, pgpKeyIds);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, identity.sign_key);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
intent.putExtra(BuildConfig.APPLICATION_ID, id);
|
||||
return intent;
|
||||
} else {
|
||||
// Get sign key
|
||||
Intent intent = new Intent(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, pgpUserIds);
|
||||
intent.putExtra(BuildConfig.APPLICATION_ID, id);
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
} else if (OpenPgpApi.ACTION_GET_SIGN_KEY_ID.equals(data.getAction())) {
|
||||
pgpSignKeyId = result.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, -1);
|
||||
db.identity().setIdentitySignKey(identity.id, pgpSignKeyId);
|
||||
|
||||
// Encrypt message
|
||||
Intent intent = new Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, pgpKeyIds);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, pgpSignKeyId);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
intent.putExtra(BuildConfig.APPLICATION_ID, id);
|
||||
return intent;
|
||||
if (EntityMessage.ENCRYPTION_SIGNONLY.equals(draft.encrypt)) {
|
||||
// Get sign key
|
||||
Intent intent = new Intent(OpenPgpApi.ACTION_GET_KEY);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpSignKeyId);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
intent.putExtra(BuildConfig.APPLICATION_ID, id);
|
||||
return intent;
|
||||
} else if (EntityMessage.ENCRYPTION_SIGNENCRYPT.equals(draft.encrypt)) {
|
||||
// Encrypt message
|
||||
Intent intent = new Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, pgpKeyIds);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, pgpSignKeyId);
|
||||
intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
|
||||
intent.putExtra(BuildConfig.APPLICATION_ID, id);
|
||||
return intent;
|
||||
} else
|
||||
throw new IllegalArgumentException("Invalid encrypt=" + draft.encrypt);
|
||||
} else if (OpenPgpApi.ACTION_SIGN_AND_ENCRYPT.equals(data.getAction())) {
|
||||
input.delete();
|
||||
|
||||
// Get signature
|
||||
//Intent intent = new Intent(OpenPgpApi.ACTION_DETACHED_SIGN);
|
||||
//intent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, pgpSignKeyId);
|
||||
//intent.putExtra(BuildConfig.APPLICATION_ID, id);
|
||||
|
||||
// send message
|
||||
return null;
|
||||
} else if (OpenPgpApi.ACTION_DETACHED_SIGN.equals(data.getAction())) {
|
||||
|
||||
EntityAttachment attachment = new EntityAttachment();
|
||||
attachment.message = id;
|
||||
attachment.sequence = db.attachment().getAttachmentSequence(id) + 1;
|
||||
attachment.name = "content.txt";
|
||||
attachment.type = pgpContentType;
|
||||
attachment.disposition = Part.INLINE;
|
||||
attachment.encryption = EntityAttachment.PGP_CONTENT;
|
||||
attachment.id = db.attachment().insertAttachment(attachment);
|
||||
|
||||
// Restore plain text without headers
|
||||
ContentType ct = new ContentType(pgpContentType);
|
||||
if (!"multipart".equals(ct.getPrimaryType()))
|
||||
try (OutputStream out = new FileOutputStream(input)) {
|
||||
out.write(pgpContent.getBytes());
|
||||
}
|
||||
|
||||
File file = attachment.getFile(context);
|
||||
input.renameTo(file);
|
||||
|
||||
db.attachment().setDownloaded(attachment.id, file.length());
|
||||
|
||||
// send message
|
||||
return null;
|
||||
} else
|
||||
|
@ -1845,7 +1942,7 @@ public class FragmentCompose extends FragmentBase {
|
|||
}
|
||||
|
||||
private void onActionSend(EntityMessage draft) {
|
||||
if (draft.encrypt != null && draft.encrypt)
|
||||
if (draft.encrypt != null && draft.encrypt != 0)
|
||||
onEncrypt(draft);
|
||||
else
|
||||
onAction(R.id.action_send);
|
||||
|
@ -2135,7 +2232,7 @@ public class FragmentCompose extends FragmentBase {
|
|||
if (plain_only)
|
||||
data.draft.plain_only = true;
|
||||
if (encrypt_default)
|
||||
data.draft.encrypt = true;
|
||||
data.draft.encrypt = EntityMessage.ENCRYPTION_SIGNENCRYPT;
|
||||
if (receipt_default)
|
||||
data.draft.receipt_request = true;
|
||||
|
||||
|
@ -2371,8 +2468,8 @@ public class FragmentCompose extends FragmentBase {
|
|||
|
||||
if (ref.plain_only != null && ref.plain_only)
|
||||
data.draft.plain_only = true;
|
||||
if (ref.encrypt != null && ref.encrypt)
|
||||
data.draft.encrypt = true;
|
||||
if (ref.encrypt != null && ref.encrypt != 0)
|
||||
data.draft.encrypt = ref.encrypt;
|
||||
|
||||
if (answer > 0) {
|
||||
EntityAnswer a = db.answer().getAnswer(answer);
|
||||
|
@ -2558,7 +2655,7 @@ public class FragmentCompose extends FragmentBase {
|
|||
}
|
||||
}
|
||||
|
||||
if (data.draft.encrypt == null || !data.draft.encrypt)
|
||||
if (data.draft.encrypt == null || data.draft.encrypt == 0)
|
||||
EntityOperation.queue(context, data.draft, EntityOperation.ADD);
|
||||
} else {
|
||||
if (data.draft.revision == null) {
|
||||
|
@ -2627,7 +2724,7 @@ public class FragmentCompose extends FragmentBase {
|
|||
Log.i("Loaded draft id=" + data.draft.id + " action=" + action);
|
||||
|
||||
working = data.draft.id;
|
||||
encrypt = (data.draft.encrypt != null && data.draft.encrypt);
|
||||
encrypt = data.draft.encrypt;
|
||||
getActivity().invalidateOptionsMenu();
|
||||
|
||||
// Show identities
|
||||
|
@ -2716,7 +2813,7 @@ public class FragmentCompose extends FragmentBase {
|
|||
if (draft == null || draft.ui_hide)
|
||||
finish();
|
||||
else {
|
||||
encrypt = (draft.encrypt != null && draft.encrypt);
|
||||
encrypt = draft.encrypt;
|
||||
getActivity().invalidateOptionsMenu();
|
||||
|
||||
Log.i("Draft content=" + draft.content);
|
||||
|
@ -2864,7 +2961,7 @@ public class FragmentCompose extends FragmentBase {
|
|||
draft.ui_hide = ui_hide;
|
||||
db.message().updateMessage(draft);
|
||||
|
||||
if (draft.content && (draft.encrypt == null || !draft.encrypt))
|
||||
if (draft.content && (draft.encrypt == null || draft.encrypt == 0))
|
||||
EntityOperation.queue(context, draft, EntityOperation.ADD);
|
||||
}
|
||||
|
||||
|
@ -3040,7 +3137,7 @@ public class FragmentCompose extends FragmentBase {
|
|||
action == R.id.action_redo ||
|
||||
action == R.id.action_check) {
|
||||
if (BuildConfig.DEBUG || dirty)
|
||||
if (draft.encrypt == null || !draft.encrypt)
|
||||
if (draft.encrypt == null || draft.encrypt == 0)
|
||||
EntityOperation.queue(context, draft, EntityOperation.ADD);
|
||||
|
||||
if (action == R.id.action_check) {
|
||||
|
@ -3602,6 +3699,7 @@ public class FragmentCompose extends FragmentBase {
|
|||
int send_delayed = prefs.getInt("send_delayed", 0);
|
||||
boolean send_dialog = prefs.getBoolean("send_dialog", true);
|
||||
|
||||
final int[] encryptValues = getResources().getIntArray(R.array.encryptValues);
|
||||
final int[] sendDelayedValues = getResources().getIntArray(R.array.sendDelayedValues);
|
||||
final String[] sendDelayedNames = getResources().getStringArray(R.array.sendDelayedNames);
|
||||
|
||||
|
@ -3612,9 +3710,9 @@ public class FragmentCompose extends FragmentBase {
|
|||
final TextView tvTo = dview.findViewById(R.id.tvTo);
|
||||
final TextView tvVia = dview.findViewById(R.id.tvVia);
|
||||
final CheckBox cbPlainOnly = dview.findViewById(R.id.cbPlainOnly);
|
||||
final CheckBox cbEncrypt = dview.findViewById(R.id.cbEncrypt);
|
||||
final CheckBox cbReceipt = dview.findViewById(R.id.cbReceipt);
|
||||
final TextView tvReceipt = dview.findViewById(R.id.tvReceipt);
|
||||
final Spinner spEncrypt = dview.findViewById(R.id.spEncrypt);
|
||||
final Spinner spPriority = dview.findViewById(R.id.spPriority);
|
||||
final TextView tvSendAt = dview.findViewById(R.id.tvSendAt);
|
||||
final ImageButton ibSendAt = dview.findViewById(R.id.ibSendAt);
|
||||
|
@ -3627,6 +3725,8 @@ public class FragmentCompose extends FragmentBase {
|
|||
tvTo.setText(null);
|
||||
tvVia.setText(null);
|
||||
tvReceipt.setVisibility(View.GONE);
|
||||
spEncrypt.setTag(0);
|
||||
spEncrypt.setSelection(0);
|
||||
spPriority.setTag(1);
|
||||
spPriority.setSelection(1);
|
||||
tvSendAt.setText(null);
|
||||
|
@ -3671,33 +3771,6 @@ public class FragmentCompose extends FragmentBase {
|
|||
}
|
||||
});
|
||||
|
||||
cbEncrypt.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
args.putBoolean("encrypt", checked);
|
||||
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
protected Void onExecute(Context context, Bundle args) {
|
||||
long id = args.getLong("id");
|
||||
boolean encrypt = args.getBoolean("encrypt");
|
||||
|
||||
DB db = DB.getInstance(context);
|
||||
db.message().setMessageEncrypt(id, encrypt);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
Helper.unexpectedError(getParentFragmentManager(), ex);
|
||||
}
|
||||
}.execute(FragmentDialogSend.this, args, "compose:encrypt");
|
||||
}
|
||||
});
|
||||
|
||||
cbReceipt.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
|
||||
|
@ -3727,6 +3800,47 @@ public class FragmentCompose extends FragmentBase {
|
|||
}
|
||||
});
|
||||
|
||||
spEncrypt.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
int last = (int) spEncrypt.getTag();
|
||||
if (last != position) {
|
||||
spEncrypt.setTag(position);
|
||||
setEncrypt(encryptValues[position]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
spEncrypt.setTag(0);
|
||||
setEncrypt(encryptValues[0]);
|
||||
}
|
||||
|
||||
private void setEncrypt(int encrypt) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", id);
|
||||
args.putInt("encrypt", encrypt);
|
||||
|
||||
new SimpleTask<Void>() {
|
||||
@Override
|
||||
protected Void onExecute(Context context, Bundle args) {
|
||||
long id = args.getLong("id");
|
||||
int encrypt = args.getInt("encrypt");
|
||||
|
||||
DB db = DB.getInstance(context);
|
||||
db.message().setMessageEncrypt(id, encrypt);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
Helper.unexpectedError(getParentFragmentManager(), ex);
|
||||
}
|
||||
}.execute(FragmentDialogSend.this, args, "compose:encrypt");
|
||||
}
|
||||
});
|
||||
|
||||
spPriority.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
|
@ -3797,13 +3911,20 @@ public class FragmentCompose extends FragmentBase {
|
|||
tvVia.setText(draft.identityEmail);
|
||||
|
||||
cbPlainOnly.setChecked(draft.plain_only != null && draft.plain_only);
|
||||
cbEncrypt.setChecked(draft.encrypt != null && draft.encrypt);
|
||||
cbReceipt.setChecked(draft.receipt_request != null && draft.receipt_request);
|
||||
|
||||
cbPlainOnly.setVisibility(draft.receipt != null && draft.receipt ? View.GONE : View.VISIBLE);
|
||||
cbEncrypt.setVisibility(draft.receipt != null && draft.receipt ? View.GONE : View.VISIBLE);
|
||||
cbReceipt.setVisibility(draft.receipt != null && draft.receipt ? View.GONE : View.VISIBLE);
|
||||
|
||||
int encrypt = (draft.encrypt == null ? EntityMessage.ENCRYPTION_NONE : draft.encrypt);
|
||||
for (int i = 0; i < encryptValues.length; i++)
|
||||
if (encryptValues[i] == encrypt) {
|
||||
spEncrypt.setTag(i);
|
||||
spEncrypt.setSelection(i);
|
||||
break;
|
||||
}
|
||||
spEncrypt.setVisibility(draft.receipt != null && draft.receipt ? View.GONE : View.VISIBLE);
|
||||
|
||||
int priority = (draft.priority == null ? 1 : draft.priority);
|
||||
spPriority.setTag(priority);
|
||||
spPriority.setSelection(priority);
|
||||
|
|
|
@ -4116,7 +4116,8 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
|
|||
|
||||
// Find encrypted data
|
||||
for (EntityAttachment attachment : attachments)
|
||||
if (EntityAttachment.PGP_MESSAGE.equals(attachment.encryption)) {
|
||||
if (EntityAttachment.PGP_MESSAGE.equals(attachment.encryption) ||
|
||||
EntityAttachment.PGP_CONTENT.equals(attachment.encryption)) {
|
||||
if (!attachment.available)
|
||||
if (auto)
|
||||
return null;
|
||||
|
@ -4126,6 +4127,13 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
|
|||
File file = attachment.getFile(context);
|
||||
in = new FileInputStream(file);
|
||||
break;
|
||||
} else if (EntityAttachment.PGP_SIGNATURE.equals(attachment.encryption)) {
|
||||
File file = attachment.getFile(context);
|
||||
byte[] signature = new byte[(int) file.length()];
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
fis.read(signature);
|
||||
}
|
||||
data.putExtra(OpenPgpApi.ACTION_DETACHED_SIGN, signature);
|
||||
}
|
||||
|
||||
if (in == null) {
|
||||
|
|
|
@ -223,7 +223,7 @@ public class MessageHelper {
|
|||
|
||||
if (message.from != null && message.from.length > 0)
|
||||
for (EntityAttachment attachment : attachments)
|
||||
if (attachment.available && EntityAttachment.PGP_KEY.equals(attachment.encryption)) {
|
||||
if (EntityAttachment.PGP_KEY.equals(attachment.encryption)) {
|
||||
InternetAddress from = (InternetAddress) message.from[0];
|
||||
File file = attachment.getFile(context);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
@ -240,21 +240,73 @@ public class MessageHelper {
|
|||
" keydata=" + sb.toString());
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc3156
|
||||
for (final EntityAttachment attachment : attachments)
|
||||
if (attachment.available && EntityAttachment.PGP_MESSAGE.equals(attachment.encryption)) {
|
||||
// https://tools.ietf.org/html/rfc3156
|
||||
Multipart multipart = new MimeMultipart("encrypted; protocol=\"application/pgp-encrypted\"");
|
||||
if (EntityAttachment.PGP_SIGNATURE.equals(attachment.encryption)) {
|
||||
Log.i("Sending signed message");
|
||||
|
||||
BodyPart pgp = new MimeBodyPart();
|
||||
pgp.setContent("", "application/pgp-encrypted");
|
||||
multipart.addBodyPart(pgp);
|
||||
for (final EntityAttachment content : attachments)
|
||||
if (EntityAttachment.PGP_CONTENT.equals(content.encryption)) {
|
||||
final ContentType ct = new ContentType(content.type);
|
||||
final ContentType cts = new ContentType(attachment.type);
|
||||
|
||||
BodyPart bpAttachment = new MimeBodyPart();
|
||||
bpAttachment.setFileName(attachment.name);
|
||||
// Build content
|
||||
FileDataSource dsContent = new FileDataSource(content.getFile(context));
|
||||
dsContent.setFileTypeMap(new FileTypeMap() {
|
||||
@Override
|
||||
public String getContentType(File file) {
|
||||
return ct.toString();
|
||||
}
|
||||
|
||||
File file = attachment.getFile(context);
|
||||
FileDataSource dataSource = new FileDataSource(file);
|
||||
dataSource.setFileTypeMap(new FileTypeMap() {
|
||||
@Override
|
||||
public String getContentType(String filename) {
|
||||
return ct.toString();
|
||||
}
|
||||
});
|
||||
BodyPart bpContent = new MimeBodyPart();
|
||||
bpContent.setDataHandler(new DataHandler(dsContent));
|
||||
|
||||
// Build signature
|
||||
BodyPart bpSignature = new MimeBodyPart();
|
||||
bpSignature.setFileName(attachment.name);
|
||||
FileDataSource dsSignature = new FileDataSource(attachment.getFile(context));
|
||||
dsSignature.setFileTypeMap(new FileTypeMap() {
|
||||
@Override
|
||||
public String getContentType(File file) {
|
||||
return cts.getBaseType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType(String filename) {
|
||||
return cts.getBaseType();
|
||||
}
|
||||
});
|
||||
bpSignature.setDataHandler(new DataHandler(dsSignature));
|
||||
bpSignature.setDisposition(Part.INLINE);
|
||||
|
||||
// Build message
|
||||
Multipart multipart = new MimeMultipart("signed;" +
|
||||
" micalg=\"" + cts.getParameter("micalg") + "\";" +
|
||||
" protocol=\"application/pgp-signature\"");
|
||||
multipart.addBodyPart(bpContent);
|
||||
multipart.addBodyPart(bpSignature);
|
||||
imessage.setContent(multipart);
|
||||
|
||||
return imessage;
|
||||
}
|
||||
throw new IllegalStateException("Content not found");
|
||||
} else if (EntityAttachment.PGP_MESSAGE.equals(attachment.encryption)) {
|
||||
Log.i("Sending encrypted message");
|
||||
|
||||
// Build header
|
||||
BodyPart bpHeader = new MimeBodyPart();
|
||||
bpHeader.setContent("Version: 1\r\n", "application/pgp-encrypted");
|
||||
|
||||
// Build content
|
||||
BodyPart bpContent = new MimeBodyPart();
|
||||
bpContent.setFileName(attachment.name);
|
||||
FileDataSource dsContent = new FileDataSource(attachment.getFile(context));
|
||||
dsContent.setFileTypeMap(new FileTypeMap() {
|
||||
@Override
|
||||
public String getContentType(File file) {
|
||||
return attachment.type;
|
||||
|
@ -265,11 +317,13 @@ public class MessageHelper {
|
|||
return attachment.type;
|
||||
}
|
||||
});
|
||||
bpAttachment.setDataHandler(new DataHandler(dataSource));
|
||||
bpAttachment.setDisposition(Part.INLINE);
|
||||
|
||||
multipart.addBodyPart(bpAttachment);
|
||||
bpContent.setDataHandler(new DataHandler(dsContent));
|
||||
bpContent.setDisposition(Part.INLINE);
|
||||
|
||||
// Build message
|
||||
Multipart multipart = new MimeMultipart("encrypted; protocol=\"application/pgp-encrypted\"");
|
||||
multipart.addBodyPart(bpHeader);
|
||||
multipart.addBodyPart(bpContent);
|
||||
imessage.setContent(multipart);
|
||||
|
||||
return imessage;
|
||||
|
@ -1112,44 +1166,51 @@ public class MessageHelper {
|
|||
// Download attachment
|
||||
File file = EntityAttachment.getFile(context, local.id, local.name);
|
||||
db.attachment().setProgress(local.id, null);
|
||||
try (InputStream is = apart.part.getInputStream()) {
|
||||
long size = 0;
|
||||
long total = apart.part.getSize();
|
||||
int lastprogress = 0;
|
||||
|
||||
if (EntityAttachment.PGP_CONTENT.equals(apart.encrypt)) {
|
||||
try (OutputStream os = new FileOutputStream(file)) {
|
||||
byte[] buffer = new byte[Helper.BUFFER_SIZE];
|
||||
for (int len = is.read(buffer); len != -1; len = is.read(buffer)) {
|
||||
size += len;
|
||||
os.write(buffer, 0, len);
|
||||
apart.part.writeTo(os);
|
||||
}
|
||||
db.attachment().setDownloaded(local.id, file.length());
|
||||
} else
|
||||
try (InputStream is = apart.part.getInputStream()) {
|
||||
long size = 0;
|
||||
long total = apart.part.getSize();
|
||||
int lastprogress = 0;
|
||||
|
||||
// Update progress
|
||||
if (total > 0) {
|
||||
int progress = (int) (size * 100 / total / 20 * 20);
|
||||
if (progress != lastprogress) {
|
||||
lastprogress = progress;
|
||||
db.attachment().setProgress(local.id, progress);
|
||||
try (OutputStream os = new FileOutputStream(file)) {
|
||||
byte[] buffer = new byte[Helper.BUFFER_SIZE];
|
||||
for (int len = is.read(buffer); len != -1; len = is.read(buffer)) {
|
||||
size += len;
|
||||
os.write(buffer, 0, len);
|
||||
|
||||
// Update progress
|
||||
if (total > 0) {
|
||||
int progress = (int) (size * 100 / total / 20 * 20);
|
||||
if (progress != lastprogress) {
|
||||
lastprogress = progress;
|
||||
db.attachment().setProgress(local.id, progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store attachment data
|
||||
db.attachment().setDownloaded(local.id, size);
|
||||
|
||||
Log.i("Downloaded attachment size=" + size);
|
||||
} catch (FolderClosedIOException ex) {
|
||||
db.attachment().setError(local.id, Helper.formatThrowable(ex));
|
||||
throw new FolderClosedException(ex.getFolder(), "downloadAttachment", ex);
|
||||
} catch (MessageRemovedIOException ex) {
|
||||
db.attachment().setError(local.id, Helper.formatThrowable(ex));
|
||||
throw new MessagingException("downloadAttachment", ex);
|
||||
} catch (Throwable ex) {
|
||||
// Reset progress on failure
|
||||
Log.e(ex);
|
||||
db.attachment().setError(local.id, Helper.formatThrowable(ex));
|
||||
throw ex;
|
||||
}
|
||||
|
||||
// Store attachment data
|
||||
db.attachment().setDownloaded(local.id, size);
|
||||
|
||||
Log.i("Downloaded attachment size=" + size);
|
||||
} catch (FolderClosedIOException ex) {
|
||||
db.attachment().setError(local.id, Helper.formatThrowable(ex));
|
||||
throw new FolderClosedException(ex.getFolder(), "downloadAttachment", ex);
|
||||
} catch (MessageRemovedIOException ex) {
|
||||
db.attachment().setError(local.id, Helper.formatThrowable(ex));
|
||||
throw new MessagingException("downloadAttachment", ex);
|
||||
} catch (Throwable ex) {
|
||||
// Reset progress on failure
|
||||
Log.e(ex);
|
||||
db.attachment().setError(local.id, Helper.formatThrowable(ex));
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
String getWarnings(String existing) {
|
||||
|
@ -1165,18 +1226,56 @@ public class MessageHelper {
|
|||
class AttachmentPart {
|
||||
String disposition;
|
||||
String filename;
|
||||
boolean pgp;
|
||||
Integer encrypt;
|
||||
Part part;
|
||||
EntityAttachment attachment;
|
||||
}
|
||||
|
||||
MessageParts getMessageParts() throws IOException, MessagingException {
|
||||
MessageParts parts = new MessageParts();
|
||||
getMessageParts(imessage, parts, false);
|
||||
|
||||
try {
|
||||
if (imessage.isMimeType("multipart/signed")) {
|
||||
Multipart multipart = (Multipart) imessage.getContent();
|
||||
if (multipart.getCount() == 2) {
|
||||
getMessageParts(multipart.getBodyPart(0), parts, null);
|
||||
getMessageParts(multipart.getBodyPart(1), parts, EntityAttachment.PGP_SIGNATURE);
|
||||
|
||||
AttachmentPart apart = new AttachmentPart();
|
||||
apart.disposition = Part.INLINE;
|
||||
apart.filename = "content.txt";
|
||||
apart.encrypt = EntityAttachment.PGP_CONTENT;
|
||||
apart.part = multipart.getBodyPart(0);
|
||||
|
||||
ContentType ct = new ContentType(apart.part.getContentType());
|
||||
|
||||
apart.attachment = new EntityAttachment();
|
||||
apart.attachment.disposition = apart.disposition;
|
||||
apart.attachment.name = apart.filename;
|
||||
apart.attachment.type = ct.getBaseType().toLowerCase(Locale.ROOT);
|
||||
apart.attachment.encryption = apart.encrypt;
|
||||
|
||||
parts.attachments.add(apart);
|
||||
|
||||
return parts;
|
||||
}
|
||||
} else if (imessage.isMimeType("multipart/encrypted")) {
|
||||
Multipart multipart = (Multipart) imessage.getContent();
|
||||
if (multipart.getCount() == 2) {
|
||||
// Ignore header
|
||||
getMessageParts(multipart.getBodyPart(1), parts, EntityAttachment.PGP_MESSAGE);
|
||||
return parts;
|
||||
}
|
||||
}
|
||||
} catch (ParseException ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
|
||||
getMessageParts(imessage, parts, null);
|
||||
return parts;
|
||||
}
|
||||
|
||||
private void getMessageParts(Part part, MessageParts parts, boolean pgp) throws IOException, MessagingException {
|
||||
private void getMessageParts(Part part, MessageParts parts, Integer encrypt) throws IOException, MessagingException {
|
||||
try {
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.i("Part class=" + part.getClass() + " type=" + part.getContentType());
|
||||
|
@ -1194,19 +1293,7 @@ public class MessageHelper {
|
|||
|
||||
for (int i = 0; i < multipart.getCount(); i++)
|
||||
try {
|
||||
Part cpart = multipart.getBodyPart(i);
|
||||
|
||||
try {
|
||||
ContentType ct = new ContentType(cpart.getContentType());
|
||||
if ("application/pgp-encrypted".equals(ct.getBaseType().toLowerCase(Locale.ROOT))) {
|
||||
pgp = true;
|
||||
continue;
|
||||
}
|
||||
} catch (ParseException ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
|
||||
getMessageParts(cpart, parts, pgp);
|
||||
getMessageParts(multipart.getBodyPart(i), parts, encrypt);
|
||||
} catch (ParseException ex) {
|
||||
// Nested body: try to continue
|
||||
// ParseException: In parameter list boundary="...">, expected parameter name, got ";"
|
||||
|
@ -1263,7 +1350,7 @@ public class MessageHelper {
|
|||
AttachmentPart apart = new AttachmentPart();
|
||||
apart.disposition = disposition;
|
||||
apart.filename = filename;
|
||||
apart.pgp = pgp;
|
||||
apart.encrypt = encrypt;
|
||||
apart.part = part;
|
||||
|
||||
String[] cid = null;
|
||||
|
@ -1276,12 +1363,12 @@ public class MessageHelper {
|
|||
}
|
||||
|
||||
apart.attachment = new EntityAttachment();
|
||||
apart.attachment.disposition = apart.disposition;
|
||||
apart.attachment.name = apart.filename;
|
||||
apart.attachment.type = contentType.getBaseType().toLowerCase(Locale.ROOT);
|
||||
apart.attachment.disposition = apart.disposition;
|
||||
apart.attachment.size = (long) apart.part.getSize();
|
||||
apart.attachment.cid = (cid == null || cid.length == 0 ? null : MimeUtility.unfold(cid[0]));
|
||||
apart.attachment.encryption = (apart.pgp ? EntityAttachment.PGP_MESSAGE : null);
|
||||
apart.attachment.encryption = apart.encrypt;
|
||||
|
||||
if ("text/calendar".equalsIgnoreCase(apart.attachment.type) &&
|
||||
TextUtils.isEmpty(apart.attachment.name))
|
||||
|
|
|
@ -45,6 +45,7 @@ public class TupleMessageEx extends EntityMessage {
|
|||
public int unseen;
|
||||
public int unflagged;
|
||||
public int drafts;
|
||||
public int signed;
|
||||
public int encrypted;
|
||||
public int visible;
|
||||
public Long totalSize;
|
||||
|
@ -79,6 +80,7 @@ public class TupleMessageEx extends EntityMessage {
|
|||
this.unseen == other.unseen &&
|
||||
this.unflagged == other.unflagged &&
|
||||
this.drafts == other.drafts &&
|
||||
this.signed == other.signed &&
|
||||
this.encrypted == other.encrypted &&
|
||||
this.visible == other.visible &&
|
||||
Objects.equals(this.totalSize, other.totalSize) &&
|
||||
|
|
10
app/src/main/res/drawable/baseline_security_24.xml
Normal file
10
app/src/main/res/drawable/baseline_security_24.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12L21,5l-9,-4zM12,11.99h7c-0.53,4.12 -3.28,7.79 -7,8.94L12,12L5,12L5,6.3l7,-3.11v8.8z"/>
|
||||
</vector>
|
|
@ -111,16 +111,6 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvVia" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbEncrypt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_encrypt"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbPlainOnly" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/cbReceipt"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -129,7 +119,7 @@
|
|||
android:text="@string/title_send_receipt"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbEncrypt" />
|
||||
app:layout_constraintTop_toBottomOf="@id/cbPlainOnly" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvReceipt"
|
||||
|
@ -141,6 +131,26 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/cbReceipt" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvEncrypt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/title_send_encryption"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvReceipt" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spEncrypt"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:entries="@array/encryptNames"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvEncrypt" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPriority"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -149,7 +159,7 @@
|
|||
android:text="@string/title_send_priority"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvReceipt" />
|
||||
app:layout_constraintTop_toBottomOf="@id/spEncrypt" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spPriority"
|
||||
|
|
|
@ -148,13 +148,35 @@
|
|||
app:layout_constraintTop_toTopOf="@id/ivLowPriority" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivEncrypted"
|
||||
android:id="@+id/ivSigned"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/title_legend_priority"
|
||||
android:padding="12dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/ivLowPriority"
|
||||
app:srcCompat="@drawable/baseline_security_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSigned"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/title_legend_signed"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
|
||||
app:layout_constraintBottom_toBottomOf="@id/ivSigned"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/ivSigned"
|
||||
app:layout_constraintTop_toTopOf="@id/ivSigned" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivEncrypted"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/title_legend_priority"
|
||||
android:padding="12dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/ivSigned"
|
||||
app:srcCompat="@drawable/baseline_lock_24" />
|
||||
|
||||
<TextView
|
||||
|
|
|
@ -94,14 +94,25 @@
|
|||
app:layout_constraintTop_toTopOf="@+id/tvFrom"
|
||||
app:srcCompat="@drawable/baseline_low_priority_24" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivSigned"
|
||||
android:layout_width="21dp"
|
||||
android:layout_height="21dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:contentDescription="@string/title_legend_signed"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/tvFrom"
|
||||
app:layout_constraintStart_toEndOf="@id/ivPriorityLow"
|
||||
app:layout_constraintTop_toTopOf="@+id/tvFrom"
|
||||
app:srcCompat="@drawable/baseline_security_24" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivEncrypted"
|
||||
android:layout_width="21dp"
|
||||
android:layout_height="21dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:contentDescription="@string/title_legend_priority_low"
|
||||
android:contentDescription="@string/title_legend_encrypted"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/tvFrom"
|
||||
app:layout_constraintStart_toEndOf="@id/ivPriorityLow"
|
||||
app:layout_constraintStart_toEndOf="@id/ivSigned"
|
||||
app:layout_constraintTop_toTopOf="@+id/tvFrom"
|
||||
app:srcCompat="@drawable/baseline_lock_24" />
|
||||
|
||||
|
|
|
@ -46,6 +46,22 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/ibFull" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/ibVerify"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="@null"
|
||||
android:contentDescription="@string/title_sign"
|
||||
android:foregroundTint="?android:attr/textColorSecondary"
|
||||
android:padding="3dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:tooltipText="@string/title_sign"
|
||||
app:layout_constraintEnd_toStartOf="@+id/ibDecrypt"
|
||||
app:layout_constraintTop_toBottomOf="@id/vSeparatorBottom"
|
||||
app:srcCompat="@drawable/baseline_security_24" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/ibDecrypt"
|
||||
android:layout_width="36dp"
|
||||
|
|
|
@ -93,14 +93,25 @@
|
|||
app:layout_constraintTop_toTopOf="@+id/tvFrom"
|
||||
app:srcCompat="@drawable/baseline_low_priority_24" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivSigned"
|
||||
android:layout_width="21dp"
|
||||
android:layout_height="21dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:contentDescription="@string/title_legend_signed"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/tvFrom"
|
||||
app:layout_constraintStart_toEndOf="@id/ivPriorityLow"
|
||||
app:layout_constraintTop_toTopOf="@+id/tvFrom"
|
||||
app:srcCompat="@drawable/baseline_security_24" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivEncrypted"
|
||||
android:layout_width="21dp"
|
||||
android:layout_height="21dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:contentDescription="@string/title_legend_priority_low"
|
||||
android:contentDescription="@string/title_legend_encrypted"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/tvFrom"
|
||||
app:layout_constraintStart_toEndOf="@id/ivPriorityLow"
|
||||
app:layout_constraintStart_toEndOf="@id/ivSigned"
|
||||
app:layout_constraintTop_toTopOf="@+id/tvFrom"
|
||||
app:srcCompat="@drawable/baseline_lock_24" />
|
||||
|
||||
|
|
|
@ -648,6 +648,7 @@
|
|||
<string name="title_send_via">Send via</string>
|
||||
<string name="title_send_with_options">Send …</string>
|
||||
<string name="title_send_at">Send at …</string>
|
||||
<string name="title_send_encryption">Encryption</string>
|
||||
<string name="title_send_priority">Priority</string>
|
||||
<string name="title_no_server">No server found at \'%1$s\'</string>
|
||||
|
||||
|
@ -688,6 +689,7 @@
|
|||
<string name="title_queued">Sending message</string>
|
||||
<string name="title_queued_at">Message will be sent around %1$s</string>
|
||||
|
||||
<string name="title_sign">Sign</string>
|
||||
<string name="title_encrypt">Encrypt</string>
|
||||
<string name="title_decrypt">Decrypt</string>
|
||||
<string name="title_resync">Resync</string>
|
||||
|
@ -828,6 +830,7 @@
|
|||
<string name="title_legend_draft">Has draft</string>
|
||||
<string name="title_legend_priority">Has high priority</string>
|
||||
<string name="title_legend_priority_low">Has low priority</string>
|
||||
<string name="title_legend_signed">Is signed</string>
|
||||
<string name="title_legend_encrypted">Is encrypted</string>
|
||||
<string name="title_legend_auth">Authentication failed</string>
|
||||
<string name="title_legend_snoozed">Is snoozed</string>
|
||||
|
@ -1148,6 +1151,18 @@
|
|||
<item>Large</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="encryptNames">
|
||||
<item>None</item>
|
||||
<item>PGP sign-only</item>
|
||||
<item>PGP sign+encrypt</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="encryptValues" translatable="false">
|
||||
<item>0</item>
|
||||
<item>2</item>
|
||||
<item>1</item>
|
||||
</integer-array>
|
||||
|
||||
<string name="fingerprint" translatable="false">17BA15C1AF55D925F98B99CEA4375D4CDF4C174B</string>
|
||||
<string name="public_key" translatable="false">MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtFbxEbzL8u5accPGgBw/XdyiSS5BBE6ZQ9ELpKyJ/OQN+kdYniCAOw3lsQ/GuJScy4Y2HobqbBgLL8GLHG+Yu2EHC9dLjA3v2Mc25vvnfn86BsrpQvz1poN2n+roTBdq09FWbtebJ8m0hDBVmtfRi7RhTKIL4No3kodLhksdnucKjcFheubebWKgpmvbmw7NwuELhaZmyhw8WTtnQ4rZPMhjY1JJZgzwNExXgD7zzg4pJPkuQlfkuRkkvBpHpi3C7VDnYjrBlLHngI4wv3wxQBVwJqlvAT9PmX8dOVnTsWWdJdLQBZVWphuqVY54kjBIovN+o8w03WjsV9QiOQq+XwIDAQAB</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue