mirror of
https://github.com/M66B/FairEmail.git
synced 2026-04-14 21:13:37 +02:00
Added read receipts
This commit is contained in:
@@ -2828,16 +2828,20 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
||||
View anchor = bnvActions.findViewById(R.id.action_reply);
|
||||
PopupMenu popupMenu = new PopupMenu(context, anchor);
|
||||
popupMenu.inflate(R.menu.menu_reply);
|
||||
popupMenu.getMenu().findItem(R.id.menu_reply_receipt).setVisible(data.message.receipt_to != null);
|
||||
|
||||
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem target) {
|
||||
switch (target.getItemId()) {
|
||||
case R.id.menu_reply_to_sender:
|
||||
onMenuReply(data, false);
|
||||
onMenuReply(data, "reply");
|
||||
return true;
|
||||
case R.id.menu_reply_to_all:
|
||||
onMenuReply(data, true);
|
||||
onMenuReply(data, "reply_all");
|
||||
return true;
|
||||
case R.id.menu_reply_receipt:
|
||||
onMenuReply(data, "receipt");
|
||||
return true;
|
||||
case R.id.menu_reply_template:
|
||||
onMenuAnswer(data);
|
||||
@@ -2851,9 +2855,10 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
||||
|
||||
}
|
||||
|
||||
private void onMenuReply(final ActionData data, final boolean all) {
|
||||
private void onMenuReply(final ActionData data, String action) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", data.message.id);
|
||||
args.putString("action", action);
|
||||
|
||||
new SimpleTask<Boolean>() {
|
||||
@Override
|
||||
@@ -2869,7 +2874,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
||||
@Override
|
||||
protected void onExecuted(Bundle args, Boolean available) {
|
||||
final Intent reply = new Intent(context, ActivityCompose.class)
|
||||
.putExtra("action", all ? "reply_all" : "reply")
|
||||
.putExtra("action", args.getString("action"))
|
||||
.putExtra("reference", data.message.id);
|
||||
if (available)
|
||||
context.startActivity(reply);
|
||||
|
||||
@@ -1176,6 +1176,7 @@ class Core {
|
||||
message.deliveredto = helper.getDeliveredTo();
|
||||
message.thread = helper.getThreadId(context, account.id, uid);
|
||||
message.receipt_request = helper.getReceiptRequested();
|
||||
message.receipt_to = helper.getReceiptTo();
|
||||
message.dkim = MessageHelper.getAuthentication("dkim", authentication);
|
||||
message.spf = MessageHelper.getAuthentication("spf", authentication);
|
||||
message.dmarc = MessageHelper.getAuthentication("dmarc", authentication);
|
||||
|
||||
@@ -51,7 +51,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
|
||||
// https://developer.android.com/topic/libraries/architecture/room.html
|
||||
|
||||
@Database(
|
||||
version = 68,
|
||||
version = 69,
|
||||
entities = {
|
||||
EntityIdentity.class,
|
||||
EntityAccount.class,
|
||||
@@ -724,6 +724,13 @@ public abstract class DB extends RoomDatabase {
|
||||
db.execSQL("UPDATE message SET revisions = revision");
|
||||
}
|
||||
})
|
||||
.addMigrations(new Migration(68, 69) {
|
||||
@Override
|
||||
public void migrate(SupportSQLiteDatabase db) {
|
||||
Log.i("DB migration from version " + startVersion + " to " + endVersion);
|
||||
db.execSQL("ALTER TABLE `message` ADD COLUMN `receipt_to` TEXT");
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ public class EntityMessage implements Serializable {
|
||||
public String inreplyto;
|
||||
public String thread; // compose = null
|
||||
public Boolean receipt_request;
|
||||
public Address[] receipt_to;
|
||||
public Boolean dkim;
|
||||
public Boolean spf;
|
||||
public Boolean dmarc;
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
@@ -121,6 +122,7 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
|
||||
@@ -1707,7 +1709,7 @@ public class FragmentCompose extends FragmentBase {
|
||||
if (answer > 0)
|
||||
body = EntityAnswer.getAnswerText(db, answer, null) + body;
|
||||
} else {
|
||||
if ("reply".equals(action) || "reply_all".equals(action)) {
|
||||
if ("reply".equals(action) || "reply_all".equals(action) || "receipt".equals(action)) {
|
||||
if (ref.to != null && ref.to.length > 0) {
|
||||
String to = ((InternetAddress) ref.to[0]).getAddress();
|
||||
int at = to.indexOf('@');
|
||||
@@ -1719,23 +1721,28 @@ public class FragmentCompose extends FragmentBase {
|
||||
draft.inreplyto = ref.msgid;
|
||||
draft.thread = ref.thread;
|
||||
|
||||
// Prevent replying to self
|
||||
String to = null;
|
||||
String via = null;
|
||||
Address[] recipient = (ref.reply == null || ref.reply.length == 0 ? ref.from : ref.reply);
|
||||
if (recipient != null && recipient.length > 0)
|
||||
to = Helper.canonicalAddress(((InternetAddress) recipient[0]).getAddress());
|
||||
if (ref.identity != null) {
|
||||
EntityIdentity v = db.identity().getIdentity(ref.identity);
|
||||
via = Helper.canonicalAddress(v.email);
|
||||
}
|
||||
|
||||
if (to != null && to.equals(via)) {
|
||||
draft.to = ref.to;
|
||||
draft.from = ref.from;
|
||||
} else {
|
||||
draft.to = (ref.reply == null || ref.reply.length == 0 ? ref.from : ref.reply);
|
||||
if ("receipt".equals(action) && ref.receipt_to != null) {
|
||||
draft.to = ref.receipt_to;
|
||||
draft.from = ref.to;
|
||||
} else {
|
||||
// Prevent replying to self
|
||||
String to = null;
|
||||
String via = null;
|
||||
Address[] recipient = (ref.reply == null || ref.reply.length == 0 ? ref.from : ref.reply);
|
||||
if (recipient != null && recipient.length > 0)
|
||||
to = Helper.canonicalAddress(((InternetAddress) recipient[0]).getAddress());
|
||||
if (ref.identity != null) {
|
||||
EntityIdentity v = db.identity().getIdentity(ref.identity);
|
||||
via = Helper.canonicalAddress(v.email);
|
||||
}
|
||||
|
||||
if (to != null && to.equals(via)) {
|
||||
draft.to = ref.to;
|
||||
draft.from = ref.from;
|
||||
} else {
|
||||
draft.to = (ref.reply == null || ref.reply.length == 0 ? ref.from : ref.reply);
|
||||
draft.from = ref.to;
|
||||
}
|
||||
}
|
||||
|
||||
if ("reply_all".equals(action)) {
|
||||
@@ -1755,6 +1762,8 @@ public class FragmentCompose extends FragmentBase {
|
||||
}
|
||||
}
|
||||
draft.cc = addresses.toArray(new Address[0]);
|
||||
} else if ("receipt".equals(action)) {
|
||||
draft.receipt_request = true;
|
||||
}
|
||||
|
||||
} else if ("forward".equals(action)) {
|
||||
@@ -1769,6 +1778,15 @@ public class FragmentCompose extends FragmentBase {
|
||||
draft.subject = context.getString(R.string.title_subject_reply, subject);
|
||||
else
|
||||
draft.subject = ref.subject;
|
||||
} else if ("receipt".equals(action)) {
|
||||
draft.subject = context.getString(R.string.title_receipt_subject, subject);
|
||||
|
||||
Configuration configuration = new Configuration(context.getResources().getConfiguration());
|
||||
configuration.setLocale(new Locale("en"));
|
||||
Resources res = context.createConfigurationContext(configuration).getResources();
|
||||
|
||||
body = "<p>" + context.getString(R.string.title_receipt_text) + "</p>";
|
||||
body += "<p>" + res.getString(R.string.title_receipt_text) + "</p>";
|
||||
} else if ("forward".equals(action)) {
|
||||
String fwd = context.getString(R.string.title_subject_forward, "");
|
||||
if (!prefix_once || !subject.startsWith(fwd))
|
||||
@@ -1840,7 +1858,7 @@ public class FragmentCompose extends FragmentBase {
|
||||
Core.updateMessageSize(context, draft.id);
|
||||
|
||||
// Write reference text
|
||||
if (ref != null && ref.content) {
|
||||
if (ref != null && ref.content && !"receipt".equals(action)) {
|
||||
String refBody = String.format("<p>%s %s:</p>\n<blockquote>%s</blockquote>",
|
||||
Html.escapeHtml(new Date(ref.received).toString()),
|
||||
Html.escapeHtml(MessageHelper.formatAddresses(ref.from)),
|
||||
|
||||
@@ -189,6 +189,7 @@ public class MessageHelper {
|
||||
return props;
|
||||
}
|
||||
|
||||
|
||||
static MimeMessageEx from(Context context, EntityMessage message, EntityIdentity identity, Session isession)
|
||||
throws MessagingException, IOException {
|
||||
DB db = DB.getInstance(context);
|
||||
@@ -291,6 +292,30 @@ public class MessageHelper {
|
||||
static void build(Context context, EntityMessage message, EntityIdentity identity, MimeMessage imessage) throws IOException, MessagingException {
|
||||
DB db = DB.getInstance(context);
|
||||
|
||||
if (message.receipt_request != null && message.receipt_request) {
|
||||
// https://www.ietf.org/rfc/rfc3798.txt
|
||||
Multipart report = new MimeMultipart("report; report-type=disposition-notification");
|
||||
|
||||
String plainContent = HtmlHelper.getText(Helper.readText(message.getFile(context)));
|
||||
|
||||
BodyPart plainPart = new MimeBodyPart();
|
||||
plainPart.setContent(plainContent, "text/plain; charset=" + Charset.defaultCharset().name());
|
||||
report.addBodyPart(plainPart);
|
||||
|
||||
BodyPart dnsPart = new MimeBodyPart();
|
||||
dnsPart.setContent("", "message/disposition-notification; name=\"MDNPart2.txt\"");
|
||||
dnsPart.setDisposition(Part.INLINE);
|
||||
report.addBodyPart(dnsPart);
|
||||
|
||||
//BodyPart headersPart = new MimeBodyPart();
|
||||
//headersPart.setContent("", "text/rfc822-headers; name=\"MDNPart3.txt\"");
|
||||
//headersPart.setDisposition(Part.INLINE);
|
||||
//report.addBodyPart(headersPart);
|
||||
|
||||
imessage.setContent(report);
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder body = new StringBuilder();
|
||||
body.append(Helper.readText(message.getFile(context)));
|
||||
|
||||
@@ -458,6 +483,26 @@ public class MessageHelper {
|
||||
imessage.getHeader("Disposition-Notification-To") != null);
|
||||
}
|
||||
|
||||
Address[] getReceiptTo() throws MessagingException {
|
||||
String to = imessage.getHeader("Disposition-Notification-To", null);
|
||||
if (to == null)
|
||||
return null;
|
||||
|
||||
InternetAddress[] address = null;
|
||||
try {
|
||||
address = InternetAddress.parse(to);
|
||||
} catch (AddressException ex) {
|
||||
Log.w(ex);
|
||||
}
|
||||
|
||||
if (address == null || address.length == 0)
|
||||
return null;
|
||||
|
||||
fix(address[0]);
|
||||
|
||||
return new Address[]{address[0]};
|
||||
}
|
||||
|
||||
String getAuthentication() throws MessagingException {
|
||||
String header = imessage.getHeader("Authentication-Results", "");
|
||||
return (header == null ? null : header.replaceAll("\\r?\\n", ""));
|
||||
|
||||
@@ -302,13 +302,15 @@ public class ServiceSend extends LifecycleService {
|
||||
imessage.setRecipients(Message.RecipientType.BCC, bcc.toArray(new Address[0]));
|
||||
}
|
||||
|
||||
// defacto standard
|
||||
if (ident.delivery_receipt)
|
||||
imessage.addHeader("Return-Receipt-To", ident.replyto == null ? ident.email : ident.replyto);
|
||||
if (message.receipt_request == null || !message.receipt_request) {
|
||||
// defacto standard
|
||||
if (ident.delivery_receipt)
|
||||
imessage.addHeader("Return-Receipt-To", ident.replyto == null ? ident.email : ident.replyto);
|
||||
|
||||
// https://tools.ietf.org/html/rfc3798
|
||||
if (ident.read_receipt)
|
||||
imessage.addHeader("Disposition-Notification-To", ident.replyto == null ? ident.email : ident.replyto);
|
||||
// https://tools.ietf.org/html/rfc3798
|
||||
if (ident.read_receipt)
|
||||
imessage.addHeader("Disposition-Notification-To", ident.replyto == null ? ident.email : ident.replyto);
|
||||
}
|
||||
|
||||
// Create transport
|
||||
// TODO: cache transport?
|
||||
|
||||
Reference in New Issue
Block a user