diff --git a/FAQ.md b/FAQ.md index 33325824eb..029c4f7926 100644 --- a/FAQ.md +++ b/FAQ.md @@ -337,27 +337,36 @@ Unfortunately, it is impossible to make everybody happy and adding lots of setti **(6) How can I login to Gmail / G suite?** -To use a Gmail/G suite account, you'll need to enable access for "less secure" apps, -see [here](https://support.google.com/accounts/answer/6010255) for Google's instructions +To use a Gmail/G suite account, +you can either enable access for "less secure apps" and use your account password +or enable two factor authentication and use an app specific password. + +Note that an app specific password is required when two factor authentication is enabled. + +**Enable "Less secure apps"** + +See [here](https://support.google.com/accounts/answer/6010255) about how to enable "less secure apps" or go [directy to the setting](https://www.google.com/settings/security/lesssecureapps). -When "less secure" apps is not enabled, + +If you use multiple Gmail accounts, make sure you change the "less secure apps" setting of the right account(s). + +Be aware that you need to leave the "less secure apps" settings screen by using the back arrow to apply the setting. + +When "less secure apps" is not enabled, you'll get the error *Authentication failed - invalid credentials* for accounts (IMAP) and *Username and Password not accepted* for identities (SMTP). -If you use multiple Gmail accounts, make sure you change the "less secure" setting of the right account(s). - -Be aware that you need to leave the "less secure" settings screen by using the back arrow to apply the setting. +**App specific password** -See [this FAQ](#user-content-faq111) for more information. +See [here](https://support.google.com/accounts/answer/185833) about how to generate an app specific password. You might get the alert "*Please log in via your web browser*". -This security measure can for example be triggered when too many IP addresses were used in a too short time or when you are using a VPN. -You can prevent this by using an app specific password. +This happens when Google considers the network that connects you to the internet (this could be a VPN) to to be unsafe. +This can be prevented by using an app specific password. -To login to Gmail / G suite you'll sometimes need an app specific password, for example when two factor authentication is enabled. -See here for instructions: [https://support.google.com/accounts/answer/185833](https://support.google.com/accounts/answer/185833). +See [here](https://support.google.com/mail/accounts/answer/78754) for troubleshooting. -If this doesn't work, see here for more solutions: [https://support.google.com/mail/accounts/answer/78754](https://support.google.com/mail/accounts/answer/78754) +See [this FAQ](#user-content-faq111) about why OAuth is not being used.
@@ -378,7 +387,7 @@ which happens when reconnecting or if you synchronize manually. **(8) Can I use a Microsoft Exchange account?** You can use a Microsoft Exchange account if it is accessible via IMAP. -See here for more information: [https://support.office.com/en-us/article/what-is-a-microsoft-exchange-account-47f000aa-c2bf-48ac-9bc2-83e5c6036793](https://support.office.com/en-us/article/what-is-a-microsoft-exchange-account-47f000aa-c2bf-48ac-9bc2-83e5c6036793) +See [here](https://support.office.com/en-us/article/what-is-a-microsoft-exchange-account-47f000aa-c2bf-48ac-9bc2-83e5c6036793) for more information. Please see [here](#frequently-requested-features) about ActiveSync support. @@ -1569,7 +1578,7 @@ You will likely need to save the associated identity again as well. For the correct settings, see [here](https://help.yahoo.com/kb/SLN4075.html). You might need to enable "*less secure sign in*" for "*outdated*" apps, -see [here](https://help.yahoo.com/kb/grant-temporary-access-outdated-apps-account-settings-sln27791.html). +see [here](https://help.yahoo.com/kb/grant-temporary-access-outdated-apps-account-settings-sln27791.html) for more information. You can directly access this setting [here](https://login.yahoo.com/account/security#less-secure-apps). Note that FairEmail is using the standard [IMAP protocol](https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol), which is really not outdated. diff --git a/app/src/main/java/eu/faircode/email/EmailProvider.java b/app/src/main/java/eu/faircode/email/EmailProvider.java index 7d34f1ae5c..63174c0d60 100644 --- a/app/src/main/java/eu/faircode/email/EmailProvider.java +++ b/app/src/main/java/eu/faircode/email/EmailProvider.java @@ -59,6 +59,7 @@ public class EmailProvider { public int smtp_port; public boolean smtp_starttls; public UserType user = UserType.EMAIL; + public String helpUrl = null; public StringBuilder documentation = null; // html enum UserType {LOCAL, EMAIL} @@ -410,14 +411,14 @@ public class EmailProvider { private static EmailProvider addSpecials(Context context, EmailProvider provider) { if ("imap.gmail.com".equals(provider.imap_host)) - addDocumentation(provider, - "https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq6", - context.getString(R.string.title_setup_instructions)); + provider.helpUrl = Helper.FAQ_URI + "#user-content-faq6"; + + if (provider.imap_host.endsWith("office365.com") || + provider.imap_host.endsWith("live.com")) + provider.helpUrl = Helper.FAQ_URI + "#user-content-faq14"; if (provider.imap_host.endsWith("yahoo.com")) - addDocumentation(provider, - "https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq88", - context.getString(R.string.title_setup_instructions)); + provider.helpUrl = Helper.FAQ_URI + "#user-content-faq88"; return provider; } diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java index d9f5c38a34..5c01297e16 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAccount.java +++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.GradientDrawable; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -127,6 +128,7 @@ public class FragmentAccount extends FragmentBase { private Button btnSave; private ContentLoadingProgressBar pbSave; private TextView tvError; + private Button btnHelp; private TextView tvInstructions; private ContentLoadingProgressBar pbWait; @@ -207,7 +209,9 @@ public class FragmentAccount extends FragmentBase { btnSave = view.findViewById(R.id.btnSave); pbSave = view.findViewById(R.id.pbSave); + tvError = view.findViewById(R.id.tvError); + btnHelp = view.findViewById(R.id.btnHelp); tvInstructions = view.findViewById(R.id.tvInstructions); pbWait = view.findViewById(R.id.pbWait); @@ -351,6 +355,22 @@ public class FragmentAccount extends FragmentBase { } }); + addBackPressedListener(new ActivityBase.IBackPressedListener() { + @Override + public boolean onBackPressed() { + onSave(true); + return true; + } + }); + + btnHelp.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_VIEW, (Uri) btnHelp.getTag()); + Helper.view(getContext(), intent); + } + }); + adapter = new ArrayAdapter<>(getContext(), R.layout.spinner_item1, android.R.id.text1, new ArrayList()); adapter.setDropDownViewResource(R.layout.spinner_item1_dropdown); @@ -366,14 +386,6 @@ public class FragmentAccount extends FragmentBase { spLeft.setAdapter(adapterSwipe); spRight.setAdapter(adapterSwipe); - addBackPressedListener(new ActivityBase.IBackPressedListener() { - @Override - public boolean onBackPressed() { - onSave(true); - return true; - } - }); - // Initialize Helper.setViewsEnabled(view, false); @@ -393,7 +405,9 @@ public class FragmentAccount extends FragmentBase { btnSave.setVisibility(View.GONE); pbSave.setVisibility(View.GONE); + tvError.setVisibility(View.GONE); + btnHelp.setVisibility(View.GONE); tvInstructions.setVisibility(View.GONE); tvInstructions.setMovementMethod(LinkMovementMethod.getInstance()); @@ -467,6 +481,7 @@ public class FragmentAccount extends FragmentBase { tvUtf8.setVisibility(View.GONE); grpFolders.setVisibility(View.GONE); tvError.setVisibility(View.GONE); + btnHelp.setVisibility(View.GONE); tvInstructions.setVisibility(View.GONE); } @@ -701,6 +716,8 @@ public class FragmentAccount extends FragmentBase { Helper.setViewsEnabled(view, false); pbSave.setVisibility(View.VISIBLE); tvError.setVisibility(View.GONE); + btnHelp.setVisibility(View.GONE); + tvInstructions.setVisibility(View.GONE); } @Override @@ -838,17 +855,17 @@ public class FragmentAccount extends FragmentBase { String accountRealm = (account == null ? null : account.realm); boolean check = (synchronize && (account == null || - !account.synchronize || - account.insecure != insecure || + !account.synchronize || account.error != null || + !account.insecure.equals(insecure) || !host.equals(account.host) || Integer.parseInt(port) != account.port || !user.equals(account.user) || !password.equals(account.password) || - !Objects.equals(realm, accountRealm) || - account.error != null)); + !Objects.equals(realm, accountRealm))); boolean reload = (check || account == null || account.synchronize != synchronize || account.notify != notify || !account.poll_interval.equals(Integer.parseInt(interval)) || account.partial_fetch != partial_fetch); + Log.i("Account check=" + check + " reload=" + reload); Long last_connected = null; if (account != null && synchronize == account.synchronize) @@ -1053,20 +1070,27 @@ public class FragmentAccount extends FragmentBase { tvError.setText(Helper.formatThrowable(ex, false)); tvError.setVisibility(View.VISIBLE); - final View target; + final EmailProvider provider = (EmailProvider) spProvider.getSelectedItem(); + if (provider != null && provider.helpUrl != null) { + Uri uri = Uri.parse(provider.helpUrl); + btnHelp.setTag(uri); + btnHelp.setVisibility(View.VISIBLE); + } - EmailProvider provider = (EmailProvider) spProvider.getSelectedItem(); if (provider != null && provider.documentation != null) { tvInstructions.setText(HtmlHelper.fromHtml(provider.documentation.toString())); tvInstructions.setVisibility(View.VISIBLE); - target = tvInstructions; - } else - target = tvError; + } new Handler().post(new Runnable() { @Override public void run() { - scroll.smoothScrollTo(0, target.getBottom()); + if (provider != null && provider.documentation != null) + scroll.smoothScrollTo(0, tvInstructions.getBottom()); + else if (provider != null && provider.helpUrl != null) + scroll.smoothScrollTo(0, btnHelp.getBottom()); + else + scroll.smoothScrollTo(0, tvError.getBottom()); } }); } diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentity.java b/app/src/main/java/eu/faircode/email/FragmentIdentity.java index 34793940e0..46d74e29c6 100644 --- a/app/src/main/java/eu/faircode/email/FragmentIdentity.java +++ b/app/src/main/java/eu/faircode/email/FragmentIdentity.java @@ -26,6 +26,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.graphics.Color; import android.graphics.drawable.GradientDrawable; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.text.Editable; @@ -122,6 +123,7 @@ public class FragmentIdentity extends FragmentBase { private Button btnSave; private ContentLoadingProgressBar pbSave; private TextView tvError; + private Button btnHelp; private TextView tvInstructions; private ContentLoadingProgressBar pbWait; @@ -198,6 +200,7 @@ public class FragmentIdentity extends FragmentBase { btnSave = view.findViewById(R.id.btnSave); pbSave = view.findViewById(R.id.pbSave); tvError = view.findViewById(R.id.tvError); + btnHelp = view.findViewById(R.id.btnHelp); tvInstructions = view.findViewById(R.id.tvInstructions); tvInstructions.setMovementMethod(LinkMovementMethod.getInstance()); @@ -214,6 +217,7 @@ public class FragmentIdentity extends FragmentBase { grpAuthorize.setVisibility(position > 0 ? View.VISIBLE : View.GONE); if (position == 0) { tvError.setVisibility(View.GONE); + btnHelp.setVisibility(View.GONE); tvInstructions.setVisibility(View.GONE); grpAdvanced.setVisibility(View.GONE); } @@ -400,6 +404,14 @@ public class FragmentIdentity extends FragmentBase { } }); + btnHelp.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_VIEW, (Uri) btnHelp.getTag()); + Helper.view(getContext(), intent); + } + }); + // Initialize Helper.setViewsEnabled(view, false); btnAutoConfig.setEnabled(false); @@ -411,6 +423,7 @@ public class FragmentIdentity extends FragmentBase { btnSave.setVisibility(View.GONE); pbSave.setVisibility(View.GONE); tvError.setVisibility(View.GONE); + btnHelp.setVisibility(View.GONE); tvInstructions.setVisibility(View.GONE); grpAuthorize.setVisibility(View.GONE); @@ -508,6 +521,7 @@ public class FragmentIdentity extends FragmentBase { Helper.setViewsEnabled(view, false); pbSave.setVisibility(View.VISIBLE); tvError.setVisibility(View.GONE); + btnHelp.setVisibility(View.GONE); tvInstructions.setVisibility(View.GONE); } @@ -645,13 +659,13 @@ public class FragmentIdentity extends FragmentBase { String identityRealm = (identity == null ? null : identity.realm); boolean check = (synchronize && (identity == null || - !identity.synchronize || - identity.insecure != insecure || + !identity.synchronize || identity.error != null || + !identity.insecure.equals(insecure) || !host.equals(identity.host) || Integer.parseInt(port) != identity.port || !user.equals(identity.user) || !password.equals(identity.password) || !Objects.equals(realm, identityRealm) || - use_ip != identity.use_ip || - identity.error != null)); + use_ip != identity.use_ip)); + Log.i("Identity check=" + check); Long last_connected = null; if (identity != null && synchronize == identity.synchronize) @@ -774,20 +788,27 @@ public class FragmentIdentity extends FragmentBase { tvError.setText(Helper.formatThrowable(ex, false)); tvError.setVisibility(View.VISIBLE); - final View target; + final EmailProvider provider = (EmailProvider) spProvider.getSelectedItem(); + if (provider != null && provider.helpUrl != null) { + Uri uri = Uri.parse(provider.helpUrl); + btnHelp.setTag(uri); + btnHelp.setVisibility(View.VISIBLE); + } - EmailProvider provider = (EmailProvider) spProvider.getSelectedItem(); if (provider != null && provider.documentation != null) { tvInstructions.setText(HtmlHelper.fromHtml(provider.documentation.toString())); tvInstructions.setVisibility(View.VISIBLE); - target = tvInstructions; - } else - target = tvError; + } new Handler().post(new Runnable() { @Override public void run() { - scroll.smoothScrollTo(0, target.getBottom()); + if (provider != null && provider.documentation != null) + scroll.smoothScrollTo(0, tvInstructions.getBottom()); + else if (provider != null && provider.helpUrl != null) + scroll.smoothScrollTo(0, btnHelp.getBottom()); + else + scroll.smoothScrollTo(0, tvError.getBottom()); } }); } diff --git a/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java b/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java index 3ac20282ca..9af3e9fbf9 100644 --- a/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java +++ b/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java @@ -24,7 +24,9 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.text.TextUtils; import android.text.method.LinkMovementMethod; import android.util.Patterns; @@ -38,6 +40,7 @@ import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.EditText; +import android.widget.ScrollView; import android.widget.TextView; import androidx.annotation.NonNull; @@ -64,6 +67,7 @@ import static android.app.Activity.RESULT_OK; public class FragmentQuickSetup extends FragmentBase { private ViewGroup view; + private ScrollView scroll; private EditText etName; private EditText etEmail; @@ -71,6 +75,7 @@ public class FragmentQuickSetup extends FragmentBase { private Button btnCheck; private TextView tvError; + private Button btnHelp; private TextView tvInstructions; private TextView tvImap; @@ -87,6 +92,7 @@ public class FragmentQuickSetup extends FragmentBase { setHasOptionsMenu(true); view = (ViewGroup) inflater.inflate(R.layout.fragment_quick_setup, container, false); + scroll = view.findViewById(R.id.scroll); // Get controls etName = view.findViewById(R.id.etName); @@ -95,6 +101,7 @@ public class FragmentQuickSetup extends FragmentBase { btnCheck = view.findViewById(R.id.btnCheck); tvError = view.findViewById(R.id.tvError); + btnHelp = view.findViewById(R.id.btnHelp); tvInstructions = view.findViewById(R.id.tvInstructions); tvImap = view.findViewById(R.id.tvImap); @@ -130,8 +137,17 @@ public class FragmentQuickSetup extends FragmentBase { } }); + btnHelp.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_VIEW, (Uri) btnHelp.getTag()); + Helper.view(getContext(), intent); + } + }); + // Initialize tvError.setVisibility(View.GONE); + btnHelp.setVisibility(View.GONE); tvInstructions.setVisibility(View.GONE); tvInstructions.setMovementMethod(LinkMovementMethod.getInstance()); grpSetup.setVisibility(View.GONE); @@ -181,6 +197,7 @@ public class FragmentQuickSetup extends FragmentBase { Helper.setViewsEnabled(view, false); tvError.setVisibility(View.GONE); + btnHelp.setVisibility(View.GONE); tvInstructions.setVisibility(View.GONE); grpSetup.setVisibility(check ? View.GONE : View.VISIBLE); } @@ -207,6 +224,8 @@ public class FragmentQuickSetup extends FragmentBase { String[] dparts = email.split("@"); EmailProvider provider = EmailProvider.fromDomain(context, dparts[1]); + if (provider.helpUrl != null) + args.putString("help", provider.helpUrl); if (provider.documentation != null) args.putString("documentation", provider.documentation.toString()); @@ -377,18 +396,37 @@ public class FragmentQuickSetup extends FragmentBase { } @Override - protected void onException(Bundle args, Throwable ex) { - if (args.containsKey("documentation")) { - tvInstructions.setText(HtmlHelper.fromHtml(args.getString("documentation"))); - tvInstructions.setVisibility(View.VISIBLE); - } - + protected void onException(final Bundle args, Throwable ex) { if (ex instanceof IllegalArgumentException || ex instanceof UnknownHostException) Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show(); else { tvError.setText(Helper.formatThrowable(ex, false)); tvError.setVisibility(View.VISIBLE); } + + if (args.containsKey("help")) { + Uri uri = Uri.parse(args.getString("help")); + btnHelp.setTag(uri); + btnHelp.setVisibility(View.VISIBLE); + } + + if (args.containsKey("documentation")) { + tvInstructions.setText(HtmlHelper.fromHtml(args.getString("documentation"))); + tvInstructions.setVisibility(View.VISIBLE); + } + + new Handler().post(new Runnable() { + @Override + public void run() { + if (args.containsKey("documentation")) + scroll.smoothScrollTo(0, tvInstructions.getBottom()); + else if (args.containsKey("help")) + scroll.smoothScrollTo(0, btnHelp.getBottom()); + else if (tvError.getVisibility() == View.VISIBLE) + scroll.smoothScrollTo(0, tvError.getBottom()); + } + }); + } }.execute(FragmentQuickSetup.this, args, "setup:quick"); } diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml index e95debb087..a2959c3a42 100644 --- a/app/src/main/res/layout/fragment_account.xml +++ b/app/src/main/res/layout/fragment_account.xml @@ -703,6 +703,18 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/btnSave" /> +