mirror of
https://github.com/etesync/android
synced 2025-03-25 03:45:46 +00:00
New initial server configuration detection
* separate initial server configuration (= principal and/or a certain collection) detection from collection refresh (to be done) * GUI: LoginActivity
This commit is contained in:
parent
515969c4b8
commit
ba0350c83d
@ -32,6 +32,7 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'IconColors'
|
||||
disable 'IconLauncherShape'
|
||||
|
@ -112,7 +112,7 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.AddAccountActivity"
|
||||
android:name=".ui.setup.LoginActivity"
|
||||
android:label="@string/login_title"
|
||||
android:parentActivityName=".ui.AccountsActivity"
|
||||
android:noHistory="true">
|
||||
|
@ -13,6 +13,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Constants {
|
||||
|
||||
public static final String
|
||||
ACCOUNT_TYPE = "bitfire.at.davdroid";
|
||||
|
||||
@ -26,5 +27,6 @@ public class Constants {
|
||||
NOTIFICATION_CALENDAR_SYNC = 11,
|
||||
NOTIFICATION_TASK_SYNC = 12;
|
||||
|
||||
public final static Uri webUri = Uri.parse("https://davdroid.bitfire.at/?pk_campaign=davdroid-app");
|
||||
public static final Uri webUri = Uri.parse("https://davdroid.bitfire.at/?pk_campaign=davdroid-app");
|
||||
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.squareup.okhttp.HttpUrl;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.xbill.DNS.Lookup;
|
||||
@ -22,14 +21,10 @@ import org.xbill.DNS.Type;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import at.bitfire.dav4android.DavResource;
|
||||
import at.bitfire.dav4android.UrlUtils;
|
||||
@ -47,9 +42,12 @@ import at.bitfire.dav4android.property.CurrentUserPrivilegeSet;
|
||||
import at.bitfire.dav4android.property.DisplayName;
|
||||
import at.bitfire.dav4android.property.ResourceType;
|
||||
import at.bitfire.dav4android.property.SupportedCalendarComponentSet;
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.HttpClient;
|
||||
import at.bitfire.davdroid.log.StringLogger;
|
||||
import at.bitfire.davdroid.ui.setup.LoginCredentialsFragment;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
public class DavResourceFinder {
|
||||
protected enum Service {
|
||||
@ -64,245 +62,140 @@ public class DavResourceFinder {
|
||||
protected final Logger log;
|
||||
protected final Context context;
|
||||
protected final HttpClient httpClient;
|
||||
protected final ServerInfo serverInfo;
|
||||
protected final LoginCredentialsFragment.LoginCredentials credentials;
|
||||
|
||||
protected Map<HttpUrl, ServerInfo.ResourceInfo>
|
||||
addressbooks = new HashMap<>(),
|
||||
calendars = new HashMap<>(),
|
||||
taskLists = new HashMap<>();
|
||||
protected HttpUrl carddavPrincipal, caldavPrincipal;
|
||||
protected Map<HttpUrl, ServerConfiguration.Collection>
|
||||
addressBooks = new HashMap<>(),
|
||||
calendars = new HashMap<>();
|
||||
|
||||
|
||||
public DavResourceFinder(Logger log, Context context, ServerInfo serverInfo) {
|
||||
this.log = log;
|
||||
public DavResourceFinder(Context context, LoginCredentialsFragment.LoginCredentials credentials) {
|
||||
this.context = context;
|
||||
this.serverInfo = serverInfo;
|
||||
this.credentials = credentials;
|
||||
|
||||
httpClient = new HttpClient(log, context, serverInfo.getUserName(), serverInfo.getPassword(), serverInfo.authPreemptive);
|
||||
log = new StringLogger("DavResourceFinder", true);
|
||||
httpClient = new HttpClient(log, context, credentials.getUserName(), credentials.getPassword(), credentials.isAuthPreemptive());
|
||||
}
|
||||
|
||||
public void findResources() {
|
||||
try {
|
||||
findResources(Service.CARDDAV);
|
||||
findResources(Service.CALDAV);
|
||||
} catch(URISyntaxException e) {
|
||||
log.warn("Invalid user-given URI", e);
|
||||
}
|
||||
|
||||
public ServerConfiguration findInitialConfiguration() {
|
||||
addressBooks.clear();
|
||||
findInitialConfiguration(Service.CARDDAV);
|
||||
|
||||
calendars.clear();
|
||||
findInitialConfiguration(Service.CALDAV);
|
||||
|
||||
return new ServerConfiguration(
|
||||
carddavPrincipal, addressBooks.values().toArray(new ServerConfiguration.Collection[0]),
|
||||
caldavPrincipal, calendars.values().toArray(new ServerConfiguration.Collection[0]),
|
||||
log.toString()
|
||||
);
|
||||
}
|
||||
|
||||
public void findResources(Service service) throws URISyntaxException {
|
||||
URI baseURI = serverInfo.getBaseURI();
|
||||
protected void findInitialConfiguration(Service service) {
|
||||
// user-given base URI (mailto or URL)
|
||||
URI baseURI = credentials.getUri();
|
||||
|
||||
// domain for service discovery
|
||||
String domain = null;
|
||||
|
||||
HttpUrl principalUrl = null;
|
||||
Set<HttpUrl> homeSets = new HashSet<>();
|
||||
HttpUrl principal = null;
|
||||
|
||||
if (service == Service.CALDAV) {
|
||||
calendars.clear();
|
||||
taskLists.clear();
|
||||
} else if (service == Service.CARDDAV)
|
||||
addressbooks.clear();
|
||||
// Step 1a (only when user-given URI is URL):
|
||||
// * Check whether URL represents a calendar/address-book collection itself,
|
||||
// * and/or whether it has a current-user-principal,
|
||||
// * or whether it represents a principal itself.
|
||||
if ("http".equalsIgnoreCase(baseURI.getScheme()) || "https".equalsIgnoreCase(baseURI.getScheme())) {
|
||||
HttpUrl baseURL = HttpUrl.get(baseURI);
|
||||
|
||||
log.info("*** STARTING COLLECTION DISCOVERY FOR SERVICE " + service.name.toUpperCase(Locale.US) + "***");
|
||||
if ("http".equals(baseURI.getScheme()) || "https".equals(baseURI.getScheme())) {
|
||||
HttpUrl userURL = HttpUrl.get(baseURI);
|
||||
// remember domain for service discovery (if required)
|
||||
// try service discovery only for https:// URLs because only secure service discovery is implemented
|
||||
if ("https".equalsIgnoreCase(baseURL.scheme()))
|
||||
domain = baseURI.getHost();
|
||||
|
||||
/* check whether:
|
||||
1. user-given URL is a calendar
|
||||
2. user-given URL has a calendar-home-set property (i.e. is a principal URL)
|
||||
*/
|
||||
log.info("Check whether user-given URL is a calendar collection and/or contains home-set and/or has current-user-principal");
|
||||
DavResource davBase = new DavResource(log, httpClient, userURL);
|
||||
log.info("Checking user-given URL: " + baseURL.toString());
|
||||
try {
|
||||
if (service == Service.CALDAV) {
|
||||
davBase.propfind(0,
|
||||
CalendarHomeSet.NAME, SupportedCalendarComponentSet.NAME,
|
||||
ResourceType.NAME, DisplayName.NAME, CalendarColor.NAME, CalendarDescription.NAME, CalendarTimezone.NAME, CurrentUserPrivilegeSet.NAME,
|
||||
CurrentUserPrincipal.NAME
|
||||
);
|
||||
addIfCalendar(davBase);
|
||||
} else if (service == Service.CARDDAV) {
|
||||
DavResource davBase = new DavResource(log, httpClient, baseURL);
|
||||
|
||||
if (service == Service.CARDDAV) {
|
||||
davBase.propfind(0,
|
||||
AddressbookHomeSet.NAME,
|
||||
ResourceType.NAME, DisplayName.NAME, AddressbookDescription.NAME, CurrentUserPrivilegeSet.NAME,
|
||||
CurrentUserPrincipal.NAME
|
||||
);
|
||||
addIfAddressBook(davBase);
|
||||
} else if (service == Service.CALDAV) {
|
||||
davBase.propfind(0,
|
||||
CalendarHomeSet.NAME, SupportedCalendarComponentSet.NAME,
|
||||
ResourceType.NAME, DisplayName.NAME, CalendarColor.NAME, CalendarDescription.NAME, CalendarTimezone.NAME, CurrentUserPrivilegeSet.NAME,
|
||||
CurrentUserPrincipal.NAME
|
||||
);
|
||||
addIfCalendar(davBase);
|
||||
}
|
||||
|
||||
// check for current-user-principal
|
||||
CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal)davBase.properties.get(CurrentUserPrincipal.NAME);
|
||||
if (currentUserPrincipal != null && currentUserPrincipal.href != null)
|
||||
principal = davBase.location.resolve(currentUserPrincipal.href);
|
||||
|
||||
// check for resourcetype = principal
|
||||
if (principal == null) {
|
||||
ResourceType resourceType = (ResourceType)davBase.properties.get(ResourceType.NAME);
|
||||
if (resourceType.types.contains(ResourceType.PRINCIPAL))
|
||||
principal = davBase.location;
|
||||
}
|
||||
|
||||
// If a principal has been detected successfully, ensure that it provides the required service.
|
||||
if (principal != null && !providesService(principal, service))
|
||||
principal = null;
|
||||
|
||||
} catch (IOException|HttpException|DavException e) {
|
||||
log.debug("PROPFIND on user-given URL failed", e);
|
||||
}
|
||||
|
||||
if (service == Service.CALDAV) {
|
||||
CalendarHomeSet calendarHomeSet = (CalendarHomeSet)davBase.properties.get(CalendarHomeSet.NAME);
|
||||
if (calendarHomeSet != null) {
|
||||
log.info("Found <calendar-home-set> at user-given URL");
|
||||
for (String href : calendarHomeSet.hrefs) {
|
||||
HttpUrl url = userURL.resolve(href);
|
||||
if (url != null)
|
||||
homeSets.add(url);
|
||||
}
|
||||
}
|
||||
} else if (service == Service.CARDDAV) {
|
||||
AddressbookHomeSet addressbookHomeSet = (AddressbookHomeSet) davBase.properties.get(AddressbookHomeSet.NAME);
|
||||
if (addressbookHomeSet != null) {
|
||||
log.info("Found <addressbook-home-set> at user-given URL");
|
||||
for (String href : addressbookHomeSet.hrefs) {
|
||||
HttpUrl url = userURL.resolve(href);
|
||||
if (url != null)
|
||||
homeSets.add(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* When home sets haven already been found, skip further searching.
|
||||
* Otherwise (no home sets found), treat the user-given URL as "initial context path" for service discovery.
|
||||
*
|
||||
* Keep in mind that the CalDAV principal URL must not be the CardDAV principal URL! */
|
||||
if (homeSets.isEmpty())
|
||||
// Step 1b: Try well-known URL, too
|
||||
if (principal == null)
|
||||
try {
|
||||
log.info("No home sets found, looking for <current-user-principal>");
|
||||
|
||||
davBase.options();
|
||||
if ((service == Service.CALDAV && davBase.capabilities.contains("calendar-access")) ||
|
||||
(service == Service.CARDDAV && davBase.capabilities.contains("addressbook"))) {
|
||||
CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal)davBase.properties.get(CurrentUserPrincipal.NAME);
|
||||
if (currentUserPrincipal != null && currentUserPrincipal.href != null)
|
||||
principalUrl = davBase.location.resolve(currentUserPrincipal.href);
|
||||
}
|
||||
} catch(IOException|HttpException|DavException e) {
|
||||
log.debug("Couldn't find <current-user-principal> at user-given URL", e);
|
||||
principal = getCurrentUserPrincipal(baseURL.resolve("/.well-known/" + service.name), service);
|
||||
} catch (IOException|HttpException|DavException e) {
|
||||
log.debug("Well-known URL detection failed", e);
|
||||
}
|
||||
|
||||
if (principalUrl == null)
|
||||
try {
|
||||
log.info("User-given URL doesn't contain <current-user-principal>, trying /.well-known/" + service.name);
|
||||
principalUrl = getCurrentUserPrincipal(userURL.resolve("/.well-known/" + service.name));
|
||||
} catch(IOException|HttpException|DavException e) {
|
||||
log.debug("Couldn't determine <current-user-principal> from well-known " + service + " path", e);
|
||||
}
|
||||
|
||||
if (principalUrl == null)
|
||||
// still no principal URL, try service discovery with "domain" = user-given host name
|
||||
domain = baseURI.getHost();
|
||||
|
||||
} else if ("mailto".equals(baseURI.getScheme())) {
|
||||
} else if ("mailto".equalsIgnoreCase(baseURI.getScheme())) {
|
||||
String mailbox = baseURI.getSchemeSpecificPart();
|
||||
|
||||
// determine service FQDN
|
||||
int posAt = mailbox.lastIndexOf("@");
|
||||
if (posAt == -1)
|
||||
throw new URISyntaxException(mailbox, "Missing @ sign");
|
||||
|
||||
domain = mailbox.substring(posAt + 1);
|
||||
if (posAt != -1)
|
||||
domain = mailbox.substring(posAt + 1);
|
||||
}
|
||||
|
||||
if (principalUrl == null && domain != null) {
|
||||
log.info("No principal URL yet, trying SRV/TXT records with domain " + domain);
|
||||
// Step 2: If user-given URL didn't reveal a principal, search for it: SERVICE DISCOVERY
|
||||
if (principal == null && domain != null) {
|
||||
log.info("No principal found at user-given URL, trying to discover");
|
||||
try {
|
||||
principalUrl = discoverPrincipalUrl(domain, service);
|
||||
principal = discoverPrincipalUrl(domain, service);
|
||||
} catch (IOException|HttpException|DavException e) {
|
||||
log.info("Couldn't find principal URL using service discovery");
|
||||
log.debug(service.name + " service discovery failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
// principal URL has been found, get addressbook-home-set/calendar-home-set
|
||||
if (principalUrl != null) {
|
||||
log.info("Principal URL=" + principalUrl + ", getting <calendar-home-set>");
|
||||
try {
|
||||
DavResource principal = new DavResource(log, httpClient, principalUrl);
|
||||
|
||||
if (service == Service.CALDAV) {
|
||||
principal.propfind(0, CalendarHomeSet.NAME);
|
||||
CalendarHomeSet calendarHomeSet = (CalendarHomeSet) principal.properties.get(CalendarHomeSet.NAME);
|
||||
if (calendarHomeSet != null) {
|
||||
log.info("Found <calendar-home-set> at principal URL");
|
||||
for (String href : calendarHomeSet.hrefs) {
|
||||
HttpUrl url = principal.location.resolve(href);
|
||||
if (url != null)
|
||||
homeSets.add(url);
|
||||
}
|
||||
}
|
||||
} else if (service == Service.CARDDAV) {
|
||||
principal.propfind(0, AddressbookHomeSet.NAME);
|
||||
AddressbookHomeSet addressbookHomeSet = (AddressbookHomeSet) principal.properties.get(AddressbookHomeSet.NAME);
|
||||
if (addressbookHomeSet != null) {
|
||||
log.info("Found <addressbook-home-set> at principal URL");
|
||||
for (String href : addressbookHomeSet.hrefs) {
|
||||
HttpUrl url = principal.location.resolve(href);
|
||||
if (url != null)
|
||||
homeSets.add(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException|HttpException|DavException e) {
|
||||
log.debug("PROPFIND on " + principalUrl + " failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
// now query all home sets
|
||||
for (HttpUrl url : homeSets)
|
||||
if (service == Service.CALDAV)
|
||||
try {
|
||||
log.info("Listing calendar collections in home set " + url);
|
||||
DavResource homeSet = new DavResource(log, httpClient, url);
|
||||
homeSet.propfind(1, SupportedCalendarComponentSet.NAME, ResourceType.NAME, DisplayName.NAME, CurrentUserPrivilegeSet.NAME,
|
||||
CalendarColor.NAME, CalendarDescription.NAME, CalendarTimezone.NAME);
|
||||
|
||||
// home set should not be a calendar, but some servers have only one calendar and it's the home set
|
||||
addIfCalendar(homeSet);
|
||||
|
||||
// members of the home set can be calendars, too
|
||||
for (DavResource member : homeSet.members)
|
||||
addIfCalendar(member);
|
||||
} catch (IOException | HttpException | DavException e) {
|
||||
log.debug("PROPFIND on " + url + " failed", e);
|
||||
}
|
||||
else if (service == Service.CARDDAV)
|
||||
try {
|
||||
log.info("Listing address books in home set " + url);
|
||||
DavResource homeSet = new DavResource(log, httpClient, url);
|
||||
homeSet.propfind(1, ResourceType.NAME, DisplayName.NAME, CurrentUserPrivilegeSet.NAME, AddressbookDescription.NAME);
|
||||
|
||||
// home set should not be an address book, but some servers have only one address book and it's the home set
|
||||
addIfAddressBook(homeSet);
|
||||
|
||||
// members of the home set can be calendars, too
|
||||
for (DavResource member : homeSet.members)
|
||||
addIfAddressBook(member);
|
||||
} catch (IOException | HttpException | DavException e) {
|
||||
log.debug("PROPFIND on " + url + " failed", e);
|
||||
}
|
||||
|
||||
if (service == Service.CALDAV) {
|
||||
serverInfo.setCalendars(calendars.values().toArray(new ServerInfo.ResourceInfo[calendars.size()]));
|
||||
serverInfo.setTaskLists(taskLists.values().toArray(new ServerInfo.ResourceInfo[taskLists.size()]));
|
||||
} else if (service == Service.CARDDAV)
|
||||
serverInfo.setAddressBooks(addressbooks.values().toArray(new ServerInfo.ResourceInfo[addressbooks.size()]));
|
||||
if (service == Service.CALDAV)
|
||||
caldavPrincipal = principal;
|
||||
else if (service == Service.CARDDAV)
|
||||
carddavPrincipal = principal;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given DavResource is a #{@link ResourceType#ADDRESSBOOK}, add it to #{@link #addressbooks}.
|
||||
* @param dav DavResource to check
|
||||
*/
|
||||
protected void addIfAddressBook(@NonNull DavResource dav) {
|
||||
ResourceType resourceType = (ResourceType)dav.properties.get(ResourceType.NAME);
|
||||
if (resourceType != null && resourceType.types.contains(ResourceType.ADDRESSBOOK)) {
|
||||
dav.location = UrlUtils.withTrailingSlash(dav.location);
|
||||
log.info("Found address book at " + dav.location);
|
||||
|
||||
addressbooks.put(dav.location, resourceInfo(dav, ServerInfo.ResourceInfo.Type.ADDRESS_BOOK));
|
||||
addressBooks.put(dav.location, collectionInfo(dav, ServerConfiguration.Collection.Type.ADDRESS_BOOK));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given DavResource is a #{@link ResourceType#CALENDAR}:
|
||||
* <ul>
|
||||
* <li>add it to #{@link #calendars} if it supports VEVENT</li>
|
||||
* <li>add it to #{@link #taskLists} if it supports VTODO</li>
|
||||
* </ul>
|
||||
* @param dav DavResource to check
|
||||
*/
|
||||
protected void addIfCalendar(@NonNull DavResource dav) {
|
||||
ResourceType resourceType = (ResourceType)dav.properties.get(ResourceType.NAME);
|
||||
if (resourceType != null && resourceType.types.contains(ResourceType.CALENDAR)) {
|
||||
@ -315,10 +208,12 @@ public class DavResourceFinder {
|
||||
supportsEvents = supportedCalendarComponentSet.supportsEvents;
|
||||
supportsTasks = supportedCalendarComponentSet.supportsTasks;
|
||||
}
|
||||
if (supportsEvents)
|
||||
calendars.put(dav.location, resourceInfo(dav, ServerInfo.ResourceInfo.Type.CALENDAR));
|
||||
if (supportsTasks)
|
||||
taskLists.put(dav.location, resourceInfo(dav, ServerInfo.ResourceInfo.Type.CALENDAR));
|
||||
if (supportsEvents || supportsTasks) {
|
||||
ServerConfiguration.Collection info = collectionInfo(dav, ServerConfiguration.Collection.Type.CALENDAR);
|
||||
info.supportsEvents = supportsEvents;
|
||||
info.supportsTasks = supportsTasks;
|
||||
calendars.put(dav.location, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -333,7 +228,7 @@ public class DavResourceFinder {
|
||||
* @param type must be ADDRESS_BOOK or CALENDAR
|
||||
* @return ResourceInfo which represents the DavResource
|
||||
*/
|
||||
protected ServerInfo.ResourceInfo resourceInfo(DavResource dav, ServerInfo.ResourceInfo.Type type) {
|
||||
protected ServerConfiguration.Collection collectionInfo(DavResource dav, ServerConfiguration.Collection.Type type) {
|
||||
boolean readOnly = false;
|
||||
CurrentUserPrivilegeSet privilegeSet = (CurrentUserPrivilegeSet)dav.properties.get(CurrentUserPrivilegeSet.NAME);
|
||||
if (privilegeSet != null)
|
||||
@ -348,11 +243,11 @@ public class DavResourceFinder {
|
||||
|
||||
String description = null;
|
||||
Integer color = null;
|
||||
if (type == ServerInfo.ResourceInfo.Type.ADDRESS_BOOK) {
|
||||
if (type == ServerConfiguration.Collection.Type.ADDRESS_BOOK) {
|
||||
AddressbookDescription addressbookDescription = (AddressbookDescription)dav.properties.get(AddressbookDescription.NAME);
|
||||
if (addressbookDescription != null)
|
||||
description = addressbookDescription.description;
|
||||
} else if (type == ServerInfo.ResourceInfo.Type.CALENDAR) {
|
||||
} else if (type == ServerConfiguration.Collection.Type.CALENDAR) {
|
||||
CalendarDescription calendarDescription = (CalendarDescription)dav.properties.get(CalendarDescription.NAME);
|
||||
if (calendarDescription != null)
|
||||
description = calendarDescription.description;
|
||||
@ -362,7 +257,7 @@ public class DavResourceFinder {
|
||||
color = calendarColor.color;
|
||||
}
|
||||
|
||||
return new ServerInfo.ResourceInfo(
|
||||
ServerConfiguration.Collection collection = new ServerConfiguration.Collection(
|
||||
type,
|
||||
readOnly,
|
||||
UrlUtils.withTrailingSlash(dav.location).toString(),
|
||||
@ -370,10 +265,30 @@ public class DavResourceFinder {
|
||||
description,
|
||||
color
|
||||
);
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
|
||||
boolean providesService(HttpUrl url, Service service) {
|
||||
DavResource davPrincipal = new DavResource(log, httpClient, url);
|
||||
try {
|
||||
davPrincipal.options();
|
||||
|
||||
if ((service == Service.CARDDAV && davPrincipal.capabilities.contains("addressbook")) ||
|
||||
(service == Service.CALDAV && davPrincipal.capabilities.contains("calendar-access")))
|
||||
return true;
|
||||
|
||||
} catch (IOException|HttpException|DavException e) {
|
||||
log.error("Couldn't detect services on {}", url);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to find the principal URL by performing service discovery on a given domain name.
|
||||
* Only secure services (caldavs, carddavs) will be discovered!
|
||||
* @param domain domain name, e.g. "icloud.com"
|
||||
* @param service service to discover (CALDAV or CARDDAV)
|
||||
* @return principal URL, or null if none found
|
||||
@ -381,7 +296,7 @@ public class DavResourceFinder {
|
||||
protected HttpUrl discoverPrincipalUrl(String domain, Service service) throws IOException, HttpException, DavException {
|
||||
String scheme = null;
|
||||
String fqdn = null;
|
||||
Integer port = null;
|
||||
Integer port = 443;
|
||||
List<String> paths = new LinkedList<>(); // there may be multiple paths to try
|
||||
|
||||
final String query = "_" + service.name + "s._tcp." + domain;
|
||||
@ -394,29 +309,36 @@ public class DavResourceFinder {
|
||||
scheme = "https";
|
||||
fqdn = srv.getTarget().toString(true);
|
||||
port = srv.getPort();
|
||||
log.info("Found " + service + " service: fqdn=" + fqdn + ", port=" + port);
|
||||
log.info("Found " + service + " service at https://" + fqdn + ":" + port);
|
||||
|
||||
// look for TXT record too (for initial context path)
|
||||
records = new Lookup(query, Type.TXT).run();
|
||||
if (records != null)
|
||||
for (Record record : records)
|
||||
if (record instanceof TXTRecord)
|
||||
for (String segment : (List<String>) ((TXTRecord) record).getStrings())
|
||||
if (segment.startsWith("path=")) {
|
||||
paths.add(segment.substring(5));
|
||||
log.info("Found TXT record; initial context path=" + paths);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// no SRV records, try domain name as FQDN
|
||||
log.info("Didn't find " + service + " service, trying at https://" + domain + ":" + port);
|
||||
|
||||
// if there's TXT record and if it it's wrong, try well-known
|
||||
paths.add("/.well-known/" + service.name);
|
||||
// if this fails, too, try "/"
|
||||
paths.add("/");
|
||||
scheme = "https";
|
||||
fqdn = domain;
|
||||
}
|
||||
|
||||
// look for TXT record too (for initial context path)
|
||||
records = new Lookup(query, Type.TXT).run();
|
||||
if (records != null)
|
||||
for (Record record : records)
|
||||
if (record instanceof TXTRecord)
|
||||
for (String segment : (List<String>) ((TXTRecord) record).getStrings())
|
||||
if (segment.startsWith("path=")) {
|
||||
paths.add(segment.substring(5));
|
||||
log.info("Found TXT record; initial context path=" + paths);
|
||||
break;
|
||||
}
|
||||
|
||||
// if there's TXT record and if it it's wrong, try well-known
|
||||
paths.add("/.well-known/" + service.name);
|
||||
// if this fails, too, try "/"
|
||||
paths.add("/");
|
||||
|
||||
for (String path : paths)
|
||||
try {
|
||||
if (!TextUtils.isEmpty(scheme) && !TextUtils.isEmpty(fqdn) && port != null && paths != null) {
|
||||
if (!TextUtils.isEmpty(scheme) && !TextUtils.isEmpty(fqdn) && paths != null) {
|
||||
HttpUrl initialContextPath = new HttpUrl.Builder()
|
||||
.scheme(scheme)
|
||||
.host(fqdn).port(port)
|
||||
@ -424,7 +346,8 @@ public class DavResourceFinder {
|
||||
.build();
|
||||
|
||||
log.info("Trying to determine principal from initial context path=" + initialContextPath);
|
||||
HttpUrl principal = getCurrentUserPrincipal(initialContextPath);
|
||||
HttpUrl principal = getCurrentUserPrincipal(initialContextPath, service);
|
||||
|
||||
if (principal != null)
|
||||
return principal;
|
||||
}
|
||||
@ -436,17 +359,25 @@ public class DavResourceFinder {
|
||||
|
||||
/**
|
||||
* Queries a given URL for current-user-principal
|
||||
* @param url URL to query with PROPFIND (Depth: 0)
|
||||
* @return current-user-principal URL, or null if none
|
||||
* @param url URL to query with PROPFIND (Depth: 0)
|
||||
* @param service required service (may be null, in which case no service check is done)
|
||||
* @return current-user-principal URL that provides required service, or null if none
|
||||
*/
|
||||
protected HttpUrl getCurrentUserPrincipal(HttpUrl url) throws IOException, HttpException, DavException {
|
||||
protected HttpUrl getCurrentUserPrincipal(HttpUrl url, Service service) throws IOException, HttpException, DavException {
|
||||
DavResource dav = new DavResource(log, httpClient, url);
|
||||
dav.propfind(0, CurrentUserPrincipal.NAME);
|
||||
CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal) dav.properties.get(CurrentUserPrincipal.NAME);
|
||||
if (currentUserPrincipal != null && currentUserPrincipal.href != null) {
|
||||
HttpUrl principal = url.resolve(currentUserPrincipal.href);
|
||||
HttpUrl principal = dav.location.resolve(currentUserPrincipal.href);
|
||||
if (principal != null) {
|
||||
log.info("Found current-user-principal: " + principal);
|
||||
|
||||
// service check
|
||||
if (service != null && !providesService(principal, service)) {
|
||||
log.info("{} doesn't provide required {} service, dismissing", principal, service);
|
||||
principal = null;
|
||||
}
|
||||
|
||||
return principal;
|
||||
}
|
||||
}
|
||||
@ -462,4 +393,42 @@ public class DavResourceFinder {
|
||||
return (SRVRecord)records[0];
|
||||
}
|
||||
|
||||
|
||||
// data classes
|
||||
|
||||
@Data
|
||||
@ToString(exclude="logs")
|
||||
public static class ServerConfiguration {
|
||||
final public HttpUrl cardDavPrincipal;
|
||||
final public Collection[] addressBooks;
|
||||
|
||||
final public HttpUrl calDavPrincipal;
|
||||
final public Collection[] calendars;
|
||||
|
||||
final String logs;
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
public static class Collection {
|
||||
public enum Type {
|
||||
ADDRESS_BOOK,
|
||||
CALENDAR
|
||||
}
|
||||
|
||||
final Type type;
|
||||
final boolean readOnly;
|
||||
|
||||
final String url, // absolute URL of resource
|
||||
title,
|
||||
description;
|
||||
final Integer color;
|
||||
|
||||
/**
|
||||
* full VTIMEZONE definition (not the TZ ID)
|
||||
*/
|
||||
boolean supportsEvents, supportsTasks;
|
||||
String timezone;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
|
||||
import at.bitfire.davdroid.ui.AddAccountActivity;
|
||||
import at.bitfire.davdroid.ui.setup.LoginActivity;
|
||||
|
||||
public class AccountAuthenticatorService extends Service {
|
||||
private static AccountAuthenticator accountAuthenticator;
|
||||
@ -48,7 +48,7 @@ public class AccountAuthenticatorService extends Service {
|
||||
@Override
|
||||
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType,
|
||||
String[] requiredFeatures, Bundle options) throws NetworkErrorException {
|
||||
Intent intent = new Intent(context, AddAccountActivity.class);
|
||||
Intent intent = new Intent(context, LoginActivity.class);
|
||||
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
|
||||
|
@ -22,6 +22,7 @@ import android.view.View;
|
||||
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.ui.setup.LoginActivity;
|
||||
|
||||
public class AccountsActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
|
||||
|
||||
@ -37,7 +38,7 @@ public class AccountsActivity extends AppCompatActivity implements NavigationVie
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
startActivity(new Intent(AccountsActivity.this, AddAccountActivity.class));
|
||||
startActivity(new Intent(AccountsActivity.this, LoginActivity.class));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -10,6 +10,7 @@ package at.bitfire.davdroid.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.widget.AppCompatCheckBox;
|
||||
import android.text.Editable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.CompoundButton;
|
||||
@ -19,6 +20,9 @@ import android.widget.LinearLayout;
|
||||
import at.bitfire.davdroid.R;
|
||||
|
||||
public class EditPassword extends LinearLayout {
|
||||
private static final String NS_ANDROID = "http://schemas.android.com/apk/res/android";
|
||||
|
||||
EditText editPassword;
|
||||
|
||||
public EditPassword(Context context) {
|
||||
super(context, null);
|
||||
@ -29,7 +33,9 @@ public class EditPassword extends LinearLayout {
|
||||
|
||||
inflate(context, R.layout.edit_password, this);
|
||||
|
||||
final EditText editPassword = (EditText)findViewById(R.id.password);
|
||||
editPassword = (EditText)findViewById(R.id.password);
|
||||
editPassword.setHint(attrs.getAttributeResourceValue(NS_ANDROID, "hint", 0));
|
||||
editPassword.setText(attrs.getAttributeValue(NS_ANDROID, "text"));
|
||||
|
||||
AppCompatCheckBox checkShowPassword = (AppCompatCheckBox)findViewById(R.id.show_password);
|
||||
checkShowPassword.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@ -42,4 +48,16 @@ public class EditPassword extends LinearLayout {
|
||||
});
|
||||
}
|
||||
|
||||
public Editable getText() {
|
||||
return editPassword.getText();
|
||||
}
|
||||
|
||||
public void setError(CharSequence error) {
|
||||
editPassword.setError(error);
|
||||
}
|
||||
|
||||
public void setText(CharSequence text) {
|
||||
editPassword.setText(text);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui.setup;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.AsyncTaskLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
|
||||
import at.bitfire.davdroid.Constants;
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.resource.DavResourceFinder;
|
||||
import at.bitfire.davdroid.resource.DavResourceFinder.ServerConfiguration;
|
||||
import lombok.Cleanup;
|
||||
|
||||
public class DetectConfigurationFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<ServerConfiguration> {
|
||||
|
||||
static final String ARG_LOGIN_CREDENTIALS = "credentials";
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
ProgressDialog dialog = new ProgressDialog(getActivity());
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
setCancelable(false);
|
||||
|
||||
dialog.setTitle(R.string.login_configuration_detection);
|
||||
dialog.setIndeterminate(true);
|
||||
dialog.setMessage(getString(R.string.login_querying_server));
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getLoaderManager().initLoader(0, getArguments(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<ServerConfiguration> onCreateLoader(int id, Bundle args) {
|
||||
return new ServerConfigurationLoader(getContext(), args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<ServerConfiguration> loader, ServerConfiguration data) {
|
||||
// show error / continue with next fragment
|
||||
Constants.log.info("detection results: {}", data);
|
||||
dismissAllowingStateLoss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<ServerConfiguration> loader) {
|
||||
}
|
||||
|
||||
|
||||
static class ServerConfigurationLoader extends AsyncTaskLoader<ServerConfiguration> {
|
||||
final Context context;
|
||||
final LoginCredentialsFragment.LoginCredentials credentials;
|
||||
|
||||
public ServerConfigurationLoader(Context context, Bundle args) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
credentials = (LoginCredentialsFragment.LoginCredentials)args.getSerializable(ARG_LOGIN_CREDENTIALS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerConfiguration loadInBackground() {
|
||||
DavResourceFinder finder = new DavResourceFinder(context, credentials);
|
||||
ServerConfiguration configuration = finder.findInitialConfiguration();
|
||||
|
||||
try {
|
||||
@Cleanup BufferedReader logStream = new BufferedReader(new StringReader(configuration.getLogs()));
|
||||
Constants.log.info("Successful resource detection:");
|
||||
String line;
|
||||
while ((line = logStream.readLine()) != null)
|
||||
Constants.log.debug(line);
|
||||
} catch (IOException e) {
|
||||
Constants.log.error("Couldn't read resource detection logs", e);
|
||||
}
|
||||
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,20 +6,26 @@
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui;
|
||||
package at.bitfire.davdroid.ui.setup;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import at.bitfire.davdroid.R;
|
||||
|
||||
public class AddAccountActivity extends AppCompatActivity {
|
||||
public class LoginActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.login_activity);
|
||||
|
||||
if (savedInstanceState == null)
|
||||
// first call, add fragment
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.fragment, new LoginCredentialsFragment())
|
||||
.commit();
|
||||
|
||||
setContentView(R.layout.activity_add_account);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the GNU Public License v3.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*/
|
||||
|
||||
package at.bitfire.davdroid.ui.setup;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.AppCompatCheckBox;
|
||||
import android.support.v7.widget.AppCompatRadioButton;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.IDN;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import at.bitfire.davdroid.R;
|
||||
import at.bitfire.davdroid.ui.EditPassword;
|
||||
import lombok.Data;
|
||||
|
||||
public class LoginCredentialsFragment extends Fragment implements CompoundButton.OnCheckedChangeListener {
|
||||
|
||||
AppCompatRadioButton radioUseEmail;
|
||||
LinearLayout emailDetails;
|
||||
EditText editEmailAddress;
|
||||
EditPassword editEmailPassword;
|
||||
|
||||
AppCompatRadioButton radioUseURL;
|
||||
LinearLayout urlDetails;
|
||||
EditText editBaseURL, editUserName;
|
||||
EditPassword editUrlPassword;
|
||||
AppCompatCheckBox checkPreemptiveAuth;
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.login_credentials_fragment, container, false);
|
||||
|
||||
radioUseEmail = (AppCompatRadioButton)v.findViewById(R.id.login_type_email);
|
||||
emailDetails = (LinearLayout)v.findViewById(R.id.login_type_email_details);
|
||||
editEmailAddress = (EditText)v.findViewById(R.id.email_address);
|
||||
editEmailPassword = (EditPassword)v.findViewById(R.id.email_password);
|
||||
|
||||
radioUseURL = (AppCompatRadioButton)v.findViewById(R.id.login_type_url);
|
||||
urlDetails = (LinearLayout)v.findViewById(R.id.login_type_url_details);
|
||||
editBaseURL = (EditText)v.findViewById(R.id.base_url);
|
||||
editUserName = (EditText)v.findViewById(R.id.user_name);
|
||||
editUrlPassword = (EditPassword)v.findViewById(R.id.url_password);
|
||||
checkPreemptiveAuth = (AppCompatCheckBox)v.findViewById(R.id.preemptive_auth);
|
||||
|
||||
radioUseEmail.setOnCheckedChangeListener(this);
|
||||
radioUseURL.setOnCheckedChangeListener(this);
|
||||
|
||||
if (savedInstanceState == null)
|
||||
radioUseEmail.setChecked(true);
|
||||
|
||||
final Button login = (Button)v.findViewById(R.id.login);
|
||||
login.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LoginCredentials credentials = validateLoginData();
|
||||
if (credentials != null) {
|
||||
// login data OK, continue with DetectConfigurationFragment
|
||||
Bundle args = new Bundle(1);
|
||||
args.putSerializable(DetectConfigurationFragment.ARG_LOGIN_CREDENTIALS, credentials);
|
||||
|
||||
DialogFragment dialog = new DetectConfigurationFragment();
|
||||
dialog.setArguments(args);
|
||||
dialog.show(getFragmentManager(), DetectConfigurationFragment.class.getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
if (isChecked) {
|
||||
boolean loginByEmail = buttonView == radioUseEmail;
|
||||
emailDetails.setVisibility(loginByEmail ? View.VISIBLE : View.GONE);
|
||||
urlDetails.setVisibility(loginByEmail ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
protected LoginCredentials validateLoginData() {
|
||||
if (radioUseEmail.isChecked()) {
|
||||
URI uri = null;
|
||||
boolean valid = true;
|
||||
|
||||
String email = editEmailAddress.getText().toString();
|
||||
if (!email.matches(".+@.+")) {
|
||||
editEmailAddress.setError(getString(R.string.login_email_address_error));
|
||||
valid = false;
|
||||
} else
|
||||
try {
|
||||
uri = new URI("mailto", email, null);
|
||||
} catch (URISyntaxException e) {
|
||||
editEmailAddress.setError(e.getLocalizedMessage());
|
||||
valid = false;
|
||||
}
|
||||
|
||||
String password = editEmailPassword.getText().toString();
|
||||
if (password.isEmpty()) {
|
||||
editEmailPassword.setError(getString(R.string.login_password_required));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return valid ? new LoginCredentials(uri, email, password, true) : null;
|
||||
|
||||
} else if (radioUseURL.isChecked()) {
|
||||
URI uri = null;
|
||||
boolean valid = true;
|
||||
|
||||
String host = null, path = null;
|
||||
int port = -1;
|
||||
|
||||
Uri baseUrl = Uri.parse(editBaseURL.getText().toString());
|
||||
String scheme = baseUrl.getScheme();
|
||||
if ("https".equalsIgnoreCase(scheme) || "http".equalsIgnoreCase(scheme)) {
|
||||
host = IDN.toASCII(baseUrl.getHost());
|
||||
if (host.isEmpty()) {
|
||||
editBaseURL.setError(getString(R.string.login_url_host_name_required));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
path = baseUrl.getEncodedPath();
|
||||
port = baseUrl.getPort();
|
||||
try {
|
||||
uri = new URI(baseUrl.getScheme(), null, host, port, path, null, null);
|
||||
} catch (URISyntaxException e) {
|
||||
editBaseURL.setError(e.getLocalizedMessage());
|
||||
valid = false;
|
||||
}
|
||||
} else {
|
||||
editBaseURL.setError(getString(R.string.login_url_must_be_http_or_https));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
String userName = editUserName.getText().toString();
|
||||
if (userName.isEmpty()) {
|
||||
editUserName.setError(getString(R.string.login_user_name_required));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
String password = editUrlPassword.getText().toString();
|
||||
if (password.isEmpty()) {
|
||||
editUrlPassword.setError(getString(R.string.login_password_required));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
return valid ? new LoginCredentials(uri, userName, password, checkPreemptiveAuth.isChecked()) : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Data
|
||||
public class LoginCredentials implements Serializable {
|
||||
final URI uri;
|
||||
final String userName, password;
|
||||
final boolean authPreemptive;
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
@ -8,8 +8,8 @@
|
||||
-->
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/right_pane"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/fragment">
|
||||
|
||||
</FrameLayout>
|
@ -12,39 +12,52 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- We don't want the keyboard up when the user arrives in this initial screen -->
|
||||
<View android:layout_height="0dp"
|
||||
android:layout_width="0dp"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:contentDescription="@null"
|
||||
android:importantForAccessibility="no">
|
||||
<requestFocus/>
|
||||
</View>
|
||||
|
||||
<ScrollView android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
android:layout_weight="1"
|
||||
android:layout_marginTop="@dimen/activity_vertical_margin"
|
||||
android:layout_marginBottom="@dimen/activity_vertical_margin"
|
||||
android:layout_marginLeft="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/activity_horizontal_margin">
|
||||
|
||||
<RadioGroup
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:animateLayoutChanges="true">
|
||||
|
||||
<android.support.v7.widget.AppCompatRadioButton
|
||||
android:id="@+id/login_type_email"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_type_email"
|
||||
android:checked="true" />
|
||||
style="@style/login_type_headline"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/login_type_email_details"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_email_address"/>
|
||||
<EditText
|
||||
android:id="@+id/email_address"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_password"/>
|
||||
android:hint="@string/login_email_address"
|
||||
android:inputType="textEmailAddress"/>
|
||||
<at.bitfire.davdroid.ui.EditPassword
|
||||
android:id="@+id/email_password"
|
||||
android:hint="@string/login_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
@ -53,42 +66,40 @@
|
||||
android:id="@+id/login_type_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_type_url" />
|
||||
android:text="@string/login_type_url"
|
||||
android:layout_marginTop="16dp"
|
||||
style="@style/login_type_headline"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/login_type_url_details"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_base_url"/>
|
||||
<EditText
|
||||
android:id="@+id/base_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_user_name"/>
|
||||
android:hint="@string/login_base_url"
|
||||
android:inputType="textUri"/>
|
||||
<EditText
|
||||
android:id="@+id/user_name"
|
||||
android:layout_width="match_parent"
|
||||
android:maxWidth="100dp"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_password"/>
|
||||
android:hint="@string/login_user_name"
|
||||
android:inputType="textNoSuggestions"/>
|
||||
<at.bitfire.davdroid.ui.EditPassword
|
||||
android:id="@+id/url_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/login_password"/>
|
||||
|
||||
<android.support.v7.widget.AppCompatCheckBox
|
||||
android:id="@+id/preemptive_auth"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_auth_preemptive"/>
|
||||
android:text="@string/login_auth_preemptive"
|
||||
android:checked="true"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@ -100,26 +111,17 @@
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/stepper_nav_bar">
|
||||
|
||||
<Button
|
||||
<Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="Back"
|
||||
android:visibility="invisible"
|
||||
style="@style/stepper_nav_button"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/login"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="Login"
|
||||
style="@style/stepper_nav_button"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="Create account"
|
||||
android:visibility="gone"
|
||||
style="@style/stepper_nav_button"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -1,61 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:scrollbars="vertical" >
|
||||
|
||||
<GridLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:columnCount="2"
|
||||
android:padding="10dp"
|
||||
android:useDefaultMargins="true" >
|
||||
|
||||
<TextView
|
||||
android:layout_columnSpan="2"
|
||||
android:layout_gravity="start"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:text="@string/setup_account_details"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
|
||||
<TextView
|
||||
android:layout_gravity="start"
|
||||
android:text="@string/setup_account_name"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/account_name"
|
||||
android:layout_gravity="fill_horizontal"
|
||||
android:hint="@string/setup_account_name_hint"
|
||||
android:inputType="textEmailAddress">
|
||||
<requestFocus />
|
||||
</EditText>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/account_name_info"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_columnSpan="2"
|
||||
android:layout_gravity="start"
|
||||
android:drawableLeft="@drawable/extra_actions_about"
|
||||
android:drawableStart="@drawable/extra_actions_about"
|
||||
android:drawablePadding="10dp"
|
||||
android:padding="10dp"
|
||||
android:text="@string/setup_account_name_info"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<Space
|
||||
android:layout_gravity="start|top"
|
||||
android:layout_row="3" />
|
||||
|
||||
</GridLayout>
|
||||
|
||||
</ScrollView>
|
@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
style="@style/TextView.Heading"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/setup_address_books" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/setup_select_address_book" />
|
||||
|
||||
</LinearLayout>
|
@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
style="@style/TextView.Heading"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/setup_calendars" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/setup_select_calendars" />
|
||||
|
||||
</LinearLayout>
|
@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:padding="20dp"
|
||||
tools:context=".MainActivity" >
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="@string/setup_install_apps_info"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:id="@+id/setup_install_tasks_app"
|
||||
android:text="@string/setup_install_tasks_app_html"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
@ -1,60 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" >
|
||||
|
||||
<GridLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:columnCount="2"
|
||||
android:padding="10dp"
|
||||
android:useDefaultMargins="true" >
|
||||
|
||||
<TextView
|
||||
android:layout_columnSpan="2"
|
||||
android:layout_gravity="start"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/login_email_description" />
|
||||
|
||||
<TextView
|
||||
android:labelFor="@+id/email_address"
|
||||
android:text="@string/login_email_address"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
<EditText
|
||||
android:id="@+id/email_address"
|
||||
android:layout_gravity="fill_horizontal"
|
||||
android:inputType="textNoSuggestions|textEmailAddress"
|
||||
android:imeOptions="actionNext"
|
||||
android:layout_width="0dp"
|
||||
android:scrollHorizontally="true"
|
||||
android:scrollbars="horizontal"
|
||||
android:hint="myaccount@myservice.com" tools:ignore="HardcodedText">
|
||||
<requestFocus />
|
||||
</EditText>
|
||||
|
||||
<TextView
|
||||
android:labelFor="@+id/password"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/login_password" />
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:layout_gravity="fill_horizontal"
|
||||
android:inputType="textPassword"
|
||||
android:layout_width="0dp"
|
||||
android:scrollHorizontally="true"
|
||||
android:scrollbars="horizontal"
|
||||
android:text="" />
|
||||
|
||||
</GridLayout>
|
||||
|
||||
</ScrollView>
|
@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="10dp"
|
||||
android:useDefaultMargins="true" >
|
||||
|
||||
<RadioGroup
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/login_type_email"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:text="@string/login_type_email" />
|
||||
<TextView
|
||||
android:labelFor="@+id/login_type_email"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="@string/login_type_email_description" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/login_type_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_type_url" />
|
||||
<TextView
|
||||
android:labelFor="@+id/login_type_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_type_url_description" />
|
||||
</RadioGroup>
|
||||
|
||||
</ScrollView>
|
@ -1,96 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<GridLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:columnCount="2"
|
||||
android:padding="10dp"
|
||||
android:useDefaultMargins="true" >
|
||||
|
||||
<TextView
|
||||
android:layout_columnSpan="2"
|
||||
android:layout_gravity="start"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/login_base_url" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/login_scheme"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:entries="@array/login_url_scheme" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/login_host_path"
|
||||
android:layout_gravity="fill_horizontal"
|
||||
android:imeOptions="flagForceAscii|actionNext"
|
||||
android:inputType="textUri"
|
||||
android:layout_width="0dp"
|
||||
android:scrollHorizontally="true"
|
||||
android:scrollbars="horizontal"
|
||||
android:hint="my.webhost.com" tools:ignore="HardcodedText">
|
||||
<requestFocus />
|
||||
</EditText>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/http_warning"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_columnSpan="2"
|
||||
android:layout_gravity="start"
|
||||
android:drawableLeft="@drawable/alerts_and_states_warning"
|
||||
android:drawableStart="@drawable/alerts_and_states_warning"
|
||||
android:drawablePadding="10dp"
|
||||
android:padding="10dp"
|
||||
android:text="@string/login_http_warning" />
|
||||
|
||||
<TextView
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/login_user_name" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/userName"
|
||||
android:layout_gravity="fill_horizontal"
|
||||
android:inputType="textNoSuggestions|textEmailAddress"
|
||||
android:layout_width="0dp"
|
||||
android:scrollHorizontally="true"
|
||||
android:scrollbars="horizontal"
|
||||
android:text="" />
|
||||
|
||||
<TextView
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/login_password" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
android:layout_gravity="fill_horizontal"
|
||||
android:inputType="textPassword"
|
||||
android:layout_width="0dp"
|
||||
android:scrollHorizontally="true"
|
||||
android:scrollbars="horizontal"
|
||||
android:text="" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/auth_preemptive"
|
||||
android:layout_columnSpan="2"
|
||||
android:checked="true"
|
||||
android:layout_gravity="start"
|
||||
android:text="@string/login_auth_preemptive" />
|
||||
|
||||
<Space android:layout_gravity="start|top" />
|
||||
|
||||
</GridLayout>
|
||||
|
||||
</ScrollView>
|
@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:divider="?android:dividerHorizontal"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="20dp"
|
||||
android:showDividers="end" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
android:paddingBottom="10dp"
|
||||
android:text="@string/setup_what_to_sync"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
|
||||
</LinearLayout>
|
@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2013 – 2015 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
style="@style/TextView.Heading"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/setup_task_lists" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/setup_select_task_lists" />
|
||||
|
||||
</LinearLayout>
|
@ -53,16 +53,16 @@ Dieses Programm ist freie Software. Sie können es unter den Bedingungen der <a
|
||||
<string name="login_type_url">Mit URL und Benutzername anmelden</string>
|
||||
<string name="login_type_url_description">Basis-URL und Benutzername werden verwendet, um die Servereinstellungen herauszufinden; z.B. bei einem eigenen Server.</string>
|
||||
<string name="login_email_description">Geben Sie Ihre Email-Adresse ein. Der Domänenname wird verwendet, um die Servereinstellungen herauszufinden.</string>
|
||||
<string name="login_email_address">Email:</string>
|
||||
<string name="login_email_address">Email-Adresse</string>
|
||||
<string-array name="login_url_scheme">
|
||||
<item>http://</item>
|
||||
<item>https://</item>
|
||||
</string-array>
|
||||
<string name="login_http_warning">Ohne Verschlüsselung (HTTPS) können Ihre Zugangsdaten, Kontakte und Termine leicht abgefangen werden.</string>
|
||||
<string name="login_user_name">Benutzername:</string>
|
||||
<string name="login_base_url">Basis-URL (Ordner werden automatisch gefunden):</string>
|
||||
<string name="login_user_name">Benutzername</string>
|
||||
<string name="login_base_url">Basis-URL:</string>
|
||||
<string name="login_auth_preemptive">Präemptive Authentifizierung (empfohlen, aber nicht kompatibel mit Digest-Auth.)</string>
|
||||
<string name="login_password">Passwort:</string>
|
||||
<string name="login_password">Passwort</string>
|
||||
<!--Settings activity-->
|
||||
<string name="settings_title">Einstellungen</string>
|
||||
<string name="settings_no_accounts">Keine DAVdroid-Konten gefunden</string>
|
||||
|
14
app/src/main/res/values/attrs.xml
Normal file
14
app/src/main/res/values/attrs.xml
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering).
|
||||
~ All rights reserved. This program and the accompanying materials
|
||||
~ are made available under the terms of the GNU Public License v3.0
|
||||
~ which accompanies this distribution, and is available at
|
||||
~ http://www.gnu.org/licenses/gpl.html
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<declare-styleable name="EditPassword">
|
||||
<attr name="text" format="string"/>
|
||||
</declare-styleable>
|
||||
</resources>
|
@ -11,7 +11,7 @@
|
||||
<dimen name="leftcol_width">320dp</dimen>
|
||||
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">20dp</dimen>
|
||||
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
|
||||
|
@ -93,23 +93,20 @@
|
||||
<!-- AddAccountActivity -->
|
||||
<string name="login_title">Add account</string>
|
||||
<string name="login_type_email">Login with email address</string>
|
||||
<string name="login_type_email_description">Service details will be auto-detected by domain name. Example: myaccount@icloud.com</string>
|
||||
<string name="login_email_address">Email address</string>
|
||||
<string name="login_email_address_error">Valid email address required</string>
|
||||
<string name="login_password">Password</string>
|
||||
<string name="login_password_required">Password required</string>
|
||||
<string name="login_type_url">Login with URL and user name</string>
|
||||
<string name="login_type_url_description">Service details will be auto-detected by initial URL and user name. Mostly used for self-hosted services.</string>
|
||||
|
||||
<string name="login_email_description">Please enter your email address. Its domain name will be used to auto-detect service settings.</string>
|
||||
<string name="login_email_address">Email:</string>
|
||||
|
||||
<string-array name="login_url_scheme">
|
||||
<item>http://</item>
|
||||
<item>https://</item>
|
||||
</string-array>
|
||||
<string name="login_http_warning">"If you don't use encryption (HTTPS), other people may easily intercept your login details, contacts and events."</string>
|
||||
<string name="login_user_name">User name:</string>
|
||||
<string name="login_base_url">Base URL (collections will be auto-detected):</string>
|
||||
<string name="login_url_must_be_http_or_https">URL must begin with http(s)://</string>
|
||||
<string name="login_url_host_name_required">Host name required</string>
|
||||
<string name="login_user_name">User name</string>
|
||||
<string name="login_user_name_required">User name required</string>
|
||||
<string name="login_base_url">Base URL</string>
|
||||
<string name="login_auth_preemptive">Preemptive authentication (recommended, but incompatible with Digest auth)</string>
|
||||
|
||||
<string name="login_password">Password:</string>
|
||||
<string name="login_configuration_detection">Configuration detection</string>
|
||||
<string name="login_querying_server">Please wait, querying server…</string>
|
||||
|
||||
<!-- Settings activity -->
|
||||
<string name="settings_title">Settings</string>
|
||||
|
@ -35,6 +35,14 @@
|
||||
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
|
||||
|
||||
|
||||
<!-- AddAccountActivity -->
|
||||
|
||||
<style name="login_type_headline">
|
||||
<item name="android:paddingLeft">14dp</item>
|
||||
<item name="android:textSize">20dp</item>
|
||||
</style>
|
||||
|
||||
|
||||
<!-- stepper (wizard) -->
|
||||
|
||||
<style name="stepper_nav_bar">
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit adb19e52298c11e1abc7d9aeee15380f835f1ba2
|
||||
Subproject commit c67d40c47e792af812866289b6809dc8ccf49fd8
|
@ -1 +1 @@
|
||||
Subproject commit 781cd6254258a243deea542ca71bd1a86c996ea6
|
||||
Subproject commit 343c3b3ec03b331202425f377b57a17ef2ec3d85
|
Loading…
Reference in New Issue
Block a user