Added setting to use unmetered connections only

pull/146/head
M66B 7 years ago
parent fc10cb600a
commit 57ad9e0e92

@ -42,6 +42,7 @@ import androidx.appcompat.widget.SwitchCompat;
public class FragmentOptions extends FragmentEx implements SharedPreferences.OnSharedPreferenceChangeListener { public class FragmentOptions extends FragmentEx implements SharedPreferences.OnSharedPreferenceChangeListener {
private SwitchCompat swEnabled; private SwitchCompat swEnabled;
private SwitchCompat swMetered;
private SwitchCompat swAvatars; private SwitchCompat swAvatars;
private SwitchCompat swIdenticons; private SwitchCompat swIdenticons;
private SwitchCompat swCompact; private SwitchCompat swCompact;
@ -67,6 +68,7 @@ public class FragmentOptions extends FragmentEx implements SharedPreferences.OnS
// Get controls // Get controls
swEnabled = view.findViewById(R.id.swEnabled); swEnabled = view.findViewById(R.id.swEnabled);
swMetered = view.findViewById(R.id.swMetered);
swCompact = view.findViewById(R.id.swCompact); swCompact = view.findViewById(R.id.swCompact);
swAvatars = view.findViewById(R.id.swAvatars); swAvatars = view.findViewById(R.id.swAvatars);
swIdenticons = view.findViewById(R.id.swIdenticons); swIdenticons = view.findViewById(R.id.swIdenticons);
@ -92,10 +94,16 @@ public class FragmentOptions extends FragmentEx implements SharedPreferences.OnS
@Override @Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("enabled", checked).apply(); prefs.edit().putBoolean("enabled", checked).apply();
if (checked) ServiceSynchronize.reload(getContext(), "enabled=" + checked);
ServiceSynchronize.start(getContext()); }
else });
ServiceSynchronize.stop(getContext());
swMetered.setChecked(prefs.getBoolean("metered", true));
swMetered.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("metered", checked).apply();
ServiceSynchronize.reload(getContext(), "metered=" + checked);
} }
}); });

@ -63,10 +63,9 @@ public class MessageHelper {
private MimeMessage imessage; private MimeMessage imessage;
private String raw = null; private String raw = null;
final static int NETWORK_TIMEOUT = 60 * 1000; // milliseconds private final static int NETWORK_TIMEOUT = 60 * 1000; // milliseconds
final static int CLOSE_TIMEOUT = 20 * 1000; // milliseconds private final static int FETCH_SIZE = 1024 * 1024; // bytes, default 16K
final static int FETCH_SIZE = 1024 * 1024; // bytes, default 16K private final static int POOL_TIMEOUT = 3 * 60 * 1000; // milliseconds, default 45 sec
final static int POOL_TIMEOUT = 3 * 60 * 1000; // milliseconds, default 45 sec
static Properties getSessionProperties(int auth_type, boolean insecure) { static Properties getSessionProperties(int auth_type, boolean insecure) {
Properties props = new Properties(); Properties props = new Properties();

@ -22,22 +22,14 @@ package eu.faircode.email;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.util.Log;
public class ReceiverAutostart extends BroadcastReceiver { public class ReceiverAutostart extends BroadcastReceiver {
@Override @Override
public void onReceive(final Context context, Intent intent) { public void onReceive(final Context context, Intent intent) {
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction()) || if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction()) ||
Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())) Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())) {
new Thread(new Runnable() { EntityLog.log(context, intent.getAction());
@Override ServiceSynchronize.init(context);
public void run() { }
int synchronizing = DB.getInstance(context).account().getSynchronizingAccountCount();
Log.i(Helper.TAG, "Synchronizing accounts=" + synchronizing);
if (synchronizing > 0)
ServiceSynchronize.init(context);
JobDaily.schedule(context);
}
}).start();
} }
} }

@ -36,7 +36,6 @@ import android.media.RingtoneManager;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.Network; import android.net.Network;
import android.net.NetworkCapabilities; import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest; import android.net.NetworkRequest;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
@ -136,9 +135,10 @@ public class ServiceSynchronize extends LifecycleService {
private static final int CONNECT_BACKOFF_AlARM = 15; // minutes private static final int CONNECT_BACKOFF_AlARM = 15; // minutes
private static final int SYNC_BATCH_SIZE = 20; private static final int SYNC_BATCH_SIZE = 20;
private static final int DOWNLOAD_BATCH_SIZE = 20; private static final int DOWNLOAD_BATCH_SIZE = 20;
private static final long RECONNECT_BACKOFF = 60 * 1000L; // milliseconds private static final long RECONNECT_BACKOFF = 90 * 1000L; // milliseconds
private static final int PREVIEW_SIZE = 250; private static final int PREVIEW_SIZE = 250;
private static final int ACCOUNT_ERROR_AFTER = 90; // minutes private static final int ACCOUNT_ERROR_AFTER = 90; // minutes
private static final long STOP_DELAY = 5000L; // milliseconds
static final int PI_WHY = 1; static final int PI_WHY = 1;
static final int PI_CLEAR = 2; static final int PI_CLEAR = 2;
@ -242,7 +242,7 @@ public class ServiceSynchronize extends LifecycleService {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
cm.unregisterNetworkCallback(serviceManager); cm.unregisterNetworkCallback(serviceManager);
serviceManager.onLost(null); serviceManager.service_destroy();
Widget.update(this, -1); Widget.update(this, -1);
@ -279,13 +279,15 @@ public class ServiceSynchronize extends LifecycleService {
prefs.edit().putBoolean("why", true).apply(); prefs.edit().putBoolean("why", true).apply();
startActivity(why); startActivity(why);
} }
} else if ("start".equals(action))
serviceManager.queue_start(); } else if ("init".equals(action)) {
else if ("stop".equals(action)) // Network events will manage the service
serviceManager.queue_stop(); serviceManager.service_init();
else if ("reload".equals(action))
serviceManager.queue_reload(); } else if ("reload".equals(action)) {
else if ("clear".equals(action)) { serviceManager.queue_reload(true, intent.getStringExtra("reason"));
} else if ("clear".equals(action)) {
new SimpleTask<Void>() { new SimpleTask<Void>() {
@Override @Override
protected Void onLoad(Context context, Bundle args) { protected Void onLoad(Context context, Bundle args) {
@ -293,6 +295,7 @@ public class ServiceSynchronize extends LifecycleService {
return null; return null;
} }
}.load(this, new Bundle()); }.load(this, new Bundle());
} else if (action.startsWith("seen:") || } else if (action.startsWith("seen:") ||
action.startsWith("archive:") || action.startsWith("archive:") ||
action.startsWith("trash:") || action.startsWith("trash:") ||
@ -668,6 +671,8 @@ public class ServiceSynchronize extends LifecycleService {
nm.notify(action, 1, getNotificationError(action, ex).build()); nm.notify(action, 1, getNotificationError(action, ex).build());
} }
// connection failure: Too many simultaneous connections
if (BuildConfig.DEBUG && if (BuildConfig.DEBUG &&
!(ex instanceof SendFailedException) && !(ex instanceof SendFailedException) &&
!(ex instanceof MailConnectException) && !(ex instanceof MailConnectException) &&
@ -731,7 +736,7 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(Helper.TAG, account.name + " event: " + e.getMessage()); Log.i(Helper.TAG, account.name + " event: " + e.getMessage());
if (BuildConfig.DEBUG) if (BuildConfig.DEBUG)
db.account().setAccountError(account.id, e.getMessage()); db.account().setAccountError(account.id, e.getMessage());
state.semaphore.release(); state.thread.interrupt();
yieldWakelock(); yieldWakelock();
} finally { } finally {
wl.release(); wl.release();
@ -751,7 +756,6 @@ public class ServiceSynchronize extends LifecycleService {
wl.acquire(); wl.acquire();
Log.i(Helper.TAG, "Folder created=" + e.getFolder().getFullName()); Log.i(Helper.TAG, "Folder created=" + e.getFolder().getFullName());
reload(ServiceSynchronize.this, "folder created"); reload(ServiceSynchronize.this, "folder created");
yieldWakelock();
} finally { } finally {
wl.release(); wl.release();
} }
@ -769,7 +773,6 @@ public class ServiceSynchronize extends LifecycleService {
Log.i(Helper.TAG, "Renamed to " + name + " count=" + count); Log.i(Helper.TAG, "Renamed to " + name + " count=" + count);
reload(ServiceSynchronize.this, "folder renamed"); reload(ServiceSynchronize.this, "folder renamed");
yieldWakelock();
} finally { } finally {
wl.release(); wl.release();
} }
@ -781,7 +784,6 @@ public class ServiceSynchronize extends LifecycleService {
wl.acquire(); wl.acquire();
Log.i(Helper.TAG, "Folder deleted=" + e.getFolder().getFullName()); Log.i(Helper.TAG, "Folder deleted=" + e.getFolder().getFullName());
reload(ServiceSynchronize.this, "folder deleted"); reload(ServiceSynchronize.this, "folder deleted");
yieldWakelock();
} finally { } finally {
wl.release(); wl.release();
} }
@ -852,7 +854,11 @@ public class ServiceSynchronize extends LifecycleService {
try { try {
ifolder.open(Folder.READ_WRITE); ifolder.open(Folder.READ_WRITE);
} catch (Throwable ex) { } catch (Throwable ex) {
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); if (ex instanceof MessagingException && "connection failure".equals(ex.getMessage())) {
Throwable ex1 = new MessagingException("Too many simultaneous connections?", (MessagingException) ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex1));
} else
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
throw ex; throw ex;
} }
folders.put(folder, ifolder); folders.put(folder, ifolder);
@ -923,10 +929,8 @@ public class ServiceSynchronize extends LifecycleService {
} catch (Throwable ex) { } catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account.name, folder.name, ex); reportError(account.name, folder.name, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.thread.interrupt();
state.semaphore.release();
yieldWakelock(); yieldWakelock();
} finally { } finally {
wl.release(); wl.release();
@ -954,10 +958,9 @@ public class ServiceSynchronize extends LifecycleService {
} catch (Throwable ex) { } catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account.name, folder.name, ex); reportError(account.name, folder.name, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.thread.interrupt();
state.semaphore.release(); yieldWakelock();
} finally { } finally {
wl.release(); wl.release();
} }
@ -1008,10 +1011,8 @@ public class ServiceSynchronize extends LifecycleService {
} catch (Throwable ex) { } catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account.name, folder.name, ex); reportError(account.name, folder.name, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.thread.interrupt();
state.semaphore.release();
yieldWakelock(); yieldWakelock();
} finally { } finally {
wl.release(); wl.release();
@ -1022,10 +1023,8 @@ public class ServiceSynchronize extends LifecycleService {
} catch (Throwable ex) { } catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account.name, folder.name, ex); reportError(account.name, folder.name, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.thread.interrupt();
state.semaphore.release();
yieldWakelock(); yieldWakelock();
} finally { } finally {
wl.release(); wl.release();
@ -1051,10 +1050,8 @@ public class ServiceSynchronize extends LifecycleService {
} catch (Throwable ex) { } catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account.name, folder.name, ex); reportError(account.name, folder.name, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
state.thread.interrupt();
state.semaphore.release();
yieldWakelock(); yieldWakelock();
} finally { } finally {
Log.i(Helper.TAG, folder.name + " end idle"); Log.i(Helper.TAG, folder.name + " end idle");
@ -1129,7 +1126,6 @@ public class ServiceSynchronize extends LifecycleService {
} catch (Throwable ex) { } catch (Throwable ex) {
Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex)); Log.e(Helper.TAG, folder.name + " " + ex + "\n" + Log.getStackTraceString(ex));
reportError(account.name, folder.name, ex); reportError(account.name, folder.name, ex);
db.folder().setFolderError(folder.id, Helper.formatThrowable(ex)); db.folder().setFolderError(folder.id, Helper.formatThrowable(ex));
} finally { } finally {
if (shouldClose) { if (shouldClose) {
@ -1248,29 +1244,11 @@ public class ServiceSynchronize extends LifecycleService {
// Close store // Close store
try { try {
Thread t = new Thread(new Runnable() { EntityLog.log(ServiceSynchronize.this, account.name + " store closing");
@Override istore.close();
public void run() { EntityLog.log(ServiceSynchronize.this, account.name + " store closed");
try { } catch (Throwable ex) {
EntityLog.log(ServiceSynchronize.this, account.name + " store closing"); Log.w(Helper.TAG, account.name + " " + ex + "\n" + Log.getStackTraceString(ex));
istore.close();
EntityLog.log(ServiceSynchronize.this, account.name + " store closed");
} catch (Throwable ex) {
Log.w(Helper.TAG, account.name + " " + ex + "\n" + Log.getStackTraceString(ex));
}
}
});
t.start();
try {
t.join(MessageHelper.CLOSE_TIMEOUT);
if (t.isAlive()) {
Log.w(Helper.TAG, account.name + " Close timeout");
t.interrupt();
}
} catch (InterruptedException ex) {
Log.w(Helper.TAG, account.name + " close wait " + ex.toString());
t.interrupt();
}
} finally { } finally {
EntityLog.log(this, account.name + " closed"); EntityLog.log(this, account.name + " closed");
db.account().setAccountState(account.id, null); db.account().setAccountState(account.id, null);
@ -2271,68 +2249,78 @@ public class ServiceSynchronize extends LifecycleService {
private class ServiceManager extends ConnectivityManager.NetworkCallback { private class ServiceManager extends ConnectivityManager.NetworkCallback {
private ServiceState state; private ServiceState state;
private boolean running = false; private boolean started = false;
private int queued = 0; private int queued = 0;
private long lastLost = 0; private long lastLost = 0;
private EntityFolder outbox = null; private EntityFolder outbox = null;
private ExecutorService lifecycle = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory); private ExecutorService lifecycle = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
private ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory); private ExecutorService executor = Executors.newSingleThreadExecutor(Helper.backgroundThreadFactory);
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ServiceSynchronize.this);
boolean metered = prefs.getBoolean("metered", true);
ConnectivityManager cm = getSystemService(ConnectivityManager.class);
NetworkCapabilities nc = cm.getNetworkCapabilities(network);
boolean unmetered = nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
if (!started && (metered || unmetered))
EntityLog.log(ServiceSynchronize.this, "Network " + network + " capabilities " + capabilities);
if (!started && suitableNetwork())
queue_reload(true, "connect " + network);
}
@Override @Override
public void onAvailable(Network network) { public void onAvailable(Network network) {
ConnectivityManager cm = getSystemService(ConnectivityManager.class); ConnectivityManager cm = getSystemService(ConnectivityManager.class);
NetworkInfo ni = cm.getNetworkInfo(network); EntityLog.log(ServiceSynchronize.this, "Available " + network + " " + cm.getNetworkInfo(network));
EntityLog.log(ServiceSynchronize.this, "Network available " + network + " running=" + running + " " + ni);
if (!running) { if (!started && suitableNetwork())
running = true; queue_reload(true, "connect " + network);
queued++;
lifecycle.submit(new Runnable() {
@Override
public void run() {
try {
Log.i(Helper.TAG, "Starting service");
start();
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
} finally {
queued--;
}
}
});
}
} }
@Override @Override
public void onLost(Network network) { public void onLost(Network network) {
EntityLog.log(ServiceSynchronize.this, "Network lost " + network + " running=" + running); EntityLog.log(ServiceSynchronize.this, "Lost " + network);
if (running) { if (started && !suitableNetwork()) {
ConnectivityManager cm = getSystemService(ConnectivityManager.class); lastLost = new Date().getTime();
NetworkInfo ani = (network == null ? null : cm.getActiveNetworkInfo()); queue_reload(false, "disconnect " + network);
EntityLog.log(ServiceSynchronize.this, "Network active=" + (ani == null ? null : ani.toString()));
if (ani == null || !ani.isConnected()) {
EntityLog.log(ServiceSynchronize.this, "Network disconnected=" + ani);
running = false;
lastLost = new Date().getTime();
queued++;
lifecycle.submit(new Runnable() {
@Override
public void run() {
try {
stop();
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
} finally {
queued--;
}
}
});
}
} }
} }
private void start() { private boolean suitableNetwork() {
ConnectivityManager cm = getSystemService(ConnectivityManager.class);
Network network = cm.getActiveNetwork();
NetworkCapabilities nc = (network == null ? null : cm.getNetworkCapabilities(network));
boolean unmetered = (!cm.isActiveNetworkMetered() ||
(nc != null && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)));
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ServiceSynchronize.this);
boolean metered = prefs.getBoolean("metered", true);
// The connected state is deliberately ignored
return (metered || unmetered);
}
private boolean isEnabled() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ServiceSynchronize.this);
return prefs.getBoolean("enabled", true);
}
private void service_init() {
EntityLog.log(ServiceSynchronize.this, "Service init");
}
private void service_destroy() {
EntityLog.log(ServiceSynchronize.this, "Service destroy");
if (started)
queue_reload(false, "service destroy");
}
private void _start() {
EntityLog.log(ServiceSynchronize.this, "Main start queued=" + queued); EntityLog.log(ServiceSynchronize.this, "Main start queued=" + queued);
state = new ServiceState(); state = new ServiceState();
@ -2352,15 +2340,7 @@ public class ServiceSynchronize extends LifecycleService {
outbox = db.folder().getOutbox(); outbox = db.folder().getOutbox();
if (outbox == null) { if (outbox == null) {
EntityLog.log(ServiceSynchronize.this, "No outbox, halt"); EntityLog.log(ServiceSynchronize.this, "No outbox");
serviceManager.queue_stop();
return;
}
List<EntityAccount> accounts = db.account().getAccounts(true);
if (accounts.size() == 0) {
EntityLog.log(ServiceSynchronize.this, "No accounts, halt");
serviceManager.queue_stop();
return; return;
} }
@ -2391,6 +2371,7 @@ public class ServiceSynchronize extends LifecycleService {
.putExtra("folder", outbox.id)); .putExtra("folder", outbox.id));
// Start monitoring accounts // Start monitoring accounts
List<EntityAccount> accounts = db.account().getAccounts(true);
for (final EntityAccount account : accounts) { for (final EntityAccount account : accounts) {
Log.i(Helper.TAG, account.host + "/" + account.user + " run"); Log.i(Helper.TAG, account.host + "/" + account.user + " run");
final ServiceState astate = new ServiceState(); final ServiceState astate = new ServiceState();
@ -2425,8 +2406,9 @@ public class ServiceSynchronize extends LifecycleService {
for (ServiceState astate : threadState) { for (ServiceState astate : threadState) {
astate.running = false; astate.running = false;
astate.semaphore.release(); astate.semaphore.release();
join(astate.thread);
} }
for (ServiceState astate : threadState)
join(astate.thread);
threadState.clear(); threadState.clear();
// Stop monitoring outbox // Stop monitoring outbox
@ -2449,7 +2431,7 @@ public class ServiceSynchronize extends LifecycleService {
yieldWakelock(); yieldWakelock();
} }
private void stop() { private void _stop() {
PowerManager pm = getSystemService(PowerManager.class); PowerManager pm = getSystemService(PowerManager.class);
PowerManager.WakeLock wl = pm.newWakeLock( PowerManager.WakeLock wl = pm.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, PowerManager.PARTIAL_WAKE_LOCK,
@ -2471,68 +2453,41 @@ public class ServiceSynchronize extends LifecycleService {
} }
} }
private void queue_reload() { private void queue_reload(final boolean start, String reason) {
if (running) { EntityLog.log(ServiceSynchronize.this, "Reload start=" + start +
queued++; " started=" + started + " queued=" + queued + " " + reason);
lifecycle.submit(new Runnable() {
@Override
public void run() {
try {
stop();
start();
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
} finally {
queued--;
}
}
});
}
}
private void queue_start() { final boolean doStop = started;
if (!running) { final boolean doStart = (start && isEnabled() && suitableNetwork());
running = true;
queued++;
lifecycle.submit(new Runnable() {
@Override
public void run() {
try {
start();
} catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
} finally {
queued--;
}
}
});
}
}
private void queue_stop() { queued++;
if (running) { lifecycle.submit(new Runnable() {
running = false; @Override
queued++; public void run() {
lifecycle.submit(new Runnable() { try {
@Override if (doStop)
public void run() { _stop();
try { if (doStart)
stop(); _start();
} catch (Throwable ex) { } catch (Throwable ex) {
Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex)); Log.e(Helper.TAG, ex + "\n" + Log.getStackTraceString(ex));
} finally { } finally {
if (--queued == 0) { queued--;
try { if (queued == 0 && !isEnabled()) {
Thread.sleep(3000); try {
} catch (InterruptedException ignored) { Thread.sleep(STOP_DELAY);
} } catch (InterruptedException ignored) {
if (queued == 0) }
stopSelf(); if (queued == 0 && !isEnabled()) {
EntityLog.log(ServiceSynchronize.this, "Service stop");
stopSelf();
} }
} }
} }
}); }
} });
started = doStart;
} }
private BroadcastReceiver outboxReceiver = new BroadcastReceiver() { private BroadcastReceiver outboxReceiver = new BroadcastReceiver() {
@ -2598,24 +2553,20 @@ public class ServiceSynchronize extends LifecycleService {
public static void init(Context context) { public static void init(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean("enabled", true)) if (prefs.getBoolean("enabled", true)) {
start(context); ContextCompat.startForegroundService(context,
} new Intent(context, ServiceSynchronize.class)
.setAction("init"));
public static void start(Context context) { JobDaily.schedule(context);
ContextCompat.startForegroundService(context, new Intent(context, ServiceSynchronize.class).setAction("start")); }
}
public static void stop(Context context) {
ContextCompat.startForegroundService(context, new Intent(context, ServiceSynchronize.class).setAction("stop"));
} }
public static void reload(Context context, String reason) { public static void reload(Context context, String reason) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); ContextCompat.startForegroundService(context,
if (prefs.getBoolean("enabled", true)) { new Intent(context, ServiceSynchronize.class)
Log.i(Helper.TAG, "Reload because of '" + reason + "'"); .setAction("reload")
ContextCompat.startForegroundService(context, new Intent(context, ServiceSynchronize.class).setAction("reload")); .putExtra("reason", reason));
} JobDaily.schedule(context);
} }
private class ServiceState { private class ServiceState {

@ -69,9 +69,6 @@ public class ServiceTileSynchronize extends TileService implements SharedPrefere
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean enabled = !prefs.getBoolean("enabled", false); boolean enabled = !prefs.getBoolean("enabled", false);
prefs.edit().putBoolean("enabled", enabled).apply(); prefs.edit().putBoolean("enabled", enabled).apply();
if (enabled) ServiceSynchronize.reload(this, "tile=" + enabled);
ServiceSynchronize.start(this);
else
ServiceSynchronize.stop(this);
} }
} }

@ -20,6 +20,15 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swMetered"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_advanced_metered"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swEnabled" />
<androidx.appcompat.widget.SwitchCompat <androidx.appcompat.widget.SwitchCompat
android:id="@+id/swCompact" android:id="@+id/swCompact"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -27,7 +36,7 @@
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:text="@string/title_advanced_compact" android:text="@string/title_advanced_compact"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swEnabled" /> app:layout_constraintTop_toBottomOf="@id/swMetered" />
<androidx.appcompat.widget.SwitchCompat <androidx.appcompat.widget.SwitchCompat
android:id="@+id/swAvatars" android:id="@+id/swAvatars"

@ -97,6 +97,7 @@
<string name="title_advanced">Advanced options</string> <string name="title_advanced">Advanced options</string>
<string name="title_advanced_enabled">Synchronize</string> <string name="title_advanced_enabled">Synchronize</string>
<string name="title_advanced_metered">Use metered connections</string>
<string name="title_advanced_compact">Compact message view</string> <string name="title_advanced_compact">Compact message view</string>
<string name="title_advanced_avatars">Show contact photos</string> <string name="title_advanced_avatars">Show contact photos</string>
<string name="title_advanced_identicons">Show identicons</string> <string name="title_advanced_identicons">Show identicons</string>

Loading…
Cancel
Save