Merge pull request #3763 from ojiofong/oji_mail_sync

Change mail sync to work with background execution limits
This commit is contained in:
cketti 2018-12-06 19:57:00 +01:00 committed by GitHub
commit 2f8778be33
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 523 additions and 961 deletions

View file

@ -22,6 +22,7 @@ dependencies {
implementation "com.squareup.moshi:moshi:1.2.0"
implementation "com.jakewharton.timber:timber:${versions.timber}"
implementation "org.apache.james:apache-mime4j-core:${versions.mime4j}"
implementation 'com.evernote:android-job:1.2.6'
testImplementation project(':mail:testing')
testImplementation project(":backend:imap")

View file

@ -104,6 +104,8 @@ public class Account implements BaseAccount, StoreConfig {
public static final long NO_OPENPGP_KEY = 0;
public static final int UNASSIGNED_ACCOUNT_NUMBER = -1;
public static final int INTERVAL_MINUTES_NEVER = -1;
private DeletePolicy deletePolicy = DeletePolicy.NEVER;
private final String accountUuid;

View file

@ -24,7 +24,7 @@ class AccountPreferenceSerializer(
transportUri = Base64.decode(storage.getString("$accountUuid.transportUri", null))
description = storage.getString("$accountUuid.description", null)
alwaysBcc = storage.getString("$accountUuid.alwaysBcc", alwaysBcc)
automaticCheckIntervalMinutes = storage.getInt("$accountUuid.automaticCheckIntervalMinutes", -1)
automaticCheckIntervalMinutes = storage.getInt("$accountUuid.automaticCheckIntervalMinutes", Account.INTERVAL_MINUTES_NEVER)
idleRefreshMinutes = storage.getInt("$accountUuid.idleRefreshMinutes", 24)
isPushPollOnConnect = storage.getBoolean("$accountUuid.pushPollOnConnect", true)
displayCount = storage.getInt("$accountUuid.displayCount", K9.DEFAULT_VISIBLE_LIMIT)
@ -499,7 +499,7 @@ class AccountPreferenceSerializer(
fun loadDefaults(account: Account) {
with (account) {
localStorageProviderId = storageManager.defaultProviderId
automaticCheckIntervalMinutes = -1
automaticCheckIntervalMinutes = Account.INTERVAL_MINUTES_NEVER
idleRefreshMinutes = 24
isPushPollOnConnect = true
displayCount = K9.DEFAULT_VISIBLE_LIMIT

View file

@ -11,6 +11,8 @@ import android.os.StrictMode
import com.fsck.k9.autocrypt.autocryptModule
import com.fsck.k9.controller.controllerModule
import com.fsck.k9.crypto.openPgpModule
import com.fsck.k9.job.K9JobManager
import com.fsck.k9.job.jobModule
import com.fsck.k9.mail.internet.BinaryTempFileBody
import com.fsck.k9.mail.ssl.LocalKeyStore
import com.fsck.k9.mailstore.mailStoreModule
@ -18,10 +20,8 @@ import com.fsck.k9.message.extractors.extractorModule
import com.fsck.k9.message.html.htmlModule
import com.fsck.k9.message.quote.quoteModule
import com.fsck.k9.notification.coreNotificationModule
import com.fsck.k9.power.DeviceIdleManager
import com.fsck.k9.search.searchModule
import com.fsck.k9.service.BootReceiver
import com.fsck.k9.service.MailService
import com.fsck.k9.service.ShutdownReceiver
import com.fsck.k9.service.StorageGoneReceiver
import org.koin.standalone.KoinComponent
@ -31,8 +31,9 @@ import java.util.concurrent.SynchronousQueue
object Core : KoinComponent {
private val appConfig: AppConfig by inject()
private val jobManager: K9JobManager by inject()
private val componentsToDisable = listOf(BootReceiver::class.java, MailService::class.java)
private val componentsToDisable = listOf(BootReceiver::class.java)
@JvmStatic
val coreModules = listOf(
@ -45,7 +46,8 @@ object Core : KoinComponent {
htmlModule,
quoteModule,
coreNotificationModule,
controllerModule
controllerModule,
jobModule
)
/**
@ -81,33 +83,12 @@ object Core : KoinComponent {
val acctLength = Preferences.getPreferences(appContext).availableAccounts.size
val enable = acctLength > 0
setServicesEnabled(appContext, enable, null)
updateDeviceIdleReceiver(appContext, enable)
setServicesEnabled(appContext, enable)
}
private fun updateDeviceIdleReceiver(context: Context, enable: Boolean) {
val deviceIdleManager = DeviceIdleManager.getInstance(context)
if (enable) {
deviceIdleManager.registerReceiver()
} else {
deviceIdleManager.unregisterReceiver()
}
}
private fun setServicesEnabled(context: Context, enabled: Boolean, wakeLockId: Int?) {
private fun setServicesEnabled(context: Context, enabled: Boolean) {
val pm = context.packageManager
if (!enabled && pm.getComponentEnabledSetting(ComponentName(context, MailService::class.java)) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
/*
* If no accounts now exist but the service is still enabled we're about to disable it
* so we'll reschedule to kill off any existing alarms.
*/
MailService.actionReset(context, wakeLockId)
}
val classes = componentsToDisable + appConfig.componentsToDisable
for (clazz in classes) {
val alreadyEnabled = pm.getComponentEnabledSetting(ComponentName(context, clazz)) ==
@ -124,14 +105,10 @@ object Core : KoinComponent {
}
}
if (enabled && pm.getComponentEnabledSetting(ComponentName(context, MailService::class.java)) ==
PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
/*
* And now if accounts do exist then we've just enabled the service and we want to
* schedule alarms for the new accounts.
*/
MailService.actionReset(context, wakeLockId)
if (enabled) {
jobManager.scheduleAllMailJobs()
}
}
/**

View file

@ -17,6 +17,7 @@ import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@ -2403,6 +2404,25 @@ public class MessagingController {
context.startActivity(chooserIntent);
}
public void checkMailBlocking(Account account) {
final CountDownLatch latch = new CountDownLatch(1);
checkMail(context, account, true, false, new SimpleMessagingListener() {
@Override
public void checkMailFinished(Context context, Account account) {
latch.countDown();
}
});
Timber.v("checkMailBlocking(%s) about to await latch release", account.getDescription());
try {
latch.await();
Timber.v("checkMailBlocking(%s) got latch release", account.getDescription());
} catch (Exception e) {
Timber.e(e, "Interrupted while awaiting latch release");
}
}
/**
* Checks mail for one or multiple accounts. If account is null all accounts
* are checked.
@ -2862,6 +2882,10 @@ public class MessagingController {
return pushers.values();
}
public Pusher getPusher(Account account) {
return pushers.get(account);
}
public boolean setupPushing(final Account account) {
try {
Pusher previousPusher = pushers.remove(account);

View file

@ -0,0 +1,19 @@
package com.fsck.k9.job
import com.evernote.android.job.Job
import com.evernote.android.job.JobCreator
class K9JobCreator(
private val mailSyncJobManager: MailSyncJobManager,
private val pusherRefreshJobManager: PusherRefreshJobManager
) : JobCreator {
override fun create(tag: String): Job? {
return when (tag) {
MailSyncJob.TAG -> mailSyncJobManager.getJob()
PusherRefreshJob.TAG -> pusherRefreshJobManager.getJob()
else -> null
}
}
}

View file

@ -0,0 +1,58 @@
package com.fsck.k9.job
import com.evernote.android.job.JobManager
import com.fsck.k9.Preferences
import timber.log.Timber
class K9JobManager(
jobCreator: K9JobCreator,
private val jobManager: JobManager,
private val preferences: Preferences,
private val mailSyncJobManager: MailSyncJobManager,
private val pusherRefreshJobManager: PusherRefreshJobManager
) {
// It's recommended to initialize JobManager in Application onCreate()
// I.e. by calling JobManager.create(this).addJobCreator(jobCreator)
// Using this DI approach should provide a similar initialization
init {
jobManager.addJobCreator(jobCreator)
}
fun scheduleAllMailJobs() {
Timber.v("scheduling all jobs")
scheduleMailSync()
schedulePusherRefresh()
}
fun scheduleMailSync() {
cancelAllMailSyncJobs()
preferences.availableAccounts?.forEach { account ->
mailSyncJobManager.scheduleJob(account)
}
}
fun schedulePusherRefresh() {
cancelAllPusherRefreshJobs()
preferences.availableAccounts?.forEach { account ->
pusherRefreshJobManager.scheduleJob(account)
}
}
fun cancelAllMailSyncJobs() {
Timber.v("canceling mail sync job")
jobManager.cancelAllForTag(MailSyncJob.TAG)
}
fun cancelAllPusherRefreshJobs() {
Timber.v("canceling pusher refresh job")
jobManager.cancelAllForTag(PusherRefreshJob.TAG)
}
companion object {
const val EXTRA_KEY_ACCOUNT_UUID = "param_key_account_uuid"
}
}

View file

@ -0,0 +1,12 @@
package com.fsck.k9.job
import com.evernote.android.job.JobManager
import org.koin.dsl.module.applicationContext
val jobModule = applicationContext {
bean { JobManager.create(get()) as JobManager }
bean { K9JobManager(get(), get(), get(), get(), get()) }
bean { K9JobCreator(get(), get()) }
factory { MailSyncJobManager(get(), get()) }
factory { PusherRefreshJobManager(get(), get(), get()) }
}

View file

@ -0,0 +1,30 @@
package com.fsck.k9.job
import com.evernote.android.job.Job
import com.fsck.k9.Preferences
import com.fsck.k9.controller.MessagingController
class MailSyncJob(
private val messagingController: MessagingController,
private val preferences: Preferences
) : Job() {
override fun onRunJob(params: Params): Result {
params.extras.getString(K9JobManager.EXTRA_KEY_ACCOUNT_UUID, null)
?.let { accountUuid ->
preferences.getAccount(accountUuid)?.let { account ->
messagingController.checkMailBlocking(account)
}
}
return Result.SUCCESS
}
companion object {
const val TAG: String = "MailSyncJob"
}
}

View file

@ -0,0 +1,48 @@
package com.fsck.k9.job
import com.evernote.android.job.JobRequest
import com.evernote.android.job.util.support.PersistableBundleCompat
import com.fsck.k9.Account
import com.fsck.k9.Preferences
import com.fsck.k9.controller.MessagingController
import timber.log.Timber
class MailSyncJobManager(
private val messagingController: MessagingController,
private val preferences: Preferences
) {
fun getJob() = MailSyncJob(messagingController, preferences)
fun scheduleJob(account: Account) {
getSyncIntervalInMillisecondsIfEnabled(account)?.let { syncInterval ->
Timber.v("scheduling mail sync job for ${account.description}")
val extras = PersistableBundleCompat()
extras.putString(K9JobManager.EXTRA_KEY_ACCOUNT_UUID, account.uuid)
val jobRequest = JobRequest.Builder(MailSyncJob.TAG)
.setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
.setPeriodic(syncInterval)
.setExtras(extras)
.setRequirementsEnforced(true)
.build()
jobRequest.schedule()
}
}
private fun getSyncIntervalInMillisecondsIfEnabled(account: Account): Long? {
val intervalMinutes = account.automaticCheckIntervalMinutes
if (intervalMinutes <= Account.INTERVAL_MINUTES_NEVER) {
return null
}
return (intervalMinutes * 60 * 1000).toLong()
}
}

View file

@ -0,0 +1,45 @@
package com.fsck.k9.job
import com.evernote.android.job.Job
import com.fsck.k9.Preferences
import com.fsck.k9.controller.MessagingController
import timber.log.Timber
class PusherRefreshJob(
private val messagingController: MessagingController,
private val preferences: Preferences
) : Job() {
override fun onRunJob(params: Params): Result {
params.extras.getString(K9JobManager.EXTRA_KEY_ACCOUNT_UUID, null)
?.let { accountUuid ->
preferences.getAccount(accountUuid)?.let { account ->
try {
// Refresh pushers
Timber.i("Refreshing pusher for ${account.description}")
messagingController.getPusher(account)?.refresh()
// Whenever we refresh our pushers, send any unsent messages
Timber.d("trying to send mail in all folders!")
messagingController.sendPendingMessages(null)
} catch (e: Exception) {
Timber.e(e, "Exception while refreshing pushers")
return Result.RESCHEDULE
}
}
}
return Result.SUCCESS
}
companion object {
const val TAG: String = "PusherRefreshJob"
}
}

View file

@ -0,0 +1,64 @@
package com.fsck.k9.job
import android.content.Context
import com.evernote.android.job.JobRequest
import com.evernote.android.job.util.support.PersistableBundleCompat
import com.fsck.k9.Account
import com.fsck.k9.Preferences
import com.fsck.k9.controller.MessagingController
import timber.log.Timber
class PusherRefreshJobManager(
private val context: Context,
private val messagingController: MessagingController,
private val preferences: Preferences
) {
fun getJob() = PusherRefreshJob(messagingController, preferences)
fun scheduleJob(account: Account) {
if (!isPushEnabled(account)) {
return
}
getPushIntervalInMillisecondsIfEnabled(account)?.let { syncInterval ->
Timber.v("scheduling pusher refresh job for ${account.description}")
val extras = PersistableBundleCompat()
extras.putString(K9JobManager.EXTRA_KEY_ACCOUNT_UUID, account.uuid)
val jobRequest = JobRequest.Builder(PusherRefreshJob.TAG)
.setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
.setPeriodic(syncInterval)
.setExtras(extras)
.setRequirementsEnforced(true)
.build()
jobRequest.scheduleAsync()
}
}
private fun getPushIntervalInMillisecondsIfEnabled(account: Account): Long? {
val intervalMinutes = account.idleRefreshMinutes
if (intervalMinutes <= Account.INTERVAL_MINUTES_NEVER) {
return null
}
return (intervalMinutes * 60 * 1000).toLong()
}
private fun isPushEnabled(account: Account): Boolean {
if (account.isEnabled && account.isAvailable(context)) {
Timber.i("Setting up pushers for account %s", account.description)
return messagingController.setupPushing(account)
}
return false
}
}

View file

@ -1,84 +0,0 @@
package com.fsck.k9.power;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.IntentFilter;
import android.os.Build;
import android.os.PowerManager;
import timber.log.Timber;
public abstract class DeviceIdleManager {
private static DeviceIdleManager instance;
public static synchronized DeviceIdleManager getInstance(Context context) {
if (instance == null) {
DozeChecker dozeChecker = new DozeChecker(context);
if (dozeChecker.isDeviceIdleModeSupported() && !dozeChecker.isAppWhitelisted()) {
instance = RealDeviceIdleManager.newInstance(context);
} else {
instance = new NoOpDeviceIdleManager();
}
}
return instance;
}
private DeviceIdleManager() {
}
public abstract void registerReceiver();
public abstract void unregisterReceiver();
static class NoOpDeviceIdleManager extends DeviceIdleManager {
@Override
public void registerReceiver() {
// Do nothing
}
@Override
public void unregisterReceiver() {
// Do nothing
}
}
@TargetApi(Build.VERSION_CODES.M)
static class RealDeviceIdleManager extends DeviceIdleManager {
private final Context context;
private final DeviceIdleReceiver deviceIdleReceiver;
private final IntentFilter intentFilter;
private boolean registered;
static RealDeviceIdleManager newInstance(Context context) {
Context appContext = context.getApplicationContext();
return new RealDeviceIdleManager(appContext);
}
private RealDeviceIdleManager(Context context) {
this.context = context;
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
deviceIdleReceiver = new DeviceIdleReceiver(powerManager);
intentFilter = new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
}
@Override
public void registerReceiver() {
Timber.v("Registering DeviceIdleReceiver");
registered = true;
context.registerReceiver(deviceIdleReceiver, intentFilter);
}
@Override
public void unregisterReceiver() {
Timber.v("Unregistering DeviceIdleReceiver");
if (registered) {
context.unregisterReceiver(deviceIdleReceiver);
registered = false;
}
}
}
}

View file

@ -1,33 +0,0 @@
package com.fsck.k9.power;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.PowerManager;
import android.support.annotation.RequiresApi;
import com.fsck.k9.service.MailService;
import timber.log.Timber;
@RequiresApi(api = Build.VERSION_CODES.M)
class DeviceIdleReceiver extends BroadcastReceiver {
private final PowerManager powerManager;
DeviceIdleReceiver(PowerManager powerManager) {
this.powerManager = powerManager;
}
@Override
public void onReceive(Context context, Intent intent) {
boolean deviceInIdleMode = powerManager.isDeviceIdleMode();
Timber.v("Device idle mode changed. Idle: %b", deviceInIdleMode);
if (!deviceInIdleMode) {
MailService.actionReset(context, null);
}
}
}

View file

@ -1,126 +0,0 @@
package com.fsck.k9.service;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.Uri;
import timber.log.Timber;
import com.fsck.k9.K9;
import com.fsck.k9.helper.K9AlarmManager;
public class BootReceiver extends CoreReceiver {
public static final String FIRE_INTENT = "com.fsck.k9.service.BroadcastReceiver.fireIntent";
public static final String SCHEDULE_INTENT = "com.fsck.k9.service.BroadcastReceiver.scheduleIntent";
public static final String CANCEL_INTENT = "com.fsck.k9.service.BroadcastReceiver.cancelIntent";
public static final String ALARMED_INTENT = "com.fsck.k9.service.BroadcastReceiver.pendingIntent";
public static final String AT_TIME = "com.fsck.k9.service.BroadcastReceiver.atTime";
@Override
public Integer receive(Context context, Intent intent, Integer tmpWakeLockId) {
Timber.i("BootReceiver.onReceive %s", intent);
final String action = intent.getAction();
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
//K9.setServicesEnabled(context, tmpWakeLockId);
//tmpWakeLockId = null;
} else if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
MailService.actionCancel(context, tmpWakeLockId);
tmpWakeLockId = null;
} else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
MailService.actionReset(context, tmpWakeLockId);
tmpWakeLockId = null;
} else if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
MailService.connectivityChange(context, tmpWakeLockId);
tmpWakeLockId = null;
} else if ("com.android.sync.SYNC_CONN_STATUS_CHANGED".equals(action)) {
K9.BACKGROUND_OPS bOps = K9.getBackgroundOps();
if (bOps == K9.BACKGROUND_OPS.WHEN_CHECKED_AUTO_SYNC) {
MailService.actionReset(context, tmpWakeLockId);
tmpWakeLockId = null;
}
} else if (FIRE_INTENT.equals(action)) {
Intent alarmedIntent = intent.getParcelableExtra(ALARMED_INTENT);
String alarmedAction = alarmedIntent.getAction();
Timber.i("BootReceiver Got alarm to fire alarmedIntent %s", alarmedAction);
alarmedIntent.putExtra(WAKE_LOCK_ID, tmpWakeLockId);
tmpWakeLockId = null;
context.startService(alarmedIntent);
} else if (SCHEDULE_INTENT.equals(action)) {
long atTime = intent.getLongExtra(AT_TIME, -1);
Intent alarmedIntent = intent.getParcelableExtra(ALARMED_INTENT);
Timber.i("BootReceiver Scheduling intent %s for %tc", alarmedIntent, atTime);
PendingIntent pi = buildPendingIntent(context, intent);
K9AlarmManager alarmMgr = K9AlarmManager.getAlarmManager(context);
alarmMgr.set(AlarmManager.RTC_WAKEUP, atTime, pi);
} else if (CANCEL_INTENT.equals(action)) {
Intent alarmedIntent = intent.getParcelableExtra(ALARMED_INTENT);
Timber.i("BootReceiver Canceling alarmedIntent %s", alarmedIntent);
PendingIntent pi = buildPendingIntent(context, intent);
K9AlarmManager alarmMgr = K9AlarmManager.getAlarmManager(context);
alarmMgr.cancel(pi);
}
return tmpWakeLockId;
}
private PendingIntent buildPendingIntent(Context context, Intent intent) {
Intent alarmedIntent = intent.getParcelableExtra(ALARMED_INTENT);
String alarmedAction = alarmedIntent.getAction();
Intent i = new Intent(context, BootReceiver.class);
i.setAction(FIRE_INTENT);
i.putExtra(ALARMED_INTENT, alarmedIntent);
Uri uri = Uri.parse("action://" + alarmedAction);
i.setData(uri);
return PendingIntent.getBroadcast(context, 0, i, 0);
}
public static void scheduleIntent(Context context, long atTime, Intent alarmedIntent) {
Timber.i("BootReceiver Got request to schedule alarmedIntent %s", alarmedIntent.getAction());
Intent i = new Intent();
i.setClass(context, BootReceiver.class);
i.setAction(SCHEDULE_INTENT);
i.putExtra(ALARMED_INTENT, alarmedIntent);
i.putExtra(AT_TIME, atTime);
context.sendBroadcast(i);
}
public static void cancelIntent(Context context, Intent alarmedIntent) {
Timber.i("BootReceiver Got request to cancel alarmedIntent %s", alarmedIntent.getAction());
Intent i = new Intent();
i.setClass(context, BootReceiver.class);
i.setAction(CANCEL_INTENT);
i.putExtra(ALARMED_INTENT, alarmedIntent);
context.sendBroadcast(i);
}
/**
* Cancel any scheduled alarm.
*
* @param context
*/
public static void purgeSchedule(final Context context) {
final K9AlarmManager alarmService = K9AlarmManager.getAlarmManager(context);
alarmService.cancel(PendingIntent.getBroadcast(context, 0, new Intent() {
@Override
public boolean filterEquals(final Intent other) {
// we want to match all intents
return true;
}
}, 0));
}
}

View file

@ -0,0 +1,103 @@
package com.fsck.k9.service
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.Uri
import com.fsck.k9.K9
import com.fsck.k9.helper.K9AlarmManager
import com.fsck.k9.job.K9JobManager
import org.koin.standalone.KoinComponent
import org.koin.standalone.inject
import timber.log.Timber
class BootReceiver : CoreReceiver(), KoinComponent {
private val jobManager: K9JobManager by inject()
override fun receive(context: Context, intent: Intent, _tmpWakeLockId: Int?): Int? {
var tmpWakeLockId = _tmpWakeLockId
Timber.i("BootReceiver.onReceive %s", intent)
val action = intent.action
if (Intent.ACTION_BOOT_COMPLETED == action) {
//K9.setServicesEnabled(context, tmpWakeLockId);
//tmpWakeLockId = null;
} else if ("com.android.sync.SYNC_CONN_STATUS_CHANGED" == action) {
val bOps = K9.getBackgroundOps()
if (bOps == K9.BACKGROUND_OPS.WHEN_CHECKED_AUTO_SYNC) {
jobManager.scheduleAllMailJobs()
}
} else if (FIRE_INTENT == action) {
val alarmedIntent = intent.getParcelableExtra<Intent>(ALARMED_INTENT)
val alarmedAction = alarmedIntent.action
Timber.i("BootReceiver Got alarm to fire alarmedIntent %s", alarmedAction)
alarmedIntent.putExtra(CoreReceiver.WAKE_LOCK_ID, tmpWakeLockId)
tmpWakeLockId = null
context.startService(alarmedIntent)
} else if (SCHEDULE_INTENT == action) {
val atTime = intent.getLongExtra(AT_TIME, -1)
val alarmedIntent = intent.getParcelableExtra<Intent>(ALARMED_INTENT)
Timber.i("BootReceiver Scheduling intent %s for %tc", alarmedIntent, atTime)
val pi = buildPendingIntent(context, intent)
val alarmMgr = K9AlarmManager.getAlarmManager(context)
alarmMgr.set(AlarmManager.RTC_WAKEUP, atTime, pi)
}
return tmpWakeLockId
}
private fun buildPendingIntent(context: Context, intent: Intent): PendingIntent {
val alarmedIntent = intent.getParcelableExtra<Intent>(ALARMED_INTENT)
val alarmedAction = alarmedIntent.action
val i = Intent(context, BootReceiver::class.java)
i.action = FIRE_INTENT
i.putExtra(ALARMED_INTENT, alarmedIntent)
val uri = Uri.parse("action://" + alarmedAction!!)
i.data = uri
return PendingIntent.getBroadcast(context, 0, i, 0)
}
companion object {
const val FIRE_INTENT = "com.fsck.k9.service.BroadcastReceiver.fireIntent"
const val SCHEDULE_INTENT = "com.fsck.k9.service.BroadcastReceiver.scheduleIntent"
const val ALARMED_INTENT = "com.fsck.k9.service.BroadcastReceiver.pendingIntent"
const val AT_TIME = "com.fsck.k9.service.BroadcastReceiver.atTime"
@JvmStatic
fun scheduleIntent(context: Context, atTime: Long, alarmedIntent: Intent) {
Timber.i("BootReceiver Got request to schedule alarmedIntent %s", alarmedIntent.action)
val i = Intent()
i.setClass(context, BootReceiver::class.java)
i.action = SCHEDULE_INTENT
i.putExtra(ALARMED_INTENT, alarmedIntent)
i.putExtra(AT_TIME, atTime)
context.sendBroadcast(i)
}
/**
* Cancel any scheduled alarm.
*
* @param context
*/
@JvmStatic
fun purgeSchedule(context: Context) {
val alarmService = K9AlarmManager.getAlarmManager(context)
alarmService.cancel(PendingIntent.getBroadcast(context, 0, object : Intent() {
override fun filterEquals(other: Intent): Boolean {
// we want to match all intents
return true
}
}, 0))
}
}
}

View file

@ -1,21 +1,27 @@
package com.fsck.k9.service;
import android.annotation.SuppressLint;
import android.app.Service;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.PowerManager;
import com.fsck.k9.DI;
import com.fsck.k9.K9;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.power.TracingPowerManager;
import com.fsck.k9.power.TracingPowerManager.TracingWakeLock;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.PowerManager;
import timber.log.Timber;
import com.fsck.k9.K9;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.power.TracingPowerManager;
import com.fsck.k9.power.TracingPowerManager.TracingWakeLock;
/**
* {@code CoreService} is the base class for all K-9 Services.
@ -117,7 +123,7 @@ public abstract class CoreService extends Service {
* lock is created, registered, and added to {@code intent}.
*/
protected static void addWakeLockId(Context context, Intent intent, Integer wakeLockId,
boolean createIfNotExists) {
boolean createIfNotExists) {
if (wakeLockId != null) {
intent.putExtra(BootReceiver.WAKE_LOCK_ID, wakeLockId);
@ -125,7 +131,7 @@ public abstract class CoreService extends Service {
}
if (createIfNotExists) {
addWakeLock(context,intent);
addWakeLock(context,intent);
}
}
@ -278,8 +284,8 @@ public abstract class CoreService extends Service {
* If this parameter is {@code null} you need to call {@code setAutoShutdown(false)}
* otherwise the auto shutdown code will stop the service.
*/
public void execute(Context context, final Runnable runner, int wakeLockTime,
final Integer startId) {
public void execute(final Context context, final Runnable runner, int wakeLockTime,
final Integer startId) {
boolean serviceShutdownScheduled = false;
final boolean autoShutdown = mAutoShutdown;
@ -293,8 +299,7 @@ public abstract class CoreService extends Service {
Runnable myRunner = new Runnable() {
public void run() {
try {
// Get the sync status
boolean oldIsSyncDisabled = MailService.isSyncDisabled();
boolean oldIsSyncDisabled = CoreService.isMailSyncDisabled(context);
Timber.d("CoreService (%s) running Runnable %d with startId %d",
className, runner.hashCode(), startId);
@ -304,8 +309,9 @@ public abstract class CoreService extends Service {
// If the sync status changed while runner was executing, notify
// MessagingController
if (MailService.isSyncDisabled() != oldIsSyncDisabled) {
MessagingController.getInstance(getApplication()).systemStatusChanged();
if (CoreService.isMailSyncDisabled(context) != oldIsSyncDisabled) {
MessagingController messagingController = DI.get(MessagingController.class);
messagingController.systemStatusChanged();
}
} finally {
// Making absolutely sure stopSelf() will be called
@ -404,4 +410,28 @@ public abstract class CoreService extends Service {
// Unused
return null;
}
public static boolean isMailSyncDisabled(Context context){
final boolean hasConnectivity = Utility.hasConnectivity(context);
@SuppressLint("MissingPermission")
final boolean autoSync = ContentResolver.getMasterSyncAutomatically();
boolean doBackground = true;
K9.BACKGROUND_OPS bOps = K9.getBackgroundOps();
switch (bOps) {
case NEVER:
doBackground = false;
break;
case ALWAYS:
doBackground = true;
break;
case WHEN_CHECKED_AUTO_SYNC:
doBackground = autoSync;
break;
}
return !(doBackground && hasConnectivity);
}
}

View file

@ -1,429 +0,0 @@
package com.fsck.k9.service;
import java.util.Collection;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.SystemClock;
import com.fsck.k9.Account;
import com.fsck.k9.Account.FolderMode;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Pusher;
import com.fsck.k9.preferences.Storage;
import com.fsck.k9.preferences.StorageEditor;
import timber.log.Timber;
public class MailService extends CoreService {
private static final String ACTION_CHECK_MAIL = "com.fsck.k9.intent.action.MAIL_SERVICE_WAKEUP";
private static final String ACTION_RESET = "com.fsck.k9.intent.action.MAIL_SERVICE_RESET";
private static final String ACTION_RESCHEDULE_POLL = "com.fsck.k9.intent.action.MAIL_SERVICE_RESCHEDULE_POLL";
private static final String ACTION_CANCEL = "com.fsck.k9.intent.action.MAIL_SERVICE_CANCEL";
private static final String ACTION_REFRESH_PUSHERS = "com.fsck.k9.intent.action.MAIL_SERVICE_REFRESH_PUSHERS";
private static final String ACTION_RESTART_PUSHERS = "com.fsck.k9.intent.action.MAIL_SERVICE_RESTART_PUSHERS";
private static final String CONNECTIVITY_CHANGE = "com.fsck.k9.intent.action.MAIL_SERVICE_CONNECTIVITY_CHANGE";
private static final String CANCEL_CONNECTIVITY_NOTICE = "com.fsck.k9.intent.action.MAIL_SERVICE_CANCEL_CONNECTIVITY_NOTICE";
private static long nextCheck = -1;
private static boolean pushingRequested = false;
private static boolean pollingRequested = false;
private static boolean syncNoBackground = false;
private static boolean syncNoConnectivity = false;
private static boolean syncBlocked = false;
public static void actionReset(Context context, Integer wakeLockId) {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_RESET);
addWakeLockId(context, i, wakeLockId, true);
context.startService(i);
}
public static void actionRestartPushers(Context context, Integer wakeLockId) {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_RESTART_PUSHERS);
addWakeLockId(context, i, wakeLockId, true);
context.startService(i);
}
public static void actionReschedulePoll(Context context, Integer wakeLockId) {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_RESCHEDULE_POLL);
addWakeLockId(context, i, wakeLockId, true);
context.startService(i);
}
public static void actionCancel(Context context, Integer wakeLockId) {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.ACTION_CANCEL);
addWakeLockId(context, i, wakeLockId, false); // CK:Q: why should we not create a wake lock if one is not already existing like for example in actionReschedulePoll?
context.startService(i);
}
public static void connectivityChange(Context context, Integer wakeLockId) {
Intent i = new Intent();
i.setClass(context, MailService.class);
i.setAction(MailService.CONNECTIVITY_CHANGE);
addWakeLockId(context, i, wakeLockId, false); // CK:Q: why should we not create a wake lock if one is not already existing like for example in actionReschedulePoll?
context.startService(i);
}
@Override
public void onCreate() {
super.onCreate();
Timber.v("***** MailService *****: onCreate");
}
@Override
public int startService(Intent intent, int startId) {
long startTime = SystemClock.elapsedRealtime();
boolean oldIsSyncDisabled = isSyncDisabled();
boolean doBackground = true;
final boolean hasConnectivity = Utility.hasConnectivity(getApplication());
boolean autoSync = ContentResolver.getMasterSyncAutomatically();
K9.BACKGROUND_OPS bOps = K9.getBackgroundOps();
switch (bOps) {
case NEVER:
doBackground = false;
break;
case ALWAYS:
doBackground = true;
break;
case WHEN_CHECKED_AUTO_SYNC:
doBackground = autoSync;
break;
}
syncNoBackground = !doBackground;
syncNoConnectivity = !hasConnectivity;
syncBlocked = !(doBackground && hasConnectivity);
Timber.i("MailService.onStart(%s, %d), hasConnectivity = %s, doBackground = %s",
intent, startId, hasConnectivity, doBackground);
// MessagingController.getInstance(getApplication()).addListener(mListener);
if (ACTION_CHECK_MAIL.equals(intent.getAction())) {
Timber.i("***** MailService *****: checking mail");
if (hasConnectivity && doBackground) {
PollService.startService(this);
}
reschedulePollInBackground(hasConnectivity, doBackground, startId, false);
} else if (ACTION_CANCEL.equals(intent.getAction())) {
Timber.v("***** MailService *****: cancel");
cancel();
} else if (ACTION_RESET.equals(intent.getAction())) {
Timber.v("***** MailService *****: reschedule");
rescheduleAllInBackground(hasConnectivity, doBackground, startId);
} else if (ACTION_RESTART_PUSHERS.equals(intent.getAction())) {
Timber.v("***** MailService *****: restarting pushers");
reschedulePushersInBackground(hasConnectivity, doBackground, startId);
} else if (ACTION_RESCHEDULE_POLL.equals(intent.getAction())) {
Timber.v("***** MailService *****: rescheduling poll");
reschedulePollInBackground(hasConnectivity, doBackground, startId, true);
} else if (ACTION_REFRESH_PUSHERS.equals(intent.getAction())) {
refreshPushersInBackground(hasConnectivity, doBackground, startId);
} else if (CONNECTIVITY_CHANGE.equals(intent.getAction())) {
rescheduleAllInBackground(hasConnectivity, doBackground, startId);
Timber.i("Got connectivity action with hasConnectivity = %s, doBackground = %s",
hasConnectivity, doBackground);
} else if (CANCEL_CONNECTIVITY_NOTICE.equals(intent.getAction())) {
/* do nothing */
}
if (isSyncDisabled() != oldIsSyncDisabled) {
MessagingController.getInstance(getApplication()).systemStatusChanged();
}
Timber.i("MailService.onStart took %d ms", SystemClock.elapsedRealtime() - startTime);
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
Timber.v("***** MailService *****: onDestroy()");
super.onDestroy();
// MessagingController.getInstance(getApplication()).removeListener(mListener);
}
private void cancel() {
Intent i = new Intent(this, MailService.class);
i.setAction(ACTION_CHECK_MAIL);
BootReceiver.cancelIntent(this, i);
}
private final static String PREVIOUS_INTERVAL = "MailService.previousInterval";
private final static String LAST_CHECK_END = "MailService.lastCheckEnd";
public static void saveLastCheckEnd(Context context) {
long lastCheckEnd = System.currentTimeMillis();
Timber.i("Saving lastCheckEnd = %tc", lastCheckEnd);
Preferences prefs = Preferences.getPreferences(context);
Storage storage = prefs.getStorage();
StorageEditor editor = storage.edit();
editor.putLong(LAST_CHECK_END, lastCheckEnd);
editor.commit();
}
private void rescheduleAllInBackground(final boolean hasConnectivity,
final boolean doBackground, Integer startId) {
execute(getApplication(), new Runnable() {
@Override
public void run() {
reschedulePoll(hasConnectivity, doBackground, true);
reschedulePushers(hasConnectivity, doBackground);
}
}, K9.MAIL_SERVICE_WAKE_LOCK_TIMEOUT, startId);
}
private void reschedulePollInBackground(final boolean hasConnectivity,
final boolean doBackground, Integer startId, final boolean considerLastCheckEnd) {
execute(getApplication(), new Runnable() {
public void run() {
reschedulePoll(hasConnectivity, doBackground, considerLastCheckEnd);
}
}, K9.MAIL_SERVICE_WAKE_LOCK_TIMEOUT, startId);
}
private void reschedulePushersInBackground(final boolean hasConnectivity,
final boolean doBackground, Integer startId) {
execute(getApplication(), new Runnable() {
public void run() {
reschedulePushers(hasConnectivity, doBackground);
}
}, K9.MAIL_SERVICE_WAKE_LOCK_TIMEOUT, startId);
}
private void refreshPushersInBackground(boolean hasConnectivity, boolean doBackground,
Integer startId) {
if (hasConnectivity && doBackground) {
execute(getApplication(), new Runnable() {
public void run() {
refreshPushers();
schedulePushers();
}
}, K9.MAIL_SERVICE_WAKE_LOCK_TIMEOUT, startId);
}
}
private void reschedulePoll(final boolean hasConnectivity, final boolean doBackground,
boolean considerLastCheckEnd) {
if (!(hasConnectivity && doBackground)) {
Timber.i("No connectivity, canceling check for %s", getApplication().getPackageName());
nextCheck = -1;
cancel();
return;
}
Preferences prefs = Preferences.getPreferences(MailService.this);
Storage storage = prefs.getStorage();
int previousInterval = storage.getInt(PREVIOUS_INTERVAL, -1);
long lastCheckEnd = storage.getLong(LAST_CHECK_END, -1);
long now = System.currentTimeMillis();
if (lastCheckEnd > now) {
Timber.i("The database claims that the last time mail was checked was in the future (%tc). To try to get " +
"things back to normal, the last check time has been reset to: %tc", lastCheckEnd, now);
lastCheckEnd = now;
}
int shortestInterval = -1;
for (Account account : prefs.getAvailableAccounts()) {
if (account.getAutomaticCheckIntervalMinutes() != -1 &&
account.getFolderSyncMode() != FolderMode.NONE &&
(account.getAutomaticCheckIntervalMinutes() < shortestInterval ||
shortestInterval == -1)) {
shortestInterval = account.getAutomaticCheckIntervalMinutes();
}
}
StorageEditor editor = storage.edit();
editor.putInt(PREVIOUS_INTERVAL, shortestInterval);
editor.commit();
if (shortestInterval == -1) {
Timber.i("No next check scheduled for package %s", getApplication().getPackageName());
nextCheck = -1;
pollingRequested = false;
cancel();
} else {
long delay = (shortestInterval * (60 * 1000));
long base = (previousInterval == -1 || lastCheckEnd == -1 ||
!considerLastCheckEnd ? System.currentTimeMillis() : lastCheckEnd);
long nextTime = base + delay;
Timber.i("previousInterval = %d, shortestInterval = %d, lastCheckEnd = %tc, considerLastCheckEnd = %b",
previousInterval,
shortestInterval,
lastCheckEnd,
considerLastCheckEnd);
nextCheck = nextTime;
pollingRequested = true;
try {
Timber.i("Next check for package %s scheduled for %tc", getApplication().getPackageName(), nextTime);
} catch (Exception e) {
// I once got a NullPointerException deep in new Date();
Timber.e(e, "Exception while logging");
}
Intent i = new Intent(this, MailService.class);
i.setAction(ACTION_CHECK_MAIL);
BootReceiver.scheduleIntent(MailService.this, nextTime, i);
}
}
public static boolean isSyncDisabled() {
return syncBlocked || (!pollingRequested && !pushingRequested);
}
public static boolean hasNoConnectivity() {
return syncNoConnectivity;
}
public static boolean isSyncNoBackground() {
return syncNoBackground;
}
public static boolean isSyncBlocked() {
return syncBlocked;
}
public static boolean isPollAndPushDisabled() {
return (!pollingRequested && !pushingRequested);
}
private void stopPushers() {
MessagingController.getInstance(getApplication()).stopAllPushing();
PushService.stopService(MailService.this);
}
private void reschedulePushers(boolean hasConnectivity, boolean doBackground) {
Timber.i("Rescheduling pushers");
stopPushers();
if (!(hasConnectivity && doBackground)) {
Timber.i("Not scheduling pushers: connectivity? %s -- doBackground? %s", hasConnectivity, doBackground);
return;
}
setupPushers();
schedulePushers();
}
private void setupPushers() {
boolean pushing = false;
for (Account account : Preferences.getPreferences(MailService.this).getAccounts()) {
Timber.i("Setting up pushers for account %s", account.getDescription());
if (account.isEnabled() && account.isAvailable(getApplicationContext())) {
pushing |= MessagingController.getInstance(getApplication()).setupPushing(account);
} else {
//TODO: setupPushing of unavailable accounts when they become available (sd-card inserted)
}
}
if (pushing) {
PushService.startService(MailService.this);
}
pushingRequested = pushing;
}
private void refreshPushers() {
try {
long nowTime = System.currentTimeMillis();
Timber.i("Refreshing pushers");
Collection<Pusher> pushers = MessagingController.getInstance(getApplication()).getPushers();
for (Pusher pusher : pushers) {
long lastRefresh = pusher.getLastRefresh();
int refreshInterval = pusher.getRefreshInterval();
long sinceLast = nowTime - lastRefresh;
if (sinceLast + 10000 > refreshInterval) { // Add 10 seconds to keep pushers in sync, avoid drift
Timber.d("PUSHREFRESH: refreshing lastRefresh = %d, interval = %d, nowTime = %d, " +
"sinceLast = %d",
lastRefresh,
refreshInterval,
nowTime,
sinceLast);
pusher.refresh();
pusher.setLastRefresh(nowTime);
} else {
Timber.d("PUSHREFRESH: NOT refreshing lastRefresh = %d, interval = %d, nowTime = %d, " +
"sinceLast = %d",
lastRefresh,
refreshInterval,
nowTime,
sinceLast);
}
}
// Whenever we refresh our pushers, send any unsent messages
Timber.d("PUSHREFRESH: trying to send mail in all folders!");
MessagingController.getInstance(getApplication()).sendPendingMessages(null);
} catch (Exception e) {
Timber.e(e, "Exception while refreshing pushers");
}
}
private void schedulePushers() {
int minInterval = -1;
Collection<Pusher> pushers = MessagingController.getInstance(getApplication()).getPushers();
for (Pusher pusher : pushers) {
int interval = pusher.getRefreshInterval();
if (interval > 0 && (interval < minInterval || minInterval == -1)) {
minInterval = interval;
}
}
Timber.v("Pusher refresh interval = %d", minInterval);
if (minInterval > 0) {
long nextTime = System.currentTimeMillis() + minInterval;
Timber.d("Next pusher refresh scheduled for %tc", nextTime);
Intent i = new Intent(this, MailService.class);
i.setAction(ACTION_REFRESH_PUSHERS);
BootReceiver.scheduleIntent(MailService.this, nextTime, i);
}
}
@Override
public IBinder onBind(Intent intent) {
// Unused
return null;
}
public static long getNextPollTime() {
return nextCheck;
}
}

View file

@ -1,148 +0,0 @@
package com.fsck.k9.service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.PowerManager;
import timber.log.Timber;
import com.fsck.k9.*;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.SimpleMessagingListener;
import com.fsck.k9.power.TracingPowerManager;
import com.fsck.k9.power.TracingPowerManager.TracingWakeLock;
import java.util.HashMap;
import java.util.Map;
public class PollService extends CoreService {
private static final String START_SERVICE = "com.fsck.k9.service.PollService.startService";
private static final String STOP_SERVICE = "com.fsck.k9.service.PollService.stopService";
private Listener mListener = new Listener();
public static void startService(Context context) {
Intent i = new Intent();
i.setClass(context, PollService.class);
i.setAction(PollService.START_SERVICE);
addWakeLock(context, i);
context.startService(i);
}
public static void stopService(Context context) {
Intent i = new Intent();
i.setClass(context, PollService.class);
i.setAction(PollService.STOP_SERVICE);
addWakeLock(context, i);
context.startService(i);
}
@Override
public void onCreate() {
super.onCreate();
setAutoShutdown(false);
}
@Override
public int startService(Intent intent, int startId) {
if (START_SERVICE.equals(intent.getAction())) {
Timber.i("PollService started with startId = %d", startId);
MessagingController controller = MessagingController.getInstance(getApplication());
Listener listener = (Listener)controller.getCheckMailListener();
if (listener == null) {
Timber.i("***** PollService *****: starting new check");
mListener.setStartId(startId);
mListener.wakeLockAcquire();
controller.setCheckMailListener(mListener);
controller.checkMail(this, null, false, false, mListener);
} else {
Timber.i("***** PollService *****: renewing WakeLock");
listener.setStartId(startId);
listener.wakeLockAcquire();
}
} else if (STOP_SERVICE.equals(intent.getAction())) {
Timber.i("PollService stopping");
stopSelf();
}
return START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
class Listener extends SimpleMessagingListener {
Map<String, Integer> accountsChecked = new HashMap<>();
private TracingWakeLock wakeLock = null;
private int startId = -1;
// wakelock strategy is to be very conservative. If there is any reason to release, then release
// don't want to take the chance of running wild
public synchronized void wakeLockAcquire() {
TracingWakeLock oldWakeLock = wakeLock;
TracingPowerManager pm = TracingPowerManager.getPowerManager(PollService.this);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "PollService wakeLockAcquire");
wakeLock.setReferenceCounted(false);
wakeLock.acquire(K9.WAKE_LOCK_TIMEOUT);
if (oldWakeLock != null) {
oldWakeLock.release();
}
}
public synchronized void wakeLockRelease() {
if (wakeLock != null) {
wakeLock.release();
wakeLock = null;
}
}
@Override
public void checkMailStarted(Context context, Account account) {
accountsChecked.clear();
}
@Override
public void synchronizeMailboxFinished(
Account account,
String folderServerId,
int totalMessagesInMailbox,
int numNewMessages) {
if (account.isNotifyNewMail()) {
Integer existingNewMessages = accountsChecked.get(account.getUuid());
if (existingNewMessages == null) {
existingNewMessages = 0;
}
accountsChecked.put(account.getUuid(), existingNewMessages + numNewMessages);
}
}
private void release() {
MessagingController controller = MessagingController.getInstance(getApplication());
controller.setCheckMailListener(null);
MailService.saveLastCheckEnd(getApplication());
MailService.actionReschedulePoll(PollService.this, null);
wakeLockRelease();
Timber.i("PollService stopping with startId = %d", startId);
stopSelf(startId);
}
@Override
public void checkMailFinished(Context context, Account account) {
Timber.v("***** PollService *****: checkMailFinished");
release();
}
public int getStartId() {
return startId;
}
public void setStartId(int startId) {
this.startId = startId;
}
}
}

View file

@ -331,15 +331,6 @@
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.DEVICE_STORAGE_LOW"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.DEVICE_STORAGE_OK"/>
</intent-filter>
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
<intent-filter>
<action android:name="android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"/>
</intent-filter>
@ -417,10 +408,6 @@
android:enabled="true"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<service
android:name=".service.MailService"
android:enabled="true"/>
<service
android:name=".notification.NotificationActionService"
android:enabled="true"/>
@ -429,10 +416,6 @@
android:name=".service.PushService"
android:enabled="true"/>
<service
android:name=".service.PollService"
android:enabled="true"/>
<service
android:name=".external.remotecontrol.RemoteControlService"
android:enabled="true"
@ -460,11 +443,11 @@
android:authorities="${applicationId}.attachmentprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="de.cketti.safecontentresolver.ALLOW_INTERNAL_ACCESS"
android:value="true" />
</provider>
<provider

View file

@ -1,23 +1,25 @@
package com.fsck.k9.external.remotecontrol;
import java.util.List;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
import com.fsck.k9.Account;
import com.fsck.k9.Account.FolderMode;
import com.fsck.k9.DI;
import com.fsck.k9.K9;
import com.fsck.k9.K9.BACKGROUND_OPS;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.job.K9JobManager;
import com.fsck.k9.preferences.Storage;
import com.fsck.k9.preferences.StorageEditor;
import com.fsck.k9.service.BootReceiver;
import com.fsck.k9.service.CoreService;
import com.fsck.k9.service.MailService;
import java.util.List;
import timber.log.Timber;
import static com.fsck.k9.external.remotecontrol.K9RemoteControl.K9_ACCOUNT_UUID;
@ -37,6 +39,9 @@ public class RemoteControlService extends CoreService {
private final static String SET_ACTION = "com.fsck.k9.service.RemoteControlService.SET_ACTION";
private final Preferences preferences = DI.get(Preferences.class);
private final K9JobManager jobManager = DI.get(K9JobManager.class);
public static void set(Context context, Intent i, Integer wakeLockId) {
// Intent i = new Intent();
i.setClass(context, RemoteControlService.class);
@ -50,15 +55,14 @@ public class RemoteControlService extends CoreService {
@Override
public int startService(final Intent intent, final int startId) {
Timber.i("RemoteControlService started with startId = %d", startId);
final Preferences preferences = Preferences.getPreferences(this);
if (RESCHEDULE_ACTION.equals(intent.getAction())) {
Timber.i("RemoteControlService requesting MailService poll reschedule");
MailService.actionReschedulePoll(this, null);
Timber.i("RemoteControlService requesting jobmanager mail poll reschedule");
jobManager.scheduleMailSync();
}
if (PUSH_RESTART_ACTION.equals(intent.getAction())) {
Timber.i("RemoteControlService requesting MailService push restart");
MailService.actionRestartPushers(this, null);
Timber.i("RemoteControlService requesting jobmanager push restart");
jobManager.schedulePusherRefresh();
} else if (RemoteControlService.SET_ACTION.equals(intent.getAction())) {
Timber.i("RemoteControlService got request to change settings");
execute(getApplication(), new Runnable() {

View file

@ -5,14 +5,13 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.text.format.DateUtils;
import com.fsck.k9.Account;
import com.fsck.k9.AccountStats;
import com.fsck.k9.K9;
import com.fsck.k9.ui.R;
import com.fsck.k9.controller.SimpleMessagingListener;
import com.fsck.k9.service.MailService;
import com.fsck.k9.service.CoreService;
import com.fsck.k9.ui.R;
import net.jcip.annotations.GuardedBy;
@ -49,24 +48,7 @@ public class ActivityListener extends SimpleMessagingListener {
}
}
long nextPollTime = MailService.getNextPollTime();
if (nextPollTime != -1) {
CharSequence relativeTimeSpanString = DateUtils.getRelativeTimeSpanString(
nextPollTime, System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0);
return context.getString(R.string.status_next_poll, relativeTimeSpanString);
} else if (K9.isDebug() && MailService.isSyncDisabled()) {
if (MailService.hasNoConnectivity()) {
return context.getString(R.string.status_no_network);
} else if (MailService.isSyncNoBackground()) {
return context.getString(R.string.status_no_background);
} else if (MailService.isSyncBlocked()) {
return context.getString(R.string.status_syncing_blocked);
} else if (MailService.isPollAndPushDisabled()) {
return context.getString(R.string.status_poll_and_push_disabled);
} else {
return context.getString(R.string.status_syncing_off);
}
} else if (MailService.isSyncDisabled()) {
if (CoreService.isMailSyncDisabled(context)) {
return context.getString(R.string.status_syncing_off);
} else {
return "";

View file

@ -1,12 +1,6 @@
package com.fsck.k9.activity;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
@ -51,17 +45,24 @@ import com.fsck.k9.activity.setup.FolderSettings;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.controller.SimpleMessagingListener;
import com.fsck.k9.ui.helper.SizeFormatter;
import com.fsck.k9.job.K9JobManager;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.power.TracingPowerManager;
import com.fsck.k9.power.TracingPowerManager.TracingWakeLock;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.SearchField;
import com.fsck.k9.service.MailService;
import com.fsck.k9.ui.helper.SizeFormatter;
import com.fsck.k9.ui.settings.SettingsActivity;
import com.fsck.k9.view.ColorChip;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import de.cketti.library.changelog.ChangeLog;
import timber.log.Timber;
@ -78,6 +79,7 @@ public class FolderList extends K9ListActivity {
private static final boolean REFRESH_REMOTE = true;
private final ColorChipProvider colorChipProvider = DI.get(ColorChipProvider.class);
private final K9JobManager jobManager = DI.get(K9JobManager.class);
private ListView listView;
@ -423,7 +425,7 @@ public class FolderList extends K9ListActivity {
account.setFolderDisplayMode(newMode);
Preferences.getPreferences(getApplicationContext()).saveAccount(account);
if (account.getFolderPushMode() != FolderMode.NONE) {
MailService.actionRestartPushers(this, null);
jobManager.schedulePusherRefresh();
}
adapter.getFilter().filter(null);
onRefresh(false);

View file

@ -2,11 +2,6 @@
package com.fsck.k9.activity.setup;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
@ -32,14 +27,13 @@ import com.fsck.k9.Account.FolderMode;
import com.fsck.k9.DI;
import com.fsck.k9.LocalKeyStoreManager;
import com.fsck.k9.Preferences;
import com.fsck.k9.backend.BackendManager;
import com.fsck.k9.preferences.Protocols;
import com.fsck.k9.ui.R;
import com.fsck.k9.account.AccountCreator;
import com.fsck.k9.activity.K9Activity;
import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection;
import com.fsck.k9.backend.BackendManager;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.job.K9JobManager;
import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.MailServerDirection;
@ -47,9 +41,16 @@ import com.fsck.k9.mail.NetworkType;
import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.store.imap.ImapStoreSettings;
import com.fsck.k9.mail.store.webdav.WebDavStoreSettings;
import com.fsck.k9.service.MailService;
import com.fsck.k9.preferences.Protocols;
import com.fsck.k9.ui.R;
import com.fsck.k9.view.ClientCertificateSpinner;
import com.fsck.k9.view.ClientCertificateSpinner.OnClientCertificateChangedListener;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import timber.log.Timber;
public class AccountSetupIncoming extends K9Activity implements OnClickListener {
@ -60,6 +61,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
private final MessagingController messagingController = DI.get(MessagingController.class);
private final BackendManager backendManager = DI.get(BackendManager.class);
private final K9JobManager jobManager = DI.get(K9JobManager.class);
private String mStoreType;
private EditText mUsernameView;
@ -514,7 +516,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
if (Intent.ACTION_EDIT.equals(getIntent().getAction())) {
boolean isPushCapable = messagingController.isPushCapable(mAccount);
if (isPushCapable && mAccount.getFolderPushMode() != FolderMode.NONE) {
MailService.actionRestartPushers(this, null);
jobManager.schedulePusherRefresh();
}
Preferences.getPreferences(getApplicationContext()).saveAccount(mAccount);
finish();

View file

@ -17,12 +17,13 @@ import com.fsck.k9.ui.R;
import com.fsck.k9.activity.FolderInfoHolder;
import com.fsck.k9.activity.K9PreferenceActivity;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.job.K9JobManager;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Folder.FolderClass;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.service.MailService;
import timber.log.Timber;
public class FolderSettings extends K9PreferenceActivity {
@ -39,6 +40,7 @@ public class FolderSettings extends K9PreferenceActivity {
private static final String PREFERENCE_INTEGRATE = "folder_settings_include_in_integrated_inbox";
private final MessagingController messagingController = DI.get(MessagingController.class);
private final K9JobManager jobManager = DI.get(K9JobManager.class);
private LocalFolder mFolder;
@ -160,7 +162,7 @@ public class FolderSettings extends K9PreferenceActivity {
if (oldPushClass != newPushClass
|| (newPushClass != FolderClass.NO_CLASS && oldDisplayClass != newDisplayClass)) {
MailService.actionRestartPushers(getApplication(), null);
jobManager.schedulePusherRefresh();
}
}

View file

@ -1,18 +1,17 @@
package com.fsck.k9.ui.settings.account
import android.content.Context
import android.support.v7.preference.PreferenceDataStore
import com.fsck.k9.Account
import com.fsck.k9.Account.SpecialFolderSelection
import com.fsck.k9.Preferences
import com.fsck.k9.service.MailService
import com.fsck.k9.job.K9JobManager
import java.util.concurrent.ExecutorService
class AccountSettingsDataStore(
private val context: Context,
private val preferences: Preferences,
private val executorService: ExecutorService,
private val account: Account
private val account: Account,
private val jobManager: K9JobManager
) : PreferenceDataStore() {
override fun getBoolean(key: String, defValue: Boolean): Boolean {
@ -216,11 +215,11 @@ class AccountSettingsDataStore(
}
private fun reschedulePoll() {
MailService.actionReschedulePoll(context, null)
jobManager.scheduleMailSync()
}
private fun restartPushers() {
MailService.actionRestartPushers(context, null)
jobManager.schedulePusherRefresh()
}
private fun extractFolderName(preferenceValue: String): String? {

View file

@ -1,16 +1,16 @@
package com.fsck.k9.ui.settings.account
import android.content.Context
import com.fsck.k9.Account
import com.fsck.k9.Preferences
import com.fsck.k9.job.K9JobManager
import java.util.concurrent.ExecutorService
class AccountSettingsDataStoreFactory(
private val context: Context,
private val preferences: Preferences,
private val jobManager: K9JobManager,
private val executorService: ExecutorService
) {
fun create(account: Account): AccountSettingsDataStore {
return AccountSettingsDataStore(context, preferences, executorService, account)
return AccountSettingsDataStore(preferences, executorService, account, jobManager)
}
}

View file

@ -1,17 +1,16 @@
package com.fsck.k9.ui.settings.general
import android.content.Context
import android.support.v4.app.FragmentActivity
import android.support.v7.preference.PreferenceDataStore
import com.fsck.k9.K9
import com.fsck.k9.K9.Theme
import com.fsck.k9.Preferences
import com.fsck.k9.service.MailService
import com.fsck.k9.job.K9JobManager
import java.util.concurrent.ExecutorService
class GeneralSettingsDataStore(
private val context: Context,
private val preferences: Preferences,
private val jobManager: K9JobManager,
private val executorService: ExecutorService
) : PreferenceDataStore() {
var activity: FragmentActivity? = null
@ -241,14 +240,10 @@ class GeneralSettingsDataStore(
val newBackgroundOps = K9.BACKGROUND_OPS.valueOf(value)
if (newBackgroundOps != K9.getBackgroundOps()) {
K9.setBackgroundOps(value)
resetMailService()
jobManager.scheduleAllMailJobs()
}
}
private fun resetMailService() {
MailService.actionReset(context, null)
}
private fun recreateActivity() {
activity?.recreate()
}