2019-02-27 15:05:15 +00:00
|
|
|
package eu.faircode.email;
|
|
|
|
|
|
|
|
import android.app.IntentService;
|
|
|
|
import android.content.Context;
|
|
|
|
import android.content.Intent;
|
|
|
|
import android.content.SharedPreferences;
|
|
|
|
import android.content.pm.PackageManager;
|
|
|
|
import android.net.Uri;
|
2019-02-27 16:48:03 +00:00
|
|
|
import android.os.PowerManager;
|
2019-02-27 15:05:15 +00:00
|
|
|
import android.preference.PreferenceManager;
|
|
|
|
|
2019-03-02 07:15:49 +00:00
|
|
|
import java.util.Date;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Properties;
|
|
|
|
|
|
|
|
import javax.mail.Folder;
|
|
|
|
import javax.mail.MessagingException;
|
|
|
|
import javax.mail.Session;
|
|
|
|
import javax.mail.Store;
|
|
|
|
|
2019-02-27 15:05:15 +00:00
|
|
|
import androidx.annotation.Nullable;
|
|
|
|
|
|
|
|
public class ServiceUI extends IntentService {
|
2019-02-27 16:48:03 +00:00
|
|
|
private PowerManager.WakeLock wl;
|
2019-03-02 07:15:49 +00:00
|
|
|
private Map<EntityAccount, Store> accountStore = new HashMap<>();
|
2019-02-27 16:48:03 +00:00
|
|
|
|
2019-02-27 15:05:15 +00:00
|
|
|
static final int PI_WHY = 1;
|
|
|
|
static final int PI_SUMMARY = 2;
|
|
|
|
static final int PI_CLEAR = 3;
|
|
|
|
static final int PI_SEEN = 4;
|
|
|
|
static final int PI_ARCHIVE = 5;
|
|
|
|
static final int PI_TRASH = 6;
|
|
|
|
static final int PI_IGNORED = 7;
|
|
|
|
static final int PI_SNOOZED = 8;
|
|
|
|
|
|
|
|
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");
|
|
|
|
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
|
|
|
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":ui");
|
|
|
|
wl.acquire();
|
|
|
|
super.onCreate();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onDestroy() {
|
|
|
|
Log.i("Service UI destroy");
|
2019-03-02 07:15:49 +00:00
|
|
|
|
|
|
|
final DB db = DB.getInstance(this);
|
|
|
|
|
|
|
|
new Thread(new Runnable() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
try {
|
|
|
|
for (EntityAccount account : accountStore.keySet())
|
|
|
|
try {
|
|
|
|
Log.i(account.name + " closing");
|
|
|
|
db.account().setAccountState(account.id, "closing");
|
|
|
|
accountStore.get(account).close();
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.w(ex);
|
|
|
|
} finally {
|
|
|
|
Log.i(account.name + " closed");
|
|
|
|
db.account().setAccountState(account.id, null);
|
|
|
|
}
|
|
|
|
accountStore.clear();
|
|
|
|
} finally {
|
|
|
|
wl.release();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}).start();
|
|
|
|
|
2019-02-27 16:48:03 +00:00
|
|
|
super.onDestroy();
|
|
|
|
}
|
|
|
|
|
2019-02-27 15:05:15 +00:00
|
|
|
@Override
|
|
|
|
protected void onHandleIntent(@Nullable Intent intent) {
|
2019-02-27 16:48:03 +00:00
|
|
|
Log.i("Service UI intent=" + intent);
|
|
|
|
|
2019-02-27 15:05:15 +00:00
|
|
|
if (intent == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
String action = intent.getAction();
|
|
|
|
if (action == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
String[] parts = action.split(":");
|
|
|
|
long id = (parts.length > 1 ? Long.parseLong(parts[1]) : -1);
|
|
|
|
|
|
|
|
switch (parts[0]) {
|
|
|
|
case "why":
|
|
|
|
onWhy();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "summary":
|
|
|
|
onSummary();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "clear":
|
|
|
|
onClear();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "seen":
|
|
|
|
onSeen(id);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "archive":
|
|
|
|
onArchive(id);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "trash":
|
|
|
|
onTrash(id);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "ignore":
|
|
|
|
onIgnore(id);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "snooze":
|
2019-02-28 18:13:28 +00:00
|
|
|
// AlarmManager.RTC_WAKEUP
|
2019-02-27 15:05:15 +00:00
|
|
|
onSnooze(id);
|
|
|
|
break;
|
|
|
|
|
2019-03-02 07:15:49 +00:00
|
|
|
case "process":
|
|
|
|
onProcessOperations(id);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "fsync":
|
|
|
|
onFolderSync(id);
|
|
|
|
break;
|
|
|
|
|
2019-02-27 15:05:15 +00:00
|
|
|
default:
|
|
|
|
Log.w("Unknown action: " + parts[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onWhy() {
|
|
|
|
Intent why = new Intent(Intent.ACTION_VIEW);
|
|
|
|
why.setData(Uri.parse(Helper.FAQ_URI + "#user-content-faq2"));
|
|
|
|
why.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
|
|
|
|
PackageManager pm = getPackageManager();
|
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
|
|
|
if (prefs.getBoolean("why", false) || why.resolveActivity(pm) == null) {
|
|
|
|
Intent view = new Intent(this, ActivityView.class);
|
|
|
|
view.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
startActivity(view);
|
|
|
|
} else {
|
|
|
|
prefs.edit().putBoolean("why", true).apply();
|
|
|
|
startActivity(why);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onSummary() {
|
|
|
|
DB.getInstance(this).message().ignoreAll();
|
|
|
|
|
|
|
|
Intent view = new Intent(this, ActivityView.class);
|
|
|
|
view.setAction("unified");
|
|
|
|
view.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
startActivity(view);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onClear() {
|
|
|
|
DB.getInstance(this).message().ignoreAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onSeen(long id) {
|
|
|
|
DB db = DB.getInstance(this);
|
|
|
|
try {
|
|
|
|
db.beginTransaction();
|
|
|
|
|
|
|
|
EntityMessage message = db.message().getMessage(id);
|
|
|
|
if (message != null)
|
|
|
|
EntityOperation.queue(this, db, message, EntityOperation.SEEN, true);
|
|
|
|
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onArchive(long id) {
|
|
|
|
DB db = DB.getInstance(this);
|
|
|
|
try {
|
|
|
|
db.beginTransaction();
|
|
|
|
|
|
|
|
EntityMessage message = db.message().getMessage(id);
|
|
|
|
if (message != null) {
|
|
|
|
EntityFolder archive = db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE);
|
|
|
|
if (archive == null)
|
|
|
|
archive = db.folder().getFolderByType(message.account, EntityFolder.TRASH);
|
|
|
|
if (archive != null)
|
|
|
|
EntityOperation.queue(this, db, message, EntityOperation.MOVE, archive.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onTrash(long id) {
|
|
|
|
DB db = DB.getInstance(this);
|
|
|
|
try {
|
|
|
|
db.beginTransaction();
|
|
|
|
|
|
|
|
EntityMessage message = db.message().getMessage(id);
|
|
|
|
if (message != null) {
|
|
|
|
EntityFolder trash = db.folder().getFolderByType(message.account, EntityFolder.TRASH);
|
|
|
|
if (trash != null)
|
|
|
|
EntityOperation.queue(this, db, message, EntityOperation.MOVE, trash.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onIgnore(long id) {
|
|
|
|
DB db = DB.getInstance(this);
|
|
|
|
try {
|
|
|
|
db.beginTransaction();
|
|
|
|
|
|
|
|
EntityMessage message = db.message().getMessage(id);
|
|
|
|
if (message != null)
|
|
|
|
db.message().setMessageUiIgnored(message.id, true);
|
|
|
|
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onSnooze(long id) {
|
|
|
|
DB db = DB.getInstance(this);
|
|
|
|
try {
|
|
|
|
db.beginTransaction();
|
|
|
|
|
|
|
|
EntityMessage message = db.message().getMessage(id);
|
|
|
|
if (message != null) {
|
|
|
|
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, db, message, EntityOperation.SEND);
|
|
|
|
} else {
|
|
|
|
EntityOperation.queue(
|
|
|
|
this, db, message, EntityOperation.SEEN, false);
|
|
|
|
db.message().setMessageUiIgnored(message.id, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
}
|
|
|
|
}
|
2019-03-02 07:15:49 +00:00
|
|
|
|
|
|
|
private void onProcessOperations(long fid) {
|
|
|
|
DB db = DB.getInstance(this);
|
|
|
|
|
|
|
|
EntityFolder folder = db.folder().getFolder(fid);
|
|
|
|
if (folder == null)
|
|
|
|
return;
|
|
|
|
EntityAccount account = db.account().getAccount(folder.account);
|
|
|
|
if (account == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Folder ifolder = null;
|
|
|
|
try {
|
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
|
|
|
boolean debug = (prefs.getBoolean("debug", false) || BuildConfig.BETA_RELEASE);
|
|
|
|
|
|
|
|
// Create session
|
|
|
|
Properties props = MessageHelper.getSessionProperties(account.auth_type, account.realm, account.insecure);
|
|
|
|
final Session isession = Session.getInstance(props, null);
|
|
|
|
isession.setDebug(debug);
|
|
|
|
|
|
|
|
Store istore = accountStore.get(account.id);
|
|
|
|
if (istore == null || !istore.isConnected()) {
|
|
|
|
// Connect account
|
|
|
|
Log.i(account.name + " connecting");
|
|
|
|
db.account().setAccountState(account.id, "connecting");
|
|
|
|
istore = isession.getStore(account.getProtocol());
|
|
|
|
Helper.connect(this, istore, account);
|
|
|
|
db.account().setAccountState(account.id, "connected");
|
|
|
|
db.account().setAccountConnected(account.id, new Date().getTime());
|
|
|
|
db.account().setAccountError(account.id, null);
|
|
|
|
Log.i(account.name + " connected");
|
|
|
|
|
|
|
|
accountStore.put(account, istore);
|
|
|
|
} else
|
|
|
|
Log.i(account + " reusing connection");
|
|
|
|
|
|
|
|
// Connect folder
|
|
|
|
Log.i(folder.name + " connecting");
|
|
|
|
db.folder().setFolderState(folder.id, "connecting");
|
|
|
|
ifolder = istore.getFolder(folder.name);
|
|
|
|
ifolder.open(Folder.READ_WRITE);
|
|
|
|
db.folder().setFolderState(folder.id, "connected");
|
|
|
|
db.folder().setFolderError(folder.id, null);
|
|
|
|
Log.i(folder.name + " connected");
|
|
|
|
|
|
|
|
// Process operations
|
|
|
|
Core.processOperations(this, account, folder, isession, istore, ifolder, new Core.State());
|
|
|
|
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.w(ex);
|
|
|
|
Core.reportError(this, account, folder, ex);
|
|
|
|
db.account().setAccountError(account.id, Helper.formatThrowable(ex));
|
|
|
|
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex, false));
|
|
|
|
} finally {
|
|
|
|
if (ifolder != null)
|
|
|
|
try {
|
|
|
|
Log.i(folder.name + " closing");
|
|
|
|
db.folder().setFolderState(folder.id, "closing");
|
|
|
|
ifolder.close();
|
|
|
|
} catch (MessagingException ex) {
|
|
|
|
Log.w(ex);
|
|
|
|
} finally {
|
|
|
|
Log.i(folder.name + " closed");
|
|
|
|
db.folder().setFolderState(folder.id, null);
|
|
|
|
db.folder().setFolderSyncState(folder.id, null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void onFolderSync(long aid) {
|
|
|
|
DB db = DB.getInstance(this);
|
|
|
|
EntityAccount account = db.account().getAccount(aid);
|
|
|
|
if (account == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Store istore = null;
|
|
|
|
try {
|
|
|
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
|
|
|
boolean debug = (prefs.getBoolean("debug", false) || BuildConfig.BETA_RELEASE);
|
|
|
|
|
|
|
|
// Create session
|
|
|
|
Properties props = MessageHelper.getSessionProperties(account.auth_type, account.realm, account.insecure);
|
|
|
|
final Session isession = Session.getInstance(props, null);
|
|
|
|
isession.setDebug(debug);
|
|
|
|
|
|
|
|
// Connect account
|
|
|
|
Log.i(account.name + " connecting");
|
|
|
|
db.account().setAccountState(account.id, "connecting");
|
|
|
|
istore = isession.getStore(account.getProtocol());
|
|
|
|
Helper.connect(this, istore, account);
|
|
|
|
db.account().setAccountState(account.id, "connected");
|
|
|
|
db.account().setAccountConnected(account.id, new Date().getTime());
|
|
|
|
db.account().setAccountError(account.id, null);
|
|
|
|
Log.i(account.name + " connected");
|
|
|
|
|
|
|
|
// Synchronize folders
|
|
|
|
Core.onSynchronizeFolders(this, account, istore, new Core.State());
|
|
|
|
|
|
|
|
} catch (Throwable ex) {
|
|
|
|
Log.w(ex);
|
|
|
|
Core.reportError(this, account, null, ex);
|
|
|
|
db.account().setAccountError(account.id, Helper.formatThrowable(ex));
|
|
|
|
} finally {
|
|
|
|
if (istore != null) {
|
|
|
|
Log.i(account.name + " closing");
|
|
|
|
db.account().setAccountState(account.id, "closing");
|
|
|
|
|
|
|
|
try {
|
|
|
|
istore.close();
|
|
|
|
} catch (MessagingException ex) {
|
|
|
|
Log.e(ex);
|
|
|
|
}
|
|
|
|
|
|
|
|
Log.i(account.name + " closed");
|
|
|
|
}
|
|
|
|
|
|
|
|
db.account().setAccountState(account.id, null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void process(Context context, long folder) {
|
|
|
|
context.startService(
|
|
|
|
new Intent(context, ServiceUI.class)
|
|
|
|
.setAction("process:" + folder));
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void fsync(Context context, long account) {
|
|
|
|
context.startService(
|
|
|
|
new Intent(context, ServiceUI.class)
|
|
|
|
.setAction("fsync:" + account));
|
|
|
|
}
|
2019-02-27 15:05:15 +00:00
|
|
|
}
|