2019-02-27 15:05:15 +00:00
|
|
|
package eu.faircode.email;
|
|
|
|
|
2019-05-04 20:49:22 +00:00
|
|
|
/*
|
|
|
|
This file is part of FairEmail.
|
|
|
|
|
|
|
|
FairEmail is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
FairEmail is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
Copyright 2018-2019 by Marcel Bokhorst (M66B)
|
|
|
|
*/
|
|
|
|
|
2019-02-27 15:05:15 +00:00
|
|
|
import android.app.IntentService;
|
2019-05-21 13:55:10 +00:00
|
|
|
import android.app.NotificationManager;
|
|
|
|
import android.content.Context;
|
2019-02-27 15:05:15 +00:00
|
|
|
import android.content.Intent;
|
2019-05-06 08:33:29 +00:00
|
|
|
import android.content.SharedPreferences;
|
2019-08-24 13:00:14 +00:00
|
|
|
import android.os.Bundle;
|
|
|
|
import android.widget.Toast;
|
2019-02-27 15:05:15 +00:00
|
|
|
|
|
|
|
import androidx.annotation.Nullable;
|
2019-08-24 13:00:14 +00:00
|
|
|
import androidx.core.app.RemoteInput;
|
2019-05-06 08:33:29 +00:00
|
|
|
import androidx.preference.PreferenceManager;
|
|
|
|
|
2019-08-24 13:00:14 +00:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.util.Date;
|
2019-05-06 08:33:29 +00:00
|
|
|
import java.util.List;
|
2019-08-24 13:00:14 +00:00
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
|
|
import javax.mail.Address;
|
|
|
|
import javax.mail.internet.InternetAddress;
|
2019-02-27 15:05:15 +00:00
|
|
|
|
|
|
|
public class ServiceUI extends IntentService {
|
2019-03-14 14:57:24 +00:00
|
|
|
static final int PI_CLEAR = 1;
|
2019-05-05 20:28:07 +00:00
|
|
|
static final int PI_TRASH = 2;
|
2019-09-26 19:28:05 +00:00
|
|
|
static final int PI_JUNK = 3;
|
|
|
|
static final int PI_ARCHIVE = 4;
|
2019-10-23 10:51:20 +00:00
|
|
|
static final int PI_MOVE = 5;
|
|
|
|
static final int PI_REPLY_DIRECT = 6;
|
|
|
|
static final int PI_FLAG = 7;
|
|
|
|
static final int PI_SEEN = 8;
|
|
|
|
static final int PI_SNOOZE = 9;
|
|
|
|
static final int PI_IGNORED = 10;
|
2019-10-27 15:03:03 +00:00
|
|
|
static final int PI_THREAD = 11;
|
|
|
|
static final int PI_WAKEUP = 12;
|
2019-02-27 15:05:15 +00:00
|
|
|
|
|
|
|
public ServiceUI() {
|
|
|
|
this(ServiceUI.class.getName());
|
|
|
|
}
|
|
|
|
|
|
|
|
public ServiceUI(String name) {
|
|
|
|
super(name);
|
|
|
|
}
|
|
|
|
|
2019-02-27 16:48:03 +00:00
|
|
|
@Override
|
|
|
|
public void onCreate() {
|
|
|
|
Log.i("Service UI create");
|
|
|
|
super.onCreate();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onDestroy() {
|
|
|
|
Log.i("Service UI destroy");
|
|
|
|
super.onDestroy();
|
|
|
|
}
|
|
|
|
|
2019-02-27 15:05:15 +00:00
|
|
|
@Override
|
|
|
|
protected void onHandleIntent(@Nullable Intent intent) {
|
2019-03-02 08:16:27 +00:00
|
|
|
// Under certain circumstances, a background app is placed on a temporary whitelist for several minutes
|
|
|
|
// - Executing a PendingIntent from a notification.
|
|
|
|
// https://developer.android.com/about/versions/oreo/background#services
|
2019-02-27 16:48:03 +00:00
|
|
|
Log.i("Service UI intent=" + intent);
|
2019-07-25 08:43:07 +00:00
|
|
|
Log.logExtras(intent);
|
2019-02-27 16:48:03 +00:00
|
|
|
|
2019-02-27 15:05:15 +00:00
|
|
|
if (intent == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
String action = intent.getAction();
|
|
|
|
if (action == null)
|
|
|
|
return;
|
|
|
|
|
2019-08-24 13:00:14 +00:00
|
|
|
try {
|
|
|
|
String[] parts = action.split(":");
|
|
|
|
long id = (parts.length > 1 ? Long.parseLong(parts[1]) : -1);
|
|
|
|
String group = intent.getStringExtra("group");
|
|
|
|
|
|
|
|
switch (parts[0]) {
|
|
|
|
case "clear":
|
2019-09-01 09:32:18 +00:00
|
|
|
onClear(id);
|
2019-08-24 13:00:14 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case "trash":
|
|
|
|
cancel(group, id);
|
2019-09-26 19:28:05 +00:00
|
|
|
onMove(id, EntityFolder.TRASH);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "junk":
|
|
|
|
cancel(group, id);
|
|
|
|
onMove(id, EntityFolder.JUNK);
|
2019-08-24 13:00:14 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case "archive":
|
|
|
|
cancel(group, id);
|
2019-09-26 19:28:05 +00:00
|
|
|
onMove(id, EntityFolder.ARCHIVE);
|
2019-08-24 13:00:14 +00:00
|
|
|
break;
|
|
|
|
|
2019-10-23 10:51:20 +00:00
|
|
|
case "move":
|
|
|
|
cancel(group, id);
|
|
|
|
onMove(id);
|
|
|
|
break;
|
|
|
|
|
2019-08-24 13:00:14 +00:00
|
|
|
case "reply":
|
|
|
|
onReplyDirect(id, intent);
|
|
|
|
cancel(group, id);
|
|
|
|
onSeen(id);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "flag":
|
|
|
|
cancel(group, id);
|
|
|
|
onFlag(id);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "seen":
|
|
|
|
cancel(group, id);
|
|
|
|
onSeen(id);
|
|
|
|
break;
|
|
|
|
|
2019-09-24 07:34:43 +00:00
|
|
|
case "snooze":
|
|
|
|
cancel(group, id);
|
|
|
|
onSnooze(id);
|
|
|
|
break;
|
|
|
|
|
2019-08-24 13:00:14 +00:00
|
|
|
case "ignore":
|
2019-10-27 15:03:03 +00:00
|
|
|
boolean view = intent.getBooleanExtra("view", false);
|
|
|
|
onIgnore(id, view);
|
2019-08-24 13:00:14 +00:00
|
|
|
break;
|
|
|
|
|
2019-09-24 07:34:43 +00:00
|
|
|
case "wakeup":
|
2019-08-24 13:00:14 +00:00
|
|
|
// AlarmManager.RTC_WAKEUP
|
|
|
|
// When the alarm is dispatched, the app will also be added to the system's temporary whitelist
|
|
|
|
// for approximately 10 seconds to allow that application to acquire further wake locks in which to complete its work.
|
|
|
|
// https://developer.android.com/reference/android/app/AlarmManager
|
2019-09-24 07:34:43 +00:00
|
|
|
onWakeup(id);
|
2019-08-24 13:00:14 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new IllegalArgumentException("Unknown UI action: " + parts[0]);
|
|
|
|
}
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.e(ex);
|
2019-02-27 15:05:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-01 09:32:18 +00:00
|
|
|
private void onClear(long group) {
|
|
|
|
DB db = DB.getInstance(this);
|
2019-10-27 15:46:57 +00:00
|
|
|
int cleared = db.message().ignoreAll(group == 0 ? null : group, null);
|
2019-09-01 09:32:18 +00:00
|
|
|
Log.i("Cleared=" + cleared);
|
2019-02-27 15:05:15 +00:00
|
|
|
}
|
|
|
|
|
2019-05-21 13:55:10 +00:00
|
|
|
private void cancel(String group, long id) {
|
|
|
|
String tag = "unseen." + group + ":" + id;
|
|
|
|
|
|
|
|
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
|
|
|
nm.cancel(tag, 1);
|
|
|
|
}
|
|
|
|
|
2019-09-26 19:28:05 +00:00
|
|
|
private void onMove(long id, String folderType) {
|
2019-02-27 15:05:15 +00:00
|
|
|
DB db = DB.getInstance(this);
|
|
|
|
try {
|
|
|
|
db.beginTransaction();
|
|
|
|
|
|
|
|
EntityMessage message = db.message().getMessage(id);
|
2019-09-24 07:34:43 +00:00
|
|
|
if (message == null)
|
|
|
|
return;
|
|
|
|
|
2019-10-23 10:51:20 +00:00
|
|
|
EntityFolder folder = db.folder().getFolderByType(message.account, folderType);
|
|
|
|
if (folder != null)
|
|
|
|
EntityOperation.queue(this, message, EntityOperation.MOVE, folder.id);
|
|
|
|
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onMove(long id) {
|
|
|
|
DB db = DB.getInstance(this);
|
|
|
|
try {
|
|
|
|
db.beginTransaction();
|
|
|
|
|
|
|
|
EntityMessage message = db.message().getMessage(id);
|
|
|
|
if (message == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
EntityAccount account = db.account().getAccount(message.account);
|
|
|
|
if (account == null || account.move_to == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
EntityOperation.queue(this, message, EntityOperation.MOVE, account.move_to);
|
2019-02-27 15:05:15 +00:00
|
|
|
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-24 13:00:14 +00:00
|
|
|
private void onReplyDirect(long id, Intent intent) throws IOException {
|
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
|
|
|
boolean prefix_once = prefs.getBoolean("prefix_once", true);
|
|
|
|
boolean plain_only = prefs.getBoolean("plain_only", false);
|
|
|
|
|
|
|
|
Bundle results = RemoteInput.getResultsFromIntent(intent);
|
|
|
|
String text = results.getString("text");
|
2019-08-24 16:21:14 +00:00
|
|
|
if (text != null)
|
|
|
|
text = "<p>" + text.replaceAll("\\r?\\n", "<br>") + "</p>";
|
2019-08-24 13:00:14 +00:00
|
|
|
|
|
|
|
DB db = DB.getInstance(this);
|
|
|
|
try {
|
|
|
|
db.beginTransaction();
|
|
|
|
|
|
|
|
EntityMessage ref = db.message().getMessage(id);
|
|
|
|
if (ref == null)
|
|
|
|
throw new IllegalArgumentException("message not found");
|
|
|
|
|
|
|
|
EntityIdentity identity = db.identity().getIdentity(ref.identity);
|
|
|
|
if (identity == null)
|
|
|
|
throw new IllegalArgumentException("identity not found");
|
|
|
|
|
|
|
|
EntityFolder outbox = db.folder().getOutbox();
|
|
|
|
if (outbox == null)
|
|
|
|
throw new IllegalArgumentException("outbox not found");
|
|
|
|
|
|
|
|
String subject = (ref.subject == null ? "" : ref.subject);
|
|
|
|
if (prefix_once) {
|
|
|
|
String re = getString(R.string.title_subject_reply, "");
|
|
|
|
subject = subject.replaceAll("(?i)" + Pattern.quote(re.trim()), "").trim();
|
|
|
|
}
|
|
|
|
|
|
|
|
EntityMessage reply = new EntityMessage();
|
|
|
|
reply.account = identity.account;
|
|
|
|
reply.folder = outbox.id;
|
|
|
|
reply.identity = identity.id;
|
|
|
|
reply.msgid = EntityMessage.generateMessageId();
|
|
|
|
reply.inreplyto = ref.msgid;
|
|
|
|
reply.thread = ref.thread;
|
|
|
|
reply.to = ref.from;
|
|
|
|
reply.from = new Address[]{new InternetAddress(identity.email, identity.name)};
|
|
|
|
reply.subject = getString(R.string.title_subject_reply, subject);
|
|
|
|
reply.received = new Date().getTime();
|
|
|
|
reply.seen = true;
|
|
|
|
reply.ui_seen = true;
|
|
|
|
reply.id = db.message().insertMessage(reply);
|
|
|
|
Helper.writeText(reply.getFile(this), text);
|
|
|
|
db.message().setMessageContent(reply.id,
|
|
|
|
true,
|
|
|
|
plain_only || ref.plain_only,
|
|
|
|
HtmlHelper.getPreview(text),
|
|
|
|
null);
|
|
|
|
|
|
|
|
EntityOperation.queue(this, reply, EntityOperation.SEND);
|
|
|
|
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
|
|
|
|
ToastEx.makeText(this, R.string.title_queued, Toast.LENGTH_LONG).show();
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-06 08:33:29 +00:00
|
|
|
private void onFlag(long id) {
|
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
|
|
|
boolean threading = prefs.getBoolean("threading", true);
|
|
|
|
|
|
|
|
DB db = DB.getInstance(this);
|
|
|
|
try {
|
|
|
|
db.beginTransaction();
|
|
|
|
|
|
|
|
EntityMessage message = db.message().getMessage(id);
|
2019-09-24 07:34:43 +00:00
|
|
|
if (message == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
List<EntityMessage> messages = db.message().getMessagesByThread(
|
|
|
|
message.account, message.thread, threading ? null : id, null);
|
|
|
|
for (EntityMessage threaded : messages) {
|
|
|
|
EntityOperation.queue(this, threaded, EntityOperation.FLAG, true);
|
|
|
|
EntityOperation.queue(this, threaded, EntityOperation.SEEN, true);
|
2019-05-06 08:33:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-05 20:28:07 +00:00
|
|
|
private void onSeen(long id) {
|
2019-02-27 15:05:15 +00:00
|
|
|
DB db = DB.getInstance(this);
|
|
|
|
try {
|
|
|
|
db.beginTransaction();
|
|
|
|
|
|
|
|
EntityMessage message = db.message().getMessage(id);
|
2019-09-24 07:34:43 +00:00
|
|
|
if (message == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
EntityOperation.queue(this, message, EntityOperation.SEEN, true);
|
|
|
|
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onSnooze(long id) {
|
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
2019-10-06 19:01:01 +00:00
|
|
|
int notify_snooze_duration = prefs.getInt("default_snooze", 1);
|
2019-09-24 07:34:43 +00:00
|
|
|
|
2019-10-06 19:01:01 +00:00
|
|
|
long wakeup = new Date().getTime() + notify_snooze_duration * 3600 * 1000L;
|
2019-09-24 07:34:43 +00:00
|
|
|
|
|
|
|
DB db = DB.getInstance(this);
|
|
|
|
try {
|
|
|
|
db.beginTransaction();
|
|
|
|
|
|
|
|
EntityMessage message = db.message().getMessage(id);
|
|
|
|
if (message == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
db.message().setMessageSnoozed(id, wakeup);
|
|
|
|
EntityOperation.queue(this, message, EntityOperation.SEEN, true);
|
|
|
|
EntityMessage.snooze(this, id, wakeup);
|
2019-02-27 15:05:15 +00:00
|
|
|
|
|
|
|
db.setTransactionSuccessful();
|
2019-09-24 07:34:43 +00:00
|
|
|
|
2019-02-27 15:05:15 +00:00
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-27 15:03:03 +00:00
|
|
|
private void onIgnore(long id, boolean open) {
|
|
|
|
EntityMessage message;
|
|
|
|
|
2019-02-27 15:05:15 +00:00
|
|
|
DB db = DB.getInstance(this);
|
|
|
|
try {
|
|
|
|
db.beginTransaction();
|
|
|
|
|
2019-10-27 15:03:03 +00:00
|
|
|
message = db.message().getMessage(id);
|
2019-09-24 07:34:43 +00:00
|
|
|
if (message == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
db.message().setMessageUiIgnored(message.id, true);
|
2019-02-27 15:05:15 +00:00
|
|
|
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
2019-10-27 15:03:03 +00:00
|
|
|
|
|
|
|
if (open) {
|
|
|
|
Intent thread = new Intent(this, ActivityView.class);
|
|
|
|
thread.setAction("thread:" + message.thread);
|
|
|
|
thread.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
thread.putExtra("account", message.account);
|
|
|
|
thread.putExtra("id", message.id);
|
|
|
|
startActivity(thread);
|
|
|
|
}
|
2019-02-27 15:05:15 +00:00
|
|
|
}
|
|
|
|
|
2019-09-24 07:34:43 +00:00
|
|
|
private void onWakeup(long id) {
|
2019-02-27 15:05:15 +00:00
|
|
|
DB db = DB.getInstance(this);
|
|
|
|
try {
|
|
|
|
db.beginTransaction();
|
|
|
|
|
|
|
|
EntityMessage message = db.message().getMessage(id);
|
2019-09-26 13:28:21 +00:00
|
|
|
if (message == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
db.message().setMessageSnoozed(message.id, null);
|
|
|
|
|
|
|
|
EntityFolder folder = db.folder().getFolder(message.folder);
|
|
|
|
if (EntityFolder.OUTBOX.equals(folder.type)) {
|
|
|
|
Log.i("Delayed send id=" + message.id);
|
|
|
|
EntityOperation.queue(this, message, EntityOperation.SEND);
|
|
|
|
} else {
|
|
|
|
if (folder.notify)
|
|
|
|
EntityOperation.queue(this, message, EntityOperation.SEEN, false, false);
|
2019-02-27 15:05:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|