1
0
mirror of https://github.com/etesync/android synced 2025-06-28 02:42:37 +00:00

Service database

* HttpClient: authentication that is limited to a host name is never preemptive
* DavResourceFinder: service configuration == null means that this service is not available
* new SQLite database for CalDAV/CardDAV services
* added AccountDetailsFragment, which asks for account name and then finishes account creation
* updated AccountListFragment
This commit is contained in:
Ricki Hirner 2016-01-17 17:10:30 +01:00
parent 85a6b68a56
commit ff901ce91f
32 changed files with 675 additions and 197 deletions

View File

@ -10,19 +10,15 @@ package at.bitfire.davdroid;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import okhttp3.ConnectionSpec; import java.io.IOException;
import java.net.URISyntaxException;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response;
import okhttp3.TlsVersion;
import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.MockWebServer;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Collections;
public class HttpClientTest extends InstrumentationTestCase { public class HttpClientTest extends InstrumentationTestCase {
MockWebServer server; MockWebServer server;

View File

@ -10,8 +10,6 @@ package at.bitfire.davdroid;
import android.os.Build; import android.os.Build;
import okhttp3.mockwebserver.MockWebServer;
import junit.framework.TestCase; import junit.framework.TestCase;
import java.io.IOException; import java.io.IOException;
@ -19,6 +17,8 @@ import java.net.Socket;
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocket;
import okhttp3.mockwebserver.MockWebServer;
public class SSLSocketFactoryCompatTest extends TestCase { public class SSLSocketFactoryCompatTest extends TestCase {
SSLSocketFactoryCompat factory = new SSLSocketFactoryCompat(null); SSLSocketFactoryCompat factory = new SSLSocketFactoryCompat(null);

View File

@ -2,17 +2,14 @@ package at.bitfire.davdroid.resource;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import junit.framework.TestCase;
import java.io.IOException; import java.io.IOException;
import at.bitfire.dav4android.exception.DavException; import at.bitfire.dav4android.exception.DavException;
import at.bitfire.dav4android.exception.HttpException; import at.bitfire.dav4android.exception.HttpException;
import at.bitfire.davdroid.Constants; import at.bitfire.davdroid.Constants;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
public class DavResourceFinderTest extends InstrumentationTestCase { public class DavResourceFinderTest extends InstrumentationTestCase {

View File

@ -114,8 +114,7 @@
<activity <activity
android:name=".ui.setup.LoginActivity" 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">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
</intent-filter> </intent-filter>

View File

@ -11,22 +11,11 @@ package at.bitfire.davdroid;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import lombok.NonNull;
import okhttp3.CookieJar;
import okhttp3.Credentials;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.internal.tls.OkHostnameVerifier;
import okhttp3.logging.HttpLoggingInterceptor;
import org.slf4j.Logger; import org.slf4j.Logger;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.net.CookieManager;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
@ -34,7 +23,15 @@ import java.util.concurrent.TimeUnit;
import at.bitfire.dav4android.BasicDigestAuthenticator; import at.bitfire.dav4android.BasicDigestAuthenticator;
import de.duenndns.ssl.MemorizingTrustManager; import de.duenndns.ssl.MemorizingTrustManager;
import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import okhttp3.Credentials;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.internal.tls.OkHostnameVerifier;
import okhttp3.logging.HttpLoggingInterceptor;
public class HttpClient { public class HttpClient {
private static final int MAX_LOG_LINE_LENGTH = 85; private static final int MAX_LOG_LINE_LENGTH = 85;
@ -87,29 +84,12 @@ public class HttpClient {
return builder.build(); return builder.build();
} }
public static OkHttpClient addAuthentication(@NonNull OkHttpClient httpClient, @NonNull String host, @NonNull String username, @NonNull String password, boolean preemptive) { public static OkHttpClient addAuthentication(@NonNull OkHttpClient httpClient, @NonNull String host, @NonNull String username, @NonNull String password) {
return httpClient.newBuilder() return httpClient.newBuilder()
.authenticator(new BasicDigestAuthenticator(host, username, password)) .authenticator(new BasicDigestAuthenticator(host, username, password))
.build(); .build();
} }
//@NonNull final Logger log
/**
* Creates a new HttpClient (based on another one) which can be used to download external resources:
* 1. it does not use preemptive authentication
* 2. it only authenticates against a given host
* @param httpClient user name and password from this client will be used
* @param host authentication will be restricted to this host
*/
/*public HttpClient(Logger log, HttpClient client, String host) {
this(log, client.context);
username = client.username;
password = client.password;
setAuthenticator(new BasicDigestAuthenticator(host, username, password));
}*/
public static OkHttpClient addLogger(@NonNull OkHttpClient httpClient, @NonNull final Logger logger) { public static OkHttpClient addLogger(@NonNull OkHttpClient httpClient, @NonNull final Logger logger) {
// enable verbose logs, if requested // enable verbose logs, if requested
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {

View File

@ -18,6 +18,7 @@ import org.xbill.DNS.TXTRecord;
import org.xbill.DNS.Type; import org.xbill.DNS.Type;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable;
import java.net.URI; import java.net.URI;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -84,7 +85,11 @@ public class DavResourceFinder {
cardDavConfig = findInitialConfiguration(Service.CARDDAV), cardDavConfig = findInitialConfiguration(Service.CARDDAV),
calDavConfig = findInitialConfiguration(Service.CALDAV); calDavConfig = findInitialConfiguration(Service.CALDAV);
return new Configuration(cardDavConfig, calDavConfig, log.toString()); return new Configuration(
credentials.getUserName(), credentials.getPassword(), credentials.isAuthPreemptive(),
cardDavConfig, calDavConfig,
log.toString()
);
} }
protected Configuration.ServiceInfo findInitialConfiguration(@NonNull Service service) { protected Configuration.ServiceInfo findInitialConfiguration(@NonNull Service service) {
@ -133,7 +138,9 @@ public class DavResourceFinder {
} }
} }
return config; // return config or null if config doesn't contain useful information
boolean serviceAvailable = config.principal != null || !config.homeSets.isEmpty() || !config.collections.isEmpty();
return serviceAvailable ? config : null;
} }
protected void checkUserGivenURL(@NonNull HttpUrl baseURL, @NonNull Service service, @NonNull Configuration.ServiceInfo config) { protected void checkUserGivenURL(@NonNull HttpUrl baseURL, @NonNull Service service, @NonNull Configuration.ServiceInfo config) {
@ -405,27 +412,31 @@ public class DavResourceFinder {
@RequiredArgsConstructor @RequiredArgsConstructor
@ToString(exclude="logs") @ToString(exclude="logs")
public static class Configuration { public static class Configuration implements Serializable {
public final String userName, password;
public final boolean preemptive;
public final ServiceInfo cardDAV; public final ServiceInfo cardDAV;
public final ServiceInfo calDAV; public final ServiceInfo calDAV;
public final String logs; public final String logs;
@ToString @ToString
public static class ServiceInfo { public static class ServiceInfo implements Serializable {
@Getter @Getter
HttpUrl principal; HttpUrl principal;
@Getter @Getter
Set<HttpUrl> homeSets = new HashSet<>(); final Set<HttpUrl> homeSets = new HashSet<>();
@Getter @Getter
Map<HttpUrl, Collection> collections = new HashMap<>(); final Map<HttpUrl, Collection> collections = new HashMap<>();
} }
@Data @Data
public static class Collection { public static class Collection implements Serializable {
public enum Type { public enum Type {
ADDRESS_BOOK, ADDRESS_BOOK,
CALENDAR CALENDAR
@ -434,7 +445,7 @@ public class DavResourceFinder {
final Type type; final Type type;
final boolean readOnly; final boolean readOnly;
final String title, description; final String displayName, description;
final Integer color; final Integer color;
/** /**

View File

@ -109,11 +109,11 @@ public class AccountSettings {
} }
public static Bundle createBundle(ServerInfo serverInfo) { public static Bundle initialUserData(String userName, boolean preemptive) {
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putString(KEY_SETTINGS_VERSION, String.valueOf(CURRENT_VERSION)); bundle.putString(KEY_SETTINGS_VERSION, String.valueOf(CURRENT_VERSION));
bundle.putString(KEY_USERNAME, serverInfo.getUserName()); bundle.putString(KEY_USERNAME, userName);
bundle.putString(KEY_AUTH_PREEMPTIVE, Boolean.toString(serverInfo.isAuthPreemptive())); bundle.putString(KEY_AUTH_PREEMPTIVE, Boolean.toString(preemptive));
return bundle; return bundle;
} }

View File

@ -279,7 +279,7 @@ public class ContactsSyncManager extends SyncManager {
OkHttpClient resourceClient = HttpClient.create(context); OkHttpClient resourceClient = HttpClient.create(context);
// authenticate only against a certain host, and only upon request // authenticate only against a certain host, and only upon request
resourceClient = HttpClient.addAuthentication(resourceClient, baseUrl.host(), settings.username(), settings.password(), false); resourceClient = HttpClient.addAuthentication(resourceClient, baseUrl.host(), settings.username(), settings.password());
// allow redirects // allow redirects
resourceClient = resourceClient.newBuilder() resourceClient = resourceClient.newBuilder()

View File

@ -0,0 +1,102 @@
/*
* 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.syncadapter;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.resource.DavResourceFinder;
public class ServiceDB {
public static class Services {
public static final String
_TABLE = "services",
ID = "_id",
ACCOUNT_NAME = "account_name",
SERVICE = "service",
PRINCIPAL = "principal",
LAST_REFRESH = "last_refresh";
// allowed values for SERVICE column
public static final String
SERVICE_CALDAV = "caldav",
SERVICE_CARDDAV = "carddav";
}
public static class HomeSets {
public static final String
_TABLE = "homesets",
ID = "_id",
SERVICE_ID = "service_id",
URL = "url";
}
public static class Collections {
public static final String
_TABLE = "collections",
ID = "_id",
SERVICE_ID = "service_id",
URL = "url",
DISPLAY_NAME = "display_name",
DESCRIPTION = "description";
public static ContentValues fromCollection(DavResourceFinder.Configuration.Collection collection) {
ContentValues values = new ContentValues();
values.put(DISPLAY_NAME, collection.getDisplayName());
values.put(DESCRIPTION, collection.getDescription());
return values;
}
}
public static class OpenHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "services.db";
private static final int DATABASE_VERSION = 1;
public OpenHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
Constants.log.info("Creating services database");
db.execSQL("CREATE TABLE " + Services._TABLE + "(" +
Services.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Services.ACCOUNT_NAME + " TEXT NOT NULL," +
Services.SERVICE + " TEXT NOT NULL," +
Services.PRINCIPAL + " TEXT NULL, " +
Services.LAST_REFRESH + " INTEGER NULL" +
")");
db.execSQL("CREATE TABLE " + HomeSets._TABLE + "(" +
HomeSets.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
HomeSets.SERVICE_ID + " INTEGER NOT NULL," +
HomeSets.URL + " TEXT NOT NULL" +
")");
db.execSQL("CREATE TABLE " + Collections._TABLE + "(" +
Collections.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Collections.SERVICE_ID + " INTEGER NOT NULL," +
Collections.URL + " TEXT NOT NULL," +
Collections.DISPLAY_NAME + " TEXT NULL," +
Collections.DESCRIPTION + " TEXT NULL" +
")");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}

View File

@ -12,18 +12,30 @@ import android.accounts.Account;
import android.accounts.AccountManager; import android.accounts.AccountManager;
import android.accounts.OnAccountsUpdateListener; import android.accounts.OnAccountsUpdateListener;
import android.content.Context; import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.ListFragment; import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.TextView; import android.widget.TextView;
import java.util.LinkedList;
import java.util.List;
import at.bitfire.davdroid.Constants; import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
import at.bitfire.davdroid.syncadapter.ServiceDB;
import at.bitfire.davdroid.syncadapter.ServiceDB.*;
import lombok.Cleanup;
import lombok.RequiredArgsConstructor;
public class AccountListFragment extends ListFragment implements OnAccountsUpdateListener { public class AccountListFragment extends ListFragment implements OnAccountsUpdateListener, LoaderManager.LoaderCallbacks<List<AccountListFragment.AccountInfo>> {
protected AccountManager accountManager; protected AccountManager accountManager;
@ -32,11 +44,17 @@ public class AccountListFragment extends ListFragment implements OnAccountsUpdat
setListAdapter(new AccountListAdapter(getContext())); setListAdapter(new AccountListAdapter(getContext()));
accountManager = AccountManager.get(getContext()); accountManager = AccountManager.get(getContext());
accountManager.addOnAccountsUpdatedListener(this, null, true); accountManager.addOnAccountsUpdatedListener(this, null, false);
return inflater.inflate(R.layout.account_list, container, false); return inflater.inflate(R.layout.account_list, container, false);
} }
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getLoaderManager().initLoader(0, getArguments(), this);
}
@Override @Override
public void onDestroyView() { public void onDestroyView() {
accountManager.removeOnAccountsUpdatedListener(this); accountManager.removeOnAccountsUpdatedListener(this);
@ -45,17 +63,35 @@ public class AccountListFragment extends ListFragment implements OnAccountsUpdat
@Override @Override
public void onAccountsUpdated(Account[] accounts) { public void onAccountsUpdated(Account[] accounts) {
getLoaderManager().restartLoader(0, null, this);
}
@Override
public Loader<List<AccountInfo>> onCreateLoader(int id, Bundle args) {
return new AccountLoader(getContext());
}
@Override
public void onLoadFinished(Loader<List<AccountInfo>> loader, List<AccountInfo> accounts) {
AccountListAdapter adapter = (AccountListAdapter)getListAdapter(); AccountListAdapter adapter = (AccountListAdapter)getListAdapter();
if (adapter != null) { if (adapter != null) {
adapter.clear(); adapter.clear();
for (Account account : accounts) adapter.addAll(accounts);
if (Constants.ACCOUNT_TYPE.equals(account.type))
adapter.add(account);
} }
} }
@Override
public void onLoaderReset(Loader<List<AccountInfo>> loader) {
}
class AccountListAdapter extends ArrayAdapter<Account> {
@RequiredArgsConstructor
public static class AccountInfo {
final Account account;
boolean hasCardDAV, hasCalDAV;
}
static class AccountListAdapter extends ArrayAdapter<AccountInfo> {
public AccountListAdapter(Context context) { public AccountListAdapter(Context context) {
super(context, R.layout.account_list_item); super(context, R.layout.account_list_item);
@ -70,12 +106,66 @@ public class AccountListFragment extends ListFragment implements OnAccountsUpdat
v = inflater.inflate(R.layout.account_list_item, parent, false); v = inflater.inflate(R.layout.account_list_item, parent, false);
} }
Account account = getItem(position); AccountInfo info = getItem(position);
TextView tvName = (TextView)v.findViewById(R.id.account_name); TextView tv = (TextView)v.findViewById(R.id.account_name);
tvName.setText(account.name); tv.setText(info.account.name);
tv = (TextView)v.findViewById(R.id.carddav);
tv.setVisibility(info.hasCardDAV ? View.VISIBLE : View.GONE);
tv = (TextView)v.findViewById(R.id.caldav);
tv.setVisibility(info.hasCalDAV ? View.VISIBLE : View.GONE);
return v; return v;
} }
} }
static class AccountLoader extends AsyncTaskLoader<List<AccountInfo>> {
final AccountManager accountManager;
final OpenHelper dbHelper;
public AccountLoader(Context context) {
super(context);
accountManager = AccountManager.get(context);
dbHelper = new OpenHelper(context);
}
@Override
protected void onStartLoading() {
forceLoad();
}
@Override
public List<AccountInfo> loadInBackground() {
List<AccountInfo> accounts = new LinkedList<>();
try {
SQLiteDatabase db = dbHelper.getReadableDatabase();
for (Account account : accountManager.getAccountsByType(Constants.ACCOUNT_TYPE)) {
AccountInfo info = new AccountInfo(account);
// query services of this account
@Cleanup Cursor cursor = db.query(
Services._TABLE,
new String[] { Services.SERVICE },
Services.ACCOUNT_NAME + "=?", new String[] { account.name },
null, null, null);
while (cursor.moveToNext()) {
String service = cursor.getString(0);
if (Services.SERVICE_CARDDAV.equals(service))
info.hasCardDAV = true;
if (Services.SERVICE_CALDAV.equals(service))
info.hasCalDAV = true;
}
accounts.add(info);
}
} finally {
dbHelper.close();
}
return accounts;
}
}
} }

View File

@ -10,7 +10,6 @@ package at.bitfire.davdroid.ui;
import android.accounts.Account; import android.accounts.Account;
import android.accounts.AccountManager; import android.accounts.AccountManager;
import android.app.Activity;
import android.app.LoaderManager; import android.app.LoaderManager;
import android.content.AsyncTaskLoader; import android.content.AsyncTaskLoader;
import android.content.ContentResolver; import android.content.ContentResolver;
@ -23,6 +22,7 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.provider.CalendarContract; import android.provider.CalendarContract;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -42,7 +42,7 @@ import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
import at.bitfire.davdroid.syncadapter.AccountSettings; import at.bitfire.davdroid.syncadapter.AccountSettings;
public class DebugInfoActivity extends Activity implements LoaderManager.LoaderCallbacks<String> { public class DebugInfoActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<String> {
public static final String public static final String
KEY_EXCEPTION = "exception", KEY_EXCEPTION = "exception",
KEY_LOGS = "logs", KEY_LOGS = "logs",
@ -67,7 +67,7 @@ public class DebugInfoActivity extends Activity implements LoaderManager.LoaderC
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.exception_details_activity, menu); getMenuInflater().inflate(R.menu.activity_debug_info, menu);
return true; return true;
} }

View File

@ -0,0 +1,136 @@
/*
* 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.accounts.Account;
import android.accounts.AccountManager;
import android.content.ContentValues;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import java.util.Map;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.resource.DavResourceFinder;
import at.bitfire.davdroid.syncadapter.AccountSettings;
import at.bitfire.davdroid.syncadapter.ServiceDB.*;
import lombok.Cleanup;
import okhttp3.HttpUrl;
public class AccountDetailsFragment extends Fragment {
private static final String KEY_CONFIG = "config";
public static AccountDetailsFragment newInstance(DavResourceFinder.Configuration config) {
Bundle args = new Bundle(1);
args.putSerializable(KEY_CONFIG, config);
AccountDetailsFragment fragment = new AccountDetailsFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.login_account_details, container, false);
Button btnBack = (Button)v.findViewById(R.id.back);
btnBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getFragmentManager().popBackStack();
}
});
final EditText editName = (EditText)v.findViewById(R.id.account_name);
Button btnCreate = (Button)v.findViewById(R.id.create_account);
btnCreate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = editName.getText().toString();
if (name.isEmpty())
editName.setError(getString(R.string.login_account_name_required));
else {
if (createAccount(name, (DavResourceFinder.Configuration)getArguments().getSerializable(KEY_CONFIG)))
getActivity().finish();
else
Snackbar.make(v, R.string.login_account_not_created, Snackbar.LENGTH_LONG).show();
}
}
});
return v;
}
protected boolean createAccount(String accountName, DavResourceFinder.Configuration config) {
Account account = new Account(accountName, Constants.ACCOUNT_TYPE);
Constants.log.info("Creating account {}, initial config: {}", accountName, config);
// create Android account
AccountManager accountManager = AccountManager.get(getContext());
Bundle userData = AccountSettings.initialUserData(config.userName, config.preemptive);
if (!accountManager.addAccountExplicitly(account, config.password, userData))
return false;
// add entries for account to service DB
@Cleanup OpenHelper dbHelper = new OpenHelper(getContext());
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransactionNonExclusive();
try {
if (config.cardDAV != null)
insertService(db, accountName, Services.SERVICE_CARDDAV, config.cardDAV);
if (config.calDAV != null)
insertService(db, accountName, Services.SERVICE_CALDAV, config.calDAV);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return true;
}
protected void insertService(SQLiteDatabase db, String accountName, String service, DavResourceFinder.Configuration.ServiceInfo info) {
ContentValues values = new ContentValues();
// insert service
values.put(Services.ACCOUNT_NAME, accountName);
values.put(Services.SERVICE, service);
if (info.getPrincipal() != null)
values.put(Services.PRINCIPAL, info.getPrincipal().toString());
long serviceID = db.insertOrThrow(Services._TABLE, null, values);
// insert home sets
for (HttpUrl homeSet : info.getHomeSets()) {
values.clear();
values.put(HomeSets.SERVICE_ID, serviceID);
values.put(HomeSets.URL, homeSet.toString());
db.insertOrThrow(HomeSets._TABLE, null, values);
}
// insert collections
for (Map.Entry<HttpUrl, DavResourceFinder.Configuration.Collection> entry : info.getCollections().entrySet()) {
values = Collections.fromCollection(entry.getValue());
values.put(Collections.SERVICE_ID, serviceID);
values.put(Collections.URL, entry.getKey().toString());
db.insertOrThrow(Collections._TABLE, null, values);
}
}
}

View File

@ -11,11 +11,14 @@ package at.bitfire.davdroid.ui.setup;
import android.app.Dialog; import android.app.Dialog;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader; import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v7.app.AlertDialog;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
@ -25,6 +28,7 @@ import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
import at.bitfire.davdroid.resource.DavResourceFinder; import at.bitfire.davdroid.resource.DavResourceFinder;
import at.bitfire.davdroid.resource.DavResourceFinder.Configuration; import at.bitfire.davdroid.resource.DavResourceFinder.Configuration;
import at.bitfire.davdroid.ui.DebugInfoActivity;
import lombok.Cleanup; import lombok.Cleanup;
public class DetectConfigurationFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Configuration> { public class DetectConfigurationFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Configuration> {
@ -57,8 +61,18 @@ public class DetectConfigurationFragment extends DialogFragment implements Loade
@Override @Override
public void onLoadFinished(Loader<Configuration> loader, Configuration data) { public void onLoadFinished(Loader<Configuration> loader, Configuration data) {
// show error / continue with next fragment if (data.calDAV == null && data.cardDAV == null)
Constants.log.info("detection results: {}", data); // no service found: show error message
getFragmentManager().beginTransaction()
.add(NothingDetectedFragment.newInstance(data.logs), null)
.commitAllowingStateLoss();
else
// service found: continue
getFragmentManager().beginTransaction()
.replace(R.id.fragment, AccountDetailsFragment.newInstance(data))
.addToBackStack(null)
.commitAllowingStateLoss();
dismissAllowingStateLoss(); dismissAllowingStateLoss();
} }
@ -67,6 +81,41 @@ public class DetectConfigurationFragment extends DialogFragment implements Loade
} }
public static class NothingDetectedFragment extends DialogFragment {
private static String KEY_LOGS = "logs";
public static NothingDetectedFragment newInstance(String logs) {
Bundle args = new Bundle();
args.putString(KEY_LOGS, logs);
NothingDetectedFragment fragment = new NothingDetectedFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.login_configuration_detection)
.setIcon(R.drawable.ic_error_dark)
.setMessage(R.string.login_no_caldav_carddav)
.setNeutralButton(R.string.login_view_logs, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(getActivity(), DebugInfoActivity.class);
intent.putExtra(DebugInfoActivity.KEY_LOGS, getArguments().getString(KEY_LOGS));
startActivity(intent);
}
})
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// dismiss
}
})
.create();
}
}
static class ServerConfigurationLoader extends AsyncTaskLoader<Configuration> { static class ServerConfigurationLoader extends AsyncTaskLoader<Configuration> {
final Context context; final Context context;
final LoginCredentialsFragment.LoginCredentials credentials; final LoginCredentialsFragment.LoginCredentials credentials;

View File

@ -8,9 +8,13 @@
package at.bitfire.davdroid.ui.setup; package at.bitfire.davdroid.ui.setup;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.R; import at.bitfire.davdroid.R;
public class LoginActivity extends AppCompatActivity { public class LoginActivity extends AppCompatActivity {
@ -28,4 +32,13 @@ public class LoginActivity extends AppCompatActivity {
} }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_login, menu);
return true;
}
public void showHelp(MenuItem item) {
startActivity(new Intent(Intent.ACTION_VIEW, Constants.webUri.buildUpon().appendEncodedPath("configuration/").build()));
}
} }

View File

@ -0,0 +1,18 @@
<!--
~ 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
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:alpha="0.54" >
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-2h2v2zm0,-4h-2V7h2v6z"/>
</vector>

View File

@ -0,0 +1,17 @@
<!--
~ 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
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,17h-2v-2h2v2zm2.07,-7.75l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2H8c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
</vector>

View File

@ -0,0 +1,13 @@
<!--
~ 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
-->
<vector android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FFFFFFFF" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
</vector>

View File

@ -1,23 +0,0 @@
<?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
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<fragment
class="at.bitfire.davdroid.ui.AccountListFragment"
android:id="@+id/account_list"
android:layout_width="@dimen/leftcol_width"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -7,18 +7,7 @@
~ http://www.gnu.org/licenses/gpl.html ~ http://www.gnu.org/licenses/gpl.html
--> -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@id/android:list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@android:color/transparent" />
<RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -29,17 +18,23 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_alignParentTop="true" /> android:layout_alignParentTop="true" />
<ListView
android:id="@id/android:list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@android:color/transparent"
android:background="@android:color/transparent"
android:cacheColorHint="@android:color/transparent"/>
<TextView <TextView
android:id="@id/android:empty" android:id="@id/android:empty"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginBottom="72dp" android:layout_marginBottom="72dp"
android:layout_marginRight="64dp" android:layout_marginRight="64dp"
android:gravity="right|bottom" android:gravity="center"
android:textColor="@color/white" android:textColor="@color/white"
android:textAppearance="@style/TextAppearance.AppCompat.Large" android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:text="@string/account_list_empty" /> android:text="@string/account_list_empty" />
</RelativeLayout> </RelativeLayout>
</LinearLayout>

View File

@ -1,16 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto" xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:gravity="center_horizontal">
<android.support.v7.widget.CardView <android.support.v7.widget.CardView
android:layout_width="match_parent" style="@style/account_list_card"
android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_margin="16dp"
android:layout_height="wrap_content"
android:clickable="true" android:clickable="true"
android:foreground="?android:attr/selectableItemBackground" android:foreground="?android:attr/selectableItemBackground"
card_view:contentPadding="16dp" card_view:contentPadding="16dp"
@ -19,7 +19,19 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/carddav"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CardDAV"/>
<TextView
android:id="@+id/caldav"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="CalDAV"/>
<TextView <TextView
android:id="@+id/account_name" android:id="@+id/account_name"
@ -27,7 +39,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Title" android:textAppearance="@style/TextAppearance.AppCompat.Title"
tools:text="Account Name"/> tools:text="Account Name"/>
</LinearLayout> </LinearLayout>
</android.support.v7.widget.CardView> </android.support.v7.widget.CardView>
</LinearLayout>
</FrameLayout>

View File

@ -16,6 +16,15 @@
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
tools:context="at.bitfire.davdroid.ui.AccountsActivity"> tools:context="at.bitfire.davdroid.ui.AccountsActivity">
<fragment
class="at.bitfire.davdroid.ui.AccountListFragment"
android:id="@+id/account_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/account_list"
app:layout_scrollFlags="scroll"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
<android.support.design.widget.AppBarLayout <android.support.design.widget.AppBarLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -29,8 +38,6 @@
app:popupTheme="@style/AppTheme.PopupOverlay"/> app:popupTheme="@style/AppTheme.PopupOverlay"/>
</android.support.design.widget.AppBarLayout> </android.support.design.widget.AppBarLayout>
<include layout="@layout/content_accounts"/>
<android.support.design.widget.FloatingActionButton <android.support.design.widget.FloatingActionButton
android:id="@+id/fab" android:id="@+id/fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -18,7 +18,7 @@
tools:openDrawer="start"> tools:openDrawer="start">
<include <include
layout="@layout/app_bar_accounts" layout="@layout/accounts_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent"/>

View File

@ -1,23 +0,0 @@
<?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
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<fragment
class="at.bitfire.davdroid.ui.AccountListFragment"
android:id="@+id/account_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -0,0 +1,75 @@
<?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
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/login_type_headline"
android:text="@string/login_create_account"
android:layout_marginBottom="14dp"/>
<EditText
android:id="@+id/account_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/login_account_name"
android:inputType="textEmailAddress"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/login_account_name_info"/>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/stepper_nav_bar">
<Button
android:id="@+id/back"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_gravity="center"
android:text="@string/login_back"
style="@style/stepper_nav_button"/>
<Button
android:id="@+id/create_account"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_gravity="center"
android:text="@string/login_create_account"
style="@style/stepper_nav_button"/>
</LinearLayout>
</LinearLayout>

View File

@ -41,6 +41,7 @@
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:paddingLeft="14dp"
style="@style/login_type_headline"/> style="@style/login_type_headline"/>
<LinearLayout <LinearLayout
@ -68,6 +69,7 @@
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" android:layout_marginTop="16dp"
android:paddingLeft="14dp"
style="@style/login_type_headline"/> style="@style/login_type_headline"/>
<LinearLayout <LinearLayout
@ -87,7 +89,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/login_user_name" android:hint="@string/login_user_name"
android:inputType="textNoSuggestions"/> android:inputType="textEmailAddress"/>
<at.bitfire.davdroid.ui.EditPassword <at.bitfire.davdroid.ui.EditPassword
android:id="@+id/url_password" android:id="@+id/url_password"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -120,7 +122,7 @@
android:id="@+id/login" 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="@string/login_login"
style="@style/stepper_nav_button"/> style="@style/stepper_nav_button"/>
</LinearLayout> </LinearLayout>

View File

@ -7,12 +7,13 @@
~ http://www.gnu.org/licenses/gpl.html ~ http://www.gnu.org/licenses/gpl.html
--> -->
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:icon="@android:drawable/ic_menu_share" android:icon="@drawable/ic_share_light"
android:title="@string/send" android:title="@string/send"
android:showAsAction="always" app:showAsAction="always"
android:onClick="onShare" /> android:onClick="onShare"/>
</menu> </menu>

View File

@ -7,12 +7,15 @@
~ http://www.gnu.org/licenses/gpl.html ~ http://www.gnu.org/licenses/gpl.html
--> -->
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/help" android:id="@+id/help"
android:showAsAction="never" android:title="@string/help"
android:title="@string/help" android:onClick="showHelp"> android:icon="@drawable/ic_help_light"
app:showAsAction="always"
android:onClick="showHelp">
</item> </item>
</menu> </menu>

View File

@ -1,19 +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
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/add_account"
android:icon="@drawable/navigation_accept"
android:showAsAction="always|withText"
android:title="@string/setup_add_account">
</item>
</menu>

View File

@ -0,0 +1,18 @@
<?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>
<!-- AddAccountActivity -->
<style name="account_list_card">
<item name="android:layout_width">600dp</item>
</style>
</resources>

View File

@ -12,7 +12,6 @@
<!-- common strings --> <!-- common strings -->
<string name="app_name">DAVdroid</string> <string name="app_name">DAVdroid</string>
<string name="help">Help</string> <string name="help">Help</string>
<string name="next">Next</string>
<string name="please_wait">Please wait</string> <string name="please_wait">Please wait</string>
<string name="send">Send</string> <string name="send">Send</string>
<string name="skip">Skip</string> <string name="skip">Skip</string>
@ -104,9 +103,18 @@
<string name="login_user_name_required">User name required</string> <string name="login_user_name_required">User name required</string>
<string name="login_base_url">Base URL</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_auth_preemptive">Preemptive authentication (recommended, but incompatible with Digest auth)</string>
<string name="login_login">Login</string>
<string name="login_back">Back</string>
<string name="login_create_account">Create account</string>
<string name="login_account_name">Account name</string>
<string name="login_account_name_info">Use your email address as account name because Android will use the account name as ORGANIZER field for events you create. You can\'t have two accounts with the same name.</string>
<string name="login_account_name_required">Account name required</string>
<string name="login_account_not_created">Account could not be created</string>
<string name="login_configuration_detection">Configuration detection</string> <string name="login_configuration_detection">Configuration detection</string>
<string name="login_querying_server">Please wait, querying server…</string> <string name="login_querying_server">Please wait, querying server…</string>
<string name="login_no_caldav_carddav">Couldn\'t find CalDAV or CardDAV service.</string>
<string name="login_view_logs">View logs</string>
<!-- Settings activity --> <!-- Settings activity -->
<string name="settings_title">Settings</string> <string name="settings_title">Settings</string>
@ -190,7 +198,6 @@
<string name="setup_account_details">Account details</string> <string name="setup_account_details">Account details</string>
<string name="setup_account_name">Account name:</string> <string name="setup_account_name">Account name:</string>
<string name="setup_account_name_hint">My CalDAV/CardDAV Account</string> <string name="setup_account_name_hint">My CalDAV/CardDAV Account</string>
<string name="setup_account_name_info">Use your email address as account name because Android will use the account name as ORGANIZER field for events you create. You can\'t have two accounts with the same name.</string>
<string name="setup_read_only">read-only</string> <string name="setup_read_only">read-only</string>
<!-- sync errors and DebugInfoActivity --> <!-- sync errors and DebugInfoActivity -->

View File

@ -38,8 +38,10 @@
<!-- AddAccountActivity --> <!-- AddAccountActivity -->
<style name="login_type_headline"> <style name="login_type_headline">
<item name="android:paddingLeft">14dp</item> <item name="android:textSize">22dp</item>
<item name="android:textSize">20dp</item> </style>
<style name="account_list_card">
<item name="android:layout_width">match_parent</item>
</style> </style>

@ -1 +1 @@
Subproject commit 94a5ee4aec98a29fe1c56046f9a6c71131db8b51 Subproject commit 40aef8e2d5219c7595229a225c497c0e7237ec1c