mirror of
https://github.com/etesync/android
synced 2025-06-28 02:42:37 +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'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
disable 'IconColors'
|
disable 'IconColors'
|
||||||
disable 'IconLauncherShape'
|
disable 'IconLauncherShape'
|
||||||
|
@ -112,7 +112,7 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.AddAccountActivity"
|
android:name=".ui.setup.LoginActivity"
|
||||||
android:label="@string/login_title"
|
android:label="@string/login_title"
|
||||||
android:parentActivityName=".ui.AccountsActivity"
|
android:parentActivityName=".ui.AccountsActivity"
|
||||||
android:noHistory="true">
|
android:noHistory="true">
|
||||||
|
@ -13,6 +13,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class Constants {
|
public class Constants {
|
||||||
|
|
||||||
public static final String
|
public static final String
|
||||||
ACCOUNT_TYPE = "bitfire.at.davdroid";
|
ACCOUNT_TYPE = "bitfire.at.davdroid";
|
||||||
|
|
||||||
@ -26,5 +27,6 @@ public class Constants {
|
|||||||
NOTIFICATION_CALENDAR_SYNC = 11,
|
NOTIFICATION_CALENDAR_SYNC = 11,
|
||||||
NOTIFICATION_TASK_SYNC = 12;
|
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 android.text.TextUtils;
|
||||||
|
|
||||||
import com.squareup.okhttp.HttpUrl;
|
import com.squareup.okhttp.HttpUrl;
|
||||||
import com.squareup.okhttp.OkHttpClient;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.xbill.DNS.Lookup;
|
import org.xbill.DNS.Lookup;
|
||||||
@ -22,14 +21,10 @@ import org.xbill.DNS.Type;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import at.bitfire.dav4android.DavResource;
|
import at.bitfire.dav4android.DavResource;
|
||||||
import at.bitfire.dav4android.UrlUtils;
|
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.DisplayName;
|
||||||
import at.bitfire.dav4android.property.ResourceType;
|
import at.bitfire.dav4android.property.ResourceType;
|
||||||
import at.bitfire.dav4android.property.SupportedCalendarComponentSet;
|
import at.bitfire.dav4android.property.SupportedCalendarComponentSet;
|
||||||
import at.bitfire.davdroid.Constants;
|
|
||||||
import at.bitfire.davdroid.HttpClient;
|
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.NonNull;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
public class DavResourceFinder {
|
public class DavResourceFinder {
|
||||||
protected enum Service {
|
protected enum Service {
|
||||||
@ -64,245 +62,140 @@ public class DavResourceFinder {
|
|||||||
protected final Logger log;
|
protected final Logger log;
|
||||||
protected final Context context;
|
protected final Context context;
|
||||||
protected final HttpClient httpClient;
|
protected final HttpClient httpClient;
|
||||||
protected final ServerInfo serverInfo;
|
protected final LoginCredentialsFragment.LoginCredentials credentials;
|
||||||
|
|
||||||
protected Map<HttpUrl, ServerInfo.ResourceInfo>
|
protected HttpUrl carddavPrincipal, caldavPrincipal;
|
||||||
addressbooks = new HashMap<>(),
|
protected Map<HttpUrl, ServerConfiguration.Collection>
|
||||||
calendars = new HashMap<>(),
|
addressBooks = new HashMap<>(),
|
||||||
taskLists = new HashMap<>();
|
calendars = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
public DavResourceFinder(Logger log, Context context, ServerInfo serverInfo) {
|
public DavResourceFinder(Context context, LoginCredentialsFragment.LoginCredentials credentials) {
|
||||||
this.log = log;
|
|
||||||
this.context = context;
|
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 {
|
public ServerConfiguration findInitialConfiguration() {
|
||||||
findResources(Service.CARDDAV);
|
addressBooks.clear();
|
||||||
findResources(Service.CALDAV);
|
findInitialConfiguration(Service.CARDDAV);
|
||||||
} catch(URISyntaxException e) {
|
|
||||||
log.warn("Invalid user-given URI", e);
|
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 {
|
protected void findInitialConfiguration(Service service) {
|
||||||
URI baseURI = serverInfo.getBaseURI();
|
// user-given base URI (mailto or URL)
|
||||||
|
URI baseURI = credentials.getUri();
|
||||||
|
|
||||||
|
// domain for service discovery
|
||||||
String domain = null;
|
String domain = null;
|
||||||
|
|
||||||
HttpUrl principalUrl = null;
|
HttpUrl principal = null;
|
||||||
Set<HttpUrl> homeSets = new HashSet<>();
|
|
||||||
|
|
||||||
if (service == Service.CALDAV) {
|
// Step 1a (only when user-given URI is URL):
|
||||||
calendars.clear();
|
// * Check whether URL represents a calendar/address-book collection itself,
|
||||||
taskLists.clear();
|
// * and/or whether it has a current-user-principal,
|
||||||
} else if (service == Service.CARDDAV)
|
// * or whether it represents a principal itself.
|
||||||
addressbooks.clear();
|
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) + "***");
|
// remember domain for service discovery (if required)
|
||||||
if ("http".equals(baseURI.getScheme()) || "https".equals(baseURI.getScheme())) {
|
// try service discovery only for https:// URLs because only secure service discovery is implemented
|
||||||
HttpUrl userURL = HttpUrl.get(baseURI);
|
if ("https".equalsIgnoreCase(baseURL.scheme()))
|
||||||
|
domain = baseURI.getHost();
|
||||||
|
|
||||||
/* check whether:
|
log.info("Checking user-given URL: " + baseURL.toString());
|
||||||
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);
|
|
||||||
try {
|
try {
|
||||||
if (service == Service.CALDAV) {
|
DavResource davBase = new DavResource(log, httpClient, baseURL);
|
||||||
davBase.propfind(0,
|
|
||||||
CalendarHomeSet.NAME, SupportedCalendarComponentSet.NAME,
|
if (service == Service.CARDDAV) {
|
||||||
ResourceType.NAME, DisplayName.NAME, CalendarColor.NAME, CalendarDescription.NAME, CalendarTimezone.NAME, CurrentUserPrivilegeSet.NAME,
|
|
||||||
CurrentUserPrincipal.NAME
|
|
||||||
);
|
|
||||||
addIfCalendar(davBase);
|
|
||||||
} else if (service == Service.CARDDAV) {
|
|
||||||
davBase.propfind(0,
|
davBase.propfind(0,
|
||||||
AddressbookHomeSet.NAME,
|
AddressbookHomeSet.NAME,
|
||||||
ResourceType.NAME, DisplayName.NAME, AddressbookDescription.NAME, CurrentUserPrivilegeSet.NAME,
|
ResourceType.NAME, DisplayName.NAME, AddressbookDescription.NAME, CurrentUserPrivilegeSet.NAME,
|
||||||
CurrentUserPrincipal.NAME
|
CurrentUserPrincipal.NAME
|
||||||
);
|
);
|
||||||
addIfAddressBook(davBase);
|
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) {
|
} catch (IOException|HttpException|DavException e) {
|
||||||
log.debug("PROPFIND on user-given URL failed", e);
|
log.debug("PROPFIND on user-given URL failed", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (service == Service.CALDAV) {
|
// Step 1b: Try well-known URL, too
|
||||||
CalendarHomeSet calendarHomeSet = (CalendarHomeSet)davBase.properties.get(CalendarHomeSet.NAME);
|
if (principal == null)
|
||||||
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())
|
|
||||||
try {
|
try {
|
||||||
log.info("No home sets found, looking for <current-user-principal>");
|
principal = getCurrentUserPrincipal(baseURL.resolve("/.well-known/" + service.name), service);
|
||||||
|
} catch (IOException|HttpException|DavException e) {
|
||||||
davBase.options();
|
log.debug("Well-known URL detection failed", e);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (principalUrl == null)
|
} else if ("mailto".equalsIgnoreCase(baseURI.getScheme())) {
|
||||||
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())) {
|
|
||||||
String mailbox = baseURI.getSchemeSpecificPart();
|
String mailbox = baseURI.getSchemeSpecificPart();
|
||||||
|
|
||||||
// determine service FQDN
|
|
||||||
int posAt = mailbox.lastIndexOf("@");
|
int posAt = mailbox.lastIndexOf("@");
|
||||||
if (posAt == -1)
|
if (posAt != -1)
|
||||||
throw new URISyntaxException(mailbox, "Missing @ sign");
|
|
||||||
|
|
||||||
domain = mailbox.substring(posAt + 1);
|
domain = mailbox.substring(posAt + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (principalUrl == null && domain != null) {
|
// Step 2: If user-given URL didn't reveal a principal, search for it: SERVICE DISCOVERY
|
||||||
log.info("No principal URL yet, trying SRV/TXT records with domain " + domain);
|
if (principal == null && domain != null) {
|
||||||
|
log.info("No principal found at user-given URL, trying to discover");
|
||||||
try {
|
try {
|
||||||
principalUrl = discoverPrincipalUrl(domain, service);
|
principal = discoverPrincipalUrl(domain, service);
|
||||||
} catch (IOException|HttpException|DavException e) {
|
} 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)
|
if (service == Service.CALDAV)
|
||||||
try {
|
caldavPrincipal = principal;
|
||||||
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)
|
else if (service == Service.CARDDAV)
|
||||||
try {
|
carddavPrincipal = principal;
|
||||||
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 the given DavResource is a #{@link ResourceType#ADDRESSBOOK}, add it to #{@link #addressbooks}.
|
|
||||||
* @param dav DavResource to check
|
|
||||||
*/
|
|
||||||
protected void addIfAddressBook(@NonNull DavResource dav) {
|
protected void addIfAddressBook(@NonNull DavResource dav) {
|
||||||
ResourceType resourceType = (ResourceType)dav.properties.get(ResourceType.NAME);
|
ResourceType resourceType = (ResourceType)dav.properties.get(ResourceType.NAME);
|
||||||
if (resourceType != null && resourceType.types.contains(ResourceType.ADDRESSBOOK)) {
|
if (resourceType != null && resourceType.types.contains(ResourceType.ADDRESSBOOK)) {
|
||||||
dav.location = UrlUtils.withTrailingSlash(dav.location);
|
dav.location = UrlUtils.withTrailingSlash(dav.location);
|
||||||
log.info("Found address book at " + 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) {
|
protected void addIfCalendar(@NonNull DavResource dav) {
|
||||||
ResourceType resourceType = (ResourceType)dav.properties.get(ResourceType.NAME);
|
ResourceType resourceType = (ResourceType)dav.properties.get(ResourceType.NAME);
|
||||||
if (resourceType != null && resourceType.types.contains(ResourceType.CALENDAR)) {
|
if (resourceType != null && resourceType.types.contains(ResourceType.CALENDAR)) {
|
||||||
@ -315,10 +208,12 @@ public class DavResourceFinder {
|
|||||||
supportsEvents = supportedCalendarComponentSet.supportsEvents;
|
supportsEvents = supportedCalendarComponentSet.supportsEvents;
|
||||||
supportsTasks = supportedCalendarComponentSet.supportsTasks;
|
supportsTasks = supportedCalendarComponentSet.supportsTasks;
|
||||||
}
|
}
|
||||||
if (supportsEvents)
|
if (supportsEvents || supportsTasks) {
|
||||||
calendars.put(dav.location, resourceInfo(dav, ServerInfo.ResourceInfo.Type.CALENDAR));
|
ServerConfiguration.Collection info = collectionInfo(dav, ServerConfiguration.Collection.Type.CALENDAR);
|
||||||
if (supportsTasks)
|
info.supportsEvents = supportsEvents;
|
||||||
taskLists.put(dav.location, resourceInfo(dav, ServerInfo.ResourceInfo.Type.CALENDAR));
|
info.supportsTasks = supportsTasks;
|
||||||
|
calendars.put(dav.location, info);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -333,7 +228,7 @@ public class DavResourceFinder {
|
|||||||
* @param type must be ADDRESS_BOOK or CALENDAR
|
* @param type must be ADDRESS_BOOK or CALENDAR
|
||||||
* @return ResourceInfo which represents the DavResource
|
* @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;
|
boolean readOnly = false;
|
||||||
CurrentUserPrivilegeSet privilegeSet = (CurrentUserPrivilegeSet)dav.properties.get(CurrentUserPrivilegeSet.NAME);
|
CurrentUserPrivilegeSet privilegeSet = (CurrentUserPrivilegeSet)dav.properties.get(CurrentUserPrivilegeSet.NAME);
|
||||||
if (privilegeSet != null)
|
if (privilegeSet != null)
|
||||||
@ -348,11 +243,11 @@ public class DavResourceFinder {
|
|||||||
|
|
||||||
String description = null;
|
String description = null;
|
||||||
Integer color = 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);
|
AddressbookDescription addressbookDescription = (AddressbookDescription)dav.properties.get(AddressbookDescription.NAME);
|
||||||
if (addressbookDescription != null)
|
if (addressbookDescription != null)
|
||||||
description = addressbookDescription.description;
|
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);
|
CalendarDescription calendarDescription = (CalendarDescription)dav.properties.get(CalendarDescription.NAME);
|
||||||
if (calendarDescription != null)
|
if (calendarDescription != null)
|
||||||
description = calendarDescription.description;
|
description = calendarDescription.description;
|
||||||
@ -362,7 +257,7 @@ public class DavResourceFinder {
|
|||||||
color = calendarColor.color;
|
color = calendarColor.color;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ServerInfo.ResourceInfo(
|
ServerConfiguration.Collection collection = new ServerConfiguration.Collection(
|
||||||
type,
|
type,
|
||||||
readOnly,
|
readOnly,
|
||||||
UrlUtils.withTrailingSlash(dav.location).toString(),
|
UrlUtils.withTrailingSlash(dav.location).toString(),
|
||||||
@ -370,10 +265,30 @@ public class DavResourceFinder {
|
|||||||
description,
|
description,
|
||||||
color
|
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.
|
* 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 domain domain name, e.g. "icloud.com"
|
||||||
* @param service service to discover (CALDAV or CARDDAV)
|
* @param service service to discover (CALDAV or CARDDAV)
|
||||||
* @return principal URL, or null if none found
|
* @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 {
|
protected HttpUrl discoverPrincipalUrl(String domain, Service service) throws IOException, HttpException, DavException {
|
||||||
String scheme = null;
|
String scheme = null;
|
||||||
String fqdn = null;
|
String fqdn = null;
|
||||||
Integer port = null;
|
Integer port = 443;
|
||||||
List<String> paths = new LinkedList<>(); // there may be multiple paths to try
|
List<String> paths = new LinkedList<>(); // there may be multiple paths to try
|
||||||
|
|
||||||
final String query = "_" + service.name + "s._tcp." + domain;
|
final String query = "_" + service.name + "s._tcp." + domain;
|
||||||
@ -394,7 +309,15 @@ public class DavResourceFinder {
|
|||||||
scheme = "https";
|
scheme = "https";
|
||||||
fqdn = srv.getTarget().toString(true);
|
fqdn = srv.getTarget().toString(true);
|
||||||
port = srv.getPort();
|
port = srv.getPort();
|
||||||
log.info("Found " + service + " service: fqdn=" + fqdn + ", port=" + port);
|
log.info("Found " + service + " service at https://" + fqdn + ":" + port);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// no SRV records, try domain name as FQDN
|
||||||
|
log.info("Didn't find " + service + " service, trying at https://" + domain + ":" + port);
|
||||||
|
|
||||||
|
scheme = "https";
|
||||||
|
fqdn = domain;
|
||||||
|
}
|
||||||
|
|
||||||
// look for TXT record too (for initial context path)
|
// look for TXT record too (for initial context path)
|
||||||
records = new Lookup(query, Type.TXT).run();
|
records = new Lookup(query, Type.TXT).run();
|
||||||
@ -412,11 +335,10 @@ public class DavResourceFinder {
|
|||||||
paths.add("/.well-known/" + service.name);
|
paths.add("/.well-known/" + service.name);
|
||||||
// if this fails, too, try "/"
|
// if this fails, too, try "/"
|
||||||
paths.add("/");
|
paths.add("/");
|
||||||
}
|
|
||||||
|
|
||||||
for (String path : paths)
|
for (String path : paths)
|
||||||
try {
|
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()
|
HttpUrl initialContextPath = new HttpUrl.Builder()
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.host(fqdn).port(port)
|
.host(fqdn).port(port)
|
||||||
@ -424,7 +346,8 @@ public class DavResourceFinder {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
log.info("Trying to determine principal from initial context path=" + initialContextPath);
|
log.info("Trying to determine principal from initial context path=" + initialContextPath);
|
||||||
HttpUrl principal = getCurrentUserPrincipal(initialContextPath);
|
HttpUrl principal = getCurrentUserPrincipal(initialContextPath, service);
|
||||||
|
|
||||||
if (principal != null)
|
if (principal != null)
|
||||||
return principal;
|
return principal;
|
||||||
}
|
}
|
||||||
@ -437,16 +360,24 @@ public class DavResourceFinder {
|
|||||||
/**
|
/**
|
||||||
* Queries a given URL for current-user-principal
|
* Queries a given URL for current-user-principal
|
||||||
* @param url URL to query with PROPFIND (Depth: 0)
|
* @param url URL to query with PROPFIND (Depth: 0)
|
||||||
* @return current-user-principal URL, or null if none
|
* @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);
|
DavResource dav = new DavResource(log, httpClient, url);
|
||||||
dav.propfind(0, CurrentUserPrincipal.NAME);
|
dav.propfind(0, CurrentUserPrincipal.NAME);
|
||||||
CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal) dav.properties.get(CurrentUserPrincipal.NAME);
|
CurrentUserPrincipal currentUserPrincipal = (CurrentUserPrincipal) dav.properties.get(CurrentUserPrincipal.NAME);
|
||||||
if (currentUserPrincipal != null && currentUserPrincipal.href != null) {
|
if (currentUserPrincipal != null && currentUserPrincipal.href != null) {
|
||||||
HttpUrl principal = url.resolve(currentUserPrincipal.href);
|
HttpUrl principal = dav.location.resolve(currentUserPrincipal.href);
|
||||||
if (principal != null) {
|
if (principal != null) {
|
||||||
log.info("Found current-user-principal: " + principal);
|
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;
|
return principal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -462,4 +393,42 @@ public class DavResourceFinder {
|
|||||||
return (SRVRecord)records[0];
|
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.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
|
||||||
import at.bitfire.davdroid.ui.AddAccountActivity;
|
import at.bitfire.davdroid.ui.setup.LoginActivity;
|
||||||
|
|
||||||
public class AccountAuthenticatorService extends Service {
|
public class AccountAuthenticatorService extends Service {
|
||||||
private static AccountAuthenticator accountAuthenticator;
|
private static AccountAuthenticator accountAuthenticator;
|
||||||
@ -48,7 +48,7 @@ public class AccountAuthenticatorService extends Service {
|
|||||||
@Override
|
@Override
|
||||||
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType,
|
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType,
|
||||||
String[] requiredFeatures, Bundle options) throws NetworkErrorException {
|
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);
|
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
|
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
|
||||||
|
@ -22,6 +22,7 @@ import android.view.View;
|
|||||||
|
|
||||||
import at.bitfire.davdroid.Constants;
|
import at.bitfire.davdroid.Constants;
|
||||||
import at.bitfire.davdroid.R;
|
import at.bitfire.davdroid.R;
|
||||||
|
import at.bitfire.davdroid.ui.setup.LoginActivity;
|
||||||
|
|
||||||
public class AccountsActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
|
public class AccountsActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
|
||||||
|
|
||||||
@ -37,7 +38,7 @@ public class AccountsActivity extends AppCompatActivity implements NavigationVie
|
|||||||
fab.setOnClickListener(new View.OnClickListener() {
|
fab.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
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.content.Context;
|
||||||
import android.support.v7.widget.AppCompatCheckBox;
|
import android.support.v7.widget.AppCompatCheckBox;
|
||||||
|
import android.text.Editable;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
@ -19,6 +20,9 @@ import android.widget.LinearLayout;
|
|||||||
import at.bitfire.davdroid.R;
|
import at.bitfire.davdroid.R;
|
||||||
|
|
||||||
public class EditPassword extends LinearLayout {
|
public class EditPassword extends LinearLayout {
|
||||||
|
private static final String NS_ANDROID = "http://schemas.android.com/apk/res/android";
|
||||||
|
|
||||||
|
EditText editPassword;
|
||||||
|
|
||||||
public EditPassword(Context context) {
|
public EditPassword(Context context) {
|
||||||
super(context, null);
|
super(context, null);
|
||||||
@ -29,7 +33,9 @@ public class EditPassword extends LinearLayout {
|
|||||||
|
|
||||||
inflate(context, R.layout.edit_password, this);
|
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);
|
AppCompatCheckBox checkShowPassword = (AppCompatCheckBox)findViewById(R.id.show_password);
|
||||||
checkShowPassword.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
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
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package at.bitfire.davdroid.ui;
|
package at.bitfire.davdroid.ui.setup;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
|
||||||
import at.bitfire.davdroid.R;
|
import at.bitfire.davdroid.R;
|
||||||
|
|
||||||
public class AddAccountActivity extends AppCompatActivity {
|
public class LoginActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(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"?>
|
<?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
|
~ All rights reserved. This program and the accompanying materials
|
||||||
~ are made available under the terms of the GNU Public License v3.0
|
~ are made available under the terms of the GNU Public License v3.0
|
||||||
~ which accompanies this distribution, and is available at
|
~ which accompanies this distribution, and is available at
|
||||||
@ -8,8 +8,8 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/right_pane"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" >
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/fragment">
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
@ -12,39 +12,52 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="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"
|
<ScrollView android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
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
|
<RadioGroup
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:animateLayoutChanges="true">
|
||||||
|
|
||||||
<android.support.v7.widget.AppCompatRadioButton
|
<android.support.v7.widget.AppCompatRadioButton
|
||||||
android:id="@+id/login_type_email"
|
android:id="@+id/login_type_email"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/login_type_email"
|
android:text="@string/login_type_email"
|
||||||
android:checked="true" />
|
style="@style/login_type_headline"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/login_type_email_details"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/login_email_address"/>
|
|
||||||
<EditText
|
<EditText
|
||||||
|
android:id="@+id/email_address"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="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
|
<at.bitfire.davdroid.ui.EditPassword
|
||||||
|
android:id="@+id/email_password"
|
||||||
|
android:hint="@string/login_password"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@ -53,42 +66,40 @@
|
|||||||
android:id="@+id/login_type_url"
|
android:id="@+id/login_type_url"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
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
|
<LinearLayout
|
||||||
|
android:id="@+id/login_type_url_details"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/login_base_url"/>
|
|
||||||
<EditText
|
<EditText
|
||||||
|
android:id="@+id/base_url"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/login_user_name"/>
|
android:hint="@string/login_base_url"
|
||||||
|
android:inputType="textUri"/>
|
||||||
<EditText
|
<EditText
|
||||||
|
android:id="@+id/user_name"
|
||||||
android:layout_width="match_parent"
|
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:layout_height="wrap_content"
|
||||||
android:text="@string/login_password"/>
|
android:hint="@string/login_user_name"
|
||||||
|
android:inputType="textNoSuggestions"/>
|
||||||
<at.bitfire.davdroid.ui.EditPassword
|
<at.bitfire.davdroid.ui.EditPassword
|
||||||
|
android:id="@+id/url_password"
|
||||||
android:layout_width="match_parent"
|
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.support.v7.widget.AppCompatCheckBox
|
||||||
|
android:id="@+id/preemptive_auth"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/login_auth_preemptive"/>
|
android:text="@string/login_auth_preemptive"
|
||||||
|
android:checked="true"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@ -100,26 +111,17 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@style/stepper_nav_bar">
|
style="@style/stepper_nav_bar">
|
||||||
|
|
||||||
<Button
|
<Space
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="Back"
|
|
||||||
android:visibility="invisible"
|
|
||||||
style="@style/stepper_nav_button"/>
|
style="@style/stepper_nav_button"/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
android:id="@+id/login"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="Login"
|
android:text="Login"
|
||||||
style="@style/stepper_nav_button"/>
|
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>
|
||||||
|
|
||||||
</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">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_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_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">
|
<string-array name="login_url_scheme">
|
||||||
<item>http://</item>
|
<item>http://</item>
|
||||||
<item>https://</item>
|
<item>https://</item>
|
||||||
</string-array>
|
</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_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_user_name">Benutzername</string>
|
||||||
<string name="login_base_url">Basis-URL (Ordner werden automatisch gefunden):</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_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-->
|
<!--Settings activity-->
|
||||||
<string name="settings_title">Einstellungen</string>
|
<string name="settings_title">Einstellungen</string>
|
||||||
<string name="settings_no_accounts">Keine DAVdroid-Konten gefunden</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="leftcol_width">320dp</dimen>
|
||||||
|
|
||||||
<dimen name="activity_horizontal_margin">16dp</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>
|
<dimen name="fab_margin">16dp</dimen>
|
||||||
|
|
||||||
|
@ -93,23 +93,20 @@
|
|||||||
<!-- AddAccountActivity -->
|
<!-- AddAccountActivity -->
|
||||||
<string name="login_title">Add account</string>
|
<string name="login_title">Add account</string>
|
||||||
<string name="login_type_email">Login with email address</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">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_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_email_description">Please enter your email address. Its domain name will be used to auto-detect service settings.</string>
|
<string name="login_user_name">User name</string>
|
||||||
<string name="login_email_address">Email:</string>
|
<string name="login_user_name_required">User name required</string>
|
||||||
|
<string name="login_base_url">Base URL</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_auth_preemptive">Preemptive authentication (recommended, but incompatible with Digest auth)</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 -->
|
<!-- Settings activity -->
|
||||||
<string name="settings_title">Settings</string>
|
<string name="settings_title">Settings</string>
|
||||||
|
@ -35,6 +35,14 @@
|
|||||||
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
|
<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) -->
|
<!-- stepper (wizard) -->
|
||||||
|
|
||||||
<style name="stepper_nav_bar">
|
<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