diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser index e5a97d43..3b7cce37 100644 Binary files a/.idea/caches/build_file_checksums.ser and b/.idea/caches/build_file_checksums.ser differ diff --git a/README.md b/README.md index 354b58c0..4de3c319 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ FairEmail uses: * [Android Support Library](https://developer.android.com/tools/support-library/). Copyright (C) 2011 The Android Open Source Project. [Apache license](https://android.googlesource.com/platform/frameworks/support/+/master/LICENSE.txt). * [Android Architecture Components](https://developer.android.com/topic/libraries/architecture/). Copyright 2018 The Android Open Source Project, Inc. [Apache license](https://github.com/googlesamples/android-architecture-components/blob/master/LICENSE). * [colorpicker](https://android.googlesource.com/platform/frameworks/opt/colorpicker). Copyright (C) 2013 The Android Open Source Project. [Apache license](https://android.googlesource.com/platform/frameworks/opt/colorpicker/+/master/src/com/android/colorpicker/ColorPickerDialog.java). +* [dnsjava](http://www.xbill.org/dnsjava/). Copyright (c) 1998-2011, Brian Wellington. [BSD License](https://sourceforge.net/p/dnsjava/code/HEAD/tree/trunk/LICENSE). ## License diff --git a/app/build.gradle b/app/build.gradle index 672ca7dc..ad7f6611 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -80,6 +80,7 @@ dependencies { def javamail_version = "1.6.2" def jsoup_version = "1.11.3" def jcharset_version = "2.0" + def dnsjava_version = "2.1.8" implementation "androidx.appcompat:appcompat:$androidx_version" implementation "androidx.recyclerview:recyclerview:$androidx_version" @@ -105,6 +106,9 @@ dependencies { implementation "net.freeutils:jcharset:$jcharset_version" + // http://www.xbill.org/dnsjava/ + implementation "dnsjava:dnsjava:$dnsjava_version" + // git clone https://android.googlesource.com/platform/frameworks/opt/colorpicker implementation project(path: ':colorpicker') } diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java index b28ccd9a..7f80a7a3 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAccount.java +++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java @@ -62,6 +62,11 @@ import com.google.android.material.textfield.TextInputLayout; import com.sun.mail.imap.IMAPFolder; import com.sun.mail.imap.IMAPStore; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Record; +import org.xbill.DNS.SRVRecord; +import org.xbill.DNS.Type; + import java.text.Collator; import java.util.ArrayList; import java.util.Collections; @@ -88,6 +93,8 @@ public class FragmentAccount extends FragmentEx { private ViewGroup view; private Spinner spProvider; private EditText etHost; + private EditText etDomain; + private Button btnAutoConfig; private EditText etPort; private Button btnAuthorize; private EditText etUser; @@ -139,6 +146,10 @@ public class FragmentAccount extends FragmentEx { // Get controls spProvider = view.findViewById(R.id.spProvider); + + etDomain = view.findViewById(R.id.etDomain); + btnAutoConfig = view.findViewById(R.id.btnAutoConfig); + etHost = view.findViewById(R.id.etHost); etPort = view.findViewById(R.id.etPort); @@ -217,6 +228,50 @@ public class FragmentAccount extends FragmentEx { } }); + btnAutoConfig.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + btnAutoConfig.setEnabled(false); + + Bundle args = new Bundle(); + args.putString("domain", etDomain.getText().toString()); + + new SimpleTask() { + @Override + protected SRVRecord onLoad(Context context, Bundle args) throws Throwable { + String domain = args.getString("domain"); + Record[] records = new Lookup("_imaps._tcp." + domain, Type.SRV).run(); + if (records != null) + for (int i = 0; i < records.length; i++) { + SRVRecord srv = (SRVRecord) records[i]; + Log.i(Helper.TAG, "SRV=" + srv); + return srv; + } + + throw new IllegalArgumentException(getString(R.string.title_no_settings)); + } + + @Override + protected void onLoaded(Bundle args, SRVRecord srv) { + btnAutoConfig.setEnabled(true); + if (srv != null) { + etHost.setText(srv.getTarget().toString(true)); + etPort.setText(Integer.toString(srv.getPort())); + } + } + + @Override + protected void onException(Bundle args, Throwable ex) { + btnAutoConfig.setEnabled(true); + if (ex instanceof IllegalArgumentException) + Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show(); + else + Helper.unexpectedError(getContext(), ex); + } + }.load(FragmentAccount.this, args); + } + }); + tilPassword.getEditText().addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentity.java b/app/src/main/java/eu/faircode/email/FragmentIdentity.java index 7e8f950a..70ad8454 100644 --- a/app/src/main/java/eu/faircode/email/FragmentIdentity.java +++ b/app/src/main/java/eu/faircode/email/FragmentIdentity.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.text.TextUtils; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -36,8 +37,14 @@ import android.widget.ImageButton; import android.widget.ProgressBar; import android.widget.Spinner; +import com.google.android.material.snackbar.Snackbar; import com.google.android.material.textfield.TextInputLayout; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Record; +import org.xbill.DNS.SRVRecord; +import org.xbill.DNS.Type; + import java.util.ArrayList; import java.util.List; import java.util.Properties; @@ -59,6 +66,8 @@ public class FragmentIdentity extends FragmentEx { private EditText etEmail; private EditText etReplyTo; private Spinner spProvider; + private EditText etDomain; + private Button btnAutoConfig; private EditText etHost; private CheckBox cbStartTls; private EditText etPort; @@ -94,18 +103,27 @@ public class FragmentIdentity extends FragmentEx { // Get controls etName = view.findViewById(R.id.etName); spAccount = view.findViewById(R.id.spAccount); + btnAdvanced = view.findViewById(R.id.btnAdvanced); etEmail = view.findViewById(R.id.etEmail); etReplyTo = view.findViewById(R.id.etReplyTo); + spProvider = view.findViewById(R.id.spProvider); + + etDomain = view.findViewById(R.id.etDomain); + btnAutoConfig = view.findViewById(R.id.btnAutoConfig); + etHost = view.findViewById(R.id.etHost); cbStartTls = view.findViewById(R.id.cbStartTls); etPort = view.findViewById(R.id.etPort); + etUser = view.findViewById(R.id.etUser); tilPassword = view.findViewById(R.id.tilPassword); + cbSynchronize = view.findViewById(R.id.cbSynchronize); cbPrimary = view.findViewById(R.id.cbPrimary); cbStoreSent = view.findViewById(R.id.cbStoreSent); + btnSave = view.findViewById(R.id.btnSave); pbSave = view.findViewById(R.id.pbSave); ibDelete = view.findViewById(R.id.ibDelete); @@ -197,6 +215,51 @@ public class FragmentIdentity extends FragmentEx { } }); + btnAutoConfig.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + btnAutoConfig.setEnabled(false); + + Bundle args = new Bundle(); + args.putString("domain", etDomain.getText().toString()); + + new SimpleTask() { + @Override + protected SRVRecord onLoad(Context context, Bundle args) throws Throwable { + String domain = args.getString("domain"); + Record[] records = new Lookup("_submission._tcp." + domain, Type.SRV).run(); + if (records != null) + for (int i = 0; i < records.length; i++) { + SRVRecord srv = (SRVRecord) records[i]; + Log.i(Helper.TAG, "SRV=" + srv); + return srv; + } + + throw new IllegalArgumentException(getString(R.string.title_no_settings)); + } + + @Override + protected void onLoaded(Bundle args, SRVRecord srv) { + btnAutoConfig.setEnabled(true); + if (srv != null) { + etHost.setText(srv.getTarget().toString(true)); + etPort.setText(Integer.toString(srv.getPort())); + cbStartTls.setChecked(srv.getPort() == 587); + } + } + + @Override + protected void onException(Bundle args, Throwable ex) { + btnAutoConfig.setEnabled(true); + if (ex instanceof IllegalArgumentException) + Snackbar.make(view, ex.getMessage(), Snackbar.LENGTH_LONG).show(); + else + Helper.unexpectedError(getContext(), ex); + } + }.load(FragmentIdentity.this, args); + } + }); + cbStartTls.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml index 7d33475b..5875836a 100644 --- a/app/src/main/res/layout/fragment_account.xml +++ b/app/src/main/res/layout/fragment_account.xml @@ -30,6 +30,40 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tvProvider" /> + + + + + + +