Show all detected email addresses as account name suggestion

This commit is contained in:
Ricki Hirner 2020-05-26 13:03:20 +02:00
parent b59f6c8b7e
commit 558ced82da
5 changed files with 81 additions and 27 deletions

View file

@ -124,6 +124,16 @@ class DavResourceFinderTest {
assertNull(finder.getCurrentUserPrincipal(server.url(PATH_CARDDAV), DavResourceFinder.Service.CALDAV))
}
@Test
fun testQueryEmailAddress() {
var info = ServiceInfo()
assertArrayEquals(
arrayOf("email1@example.com", "email2@example.com"),
finder.queryEmailAddress(server.url(PATH_CALDAV + SUBPATH_PRINCIPAL)).toTypedArray()
)
assertTrue(finder.queryEmailAddress(server.url(PATH_CARDDAV + SUBPATH_PRINCIPAL)).isEmpty())
}
// mock server
@ -167,12 +177,19 @@ class DavResourceFinderTest {
" <CARD:addressbook/>" +
"</resourcetype>"
PATH_CALDAV + SUBPATH_PRINCIPAL ->
props = "<CAL:calendar-user-address-set>" +
" <href>urn:unknown-entry</href>" +
" <href>mailto:email1@example.com</href>" +
" <href>mailto:email2@example.com</href>" +
"</CAL:calendar-user-address-set>"
else -> props = null
}
Logger.log.info("Sending props: $props")
return MockResponse()
.setResponseCode(207)
.setBody("<multistatus xmlns='DAV:' xmlns:CARD='urn:ietf:params:xml:ns:carddav'>" +
.setBody("<multistatus xmlns='DAV:' xmlns:CARD='urn:ietf:params:xml:ns:carddav' xmlns:CAL='urn:ietf:params:xml:ns:caldav'>" +
"<response>" +
" <href>${request.path}</href>" +
" <propstat><prop>$props</prop></propstat>" +

View file

@ -18,6 +18,7 @@ import android.provider.CalendarContract
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import androidx.fragment.app.Fragment
import androidx.lifecycle.*
import at.bitfire.davdroid.Constants
@ -59,9 +60,11 @@ class AccountDetailsFragment: Fragment() {
val config = loginModel.configuration ?: throw IllegalStateException()
model.name.value = config.calDAV?.email ?:
loginModel.credentials?.userName ?:
loginModel.credentials?.certificateAlias
// default account name
model.name.value =
config.calDAV?.emails?.firstOrNull() ?:
loginModel.credentials?.userName ?:
loginModel.credentials?.certificateAlias
// CardDAV-specific
val settings = Settings.getInstance(requireActivity())
@ -69,6 +72,12 @@ class AccountDetailsFragment: Fragment() {
if (settings.has(AccountSettings.KEY_CONTACT_GROUP_METHOD))
v.contactGroupMethod.isEnabled = false
// CalDAV-specific
config.calDAV?.let {
val accountNameAdapter = ArrayAdapter<String>(requireActivity(), android.R.layout.simple_list_item_1, it.emails)
v.accountName.setAdapter(accountNameAdapter)
}
v.createAccount.setOnClickListener {
val name = model.name.value
if (name.isNullOrBlank())

View file

@ -143,24 +143,10 @@ class DavResourceFinder(
}
}
if (config.principal != null && service == Service.CALDAV)
// query email address (CalDAV scheduling: calendar-user-address-set)
try {
DavResource(httpClient.okHttpClient, config.principal!!, log).propfind(0, CalendarUserAddressSet.NAME) { response, _ ->
response[CalendarUserAddressSet::class.java]?.let { addressSet ->
for (href in addressSet.hrefs)
try {
val uri = URI(href)
if (uri.scheme.equals("mailto", true))
config.email = uri.schemeSpecificPart
} catch(e: URISyntaxException) {
log.log(Level.WARNING, "Couldn't parse user address", e)
}
}
}
} catch(e: Exception) {
log.log(Level.WARNING, "Couldn't query user email address", e)
rethrowIfInterrupted(e)
// detect email address
if (service == Service.CALDAV)
config.principal?.let {
config.emails.addAll(queryEmailAddress(it))
}
// return config or null if config doesn't contain useful information
@ -202,6 +188,33 @@ class DavResourceFinder(
}
}
/**
* Queries a user's email address using CalDAV scheduling: calendar-user-address-set.
* @param principal principal URL of the user
* @return list of found email addresses (empty if none)
*/
fun queryEmailAddress(principal: HttpUrl): List<String> {
var mailboxes = LinkedList<String>()
try {
DavResource(httpClient.okHttpClient, principal, log).propfind(0, CalendarUserAddressSet.NAME) { response, _ ->
response[CalendarUserAddressSet::class.java]?.let { addressSet ->
for (href in addressSet.hrefs)
try {
val uri = URI(href)
if (uri.scheme.equals("mailto", true))
mailboxes.add(uri.schemeSpecificPart)
} catch(e: URISyntaxException) {
log.log(Level.WARNING, "Couldn't parse user address", e)
}
}
}
} catch(e: Exception) {
log.log(Level.WARNING, "Couldn't query user email address", e)
rethrowIfInterrupted(e)
}
return mailboxes
}
/**
* If [dav] references an address book, an address book home set, and/or a princiapl,
* it will added to, config.collections, config.homesets and/or config.principal.
@ -423,7 +436,7 @@ class DavResourceFinder(
val homeSets: MutableSet<HttpUrl> = HashSet(),
val collections: MutableMap<HttpUrl, Collection> = HashMap(),
var email: String? = null
val emails: MutableList<String> = LinkedList<String>()
)
override fun toString(): String {

View file

@ -1,9 +1,11 @@
package at.bitfire.davdroid.ui.widget
import android.text.method.LinkMovementMethod
import android.widget.AutoCompleteTextView
import android.widget.TextView
import androidx.core.text.HtmlCompat
import androidx.databinding.BindingAdapter
import androidx.databinding.InverseBindingAdapter
object BindingAdapters {
@ -23,4 +25,15 @@ object BindingAdapters {
textView.text = null
}
@BindingAdapter("unfilteredText")
@JvmStatic
fun setUnfilteredText(view: AutoCompleteTextView, text: String?) {
view.setText(text, false)
}
@InverseBindingAdapter(attribute = "unfilteredText", event = "android:textAttrChanged")
@JvmStatic
fun getUnfilteredText(textView: AutoCompleteTextView) = textView.text.toString()
}

View file

@ -44,13 +44,15 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_account_name"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
app:errorEnabled="true"
app:error="@{details.nameError}">
<com.google.android.material.textfield.TextInputEditText
<AutoCompleteTextView
android:id="@+id/accountName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={details.name}"
android:inputType="textEmailAddress"/>
unfilteredText="@={details.name}"
android:inputType="textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<TextView