Added read receipts

pull/155/head
M66B 5 years ago
parent b51247ef59
commit 06905797fa

File diff suppressed because it is too large Load Diff

@ -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);
// https://tools.ietf.org/html/rfc3798
if (ident.read_receipt)
imessage.addHeader("Disposition-Notification-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);
}
// Create transport
// TODO: cache transport?

@ -8,6 +8,10 @@
android:id="@+id/menu_reply_to_all"
android:title="@string/title_reply_to_all" />
<item
android:id="@+id/menu_reply_receipt"
android:title="@string/title_reply_receipt" />
<item
android:id="@+id/menu_reply_template"
android:title="@string/title_reply_template" />

@ -368,10 +368,14 @@
<string name="title_reply">Reply</string>
<string name="title_reply_to_sender">Sender</string>
<string name="title_reply_to_all">All</string>
<string name="title_reply_receipt">Read receipt</string>
<string name="title_reply_template">Template</string>
<string name="title_moving">Moving to %1$s</string>
<string name="title_open_with">Open with</string>
<string name="title_receipt_subject">Read receipt: %1$s</string>
<string name="title_receipt_text">This read receipt only acknowledges that the message was displayed. There is no guarantee that the recipient has read the message contents.</string>
<string name="title_no_answers">No reply templates defined</string>
<string name="title_no_viewer">No viewer app available for %1$s</string>
<string name="title_no_saf">Storage access framework not available</string>

Loading…
Cancel
Save