From 76d53e9e6b465d1ebcc21dca9c9829c75446efab Mon Sep 17 00:00:00 2001 From: M66B Date: Sun, 12 May 2019 18:41:51 +0200 Subject: [PATCH] Refactoring: connectionHelper --- .../java/eu/faircode/email/ActivityEml.java | 3 +- .../java/eu/faircode/email/ActivityView.java | 3 +- .../java/eu/faircode/email/AdapterFolder.java | 2 +- .../eu/faircode/email/AdapterMessage.java | 4 +- .../email/BoundaryCallbackMessages.java | 4 +- .../eu/faircode/email/ConnectionHelper.java | 303 ++++++++++++++++++ app/src/main/java/eu/faircode/email/Core.java | 6 +- .../java/eu/faircode/email/EmailProvider.java | 4 +- .../eu/faircode/email/FragmentAccount.java | 34 +- .../eu/faircode/email/FragmentCompose.java | 5 +- .../eu/faircode/email/FragmentFolders.java | 2 +- .../eu/faircode/email/FragmentIdentity.java | 28 +- .../eu/faircode/email/FragmentMessages.java | 4 +- .../email/FragmentOptionsConnection.java | 2 +- .../eu/faircode/email/FragmentQuickSetup.java | 14 +- .../main/java/eu/faircode/email/Helper.java | 284 ---------------- .../java/eu/faircode/email/MessageHelper.java | 2 +- .../java/eu/faircode/email/ServiceSend.java | 8 +- .../eu/faircode/email/ServiceSynchronize.java | 10 +- 19 files changed, 372 insertions(+), 350 deletions(-) create mode 100644 app/src/main/java/eu/faircode/email/ConnectionHelper.java diff --git a/app/src/main/java/eu/faircode/email/ActivityEml.java b/app/src/main/java/eu/faircode/email/ActivityEml.java index 81e79fdd5f..323046f509 100644 --- a/app/src/main/java/eu/faircode/email/ActivityEml.java +++ b/app/src/main/java/eu/faircode/email/ActivityEml.java @@ -94,7 +94,8 @@ public class ActivityEml extends ActivityBase { AssetFileDescriptor descriptor = resolver.openTypedAssetFileDescriptor(uri, "*/*", null); try (InputStream is = new BufferedInputStream(descriptor.createInputStream())) { - Properties props = MessageHelper.getSessionProperties(Helper.AUTH_TYPE_PASSWORD, null, false); + Properties props = MessageHelper.getSessionProperties( + ConnectionHelper.AUTH_TYPE_PASSWORD, null, false); Session isession = Session.getInstance(props, null); MimeMessage mmessage = new MimeMessage(isession, is); diff --git a/app/src/main/java/eu/faircode/email/ActivityView.java b/app/src/main/java/eu/faircode/email/ActivityView.java index b847056678..c1f90fac03 100644 --- a/app/src/main/java/eu/faircode/email/ActivityView.java +++ b/app/src/main/java/eu/faircode/email/ActivityView.java @@ -1335,7 +1335,8 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB } else { // Decode message - Properties props = MessageHelper.getSessionProperties(Helper.AUTH_TYPE_PASSWORD, null, false); + Properties props = MessageHelper.getSessionProperties( + ConnectionHelper.AUTH_TYPE_PASSWORD, null, false); Session isession = Session.getInstance(props, null); ByteArrayInputStream is = new ByteArrayInputStream(decrypted.toByteArray()); MimeMessage imessage = new MimeMessage(isession, is); diff --git a/app/src/main/java/eu/faircode/email/AdapterFolder.java b/app/src/main/java/eu/faircode/email/AdapterFolder.java index 295b142e63..d4812bd70f 100644 --- a/app/src/main/java/eu/faircode/email/AdapterFolder.java +++ b/app/src/main/java/eu/faircode/email/AdapterFolder.java @@ -450,7 +450,7 @@ public class AdapterFolder extends RecyclerView.Adapter= Build.VERSION_CODES.P && + !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND)) { + Log.i("isMetered: active background"); + return null; + } + + if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)) { + boolean unmetered = caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + Log.i("isMetered: active not VPN unmetered=" + unmetered); + return !unmetered; + } + + // VPN: evaluate underlying networks + + boolean underlying = false; + Network[] networks = cm.getAllNetworks(); + if (networks != null) + for (Network network : networks) { + caps = cm.getNetworkCapabilities(network); + if (caps == null) { + Log.i("isMetered: no underlying caps"); + continue; // network unknown + } + + Log.i("isMetered: underlying caps=" + caps); + + if (!caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + Log.i("isMetered: underlying no internet"); + continue; + } + + if (!caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) { + Log.i("isMetered: underlying restricted"); + continue; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && + !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND)) { + Log.i("isMetered: underlying background"); + continue; + } + + if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)) { + underlying = true; + Log.i("isMetered: underlying is connected"); + + if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) { + Log.i("isMetered: underlying is unmetered"); + return false; + } + } + } + + if (!underlying) { + Log.i("isMetered: no underlying network"); + return null; + } + + // Assume metered + Log.i("isMetered: underlying assume metered"); + return true; + } + + static void connect(Context context, IMAPStore istore, EntityAccount account) + throws MessagingException, AuthenticatorException, OperationCanceledException, IOException { + try { + istore.connect(account.host, account.port, account.user, account.password); + } catch (AuthenticationFailedException ex) { + if (account.auth_type == AUTH_TYPE_GMAIL) { + account.password = refreshToken(context, "com.google", account.user, account.password); + DB.getInstance(context).account().setAccountPassword(account.id, account.password); + istore.connect(account.host, account.port, account.user, account.password); + } else + throw ex; + } + + // https://www.ietf.org/rfc/rfc2971.txt + if (istore.hasCapability("ID")) + try { + Map id = new LinkedHashMap<>(); + id.put("name", context.getString(R.string.app_name)); + id.put("version", BuildConfig.VERSION_NAME); + Map sid = istore.id(id); + if (sid != null) { + Map crumb = new HashMap<>(); + for (String key : sid.keySet()) { + crumb.put(key, sid.get(key)); + Log.i("Server " + key + "=" + sid.get(key)); + } + Bugsnag.leaveBreadcrumb("server", BreadcrumbType.LOG, crumb); + } + } catch (MessagingException ex) { + Log.w(ex); + } + } + + static String refreshToken(Context context, String type, String name, String current) + throws AuthenticatorException, OperationCanceledException, IOException { + AccountManager am = AccountManager.get(context); + Account[] accounts = am.getAccountsByType(type); + for (Account account : accounts) + if (name.equals(account.name)) { + Log.i("Refreshing token"); + am.invalidateAuthToken(type, current); + String refreshed = am.blockingGetAuthToken(account, getAuthTokenType(type), true); + if (refreshed == null) + throw new OperationCanceledException("no token"); + Log.i("Refreshed token"); + return refreshed; + } + return current; + } + + static String getAuthTokenType(String type) { + if ("com.google".equals(type)) + return "oauth2:https://mail.google.com/"; + return null; + } + +} diff --git a/app/src/main/java/eu/faircode/email/Core.java b/app/src/main/java/eu/faircode/email/Core.java index 30c88bf6e5..0f5df0f474 100644 --- a/app/src/main/java/eu/faircode/email/Core.java +++ b/app/src/main/java/eu/faircode/email/Core.java @@ -2125,13 +2125,13 @@ class Core { } static class State { - private Helper.NetworkState networkState; + private ConnectionHelper.NetworkState networkState; private Thread thread; private Semaphore semaphore = new Semaphore(0); private boolean running = true; private boolean recoverable = true; - State(Helper.NetworkState networkState) { + State(ConnectionHelper.NetworkState networkState) { this.networkState = networkState; } @@ -2139,7 +2139,7 @@ class Core { this(parent.networkState); } - Helper.NetworkState getNetworkState() { + ConnectionHelper.NetworkState getNetworkState() { return networkState; } diff --git a/app/src/main/java/eu/faircode/email/EmailProvider.java b/app/src/main/java/eu/faircode/email/EmailProvider.java index 1a7fe08d58..4e4db985d0 100644 --- a/app/src/main/java/eu/faircode/email/EmailProvider.java +++ b/app/src/main/java/eu/faircode/email/EmailProvider.java @@ -447,8 +447,8 @@ public class EmailProvider { int getAuthType() { if ("com.google".equals(type)) - return Helper.AUTH_TYPE_GMAIL; - return Helper.AUTH_TYPE_PASSWORD; + return ConnectionHelper.AUTH_TYPE_GMAIL; + return ConnectionHelper.AUTH_TYPE_PASSWORD; } @Override diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java index 460df675af..18da738654 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAccount.java +++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java @@ -150,7 +150,7 @@ public class FragmentAccount extends FragmentBase { private long id = -1; private boolean saving = false; - private int auth_type = Helper.AUTH_TYPE_PASSWORD; + private int auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD; private int color = Color.TRANSPARENT; @Override @@ -251,7 +251,7 @@ public class FragmentAccount extends FragmentBase { return; adapterView.setTag(position); - auth_type = Helper.AUTH_TYPE_PASSWORD; + auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD; etHost.setText(provider.imap_host); etPort.setText(provider.imap_host == null ? null : Integer.toString(provider.imap_port)); @@ -314,8 +314,8 @@ public class FragmentAccount extends FragmentBase { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { String user = etUser.getText().toString(); - if (auth_type != Helper.AUTH_TYPE_PASSWORD && !user.equals(etUser.getTag())) { - auth_type = Helper.AUTH_TYPE_PASSWORD; + if (auth_type != ConnectionHelper.AUTH_TYPE_PASSWORD && !user.equals(etUser.getTag())) { + auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD; tilPassword.getEditText().setText(null); tilPassword.setEnabled(true); tilPassword.setPasswordVisibilityToggleEnabled(true); @@ -586,8 +586,8 @@ public class FragmentAccount extends FragmentBase { try { istore.connect(host, Integer.parseInt(port), user, password); } catch (AuthenticationFailedException ex) { - if (auth_type == Helper.AUTH_TYPE_GMAIL) { - password = Helper.refreshToken(context, "com.google", user, password); + if (auth_type == ConnectionHelper.AUTH_TYPE_GMAIL) { + password = ConnectionHelper.refreshToken(context, "com.google", user, password); istore.connect(host, Integer.parseInt(port), user, password); } else throw ex; @@ -884,8 +884,8 @@ public class FragmentAccount extends FragmentBase { try { istore.connect(host, Integer.parseInt(port), user, password); } catch (AuthenticationFailedException ex) { - if (auth_type == Helper.AUTH_TYPE_GMAIL) { - password = Helper.refreshToken(context, "com.google", user, password); + if (auth_type == ConnectionHelper.AUTH_TYPE_GMAIL) { + password = ConnectionHelper.refreshToken(context, "com.google", user, password); istore.connect(host, Integer.parseInt(port), user, password); } else throw ex; @@ -1129,7 +1129,7 @@ public class FragmentAccount extends FragmentBase { spProvider.setAdapter(aaProvider); if (savedInstanceState == null) { - auth_type = (account == null ? Helper.AUTH_TYPE_PASSWORD : account.auth_type); + auth_type = (account == null ? ConnectionHelper.AUTH_TYPE_PASSWORD : account.auth_type); if (account != null) { boolean found = false; @@ -1154,7 +1154,7 @@ public class FragmentAccount extends FragmentBase { rgEncryption.check(account != null && account.starttls ? R.id.radio_starttls : R.id.radio_ssl); cbInsecure.setChecked(account == null ? false : account.insecure); - etUser.setTag(account == null || auth_type == Helper.AUTH_TYPE_PASSWORD ? null : account.user); + etUser.setTag(account == null || auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD ? null : account.user); etUser.setText(account == null ? null : account.user); tilPassword.getEditText().setText(account == null ? null : account.password); etRealm.setText(account == null ? null : account.realm); @@ -1202,8 +1202,8 @@ public class FragmentAccount extends FragmentBase { Helper.setViewsEnabled(view, true); - tilPassword.setEnabled(auth_type == Helper.AUTH_TYPE_PASSWORD); - etRealm.setEnabled(auth_type == Helper.AUTH_TYPE_PASSWORD); + tilPassword.setEnabled(auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD); + etRealm.setEnabled(auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD); setColor(color); cbPrimary.setEnabled(cbSynchronize.isChecked()); @@ -1350,7 +1350,7 @@ public class FragmentAccount extends FragmentBase { am.getAuthToken( account, - Helper.getAuthTokenType(type), + ConnectionHelper.getAuthTokenType(type), new Bundle(), getActivity(), new AccountManagerCallback() { @@ -1361,7 +1361,7 @@ public class FragmentAccount extends FragmentBase { String token = bundle.getString(AccountManager.KEY_AUTHTOKEN); Log.i("Got token"); - auth_type = Helper.AUTH_TYPE_GMAIL; + auth_type = ConnectionHelper.AUTH_TYPE_GMAIL; etUser.setTag(account.name); etUser.setText(account.name); etUser.setTag(account.name); @@ -1379,9 +1379,9 @@ public class FragmentAccount extends FragmentBase { } finally { btnAuthorize.setEnabled(true); etUser.setEnabled(true); - tilPassword.setEnabled(auth_type == Helper.AUTH_TYPE_PASSWORD); - tilPassword.setPasswordVisibilityToggleEnabled(auth_type == Helper.AUTH_TYPE_PASSWORD); - etRealm.setEnabled(auth_type == Helper.AUTH_TYPE_PASSWORD); + tilPassword.setEnabled(auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD); + tilPassword.setPasswordVisibilityToggleEnabled(auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD); + etRealm.setEnabled(auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD); btnCheck.setEnabled(true); btnSave.setEnabled(true); new Handler().postDelayed(new Runnable() { diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java index 63fc356687..914f35a31a 100644 --- a/app/src/main/java/eu/faircode/email/FragmentCompose.java +++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java @@ -746,7 +746,7 @@ public class FragmentCompose extends FragmentBase { }; private void checkInternet() { - boolean suitable = Helper.getNetworkState(getContext()).isSuitable(); + boolean suitable = ConnectionHelper.getNetworkState(getContext()).isSuitable(); Boolean content = (Boolean) tvNoInternet.getTag(); tvNoInternet.setVisibility(!suitable && content != null && !content ? View.VISIBLE : View.GONE); @@ -1377,7 +1377,8 @@ public class FragmentCompose extends FragmentBase { (message.identity == null ? null : db.identity().getIdentity(message.identity)); // Build message - Properties props = MessageHelper.getSessionProperties(Helper.AUTH_TYPE_PASSWORD, null, false); + Properties props = MessageHelper.getSessionProperties( + ConnectionHelper.AUTH_TYPE_PASSWORD, null, false); Session isession = Session.getInstance(props, null); MimeMessage imessage = new MimeMessage(isession); MessageHelper.build(context, message, attachments, identity, imessage); diff --git a/app/src/main/java/eu/faircode/email/FragmentFolders.java b/app/src/main/java/eu/faircode/email/FragmentFolders.java index 44370d8465..a6d1508eb9 100644 --- a/app/src/main/java/eu/faircode/email/FragmentFolders.java +++ b/app/src/main/java/eu/faircode/email/FragmentFolders.java @@ -332,7 +332,7 @@ public class FragmentFolders extends FragmentBase { protected Void onExecute(Context context, Bundle args) { long aid = args.getLong("account"); - if (!Helper.getNetworkState(context).isSuitable()) + if (!ConnectionHelper.getNetworkState(context).isSuitable()) throw new IllegalArgumentException(context.getString(R.string.title_no_internet)); boolean now = true; diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentity.java b/app/src/main/java/eu/faircode/email/FragmentIdentity.java index 22707f6f87..a4d1804e70 100644 --- a/app/src/main/java/eu/faircode/email/FragmentIdentity.java +++ b/app/src/main/java/eu/faircode/email/FragmentIdentity.java @@ -123,7 +123,7 @@ public class FragmentIdentity extends FragmentBase { private long id = -1; private long account = -1; private boolean saving = false; - private int auth_type = Helper.AUTH_TYPE_PASSWORD; + private int auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD; private int color = Color.TRANSPARENT; private String signature = null; @@ -240,12 +240,12 @@ public class FragmentIdentity extends FragmentBase { // Copy account credentials etEmail.setText(account.user); - etUser.setTag(auth_type == Helper.AUTH_TYPE_PASSWORD ? null : account.user); + etUser.setTag(auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD ? null : account.user); etUser.setText(account.user); tilPassword.getEditText().setText(account.password); etRealm.setText(account.realm); - tilPassword.setEnabled(auth_type == Helper.AUTH_TYPE_PASSWORD); - etRealm.setEnabled(auth_type == Helper.AUTH_TYPE_PASSWORD); + tilPassword.setEnabled(auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD); + etRealm.setEnabled(auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD); } @Override @@ -261,8 +261,8 @@ public class FragmentIdentity extends FragmentBase { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { String user = etUser.getText().toString(); - if (auth_type != Helper.AUTH_TYPE_PASSWORD && !user.equals(etUser.getTag())) { - auth_type = Helper.AUTH_TYPE_PASSWORD; + if (auth_type != ConnectionHelper.AUTH_TYPE_PASSWORD && !user.equals(etUser.getTag())) { + auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD; tilPassword.getEditText().setText(null); tilPassword.setEnabled(true); etRealm.setEnabled(true); @@ -647,8 +647,8 @@ public class FragmentIdentity extends FragmentBase { try { itransport.connect(host, Integer.parseInt(port), user, password); } catch (AuthenticationFailedException ex) { - if (auth_type == Helper.AUTH_TYPE_GMAIL) { - password = Helper.refreshToken(context, "com.google", user, password); + if (auth_type == ConnectionHelper.AUTH_TYPE_GMAIL) { + password = ConnectionHelper.refreshToken(context, "com.google", user, password); itransport.connect(host, Integer.parseInt(port), user, password); } else throw ex; @@ -764,7 +764,7 @@ public class FragmentIdentity extends FragmentBase { @Override protected void onExecuted(Bundle args, final EntityIdentity identity) { if (savedInstanceState == null) { - auth_type = (identity == null ? Helper.AUTH_TYPE_PASSWORD : identity.auth_type); + auth_type = (identity == null ? ConnectionHelper.AUTH_TYPE_PASSWORD : identity.auth_type); etName.setText(identity == null ? null : identity.name); etEmail.setText(identity == null ? null : identity.email); @@ -779,7 +779,7 @@ public class FragmentIdentity extends FragmentBase { rgEncryption.check(identity != null && identity.starttls ? R.id.radio_starttls : R.id.radio_ssl); cbInsecure.setChecked(identity == null ? false : identity.insecure); etPort.setText(identity == null ? null : Long.toString(identity.port)); - etUser.setTag(identity == null || auth_type == Helper.AUTH_TYPE_PASSWORD ? null : identity.user); + etUser.setTag(identity == null || auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD ? null : identity.user); etUser.setText(identity == null ? null : identity.user); tilPassword.getEditText().setText(identity == null ? null : identity.password); etRealm.setText(identity == null ? null : identity.realm); @@ -825,8 +825,8 @@ public class FragmentIdentity extends FragmentBase { Helper.setViewsEnabled(view, true); - tilPassword.setEnabled(auth_type == Helper.AUTH_TYPE_PASSWORD); - etRealm.setEnabled(auth_type == Helper.AUTH_TYPE_PASSWORD); + tilPassword.setEnabled(auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD); + etRealm.setEnabled(auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD); setColor(color); @@ -847,7 +847,7 @@ public class FragmentIdentity extends FragmentBase { EntityAccount unselected = new EntityAccount(); unselected.id = -1L; - unselected.auth_type = Helper.AUTH_TYPE_PASSWORD; + unselected.auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD; unselected.name = getString(R.string.title_select); unselected.primary = false; accounts.add(0, unselected); @@ -885,7 +885,7 @@ public class FragmentIdentity extends FragmentBase { spAccount.setTag(pos); spAccount.setSelection(pos); // OAuth token could be updated - if (pos > 0 && accounts.get(pos).auth_type != Helper.AUTH_TYPE_PASSWORD) + if (pos > 0 && accounts.get(pos).auth_type != ConnectionHelper.AUTH_TYPE_PASSWORD) tilPassword.getEditText().setText(accounts.get(pos).password); break; } diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index e2bc45bb37..34fa8c4a19 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -803,7 +803,7 @@ public class FragmentMessages extends FragmentBase { protected Void onExecute(Context context, Bundle args) { long fid = args.getLong("folder"); - if (!Helper.getNetworkState(context).isSuitable()) + if (!ConnectionHelper.getNetworkState(context).isSuitable()) throw new IllegalArgumentException(context.getString(R.string.title_no_internet)); boolean now = true; @@ -2720,7 +2720,7 @@ public class FragmentMessages extends FragmentBase { if (download == 0) download = Long.MAX_VALUE; - boolean unmetered = Helper.getNetworkState(getContext()).isUnmetered(); + boolean unmetered = ConnectionHelper.getNetworkState(getContext()).isUnmetered(); int count = 0; int unseen = 0; diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java b/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java index 693ca16e0b..87242a2122 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java @@ -222,7 +222,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre @Override public void run() { if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) { - Helper.NetworkState networkState = Helper.getNetworkState(getContext()); + ConnectionHelper.NetworkState networkState = ConnectionHelper.getNetworkState(getContext()); 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/FragmentQuickSetup.java b/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java index 3711b62cc8..f1d291dc4e 100644 --- a/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java +++ b/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java @@ -91,7 +91,7 @@ public class FragmentQuickSetup extends FragmentBase { private Button btnSave; private Group grpSetup; - private int auth_type = Helper.AUTH_TYPE_PASSWORD; + private int auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD; @Override @Nullable @@ -138,8 +138,8 @@ public class FragmentQuickSetup extends FragmentBase { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { - if (auth_type != Helper.AUTH_TYPE_PASSWORD) { - auth_type = Helper.AUTH_TYPE_PASSWORD; + if (auth_type != ConnectionHelper.AUTH_TYPE_PASSWORD) { + auth_type = ConnectionHelper.AUTH_TYPE_PASSWORD; tilPassword.getEditText().setText(null); tilPassword.setEnabled(true); tilPassword.setPasswordVisibilityToggleEnabled(true); @@ -498,7 +498,7 @@ public class FragmentQuickSetup extends FragmentBase { am.getAuthToken( account, - Helper.getAuthTokenType(type), + ConnectionHelper.getAuthTokenType(type), new Bundle(), getActivity(), new AccountManagerCallback() { @@ -511,7 +511,7 @@ public class FragmentQuickSetup extends FragmentBase { etEmail.setText(account.name); tilPassword.getEditText().setText(token); - auth_type = Helper.AUTH_TYPE_GMAIL; + auth_type = ConnectionHelper.AUTH_TYPE_GMAIL; } catch (Throwable ex) { Log.e(ex); if (ex instanceof OperationCanceledException || @@ -523,8 +523,8 @@ public class FragmentQuickSetup extends FragmentBase { Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); } finally { etEmail.setEnabled(true); - tilPassword.setEnabled(auth_type == Helper.AUTH_TYPE_PASSWORD); - tilPassword.setPasswordVisibilityToggleEnabled(auth_type == Helper.AUTH_TYPE_PASSWORD); + tilPassword.setEnabled(auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD); + tilPassword.setPasswordVisibilityToggleEnabled(auth_type == ConnectionHelper.AUTH_TYPE_PASSWORD); btnAuthorize.setEnabled(true); btnCheck.setEnabled(true); new Handler().postDelayed(new Runnable() { diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java index 0292965f03..2bfa85cac3 100644 --- a/app/src/main/java/eu/faircode/email/Helper.java +++ b/app/src/main/java/eu/faircode/email/Helper.java @@ -19,10 +19,6 @@ package eu.faircode.email; Copyright 2018-2019 by Marcel Bokhorst (M66B) */ -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AuthenticatorException; -import android.accounts.OperationCanceledException; import android.app.usage.UsageStatsManager; import android.content.ActivityNotFoundException; import android.content.Context; @@ -40,14 +36,11 @@ import android.graphics.Point; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; -import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.PowerManager; -import android.telephony.TelephonyManager; -import android.text.TextUtils; import android.text.format.DateUtils; import android.text.format.Time; import android.view.Display; @@ -71,10 +64,7 @@ import androidx.lifecycle.LifecycleOwner; import androidx.preference.PreferenceManager; import com.android.billingclient.api.BillingClient; -import com.bugsnag.android.BreadcrumbType; -import com.bugsnag.android.Bugsnag; import com.google.android.material.bottomnavigation.BottomNavigationView; -import com.sun.mail.imap.IMAPStore; import com.sun.mail.util.MailConnectException; import org.json.JSONException; @@ -103,11 +93,8 @@ import java.text.DateFormat; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.Date; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -116,10 +103,8 @@ import java.util.Set; import java.util.concurrent.ThreadFactory; import javax.mail.Address; -import javax.mail.AuthenticationFailedException; import javax.mail.FolderClosedException; import javax.mail.MessageRemovedException; -import javax.mail.MessagingException; import javax.mail.internet.InternetAddress; import javax.net.ssl.HttpsURLConnection; @@ -133,9 +118,6 @@ public class Helper { static final int NOTIFICATION_SEND = 2; static final int NOTIFICATION_EXTERNAL = 3; - static final int AUTH_TYPE_PASSWORD = 1; - static final int AUTH_TYPE_GMAIL = 2; - static final float LOW_LIGHT = 0.6f; static final String FAQ_URI = "https://github.com/M66B/open-source-email/blob/master/FAQ.md"; @@ -149,42 +131,6 @@ public class Helper { } }; - // Roam like at home - // https://en.wikipedia.org/wiki/European_Union_roaming_regulations - private static final List RLAH_COUNTRY_CODES = Collections.unmodifiableList(Arrays.asList( - "AT", // Austria - "BE", // Belgium - "BG", // Bulgaria - "HR", // Croatia - "CY", // Cyprus - "CZ", // Czech Republic - "DK", // Denmark - "EE", // Estonia - "FI", // Finland - "FR", // France - "DE", // Germany - "GR", // Greece - "HU", // Hungary - "IS", // Iceland - "IE", // Ireland - "IT", // Italy - "LV", // Latvia - "LI", // Liechtenstein - "LT", // Lithuania - "LU", // Luxembourg - "MT", // Malta - "NL", // Netherlands - "NO", // Norway - "PL", // Poland - "PT", // Portugal - "RO", // Romania - "SK", // Slovakia - "SI", // Slovenia - "ES", // Spain - "SE", // Sweden - "GB" // United Kingdom - )); - static boolean hasPermission(Context context, String name) { return (ContextCompat.checkSelfPermission(context, name) == PackageManager.PERMISSION_GRANTED); } @@ -825,236 +771,6 @@ public class Helper { return filename.substring(index + 1); } - static class NetworkState { - private Boolean connected = null; - private Boolean suitable = null; - private Boolean unmetered = null; - private Boolean roaming = null; - - boolean isConnected() { - return (connected != null && connected); - } - - boolean isSuitable() { - return (suitable != null && suitable); - } - - boolean isUnmetered() { - return (unmetered != null && unmetered); - } - - boolean isRoaming() { - return (roaming != null && roaming); - } - - public void update(NetworkState newState) { - connected = newState.connected; - unmetered = newState.unmetered; - suitable = newState.suitable; - } - } - - static NetworkState getNetworkState(Context context) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean metered = prefs.getBoolean("metered", true); - boolean rlah = prefs.getBoolean("rlah", true); - boolean roaming = prefs.getBoolean("roaming", true); - - NetworkState state = new NetworkState(); - Boolean isMetered = isMetered(context); - state.connected = (isMetered != null); - state.unmetered = (isMetered != null && !isMetered); - state.suitable = (isMetered != null && (metered || !isMetered)); - - if (state.connected && !roaming) { - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - NetworkInfo ani = cm.getActiveNetworkInfo(); - if (ani != null) - state.roaming = ani.isRoaming(); - } else { - Network active = cm.getActiveNetwork(); - if (active != null) { - NetworkCapabilities caps = cm.getNetworkCapabilities(active); - if (caps != null) - state.roaming = !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); - } - } - - if (state.roaming != null && state.roaming && rlah) - try { - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - if (tm != null) { - String sim = tm.getSimCountryIso(); - String network = tm.getNetworkCountryIso(); - Log.i("Country SIM=" + sim + " network=" + network); - if (sim != null && network != null && - RLAH_COUNTRY_CODES.contains(sim) && - RLAH_COUNTRY_CODES.contains(network)) - state.roaming = false; - } - } catch (Throwable ex) { - Log.w(ex); - } - } - - return state; - } - - private static Boolean isMetered(Context context) { - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - NetworkInfo ani = cm.getActiveNetworkInfo(); - if (ani == null || !ani.isConnected()) - return null; - return cm.isActiveNetworkMetered(); - } - - Network active = cm.getActiveNetwork(); - if (active == null) { - Log.i("isMetered: no active network"); - return null; - } - - NetworkCapabilities caps = cm.getNetworkCapabilities(active); - if (caps == null) { - Log.i("isMetered: active no caps"); - return null; // network unknown - } - - Log.i("isMetered: active caps=" + caps); - - if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) && - !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { - Log.i("isMetered: no internet"); - return null; - } - - if (!caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) { - Log.i("isMetered: active restricted"); - return null; - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && - !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND)) { - Log.i("isMetered: active background"); - return null; - } - - if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)) { - boolean unmetered = caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); - Log.i("isMetered: active not VPN unmetered=" + unmetered); - return !unmetered; - } - - // VPN: evaluate underlying networks - - boolean underlying = false; - Network[] networks = cm.getAllNetworks(); - if (networks != null) - for (Network network : networks) { - caps = cm.getNetworkCapabilities(network); - if (caps == null) { - Log.i("isMetered: no underlying caps"); - continue; // network unknown - } - - Log.i("isMetered: underlying caps=" + caps); - - if (!caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { - Log.i("isMetered: underlying no internet"); - continue; - } - - if (!caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)) { - Log.i("isMetered: underlying restricted"); - continue; - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && - !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND)) { - Log.i("isMetered: underlying background"); - continue; - } - - if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)) { - underlying = true; - Log.i("isMetered: underlying is connected"); - - if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)) { - Log.i("isMetered: underlying is unmetered"); - return false; - } - } - } - - if (!underlying) { - Log.i("isMetered: no underlying network"); - return null; - } - - // Assume metered - Log.i("isMetered: underlying assume metered"); - return true; - } - - static void connect(Context context, IMAPStore istore, EntityAccount account) - throws MessagingException, AuthenticatorException, OperationCanceledException, IOException { - try { - istore.connect(account.host, account.port, account.user, account.password); - } catch (AuthenticationFailedException ex) { - if (account.auth_type == AUTH_TYPE_GMAIL) { - account.password = refreshToken(context, "com.google", account.user, account.password); - DB.getInstance(context).account().setAccountPassword(account.id, account.password); - istore.connect(account.host, account.port, account.user, account.password); - } else - throw ex; - } - - // https://www.ietf.org/rfc/rfc2971.txt - if (istore.hasCapability("ID")) - try { - Map id = new LinkedHashMap<>(); - id.put("name", context.getString(R.string.app_name)); - id.put("version", BuildConfig.VERSION_NAME); - Map sid = istore.id(id); - if (sid != null) { - Map crumb = new HashMap<>(); - for (String key : sid.keySet()) { - crumb.put(key, sid.get(key)); - Log.i("Server " + key + "=" + sid.get(key)); - } - Bugsnag.leaveBreadcrumb("server", BreadcrumbType.LOG, crumb); - } - } catch (MessagingException ex) { - Log.w(ex); - } - } - - static String refreshToken(Context context, String type, String name, String current) - throws AuthenticatorException, OperationCanceledException, IOException { - AccountManager am = AccountManager.get(context); - Account[] accounts = am.getAccountsByType(type); - for (Account account : accounts) - if (name.equals(account.name)) { - Log.i("Refreshing token"); - am.invalidateAuthToken(type, current); - String refreshed = am.blockingGetAuthToken(account, getAuthTokenType(type), true); - if (refreshed == null) - throw new OperationCanceledException("no token"); - Log.i("Refreshed token"); - return refreshed; - } - return current; - } - - static String getAuthTokenType(String type) { - if ("com.google".equals(type)) - return "oauth2:https://mail.google.com/"; - return null; - } - static boolean isPlayStoreInstall(Context context) { return BuildConfig.PLAY_STORE_RELEASE; } diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index 6d16a32e69..71dfc3dfcc 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -190,7 +190,7 @@ public class MessageHelper { // https://javaee.github.io/javamail/OAuth2 Log.i("Auth type=" + auth_type); - if (auth_type == Helper.AUTH_TYPE_GMAIL) { + if (auth_type == ConnectionHelper.AUTH_TYPE_GMAIL) { props.put("mail.imaps.auth.mechanisms", "XOAUTH2"); props.put("mail.imap.auth.mechanisms", "XOAUTH2"); props.put("mail.smtps.auth.mechanisms", "XOAUTH2"); diff --git a/app/src/main/java/eu/faircode/email/ServiceSend.java b/app/src/main/java/eu/faircode/email/ServiceSend.java index 6f30112546..6a9a4174e9 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSend.java +++ b/app/src/main/java/eu/faircode/email/ServiceSend.java @@ -161,7 +161,7 @@ public class ServiceSend extends LifecycleService { } private void check() { - if (!Helper.getNetworkState(ServiceSend.this).isSuitable()) + if (!ConnectionHelper.getNetworkState(ServiceSend.this).isSuitable()) return; if (thread != null && thread.isAlive()) @@ -230,7 +230,7 @@ public class ServiceSend extends LifecycleService { Log.i(outbox.name + " end op=" + op.id + "/" + op.name); } - if (!Helper.getNetworkState(ServiceSend.this).isSuitable()) + if (!ConnectionHelper.getNetworkState(ServiceSend.this).isSuitable()) break; } @@ -326,9 +326,9 @@ public class ServiceSend extends LifecycleService { try { itransport.connect(ident.host, ident.port, ident.user, ident.password); } catch (AuthenticationFailedException ex) { - if (ident.auth_type == Helper.AUTH_TYPE_GMAIL) { + if (ident.auth_type == ConnectionHelper.AUTH_TYPE_GMAIL) { EntityAccount account = db.account().getAccount(ident.account); - ident.password = Helper.refreshToken(this, "com.google", ident.user, account.password); + ident.password = ConnectionHelper.refreshToken(this, "com.google", ident.user, account.password); DB.getInstance(this).identity().setIdentityPassword(ident.id, ident.password); itransport.connect(ident.host, ident.port, ident.user, ident.password); } else diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index a992112a50..7204828e49 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -89,7 +89,7 @@ import me.leolin.shortcutbadger.ShortcutBadger; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; public class ServiceSynchronize extends LifecycleService { - private Helper.NetworkState networkState = new Helper.NetworkState(); + private ConnectionHelper.NetworkState networkState = new ConnectionHelper.NetworkState(); private Core.State state; private boolean oneshot = false; private boolean started = false; @@ -641,7 +641,7 @@ public class ServiceSynchronize extends LifecycleService { db.account().setAccountState(account.id, "connecting"); try { - Helper.connect(this, istore, account); + ConnectionHelper.connect(this, istore, account); } catch (Throwable ex) { // Report account connection error if (account.last_connected != null) { @@ -1160,7 +1160,7 @@ public class ServiceSynchronize extends LifecycleService { ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() { @Override public void onAvailable(Network network) { - networkState.update(Helper.getNetworkState(ServiceSynchronize.this)); + networkState.update(ConnectionHelper.getNetworkState(ServiceSynchronize.this)); synchronized (ServiceSynchronize.this) { try { @@ -1215,7 +1215,7 @@ public class ServiceSynchronize extends LifecycleService { @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities capabilities) { - networkState.update(Helper.getNetworkState(ServiceSynchronize.this)); + networkState.update(ConnectionHelper.getNetworkState(ServiceSynchronize.this)); synchronized (ServiceSynchronize.this) { try { @@ -1232,7 +1232,7 @@ public class ServiceSynchronize extends LifecycleService { @Override public void onLost(Network network) { - networkState.update(Helper.getNetworkState(ServiceSynchronize.this)); + networkState.update(ConnectionHelper.getNetworkState(ServiceSynchronize.this)); synchronized (ServiceSynchronize.this) { try {