diff --git a/app/src/main/java/eu/faircode/email/AdapterFolder.java b/app/src/main/java/eu/faircode/email/AdapterFolder.java index 7ba5c48db3..a29bbb16e9 100644 --- a/app/src/main/java/eu/faircode/email/AdapterFolder.java +++ b/app/src/main/java/eu/faircode/email/AdapterFolder.java @@ -315,7 +315,7 @@ public class AdapterFolder extends RecyclerView.Adapter attachments = db.attachment().getAttachments(message.id); MessageHelper helper = new MessageHelper(imessage); - Boolean isMetered = Helper.isMetered(context, false); - boolean metered = (isMetered == null || isMetered); boolean fetch = false; if (!message.content) - if (!metered || (message.size != null && message.size < maxSize)) + if (state.getNetworkState().isUnmetered() || (message.size != null && message.size < maxSize)) fetch = true; if (!fetch) for (EntityAttachment attachment : attachments) if (!attachment.available) - if (!metered || (attachment.size != null && attachment.size < maxSize)) { + if (state.getNetworkState().isUnmetered() || (attachment.size != null && attachment.size < maxSize)) { fetch = true; break; } @@ -1425,7 +1423,7 @@ class Core { MessageHelper.MessageParts parts = helper.getMessageParts(); if (!message.content) { - if (!metered || (message.size != null && message.size < maxSize)) { + if (state.getNetworkState().isUnmetered() || (message.size != null && message.size < maxSize)) { String body = parts.getHtml(context); Helper.writeText(message.getFile(context), body); db.message().setMessageContent(message.id, true, @@ -1436,7 +1434,7 @@ class Core { for (EntityAttachment attachment : attachments) if (!attachment.available) - if (!metered || (attachment.size != null && attachment.size < maxSize)) + if (state.getNetworkState().isUnmetered() || (attachment.size != null && attachment.size < maxSize)) try { parts.downloadAttachment(context, attachment.sequence - 1, attachment.id); } catch (Throwable ex) { @@ -1854,10 +1852,23 @@ class Core { } static class State { + private Helper.NetworkState networkState; private Thread thread; private Semaphore semaphore = new Semaphore(0); private boolean running = true; + State(Helper.NetworkState networkState) { + this.networkState = networkState; + } + + State(State parent) { + this(parent.networkState); + } + + Helper.NetworkState getNetworkState() { + return networkState; + } + void runnable(Runnable runnable, String name) { thread = new Thread(runnable, name); thread.setPriority(THREAD_PRIORITY_BACKGROUND); diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 986e0d6685..7b48061681 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -714,13 +714,13 @@ public class FragmentCompose extends FragmentBase { }; private void checkInternet() { - boolean internet = Helper.suitableNetwork(getContext(), false); + boolean suitable = Helper.getNetworkState(getContext()).isSuitable(); Boolean content = (Boolean) tvNoInternet.getTag(); - tvNoInternet.setVisibility(!internet && content != null && !content ? View.VISIBLE : View.GONE); + tvNoInternet.setVisibility(!suitable && content != null && !content ? View.VISIBLE : View.GONE); Boolean downloading = (Boolean) rvAttachment.getTag(); - tvNoInternetAttachments.setVisibility(!internet && downloading != null && downloading ? View.VISIBLE : View.GONE); + tvNoInternetAttachments.setVisibility(!suitable && downloading != null && downloading ? View.VISIBLE : View.GONE); } @Override diff --git a/app/src/main/java/eu/faircode/email/FragmentFolders.java b/app/src/main/java/eu/faircode/email/FragmentFolders.java index 4ce9bb6f45..ab59bd51e3 100644 --- a/app/src/main/java/eu/faircode/email/FragmentFolders.java +++ b/app/src/main/java/eu/faircode/email/FragmentFolders.java @@ -288,7 +288,7 @@ public class FragmentFolders extends FragmentBase { protected Void onExecute(Context context, Bundle args) { long aid = args.getLong("account"); - if (!Helper.suitableNetwork(context, false)) + if (!Helper.getNetworkState(context).isSuitable()) throw new IllegalArgumentException(context.getString(R.string.title_no_internet)); DB db = DB.getInstance(context); diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index 8ac1aa704b..ea75990c7a 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -515,7 +515,7 @@ public class FragmentMessages extends FragmentBase { protected Void onExecute(Context context, Bundle args) { long fid = args.getLong("folder"); - if (!Helper.suitableNetwork(context, false)) + if (!Helper.getNetworkState(context).isSuitable()) throw new IllegalArgumentException(context.getString(R.string.title_no_internet)); @@ -2096,8 +2096,7 @@ public class FragmentMessages extends FragmentBase { if (download == 0) download = Long.MAX_VALUE; - Boolean isMetered = Helper.isMetered(getContext(), false); - boolean metered = (isMetered == null || isMetered); + boolean unmetered = Helper.getNetworkState(getContext()).isUnmetered(); int count = 0; int unseen = 0; @@ -2139,7 +2138,7 @@ public class FragmentMessages extends FragmentBase { expand = messages.get(0); if (expand != null && - (expand.content || !metered || (expand.size != null && expand.size < download))) { + (expand.content || unmetered || (expand.size != null && expand.size < download))) { if (!values.containsKey("expanded")) values.put("expanded", new ArrayList()); values.get("expanded").add(expand.id); diff --git a/app/src/main/java/eu/faircode/email/FragmentOptions.java b/app/src/main/java/eu/faircode/email/FragmentOptions.java index 4d211dc330..8d42ebacac 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptions.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptions.java @@ -687,11 +687,10 @@ public class FragmentOptions extends FragmentBase implements SharedPreferences.O @Override public void run() { if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { - Boolean metered = Helper.isMetered(getContext(), false); + Helper.NetworkState networkState = Helper.getNetworkState(getContext()); - tvConnectionType.setVisibility(metered == null ? View.GONE : View.VISIBLE); - if (metered != null) - tvConnectionType.setText(metered ? R.string.title_legend_metered : R.string.title_legend_unmetered); + tvConnectionType.setText(networkState.isUnmetered() ? R.string.title_legend_unmetered : R.string.title_legend_metered); + tvConnectionType.setVisibility(networkState.isConnected() ? View.VISIBLE : View.GONE); } } }); diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java index 521dac880e..a2ad3f0394 100644 --- a/app/src/main/java/eu/faircode/email/Helper.java +++ b/app/src/main/java/eu/faircode/email/Helper.java @@ -700,17 +700,42 @@ public class Helper { return filename.substring(index + 1); } - static boolean suitableNetwork(Context context, boolean log) { + static class NetworkState { + private Boolean connected = null; + private Boolean suitable = null; + private Boolean unmetered = null; + + boolean isConnected() { + return (connected != null && connected); + } + + boolean isSuitable() { + return (suitable != null && suitable); + } + + boolean isUnmetered() { + return (unmetered != null && unmetered); + } + + public void update(NetworkState newState) { + suitable = newState.suitable; + unmetered = newState.unmetered; + } + } + + static NetworkState getNetworkState(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean metered = prefs.getBoolean("metered", true); - Boolean isMetered = isMetered(context, log); - boolean suitable = (isMetered != null && (metered || !isMetered)); - if (log) - EntityLog.log(context, "suitable=" + suitable + " metered=" + metered + " isMetered=" + isMetered); - return suitable; + + NetworkState state = new NetworkState(); + Boolean isMetered = isMetered(context, false); + state.connected = (isMetered != null); + state.unmetered = (isMetered != null && !isMetered); + state.suitable = (isMetered != null && (metered || !isMetered)); + return state; } - static Boolean isMetered(Context context, boolean log) { + private static Boolean isMetered(Context context, boolean log) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { diff --git a/app/src/main/java/eu/faircode/email/ServiceSend.java b/app/src/main/java/eu/faircode/email/ServiceSend.java index 7b6c78169a..1bdb580c54 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSend.java +++ b/app/src/main/java/eu/faircode/email/ServiceSend.java @@ -147,7 +147,7 @@ public class ServiceSend extends LifecycleService { } private void check() { - if (!Helper.suitableNetwork(ServiceSend.this, false)) + if (!Helper.getNetworkState(ServiceSend.this).isSuitable()) return; if (thread != null && thread.isAlive()) @@ -215,7 +215,7 @@ public class ServiceSend extends LifecycleService { Log.i(outbox.name + " end op=" + op.id + "/" + op.name); } - if (!Helper.suitableNetwork(ServiceSend.this, false)) + if (!Helper.getNetworkState(ServiceSend.this).isSuitable()) break; } diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index 6095c20ba5..3bd5d16361 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -83,6 +83,7 @@ import androidx.preference.PreferenceManager; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; public class ServiceSynchronize extends LifecycleService { + private Helper.NetworkState networkState = new Helper.NetworkState(); private Core.State state; private boolean started = false; private int queued = 0; @@ -284,7 +285,7 @@ public class ServiceSynchronize extends LifecycleService { private void queue_reload(final boolean start, final String reason) { final boolean doStop = started; - final boolean doStart = (start && isEnabled() && Helper.suitableNetwork(this, true)); + final boolean doStart = (start && isEnabled() && networkState.isSuitable()); EntityLog.log(this, "Queue reload" + " doStop=" + doStop + " doStart=" + doStart + " queued=" + queued + " " + reason); @@ -355,7 +356,7 @@ public class ServiceSynchronize extends LifecycleService { private void start() { EntityLog.log(this, "Main start"); - state = new Core.State(); + state = new Core.State(networkState); state.runnable(new Runnable() { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wl = pm.newWakeLock( @@ -390,7 +391,7 @@ public class ServiceSynchronize extends LifecycleService { account.deleteNotificationChannel(ServiceSynchronize.this); Log.i(account.host + "/" + account.user + " run"); - final Core.State astate = new Core.State(); + final Core.State astate = new Core.State(state); astate.runnable(new Runnable() { @Override public void run() { @@ -678,7 +679,7 @@ public class ServiceSynchronize extends LifecycleService { if (db.folder().getFolderDownload(folder.id)) Core.downloadMessage(ServiceSynchronize.this, folder, (IMAPFolder) ifolder, - (IMAPMessage) imessage, message.id); + (IMAPMessage) imessage, message.id, state); } catch (MessageRemovedException ex) { Log.w(folder.name, ex); } catch (FolderClosedException ex) { @@ -767,7 +768,7 @@ public class ServiceSynchronize extends LifecycleService { if (db.folder().getFolderDownload(folder.id)) Core.downloadMessage(ServiceSynchronize.this, folder, (IMAPFolder) ifolder, - (IMAPMessage) e.getMessage(), message.id); + (IMAPMessage) e.getMessage(), message.id, state); } catch (MessageRemovedException ex) { Log.w(folder.name, ex); } catch (FolderClosedException ex) { @@ -1087,12 +1088,14 @@ public class ServiceSynchronize extends LifecycleService { ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { + networkState.update(Helper.getNetworkState(ServiceSynchronize.this)); + synchronized (ServiceSynchronize.this) { try { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); EntityLog.log(ServiceSynchronize.this, "Available " + network + " " + cm.getNetworkInfo(network)); - if (!started && Helper.suitableNetwork(ServiceSynchronize.this, true)) + if (!started && networkState.isSuitable()) queue_reload(true, "connect " + network); } catch (Throwable ex) { Log.e(ex); @@ -1102,11 +1105,13 @@ public class ServiceSynchronize extends LifecycleService { @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) { + networkState.update(Helper.getNetworkState(ServiceSynchronize.this)); + synchronized (ServiceSynchronize.this) { try { if (!started) { EntityLog.log(ServiceSynchronize.this, "Network " + network + " capabilities " + capabilities); - if (Helper.suitableNetwork(ServiceSynchronize.this, true)) + if (networkState.isSuitable()) queue_reload(true, "capabilities " + network); } } catch (Throwable ex) { @@ -1117,11 +1122,13 @@ public class ServiceSynchronize extends LifecycleService { @Override public void onLost(Network network) { + networkState.update(Helper.getNetworkState(ServiceSynchronize.this)); + synchronized (ServiceSynchronize.this) { try { EntityLog.log(ServiceSynchronize.this, "Lost " + network); - if (started && !Helper.suitableNetwork(ServiceSynchronize.this, true)) { + if (started && !networkState.isSuitable()) { lastLost = new Date().getTime(); queue_reload(false, "disconnect " + network); } diff --git a/app/src/main/java/eu/faircode/email/ViewModelBrowse.java b/app/src/main/java/eu/faircode/email/ViewModelBrowse.java index 7e207a3660..bd42e8303d 100644 --- a/app/src/main/java/eu/faircode/email/ViewModelBrowse.java +++ b/app/src/main/java/eu/faircode/email/ViewModelBrowse.java @@ -157,7 +157,7 @@ public class ViewModelBrowse extends ViewModel { try { // Check connectivity - if (!Helper.suitableNetwork(state.context, false)) + if (!Helper.getNetworkState(state.context).isSuitable()) throw new IllegalArgumentException(state.context.getString(R.string.title_no_internet)); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(state.context);