|
|
@ -20,6 +20,7 @@ package eu.faircode.email; |
|
|
|
*/ |
|
|
|
|
|
|
|
import android.Manifest; |
|
|
|
import android.app.AlarmManager; |
|
|
|
import android.app.Notification; |
|
|
|
import android.app.NotificationManager; |
|
|
|
import android.app.PendingIntent; |
|
|
@ -78,9 +79,7 @@ import java.util.Map; |
|
|
|
import java.util.Properties; |
|
|
|
import java.util.concurrent.ExecutorService; |
|
|
|
import java.util.concurrent.Executors; |
|
|
|
import java.util.concurrent.ScheduledFuture; |
|
|
|
import java.util.concurrent.ScheduledThreadPoolExecutor; |
|
|
|
import java.util.concurrent.TimeUnit; |
|
|
|
import java.util.concurrent.Semaphore; |
|
|
|
|
|
|
|
import javax.mail.Address; |
|
|
|
import javax.mail.AuthenticationFailedException; |
|
|
@ -571,7 +570,7 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
|
|
|
|
final IMAPStore istore = (IMAPStore) isession.getStore("imaps"); |
|
|
|
final Map<EntityFolder, IMAPFolder> folders = new HashMap<>(); |
|
|
|
List<Thread> pollers = new ArrayList<>(); |
|
|
|
List<Thread> syncs = new ArrayList<>(); |
|
|
|
List<Thread> idlers = new ArrayList<>(); |
|
|
|
try { |
|
|
|
// Listen for store events |
|
|
@ -613,7 +612,6 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
|
|
|
|
// Listen for connection events |
|
|
|
istore.addConnectionListener(new ConnectionAdapter() { |
|
|
|
|
|
|
|
@Override |
|
|
|
public void opened(ConnectionEvent e) { |
|
|
|
Log.i(Helper.TAG, account.name + " opened"); |
|
|
@ -664,8 +662,8 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
db.folder().setFolderState(folder.id, "connected"); |
|
|
|
db.folder().setFolderError(folder.id, null); |
|
|
|
|
|
|
|
// Keep folder connection alive |
|
|
|
Thread poller = new Thread(new Runnable() { |
|
|
|
// Synchronize folder |
|
|
|
Thread sync = new Thread(new Runnable() { |
|
|
|
@Override |
|
|
|
public void run() { |
|
|
|
try { |
|
|
@ -797,46 +795,6 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
if (!capIdle) { |
|
|
|
Log.i(Helper.TAG, folder.name + " start polling"); |
|
|
|
|
|
|
|
PowerManager pm = getSystemService(PowerManager.class); |
|
|
|
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, account.name + "/" + folder.name); |
|
|
|
|
|
|
|
final Thread pthread = Thread.currentThread(); |
|
|
|
int rate = (folder.poll_interval == null ? 9 : folder.poll_interval); |
|
|
|
ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(1); |
|
|
|
ScheduledFuture future = scheduler.scheduleAtFixedRate(new Runnable() { |
|
|
|
@Override |
|
|
|
public void run() { |
|
|
|
Log.i(Helper.TAG, folder.name + " wakeup poll"); |
|
|
|
pthread.interrupt(); |
|
|
|
} |
|
|
|
}, rate, rate, TimeUnit.MINUTES); |
|
|
|
|
|
|
|
while (state.running) { |
|
|
|
try { |
|
|
|
Thread.sleep(Long.MAX_VALUE); |
|
|
|
} catch (InterruptedException ex) { |
|
|
|
Log.w(Helper.TAG, folder.name + " poll " + ex.toString()); |
|
|
|
} |
|
|
|
|
|
|
|
try { |
|
|
|
wl.acquire(); |
|
|
|
synchronizeMessages(account, folder, ifolder, state); |
|
|
|
} catch (Throwable ex) { |
|
|
|
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); |
|
|
|
reportError(account.name, folder.name, ex); |
|
|
|
|
|
|
|
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); |
|
|
|
} finally { |
|
|
|
wl.release(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
future.cancel(false); |
|
|
|
} |
|
|
|
} catch (Throwable ex) { |
|
|
|
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); |
|
|
|
reportError(account.name, folder.name, ex); |
|
|
@ -844,16 +802,13 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); |
|
|
|
|
|
|
|
state.thread.interrupt(); |
|
|
|
} finally { |
|
|
|
if (!capIdle) |
|
|
|
Log.i(Helper.TAG, folder.name + " end polling"); |
|
|
|
} |
|
|
|
} |
|
|
|
}, "sync.poller." + folder.id); |
|
|
|
poller.start(); |
|
|
|
pollers.add(poller); |
|
|
|
}, "sync." + folder.id); |
|
|
|
sync.start(); |
|
|
|
syncs.add(sync); |
|
|
|
|
|
|
|
// Receive folder events |
|
|
|
// Idle folder |
|
|
|
if (capIdle) { |
|
|
|
Thread idler = new Thread(new Runnable() { |
|
|
|
@Override |
|
|
@ -876,7 +831,7 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
Log.i(Helper.TAG, folder.name + " end idle"); |
|
|
|
} |
|
|
|
} |
|
|
|
}, "sync.idle." + folder.id); |
|
|
|
}, "idler." + folder.id); |
|
|
|
idler.start(); |
|
|
|
idlers.add(idler); |
|
|
|
} |
|
|
@ -884,6 +839,7 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
|
|
|
|
backoff = CONNECT_BACKOFF_START; |
|
|
|
|
|
|
|
// Process folder actions |
|
|
|
BroadcastReceiver processFolder = new BroadcastReceiver() { |
|
|
|
@Override |
|
|
|
public void onReceive(Context context, final Intent intent) { |
|
|
@ -961,52 +917,78 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
f.addAction(ACTION_SYNCHRONIZE_FOLDER); |
|
|
|
f.addAction(ACTION_PROCESS_OPERATIONS); |
|
|
|
f.addDataType("account/" + account.id); |
|
|
|
|
|
|
|
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(ServiceSynchronize.this); |
|
|
|
lbm.registerReceiver(processFolder, f); |
|
|
|
|
|
|
|
try { |
|
|
|
PowerManager pm = getSystemService(PowerManager.class); |
|
|
|
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, account.name); |
|
|
|
// Create barrier |
|
|
|
final Semaphore sem = new Semaphore(0); |
|
|
|
|
|
|
|
ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(1); |
|
|
|
ScheduledFuture future = scheduler.scheduleAtFixedRate(new Runnable() { |
|
|
|
@Override |
|
|
|
public void run() { |
|
|
|
Log.i(Helper.TAG, account.name + " wakeup check"); |
|
|
|
state.thread.interrupt(); |
|
|
|
} |
|
|
|
}, account.poll_interval, account.poll_interval, TimeUnit.MINUTES); |
|
|
|
// Keep alive |
|
|
|
final PowerManager pm = getSystemService(PowerManager.class); |
|
|
|
final PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "account." + account.id); |
|
|
|
|
|
|
|
// Keep store alive |
|
|
|
while (state.running) { |
|
|
|
EntityLog.log(this, account.name + " wait=" + account.poll_interval); |
|
|
|
final AlarmManager am = getSystemService(AlarmManager.class); |
|
|
|
final String id = BuildConfig.APPLICATION_ID + ".POLL." + account.id; |
|
|
|
final PendingIntent pi = PendingIntent.getBroadcast(ServiceSynchronize.this, 0, new Intent(id), 0); |
|
|
|
|
|
|
|
try { |
|
|
|
Thread.sleep(Long.MAX_VALUE); |
|
|
|
} catch (InterruptedException ex) { |
|
|
|
Log.w(Helper.TAG, account.name + " wait " + ex.toString()); |
|
|
|
} |
|
|
|
BroadcastReceiver alive = new BroadcastReceiver() { |
|
|
|
@Override |
|
|
|
public void onReceive(Context context, Intent intent) { |
|
|
|
EntityLog.log(context, account.name + " keep alive"); |
|
|
|
|
|
|
|
if (state.running) try { |
|
|
|
try { |
|
|
|
wl.acquire(); |
|
|
|
|
|
|
|
if (!istore.isConnected()) |
|
|
|
throw new StoreClosedException(istore); |
|
|
|
|
|
|
|
for (EntityFolder folder : folders.keySet()) |
|
|
|
if (!folders.get(folder).isOpen()) |
|
|
|
throw new FolderClosedException(folders.get(folder)); |
|
|
|
if (capIdle) { |
|
|
|
if (!folders.get(folder).isOpen()) |
|
|
|
throw new FolderClosedException(folders.get(folder)); |
|
|
|
} else |
|
|
|
synchronizeMessages(account, folder, folders.get(folder), state); |
|
|
|
} catch (Throwable ex) { |
|
|
|
Log.e(Helper.TAG, account.name + " " + ex + "\n" + Log.getStackTraceString(ex)); |
|
|
|
reportError(account.name, null, ex); |
|
|
|
|
|
|
|
db.account().setAccountError(account.id, Helper.formatThrowable(ex)); |
|
|
|
|
|
|
|
sem.release(); |
|
|
|
} finally { |
|
|
|
wl.release(); |
|
|
|
} |
|
|
|
|
|
|
|
// Reschedule alarm |
|
|
|
am.setAndAllowWhileIdle( |
|
|
|
AlarmManager.RTC_WAKEUP, |
|
|
|
System.currentTimeMillis() + account.poll_interval * 60 * 1000L, |
|
|
|
pi); |
|
|
|
} |
|
|
|
}; |
|
|
|
registerReceiver(alive, new IntentFilter(id)); |
|
|
|
|
|
|
|
future.cancel(false); |
|
|
|
// Schedule alarm |
|
|
|
EntityLog.log(this, account.name + " wait=" + account.poll_interval); |
|
|
|
am.setAndAllowWhileIdle( |
|
|
|
AlarmManager.RTC_WAKEUP, |
|
|
|
System.currentTimeMillis() + account.poll_interval * 60 * 1000L, |
|
|
|
pi); |
|
|
|
|
|
|
|
Log.i(Helper.TAG, account.name + " done running=" + state.running); |
|
|
|
// Wait for interrupt or exception |
|
|
|
try { |
|
|
|
sem.acquire(); |
|
|
|
} catch (InterruptedException ex) { |
|
|
|
Log.w(Helper.TAG, account.name + " semaphore " + ex.toString()); |
|
|
|
} finally { |
|
|
|
// Cleanup |
|
|
|
am.cancel(pi); |
|
|
|
unregisterReceiver(alive); |
|
|
|
lbm.unregisterReceiver(processFolder); |
|
|
|
} |
|
|
|
|
|
|
|
Log.i(Helper.TAG, account.name + " done running=" + state.running); |
|
|
|
} catch (Throwable ex) { |
|
|
|
Log.e(Helper.TAG, account.name + " " + ex + "\n" + Log.getStackTraceString(ex)); |
|
|
|
reportError(account.name, null, ex); |
|
|
@ -1018,10 +1000,16 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
for (EntityFolder folder : folders.keySet()) |
|
|
|
db.folder().setFolderState(folder.id, "closing"); |
|
|
|
|
|
|
|
// Stop pollers |
|
|
|
for (Thread poller : pollers) { |
|
|
|
poller.interrupt(); |
|
|
|
join(poller); |
|
|
|
// Stop syncs |
|
|
|
for (Thread sync : syncs) { |
|
|
|
sync.interrupt(); |
|
|
|
join(sync); |
|
|
|
} |
|
|
|
|
|
|
|
// Stop idlers |
|
|
|
for (Thread idler : idlers) { |
|
|
|
idler.interrupt(); |
|
|
|
join(idler); |
|
|
|
} |
|
|
|
|
|
|
|
// Close store |
|
|
@ -1053,12 +1041,6 @@ public class ServiceSynchronize extends LifecycleService { |
|
|
|
for (EntityFolder folder : folders.keySet()) |
|
|
|
db.folder().setFolderState(folder.id, null); |
|
|
|
} |
|
|
|
|
|
|
|
// Stop idlers |
|
|
|
for (Thread idler : idlers) { |
|
|
|
idler.interrupt(); |
|
|
|
join(idler); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (state.running) { |
|
|
|