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
pull/2/head
Ricki Hirner 9 years ago
parent 85a6b68a56
commit ff901ce91f

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

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

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

@ -114,8 +114,7 @@
<activity
android:name=".ui.setup.LoginActivity"
android:label="@string/login_title"
android:parentActivityName=".ui.AccountsActivity"
android:noHistory="true">
android:parentActivityName=".ui.AccountsActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>

@ -11,22 +11,11 @@ package at.bitfire.davdroid;
import android.content.Context;
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 java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.net.CookieManager;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
@ -34,7 +23,15 @@ import java.util.concurrent.TimeUnit;
import at.bitfire.dav4android.BasicDigestAuthenticator;
import de.duenndns.ssl.MemorizingTrustManager;
import lombok.NonNull;
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 {
private static final int MAX_LOG_LINE_LENGTH = 85;
@ -87,29 +84,12 @@ public class HttpClient {
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()
.authenticator(new BasicDigestAuthenticator(host, username, password))
.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) {
// enable verbose logs, if requested
if (logger.isTraceEnabled()) {

@ -18,6 +18,7 @@ import org.xbill.DNS.TXTRecord;
import org.xbill.DNS.Type;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
@ -84,7 +85,11 @@ public class DavResourceFinder {
cardDavConfig = findInitialConfiguration(Service.CARDDAV),
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) {
@ -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) {
@ -405,27 +412,31 @@ public class DavResourceFinder {
@RequiredArgsConstructor
@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 calDAV;
public final String logs;
@ToString
public static class ServiceInfo {
public static class ServiceInfo implements Serializable {
@Getter
HttpUrl principal;
@Getter
Set<HttpUrl> homeSets = new HashSet<>();
final Set<HttpUrl> homeSets = new HashSet<>();
@Getter
Map<HttpUrl, Collection> collections = new HashMap<>();
final Map<HttpUrl, Collection> collections = new HashMap<>();
}
@Data
public static class Collection {
public static class Collection implements Serializable {
public enum Type {
ADDRESS_BOOK,
CALENDAR
@ -434,7 +445,7 @@ public class DavResourceFinder {
final Type type;
final boolean readOnly;
final String title, description;
final String displayName, description;
final Integer color;
/**

@ -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.putString(KEY_SETTINGS_VERSION, String.valueOf(CURRENT_VERSION));
bundle.putString(KEY_USERNAME, serverInfo.getUserName());
bundle.putString(KEY_AUTH_PREEMPTIVE, Boolean.toString(serverInfo.isAuthPreemptive()));
bundle.putString(KEY_USERNAME, userName);
bundle.putString(KEY_AUTH_PREEMPTIVE, Boolean.toString(preemptive));
return bundle;
}

@ -279,7 +279,7 @@ public class ContactsSyncManager extends SyncManager {
OkHttpClient resourceClient = HttpClient.create(context);
// 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
resourceClient = resourceClient.newBuilder()

@ -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) {
}
}
}

@ -12,18 +12,30 @@ import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.OnAccountsUpdateListener;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
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.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.LinkedList;
import java.util.List;
import at.bitfire.davdroid.Constants;
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;
@ -32,11 +44,17 @@ public class AccountListFragment extends ListFragment implements OnAccountsUpdat
setListAdapter(new AccountListAdapter(getContext()));
accountManager = AccountManager.get(getContext());
accountManager.addOnAccountsUpdatedListener(this, null, true);
accountManager.addOnAccountsUpdatedListener(this, null, 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
public void onDestroyView() {
accountManager.removeOnAccountsUpdatedListener(this);
@ -45,17 +63,35 @@ public class AccountListFragment extends ListFragment implements OnAccountsUpdat
@Override
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();
if (adapter != null) {
adapter.clear();
for (Account account : accounts)
if (Constants.ACCOUNT_TYPE.equals(account.type))
adapter.add(account);
adapter.addAll(accounts);
}
}
@Override
public void onLoaderReset(Loader<List<AccountInfo>> loader) {
}
@RequiredArgsConstructor
public static class AccountInfo {
final Account account;
boolean hasCardDAV, hasCalDAV;
}
class AccountListAdapter extends ArrayAdapter<Account> {
static class AccountListAdapter extends ArrayAdapter<AccountInfo> {
public AccountListAdapter(Context context) {
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);
}
Account account = getItem(position);
AccountInfo info = getItem(position);
TextView tvName = (TextView)v.findViewById(R.id.account_name);
tvName.setText(account.name);
TextView tv = (TextView)v.findViewById(R.id.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;
}
}
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;
}
}
}

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

@ -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);
}
}
}

@ -11,11 +11,14 @@ package at.bitfire.davdroid.ui.setup;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
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 android.support.v7.app.AlertDialog;
import java.io.BufferedReader;
import java.io.IOException;
@ -25,6 +28,7 @@ import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.resource.DavResourceFinder;
import at.bitfire.davdroid.resource.DavResourceFinder.Configuration;
import at.bitfire.davdroid.ui.DebugInfoActivity;
import lombok.Cleanup;
public class DetectConfigurationFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Configuration> {
@ -57,8 +61,18 @@ public class DetectConfigurationFragment extends DialogFragment implements Loade
@Override
public void onLoadFinished(Loader<Configuration> loader, Configuration data) {
// show error / continue with next fragment
Constants.log.info("detection results: {}", data);
if (data.calDAV == null && data.cardDAV == null)
// 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();
}
@ -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> {
final Context context;
final LoginCredentialsFragment.LoginCredentials credentials;

@ -8,9 +8,13 @@
package at.bitfire.davdroid.ui.setup;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import at.bitfire.davdroid.Constants;
import at.bitfire.davdroid.R;
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()));
}
}

@ -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>

@ -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>

@ -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>

@ -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>

@ -7,39 +7,34 @@
~ 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">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@id/android:list"
<ImageView
android:src="@drawable/gletschersee"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@android:color/transparent" />
android:layout_alignParentTop="true" />
<RelativeLayout
<ListView
android:id="@id/android:list"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="wrap_content"
android:divider="@android:color/transparent"
android:background="@android:color/transparent"
android:cacheColorHint="@android:color/transparent"/>
<ImageView
android:src="@drawable/gletschersee"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true" />
<TextView
android:id="@id/android:empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="72dp"
android:layout_marginRight="64dp"
android:gravity="right|bottom"
android:textColor="@color/white"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:text="@string/account_list_empty" />
</RelativeLayout>
<TextView
android:id="@id/android:empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="72dp"
android:layout_marginRight="64dp"
android:gravity="center"
android:textColor="@color/white"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:text="@string/account_list_empty" />
</LinearLayout>
</RelativeLayout>

@ -1,25 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
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:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:clickable="true"
android:foreground="?android:attr/selectableItemBackground"
card_view:contentPadding="16dp"
card_view:cardBackgroundColor="#ececec"
card_view:cardElevation="2dp">
style="@style/account_list_card"
android:layout_margin="16dp"
android:layout_height="wrap_content"
android:clickable="true"
android:foreground="?android:attr/selectableItemBackground"
card_view:contentPadding="16dp"
card_view:cardBackgroundColor="#ececec"
card_view:cardElevation="2dp">
<LinearLayout
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
android:id="@+id/account_name"
@ -27,7 +39,7 @@
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
tools:text="Account Name"/>
</LinearLayout>
</android.support.v7.widget.CardView>
</FrameLayout>
</LinearLayout>

@ -16,6 +16,15 @@
android:fitsSystemWindows="true"
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:layout_width="match_parent"
android:layout_height="wrap_content"
@ -29,8 +38,6 @@
app:popupTheme="@style/AppTheme.PopupOverlay"/>
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_accounts"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"

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

@ -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>

@ -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>

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

@ -7,12 +7,13 @@
~ 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
android:icon="@android:drawable/ic_menu_share"
android:icon="@drawable/ic_share_light"
android:title="@string/send"
android:showAsAction="always"
android:onClick="onShare" />
app:showAsAction="always"
android:onClick="onShare"/>
</menu>

@ -7,12 +7,15 @@
~ 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
android:id="@+id/help"
android:showAsAction="never"
android:title="@string/help" android:onClick="showHelp">
android:title="@string/help"
android:icon="@drawable/ic_help_light"
app:showAsAction="always"
android:onClick="showHelp">
</item>
</menu>

@ -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>

@ -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>

@ -12,7 +12,6 @@
<!-- common strings -->
<string name="app_name">DAVdroid</string>
<string name="help">Help</string>
<string name="next">Next</string>
<string name="please_wait">Please wait</string>
<string name="send">Send</string>
<string name="skip">Skip</string>
@ -104,9 +103,18 @@
<string name="login_user_name_required">User name required</string>
<string name="login_base_url">Base URL</string>
<string name="login_auth_preemptive">Preemptive authentication (recommended, but incompatible with Digest auth)</string>
<string name="login_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_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 -->
<string name="settings_title">Settings</string>
@ -190,7 +198,6 @@
<string name="setup_account_details">Account details</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_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>
<!-- sync errors and DebugInfoActivity -->

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

@ -1 +1 @@
Subproject commit 94a5ee4aec98a29fe1c56046f9a6c71131db8b51
Subproject commit 40aef8e2d5219c7595229a225c497c0e7237ec1c
Loading…
Cancel
Save