diff --git a/app/src/main/java/eu/faircode/email/Core.java b/app/src/main/java/eu/faircode/email/Core.java
index 2ba574e287..4fd579c749 100644
--- a/app/src/main/java/eu/faircode/email/Core.java
+++ b/app/src/main/java/eu/faircode/email/Core.java
@@ -1802,7 +1802,7 @@ class Core {
}
}
- private static void onRule(Context context, JSONArray jargs, EntityMessage message) throws JSONException, IOException {
+ private static void onRule(Context context, JSONArray jargs, EntityMessage message) throws JSONException, IOException, AddressException {
// Download message body
DB db = DB.getInstance(context);
diff --git a/app/src/main/java/eu/faircode/email/EntityRule.java b/app/src/main/java/eu/faircode/email/EntityRule.java
index f52c941385..b42bf6cbf7 100644
--- a/app/src/main/java/eu/faircode/email/EntityRule.java
+++ b/app/src/main/java/eu/faircode/email/EntityRule.java
@@ -57,6 +57,7 @@ import javax.mail.Address;
import javax.mail.Header;
import javax.mail.Message;
import javax.mail.MessagingException;
+import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import static androidx.room.ForeignKey.CASCADE;
@@ -294,7 +295,7 @@ public class EntityRule {
return matched;
}
- boolean execute(Context context, EntityMessage message) throws JSONException, IOException {
+ boolean execute(Context context, EntityMessage message) throws JSONException, IOException, AddressException {
boolean executed = _execute(context, message);
if (id != null && executed) {
DB db = DB.getInstance(context);
@@ -303,7 +304,7 @@ public class EntityRule {
return executed;
}
- private boolean _execute(Context context, EntityMessage message) throws JSONException, IOException {
+ private boolean _execute(Context context, EntityMessage message) throws JSONException, IOException, AddressException {
JSONObject jaction = new JSONObject(action);
int type = jaction.getInt("type");
Log.i("Executing rule=" + type + ":" + name + " message=" + message.id);
@@ -408,18 +409,30 @@ public class EntityRule {
return true;
}
- private boolean onActionAnswer(Context context, EntityMessage message, JSONObject jargs) throws JSONException, IOException {
- if (!message.content) {
- EntityOperation.queue(context, message, EntityOperation.BODY);
- EntityOperation.queue(context, message, EntityOperation.RULE, this.id);
- return true;
- }
+ private boolean onActionAnswer(Context context, EntityMessage message, JSONObject jargs) throws JSONException, IOException, AddressException {
+ DB db = DB.getInstance(context);
long iid = jargs.getLong("identity");
long aid = jargs.getLong("answer");
- boolean cc = (jargs.has("cc") && jargs.getBoolean("cc"));
+ String to = jargs.optString("to");
+ boolean cc = jargs.optBoolean("cc");
+ boolean attachments = jargs.optBoolean("attachments");
- DB db = DB.getInstance(context);
+ if (!message.content)
+ EntityOperation.queue(context, message, EntityOperation.BODY);
+
+ boolean complete = true;
+ if (attachments)
+ for (EntityAttachment attachment : db.attachment().getAttachments(message.id))
+ if (!attachment.available) {
+ complete = false;
+ EntityOperation.queue(context, message, EntityOperation.ATTACHMENT, attachment.id);
+ }
+
+ if (!message.content || !complete) {
+ EntityOperation.queue(context, message, EntityOperation.RULE, this.id);
+ return true;
+ }
EntityIdentity identity = db.identity().getIdentity(iid);
if (identity == null)
@@ -448,15 +461,25 @@ public class EntityRule {
reply.folder = db.folder().getOutbox().id;
reply.identity = identity.id;
reply.msgid = EntityMessage.generateMessageId();
- reply.references = (message.references == null ? "" : message.references + " ") + message.msgid;
- reply.inreplyto = message.msgid;
- reply.thread = message.thread;
- reply.to = (message.reply == null || message.reply.length == 0 ? message.from : message.reply);
+
+ if (TextUtils.isEmpty(to)) {
+ reply.references = (message.references == null ? "" : message.references + " ") + message.msgid;
+ reply.inreplyto = message.msgid;
+ reply.thread = message.thread;
+ reply.to = (message.reply == null || message.reply.length == 0 ? message.from : message.reply);
+ } else {
+ reply.wasforwardedfrom = message.msgid;
+ reply.thread = reply.msgid; // new thread
+ reply.to = InternetAddress.parseHeader(to, false);
+ }
+
reply.from = from;
if (cc)
reply.cc = message.cc;
reply.unsubscribe = "mailto:" + identity.email;
- reply.subject = context.getString(R.string.title_subject_reply, message.subject == null ? "" : message.subject);
+ reply.subject = context.getString(
+ TextUtils.isEmpty(to) ? R.string.title_subject_reply : R.string.title_subject_forward,
+ message.subject == null ? "" : message.subject);
reply.received = new Date().getTime();
reply.sender = MessageHelper.getSortKey(reply.from);
@@ -468,6 +491,7 @@ public class EntityRule {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean extended_reply = prefs.getBoolean("extended_reply", false);
boolean quote_reply = prefs.getBoolean("quote_reply", true);
+ boolean quote = (quote_reply && TextUtils.isEmpty(to));
String body = answer.getText(message.from);
Document msg = JsoupEx.parse(body);
@@ -478,7 +502,7 @@ public class EntityRule {
div.appendChild(p);
Document answering = JsoupEx.parse(message.getFile(context));
- div.appendChild(answering.body().tagName(quote_reply ? "blockquote" : "p"));
+ div.appendChild(answering.body().tagName(quote ? "blockquote" : "p"));
msg.body().appendChild(div);
@@ -493,6 +517,9 @@ public class EntityRule {
HtmlHelper.getPreview(body),
null);
+ if (attachments)
+ EntityAttachment.copy(context, message.id, reply.id);
+
EntityOperation.queue(context, reply, EntityOperation.SEND);
// Batch send operations, wait until after commit
diff --git a/app/src/main/java/eu/faircode/email/FragmentRule.java b/app/src/main/java/eu/faircode/email/FragmentRule.java
index 50610f676e..f4b130a3a1 100644
--- a/app/src/main/java/eu/faircode/email/FragmentRule.java
+++ b/app/src/main/java/eu/faircode/email/FragmentRule.java
@@ -43,7 +43,7 @@ import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
-import android.widget.ImageView;
+import android.widget.ImageButton;
import android.widget.NumberPicker;
import android.widget.ScrollView;
import android.widget.Spinner;
@@ -88,12 +88,12 @@ public class FragmentRule extends FragmentBase {
private EditText etSender;
private CheckBox cbSender;
- private ImageView ibSender;
+ private ImageButton ibSender;
private CheckBox cbKnownSender;
private EditText etRecipient;
private CheckBox cbRecipient;
- private ImageView ibRecipient;
+ private ImageButton ibRecipient;
private EditText etSubject;
private CheckBox cbSubject;
@@ -129,7 +129,10 @@ public class FragmentRule extends FragmentBase {
private Spinner spIdent;
private Spinner spAnswer;
+ private EditText etTo;
+ private ImageButton ibTo;
private CheckBox cbCc;
+ private CheckBox cbWithAttachments;
private Button btnTtsSetup;
private Button btnTtsData;
@@ -170,8 +173,9 @@ public class FragmentRule extends FragmentBase {
private final static int REQUEST_DELETE = 4;
private final static int REQUEST_SCHEDULE_START = 5;
private final static int REQUEST_SCHEDULE_END = 6;
- private final static int REQUEST_TTS_CHECK = 7;
- private final static int REQUEST_TTS_DATA = 8;
+ private static final int REQUEST_TO = 7;
+ private final static int REQUEST_TTS_CHECK = 8;
+ private final static int REQUEST_TTS_DATA = 9;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -249,7 +253,10 @@ public class FragmentRule extends FragmentBase {
spIdent = view.findViewById(R.id.spIdent);
spAnswer = view.findViewById(R.id.spAnswer);
+ etTo = view.findViewById(R.id.etTo);
+ ibTo = view.findViewById(R.id.ibTo);
cbCc = view.findViewById(R.id.cbCc);
+ cbWithAttachments = view.findViewById(R.id.cbWithAttachments);
btnTtsSetup = view.findViewById(R.id.btnTtsSetup);
btnTtsData = view.findViewById(R.id.btnTtsData);
@@ -452,6 +459,14 @@ public class FragmentRule extends FragmentBase {
spIdent.setOnItemSelectedListener(onItemSelectedListener);
spAnswer.setOnItemSelectedListener(onItemSelectedListener);
+ ibTo.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent pick = new Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Email.CONTENT_URI);
+ startActivityForResult(Helper.getChooser(getContext(), pick), REQUEST_TO);
+ }
+ });
+
btnTtsSetup.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
@@ -623,11 +638,11 @@ public class FragmentRule extends FragmentBase {
switch (requestCode) {
case REQUEST_SENDER:
if (resultCode == RESULT_OK && data != null)
- onPickContact(data, true);
+ onPickContact(data, etSender);
break;
case REQUEST_RECIPIENT:
if (resultCode == RESULT_OK && data != null)
- onPickContact(data, true);
+ onPickContact(data, etRecipient);
break;
case REQUEST_COLOR:
if (resultCode == RESULT_OK && data != null) {
@@ -652,6 +667,10 @@ public class FragmentRule extends FragmentBase {
if (resultCode == RESULT_OK)
onScheduleEnd(data.getBundleExtra("args"));
break;
+ case REQUEST_TO:
+ if (resultCode == RESULT_OK && data != null)
+ onPickContact(data, etTo);
+ break;
case REQUEST_TTS_CHECK:
if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS)
ToastEx.makeText(getContext(), R.string.title_rule_tts_ok, Toast.LENGTH_LONG).show();
@@ -669,7 +688,7 @@ public class FragmentRule extends FragmentBase {
}
}
- private void onPickContact(Intent data, boolean sender) {
+ private void onPickContact(Intent data, final EditText et) {
Uri uri = data.getData();
if (uri == null) return;
try (Cursor cursor = getContext().getContentResolver().query(uri,
@@ -678,10 +697,7 @@ public class FragmentRule extends FragmentBase {
},
null, null, null)) {
if (cursor != null && cursor.moveToFirst())
- if (sender)
- etSender.setText(cursor.getString(0));
- else
- etRecipient.setText(cursor.getString(0));
+ et.setText(cursor.getString(0));
} catch (Throwable ex) {
Log.e(ex);
Log.unexpectedError(getParentFragmentManager(), ex);
@@ -871,7 +887,9 @@ public class FragmentRule extends FragmentBase {
break;
}
+ etTo.setText(jaction.optString("to"));
cbCc.setChecked(jaction.optBoolean("cc"));
+ cbWithAttachments.setChecked(jaction.optBoolean("attachments"));
break;
}
@@ -1173,7 +1191,9 @@ public class FragmentRule extends FragmentBase {
EntityAnswer answer = (EntityAnswer) spAnswer.getSelectedItem();
jaction.put("identity", identity == null ? -1 : identity.id);
jaction.put("answer", answer == null ? -1 : answer.id);
+ jaction.put("to", etTo.getText().toString().trim());
jaction.put("cc", cbCc.isChecked());
+ jaction.put("attachments", cbWithAttachments.isChecked());
break;
}
}
diff --git a/app/src/main/res/layout/fragment_rule.xml b/app/src/main/res/layout/fragment_rule.xml
index bc8f85101a..de31080f64 100644
--- a/app/src/main/res/layout/fragment_rule.xml
+++ b/app/src/main/res/layout/fragment_rule.xml
@@ -669,6 +669,40 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvAnswerTemplate" />
+
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@id/etTo" />
+
+
+ app:layout_constraintTop_toBottomOf="@id/cbWithAttachments" />
+ app:constraint_referenced_ids="tvAnswerIdentity,spIdent,tvAnswerTemplate,spAnswer,tvTo,etTo,ibTo,cbCc,cbWithAttachments,tvAnswerRemark" />
Add keyword
Move
Copy (label)
- Reply
+ Reply/forward
Text to speech
Automation
@@ -1131,7 +1131,9 @@
All messages in same conversation and folder
Identity
Reply template
+ Forward to
Reply to CC addresses
+ With attachments
Only one reply will be sent for any conversation, to avoid reply loops
Rule name missing
Condition missing